Contentful NodeJs SDK, Client.getEntries | Load nested ContentType fields - node.js

I have the following structure
User {
image: Asset
...
}
Comment {
author: User
...
}
BlogArticle {
slug: Text
author: User
comments: Comment[]
}
When I pull entries with the following method
const articles = await client.getEntries({ content_type: "BlogArticle" })
console.log(articles.entries.fields.comments)
I only get the sys property for the author
[
{
author: {
sys: {
...
}
fields ??????
}
}
]
PS: This is the case for all types that come in second level of nesting
I checked the docs and the apis but with no luck
Any help ?

I created a similar content model and was able to get the fields of the Author successfully. One thing you can do is use the include parameter. With the include parameter, your code should look as follow:
const articles = await client.getEntries({ content_type: "BlogArticle", include: 2 })
console.log(articles.entries.fields.comments)
You can learn more about it here

Related

Combing related data from two different sources in a single Gatsby page

I'm creating a blog where each blog page will show a map using leaflet with a GPX route on it. Below the map will be some statistics and some text and images.
I have the text and images part defined in mark down so i figured the way to handle this would be to define my gpx filename in the frontmatter like so:
---
title: Awesome Blog Post Title
author: Cancrizan
date: 2021-01-04
gpxFile: activity4835702422
---
BLOG POST here
where the field gpxFile refers to a a file in my project src/gpx/activity4835702422.gpx.
I've written a transformer plugin that will read in the GPX file so that it can be queried like this:
query MyQuery {
allActivity4835702422Gpx {
edges {
node {
geometry {
coordinates
}
}
}
}
}
and outputs something like this:
{
"data": {
"allActivity4835702422Gpx": {
"edges": [
{
"node": {
"geometry": {
"coordinates": [
[
-1.2134187016636133,
52.92038678191602,
29.399999618530273
],
[
-1.2134256586432457,
52.92039977386594,
29.399999618530273
],
...,
]
}
}
}
]
}
},
"extensions": {}
}
I want to access that node based on the frontmatter of the markdown file and i'm not sure how?
Can anyone suggest a solution or am i going about this the wrong way?
The structure and the mindset you've followed is perfectly valid, the only part you're missing is to pass the gpxFile to your template in order to create another query based on that parameter.
Your gatsby-node.js should look like something like this:
const path = require("path")
exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions
const result = await graphql(
`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
frontmatter {
title
author
date
gpxFile
slug
}
}
}
}
}
`
)
if (result.errors) {
reporter.panicOnBuild(`Error while running GraphQL query.`)
return
}
const postTemplate= path.resolve(`src/templates/post.js`);
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const path = node.frontmatter.path
createPage({
path: `/posts/${node.frontmatter.slug}`,
component: postTemplate,
context: {
gpxFile: node.frontmatter.gpxFile,
path: node.frontmatter.slug
}
})
})
}
The idea is to use context API to pass data (gpxFileData and path) to your template (postTemplate) to use it as a filter for your markdown files.
In your postTemplate, your query should look like:
export const postData = graphql`
query getArticleData($path: String!, $gpxFile: String) {
post: markdownRemark (fields: {frontmatter: { slug: { eq: $path }}}) {
html
excerpt (pruneLength: 350)
frontmatter {
title
author
date
gpxFile
slug
}
}
gpxFileData: allFile(relativePath: { in: $gpxFile })
# your gpx data here
}
}
`;
It's quite self-explanatory, basically, you are passing via context the necessary data to make a query in your template (gpxFileData) from your gatsby-node.js. There, you can create a new query, allFile, filtering by relativePath (you may need to access to file directly or use absolutePath, test it in localhost:8000/___graphql) and retrieve the whole data using props.data.post and post.data.gpxFileData.
Disclaimer: I'm assuming that you've set your filesystem (gatsby-source-filesystem) properly to use allFile across your .gpx files.

How do I select specific column data to be displayed in my bookshelf model belongsTo relationship in Nodejs?

