what is the proper way to get my google account profile image (URL) in NodeJS - google-oauth

see below error message and source code, doesn't work even after I added person fields of photos:
TypeError: Cannot read properties of undefined (reading 'url')
var p = google.people("v1");
p.people.get(
{
resourceName: "people/me",
personFields: "names,emailAddresses,photos",
auth: oauth2Client,
},
function (err, user) {
if (err) { return; }
res.json({
name: encoder.htmlEncode(user.displayName),
picture: user.image.url,
});
}
);

You should have a look into the docs. The people.get request returns a Person object. And such a Person does not have an image property, so you cant't access user.image.url because user.image does not exist. Neither does user.displayName, btw.
What a Person actually looks like is as follows (the ... meaning there are additional properties too, look up in the docs, if you need more)
{
...
coverPhotos: [{url: "https://url.to/image", ...}],
...
names: [{ displayName: "John Doe", ...}],
...
}
So to get the name and picture, first of all, instead of photos you need to request coverPhotos. Then you can extract the desired values, as follow:
p.people.get(
{
resourceName: "people/me",
personFields: "names,emailAddresses,coverPhotos",
auth: oauth2Client,
},
function (err, user) {
if (err) { return; }
let
name = user.names?.[0]?.displayName || "John Doe",
url = user.coverPhotos?.[0]?.url
res.json({
name: encoder.htmlEncode(name),
picture: user.image.url,
});
});
I used the conditional chaining ?. here, to make sure, this code does not run into an exception if for instance coverPhotos does not exist or is an empty array.

Related

Calling function in VueApollo after API response

I am using Vue and Apollo and I am making a querie that looks just like the box below.
After I get the API response, I would like to call a method from my methods object. However Vue, doesn't give me acess to it within apollo object.
I would like to know how can I call one of my methods, but only after I am sure I got that response, without having to manually trigger it with a button or something else.
apollo: {
materials: {
query: gql`
query allMaterials($tenantId: ID, $name: String) {
tenantMaterials(tenantId: $tenantId, name: $name) {
edges {
node {
name
materialType {
name
id
}
brand
vendor
size
unit
inventory
createdAt
updatedAt
isActive
updatedBy
id
}
}
totalCount
}
}
`,
variables() {
return {
name: null
};
},
fetchPolicy: "cache-and-network",
update: response => {
return response.tenantMaterials.edges;
//I want to call a function/method after this response
},
skip: false
},
}
Use update(data) or result(result, key)
update(data) {return ...} to customize the value that is set in the
vue property, for example if the field names don't match.
result(ApolloQueryResult, key) is a hook called when a result is
received (see documentation for ApolloQueryResult (opens new window)).
key is the query key in the apollo option.
https://apollo.vuejs.org/api/smart-query.html

TypeORM select data from nested relations