This is a contrived example of what I would like to do:
Suppose I have a database of teams and players:
team:
->id
->color
->rank
->division
player:
->id
->team_id
->number
->SECRET
And the following bookshelf models:
var Base = require('./base');
const Player = Base.Model.extend(
{
tableName: "players",
},
nonsecretdata: function() {
return this.belongsTo('Team')
},
{
fields: {
id: Base.Model.types.integer,
team_id: Base.Model.types.integer,
number: Base.Model.types.integer,
SECRET: Base.Model.types.string,
}
}
);
module.exports = Base.model('Player', Player);
And
var Base = require('./base');
const Team = Base.Model.extend(
{
tableName: "teams",
},
{
fields: {
id: Base.Model.types.integer,
color: Base.Model.types.string,
rank: Base.Model.types.integer,
division: Base.Model.types.string,
}
}
);
module.exports = Base.model('Team', Team);
My question is, how can I limit the scope of player such that SECRET is not grabbed by calls to join player and team with callback nonsecretdata?
I am new to Bookshelf so if any other information is needed, please let me know. Thank you
++++++++++
Edit: Do I need to create a separate model?
The only way to do this using bookshelf would be to delete the individual fields from the object after fetching the entire model.
A potentially better solution for this use case would be to define a custom Data Access Object class that uses a SQL query for the information that would like to be obtained and then use that DOA instead of using bookshelf. That way the SQL code is still abstracted away from the code that is requesting the information and the SECRET or any other potential sensitive information that is added to the table will not be included in the fetch.

GraphQL Resolver Problems

I've spent quite a bit of time reading through the GraphQL tutorials but unfortunately they don't seem to cover things in quite enough depth for me to get my head around. I'd really appreciate some help with this real world example.
In the examples the queries are placed at the root of the resolver object; I can get this to work fine for single level queries. When I attempt to resolve a nested query however the nested resolver never gets called. What I'm massively confused by is every tutorial I find that isn't issued on the graphql website put in a Query object and nest their queries underneeth that, not root level.
Consider the following Schema:
type Product {
id: String!
retailerId: String!
title: String!
description: String
price: String!
currency: String!
}
type OrderLine {
product: Product!
quantity: Int!
}
type Order {
id: String!
retailerId: String!
orderDate: Date!
orderLines: [OrderLine!]!
}
type Query {
product(id: String!): Product
order(id: String!): Order
}
schema {
query: Query
}
And the following query:
query {
order(id: "1") {
id
orderLines {
quantity
}
}
}
I have tried multiple versions of implementing the resolvers (just test data for now) and none seem to return what I exect. This is my current resolver implementation:
const resolvers = {
OrderLine: {
quantity: () => 1,
},
Order: {
orderLines: (parent: any, args: any) => { console.log("Calling order lines"); return []; },
},
Query: {
product(parent, args, ctx, other) {
return { id: args.id.toString(), test: true };
},
order: ({ id }) => { console.log("Calling order 1"); return { id: id.toString(), testOrder: true, orderLines: [] }; },
},
order: ({ id }) => { console.log("Calling order 2"); return { id: id.toString(), testOrder: true, orderLines: [] }; },
};
In the console I can oberse the "Calling order 2" log message, there are no logs to "Calling order lines" and the order lines array is empty.
So two part question:
1) Why does it hit "Calling order 2" and not "Calling order 1" in the above example?
2) Why won't the above work for the nested query Order.OrderLines?
Thanks in advance!
In query
type Query {
product(id: String!): Product
order(id: String!): Order
users: User
}
schema {
query: Query
}
In resolvers
const resolvers = {
order: ({ id }) => function
product: ({ id }) => function
}
Graphql work on query resolver concept. If you want to any query(example users) you must have
resolver(ie users) which return User having definition in type User.
Graphql query is interactive and case sensitive
The next step is to implement the resolver function for the order/product query.
In fact, one thing we haven’t mentioned yet is that not only root fields,
but virtually all fields on the types in a GraphQL schema have resolver functions.
1) Why does it hit "Calling order 2" and not "Calling order 1" in the above example?
In this Query
query {
order(id: "1") {
id
orderLines {
quantity
}
}
}
then it go to order which return Order with define type
2) Why won't the above work for the nested query Order.OrderLines?
You can only use two query first order and second product only as per your schema
Please check doc for nested query for this requirement.
If you use buildSchema to generate your schema, the only way to provide resolvers for your fields is through the root object. But this is more of a hack -- you're not actually overriding the default resolvers for the fields and as such, you're basically limited to just working with the root-level fields (as you are learning the hard way). This is why only the Query.order function is called -- this is a root-level field. Why passing functions through the root (kind of) works is explained in detail here.
The bottom line is you shouldn't be using buildSchema. If you want to use SDL to define your schema, migrate to using Apollo Server.

graphql - use queries in mutations - create a nested object

I have a very simple model with post that embeds several comments
I wondered how I should do a mutation to add a new comment to the post
As I already have queries defined to get back a postwith a given id, I wanted to try to have the following mutation syntax working
mutation {
post(id: "57334cdcb6d7fb172d4680bb") {
addComment(data: {
text: "test comment"
})
}
}
but I can't seem to find a way to make it work. Even if I'm in a mutation, output type being a post addComment is seen as a field post should have.
Do you guys have any idea ?
Thanks
You can't embed fields into other fields like that.
You would create a new input object for your post mutation
input CommentInput {
text: String
}
type Mutation {
post(id: ID!, addComment: CommentInput): Post
}
In your resolver you look for the addComment variable and call the addComment resolver with the arguments.
Your mutation would be
mutation {
post(id: "57334cdcb6d7fb172d4680bb",
addComment: {
text: "test comment"
}) {
id
comment {
text
}
}
}
I could be wrong but you may want to just create a separate updatePost mutation that accepts the post id as an argument
type Post {
comments: [ Comment ]
}
input PostInput {
comments: [ CommentInput ]
}
type Mutation {
updatePost( id: ID!, input: PostInput ): Post
}
The updatePost mutation here takes the id of the post and the updated post object as arguments and returns a type Post.
So I would use this like so:
mutation {
updatePost( id: '1234', input: { comments: []) {
id
}
}
Hope this helps!
Maybe you could create addComment mutation that you pass post id to and then return a Post.
type Mutation {
addComment( postId: ID!, input: CommentInput ): Post
}

Sails.js - Get an object (model) using multiple join

I am new to node.js and newer to Sails.js framework.
I am currently trying to work with my database, I don't understand all the things with Sails.js but I manage to do what I want step by step. (I am used to some PHP MVC frameworks so it is not too difficult to understand the structure.)
Here I am trying to get a row from my database, using 2 JOIN clause. I managed to do this using SQL and the Model.query() function, but I would like to do this in a "cleaner" way.
So I have 3 tables in my database: meta, lang and meta_lang. It's quite simple and a picture being better than words, here are some screenshots.
meta
lang
meta_lang
What I want to do is to get the row in meta_table that match with 'default' meta and 'en' lang (for example).
Here are Meta and Lang models (I created them with sails generate model command and edited them with what I needed):
Meta
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'meta'
}
}
};
Lang
module.exports = {
attributes: {
code : { type: 'string' },
metaLangs:{
collection: 'MetaLang',
via: 'lang'
}
}
};
And here is my MetaLang model, with 3 functions I created to test several methods. The first function, findCurrent, works perfectly, but as you can see I had to write SQL. That is what I want to avoid if it is possible, I find it more clean (and I would like to use Sails.js tools as often as I can).
module.exports = {
tableName: 'meta_lang',
attributes: {
title : { type: 'string' },
description : { type: 'text' },
keywords : { type: 'string' },
meta:{
model:'Meta',
columnName: 'meta_id'
},
lang:{
model:'Lang',
columnName: 'lang_id'
}
},
findCurrent: function (metaCode, langCode) {
var query = 'SELECT ml.* FROM meta_lang ml INNER JOIN meta m ON m.id = ml.meta_id INNER JOIN lang l ON l.id = ml.lang_id WHERE m.code = ? AND l.code = ?';
MetaLang.query(query, [metaCode, langCode], function(err, metaLang) {
console.log('findCurrent');
if (err) return console.log(err);
console.log(metaLang);
// OK this works exactly as I want (I would have prefered a 'findOne' result, only 1 object instead of an array with 1 object in it, but I can do with it.)
});
},
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs').exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
// I get what I expected (though not what I want): my meta + all metaLangs related to meta with code "default".
// What I want is to get ONE metaLang related to meta with code "default" AND lang with code "en".
});
},
findCurrentOthertest: function (metaCode, langCode) {
MetaLang.find().populate('meta', {where: {code:metaCode}}).populate('lang', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentOthertest');
if (err) return console.log(err);
console.log(metaLang);
// Doesn't work as I wanted: it gets ALL the metaLang rows.
});
}
};
I also tried to first get my Meta by code, then my Lang by code, and MetaLang using Meta.id and Lang.id . But I would like to avoid 3 queries when I can have only one.
What I'm looking for would be something like MetaLang.find({meta.code:"default", lang.code:"en"}).
Hope you've got all needed details, just comment and ask for more if you don't.
Do you know what populate is for ? its for including the whole associated object when loading it from the database. Its practically the join you are trying to do, if all you need is row retrieval than quering the table without populate will make both functions you built work.
To me it looks like you are re-writing how Sails did the association. Id suggest giving the Associations docs another read in Sails documentation: Associations. As depending on your case you are just trying a one-to-many association with each table, you could avoid a middle table in my guess, but to decide better id need to understand your use-case.
When I saw the mySQL code it seemed to me you are still thinking in MySQL and PHP which takes time to convert from :) forcing the joins and middle tables yourself, redoing a lot of the stuff sails automated for you. I redone your example on 'disk' adapter and it worked perfectly. The whole point of WaterlineORM is to abstract the layer of going down to SQL unless absolutely necessary. Here is what I would do for your example, first without SQL just on a disk adapter id create the models :
// Lang.js
attributes: {
id :{ type: "Integer" , autoIncrement : true, primaryKey: true },
code :"string"
}
you see what i did redundantly here ? I did not really need the Id part as Sails does it for me. Just an example.
// Meta.js
attributes: {
code :"string"
}
better :) ?
// MetaLang.js
attributes:
{
title : "string",
desc : "string",
meta_id :
{
model : "meta",
},
lang_id :
{
model : "lang",
}
}
Now after simply creating the same values as your example i run sails console type :
MetaLang.find({meta_id : 1 ,lang_id:2}).exec(function(er,res){
console.log(res);
});
Output >>>
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Now if you want to display what is meta with id 1 and what is lang with id 2, we use populate, but the referencing for join/search is just as simple as this.
sails> Meta_lang.find({meta_id : 1 ,lang_id:2}).populate('lang_id').populate('meta_id').exec(function(er,res){ console.log(res); });
undefined
sails> [ {
meta_id:
{ code: 'default',
id: 1 },
lang_id:
{ code: 'En',
id: 2 },
title: 'My blog',
id: 2 } ]
At this point, id switch adapters to MySQL and then create the MySQL tables with the same column names as above. Create the FK_constraints and voila.
Another strict policy you can add is to set up the 'via' and dominance on each model. you can read more about that in the Association documentation and it depends on the nature of association (many-to-many etc.)
To get the same result without knowing the Ids before-hand :
sails> Meta.findOne({code : "default"}).exec(function(err,needed_meta){
..... Lang.findOne({code : "En"}).exec(function(err_lang,needed_lang){
....... Meta_lang.find({meta_id : needed_meta.id , lang_id : needed_lang.id}).exec(function(err_all,result){
......... console.log(result);});
....... });
..... });
undefined
sails> [ { meta_id: 1,
lang_id: 2,
title: 'My blog',
id: 2 } ]
Have you tried:
findCurrentTest: function (metaCode, langCode) {
Meta.findByCode(metaCode).populate('metaLangs', {where: {code:langCode}}).exec(function(err, metaLang) {
console.log('findCurrentTest');
if (err) return console.log(err);
console.log(metaLang);
});
},

Resources