Using
await this.budgetRepository.createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.getMany();
I get a list with objects like this:
Budget {
id: 1,
unnecessary_property1: something,
contact: Contact {
unnecessary_property2: something,
photo: Photo {
unnecessary_property3: something,
url: "url.com"
},
},
}
But I want to select only the necessary properties in the nested objects (relations) and get a list of objects like this:
Budget {
id: 1,
contact: Contact {
photo: Photo {
url: "url.com"
},
},
}
How is that possible with TypeORM?
This is possible but we have to select everything manually with .select()
await this.budgetRepository.createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.select(['budget.id', 'contactPhoto.url']
.getMany();
If you're using repository pattern that you will be achieve the similar result with:
await this.budgetRepository.find({
relations: ["contact", "contact.photo"]
})
You would have to use the .select() function and pass the given properties you want for each entity.
for your example:
const user = await createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.select([/* everything from budget */, 'contact.photo.url'....]) // added selection
.getMany();

How to loop through an array containing objects and do comparison

I am using ionic 4. I get the result from the API then get the result show like this
[
{"name":John,"age":20},
{"name":Peter,"age":35},
{"name":Alex,"age":15}
]
But I want to get the name only to check whether have same name with my condition or not. But I cannot straight a way get the result from the API, I need to hard code to do comparison. Here is my code:
this.http.get(SERVER_URL).subscribe((res) => {
const data = [
{ name: John, age: 21 },
{ name: Thomas, age: 25 },
];
const ppl= data.find(people=> people.name === 'alex');
console.log(ppl);
});
So, My first question is How to get the name from the API directly, not like now I hard code the result from API. My Second Question is when I do comparison I want to show the result 'already exist' or 'can use this name'. Because if I write my code like this I will get the error Type 'void' is not assignable to type 'boolean':
const ppl= data.find((people)=> {
if(people.name === 'alex') {
this.text = 'already exist'
} else {
this.text = 'can use this name'
}});
console.log(ppl);
Anyone can help me? Thank you very much
Instead of defining data, use the contents of the response; res will have the exact same contents that you are declaring in data.
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// [
// {name: "John", age: 21},
// {name: "Thomas", age: 25},
// ...
// ]
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Taken from the MDN docs on Array.prototype.find():
The find() method returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.
In that case
res.find(user => user.name === 'alex')
will return a user object if any of the usernames match alex, or undefined if none of the user.name attributes match alex.
undefined evaluates to false and a user object evaluates to true in the conditional.
Keep in mind that you are comparing strings with ===, so, for example, Alex will not match alex, if you want to look into other ways to compare strings, have a look at this question.
You also might want to handle errors, how you handle them is up to you, and it will depend on the response, but you can access the error inside your subscribe like this:
this.http.get(SERVER_URL).subscribe(res => {
if (res.find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
}, error => {
console.log(error);
}, () => {
// There is also a 'complete' handler that triggers in both cases
});
Edit. API returns Object not array
If your API returns an Object instead of an array like in your question, you can still iterate over the properties
this.http.get(SERVER_URL).subscribe(res => {
// If successful, res is an array with user data like the following
// {
// key1: {name: "John", age: 21},
// key2: {name: "Thomas", age: 25},
// ...
// }
let match = false;
Object.keys(res).forEach(key => {
if (res[key].name === 'alex') {
match = true;
}
});
if (match) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}
});
Instead of Object.keys() you could use Object.values() to get an array with user objects, then use find() as before, but that seems less efficient, something like this:
if (Object.values(res).find(user => user.name === 'alex')) {
console.log ('Username has been taken');
} else {
console.log('Username is available');
}

GraphQL, Dataloader, [ORM or not], hasMany relationship understanding

I'm using for the first time Facebook's dataloader (https://github.com/facebook/dataloader).
What I don't understand is how to use it when I have 1 to many relationships.
Here it is a reproduction of my problem: https://enshrined-hydrant.glitch.me.
If you use this query in the Playground:
query {
persons {
name
bestFriend {
name
}
opponents {
name
}
}
}
you get values.
But if you open the console log here: https://glitch.com/edit/#!/enshrined-hydrant you can see these database calls I want to avoid:
My Person type is:
type Person {
id: ID!
name: String!
bestFriend: Person
opponents: [Person]
}
I can use dataloader good for bestFriend: Person but I don't understand how to use it with opponents: [Person].
As you can see the resolver has to return an array of values.
Have you any hint about this?
You need to create batched endpoints to work with dataloader - it can't do batching by itself.
For example, you probably want the following endpoints:
GET /persons - returns all people
POST /bestFriends, Array<personId>` - returns an array of best friends matchin the corresponding array of `personId`s
Then, your dataloaders can look like:
function batchedBestFriends(personIds) {
return fetch('/bestFriends', {
method: 'POST',
body: JSON.stringify(personIds))
}).then(response => response.json());
// We assume above that the API returns a straight array of the data.
// If the data was keyed, you could add another accessor such as
// .then(data => data.bestFriends)
}
// The `keys` here will just be the accumulated list of `personId`s from the `load` call in the resolver
const bestFriendLoader = new DataLoader(keys => batchedBestFriends(keys));
Now, your resolver will look something like:
const PersonType = new GraphQLObjectType({
...
bestFriend: {
type: BestFriendType,
resolve: (person, args, context) => {
return bestFriendLoader.load(person.id);
}
}
});

How to omit fields when serializing Mongoose models in MEAN [duplicate]

I have the following simple shema:
var userSchema = new Schema({
name : String,
age: Number,
_creator: Schema.ObjectId
});
var User = mongoose.model('User',userSchema);
What I want to do is create the new document and return to client, but I want to exclude the 'creator' field from one:
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
res.json(200, {user: user}); // how to exclude the _creator field?
});
});
At the end I want to send the new created user without _creator field:
{
name: 'John',
age: 45
}
Is it possible to make without extra find request to mongoose?
P.S:It's preferable to make it by
Another way to handle this on the schema level is to override toJSON for the model.
UserSchema.methods.toJSON = function() {
var obj = this.toObject()
delete obj.passwordHash
return obj
}
I came across this question looking for a way to exclude password hash from the json i served to the client, and select: false broke my verifyPassword function because it didn't retrieve the value from the database at all.
The documented way is
UserSchema.set('toJSON', {
transform: function(doc, ret, options) {
delete ret.password;
return ret;
}
});
UPDATE - You might want to use a white list:
UserSchema.set('toJSON', {
transform: function(doc, ret, options) {
var retJson = {
email: ret.email,
registered: ret.registered,
modified: ret.modified
};
return retJson;
}
});
Come across your question when I was trying to find a similar answer with pymongo. It turns out that in mongo shell, with the find() function call, you can pass a second parameter which specifies how the result document looks like. When you pass a dictionary with attribute's value being 0, you are excluding this field in all the document that come out of this query.
In your case, for example, the query will be like:
db.user.find({an_attr: a_value}, {_creator: 0});
It will exclude _creator parameter for you.
In pymongo, the find() function is pretty much the same. Not sure how it translate to mongoose though. I think it's a better solution compare to manually delete the fields afterwards.
Hope it helps.
I would use the lodash utilities .pick() or .omit()
var _ = require('lodash');
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
// Only get name and age properties
var userFiltered = _.pick(user.toObject(), ['name', 'age']);
res.json(200, {user: user});
});
});
The other example would be:
var _ = require('lodash');
app.post('/example.json', function (req, res) {
var user = new User({name: 'John', age: 45, _creator: 'some ObjectId'});
user.save(function (err) {
if (err) throw err;
// Remove _creator property
var userFiltered = _.omit(user.toObject(), ['_creator']);
res.json(200, {user: user});
});
});
You can call toObject() on the document to convert it to a plain JS object that you can freely modify:
user = user.toObject();
delete user._creator;
res.json(200, {user: user});
By following the MongoDB documentation, you can exclude fields by passing a second parameter to your query like:
User.find({_id: req.user.id}, {password: 0})
.then(users => {
res.status(STATUS_OK).json(users);
})
.catch(error => res.status(STATUS_NOT_FOUND).json({error: error}));
In this case, password will be excluded from the query.
font: https://docs.mongodb.com/v2.8/tutorial/project-fields-from-query-results/#return-all-but-the-excluded-field
I am using Mongoosemask and am very happy with it.
It does support hiding and exposing properties with other names based on your need
https://github.com/mccormicka/mongoosemask
var maskedModel = mongomask.mask(model, ['name', 'age']); //And you are done.
You can do this on the schema file itself.
// user.js
var userSchema = new Schema({
name : String,
age: Number,
_creator: Schema.ObjectId
});
userSchema.statics.toClientObject = function (user) {
const userObject = user?.toObject();
// Include fields that you want to send
const clientObject = {
name: userObject.name,
age: userObject.age,
};
return clientObject;
};
var User = mongoose.model('User',userSchema);
Now, in the controller method where you are responding back to the client, do the following
return res.json({
user: User.toClientObject(YOUR_ENTIRE_USER_DOC),
});