I have this single type in my strapi dashboard :
I Have a component called Logo
Another component called Links, it contains another component called Link
Finally a component called MenuButton.
When I go to http://localhost:1337/api/global?populate=* I got :
{
"data": {
"id": 1,
"attributes": {
"createdAt": "2021-12-27T11:54:36.177Z",
"updatedAt": "2021-12-27T11:54:54.737Z",
"publishedAt": "2021-12-27T11:54:54.731Z",
"logo": {
"id": 1,
"name": null
},
"navigation": {
"id": 1 // why I don't get links here ?
},
"menuButton": {
"id": 1,
"icon": ""
}
}
},
"meta": {
}
}
I Already published my content and allowed permissions for public.
My question is :
How can I access to the links inside navigation object ?
See my earlier answer here
Strapi 4 requires you to populate your request (see: population documentation )
which could look like this (for level 2 population):
// populate request
const qs = require('qs')
const query = qs.stringify(
{
populate: {
Product: {
populate: ['Image']
}
}
},
{
encodeValuesOnly: true
}
)
// get id
const id = yourId
// get rquest
const Response= await axios.get(
`http://localhost:1337/api/[your api]/${id }/?${query}`
)
Now media links should be included in your response
To retrieve up to 5 levels deep, you can install this package npm i strapi-plugin-populate-deep
Related
I am currently using Gatsby's collection routes API to create pages for a simple blog with data coming from Contentful.
For example, creating a page for each blogpost category :
-- src/pages/categories/{contentfulBlogPost.category}.js
export const query = graphql`
query categoriesQuery($category: String = "") {
allContentfulBlogPost(filter: { category: { eq: $category } }) {
edges {
node {
title
category
description {
description
}
...
}
}
}
}
...
[React component mapping all blogposts from each category in a list]
...
This is working fine.
But now I would like to have multiple categories per blogpost, so I switched to Contentful's references, many content-type, which allows to have multiple entries for a field :
Now the result of my graphQL query on field category2 is an array of different categories for each blogpost :
Query :
query categoriesQuery {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
Output :
{
"data": {
"allContentfulBlogPost": {
"edges": [
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
},
{
"id": "568r9e48-t1i8-sx4t8-9742-cdf70c4ed789vtu",
"name": "Test2",
"slug": "test-2"
}
]
}
},
{
"node": {
"category2": [
{
"id": "75b89e48-a8c9-54fd-9742-cdf70c416b0e",
"name": "Test",
"slug": "test"
}
]
}
},
...
Now that categories are inside an array, I don't know how to :
write a query variable to filter categories names ;
use the slug field as a route to dynamically create the page.
For blogposts authors I was doing :
query authorsQuery($author__slug: String = "") {
allContentfulBlogPost(filter: { author: { slug: { eq: $author__slug } } }) {
edges {
node {
id
author {
slug
name
}
...
}
...
}
And creating pages with src/pages/authors/{contentfulBlogPost.author__slug}.js
I guess I'll have to use the createPages API instead.
You can achieve the result using the Filesystem API, something like this may work:
src/pages/category/{contentfulBlogPost.category2__name}.js
In this case, it seems that this approach may lead to some caveats, since you may potentially create duplicated pages with the same URL (slug) because the posts can contain multiple and repeated categories.
However, I think it's more succinct to use the createPages API as you said, keeping in mind that you will need to treat the categories to avoid duplicities because they are in a one-to-many relationship.
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allContentfulBlogPost {
edges {
node {
category2 {
id
name
slug
}
}
}
}
}
`)
let categories= { slugs: [], names: [] };
result.data.allContentfulBlogPost.edges.map(({node}))=> {
let { name, slug } = node.category2;
// make some checks if needed here
categories.slugs.push(slug);
categories.names.push(name);
return new Set(categories.slugs) && new Set(categories.names);
});
categories.slugs.forEach((category, index) => {
let name = categories.names[index];
createPage({
path: `category/${category}`,
component: path.resolve(`./src/templates/your-category-template.js`),
context: {
name
}
});
});
}
The code's quite self-explanatory. Basically you are defining an empty object (categories) that contains two arrays, slugs and names:
let categories= { slugs: [], names: [] };
After that, you only need to loop through the result of the query (result) and push the field values (name, slug, and others if needed) to the previous array, making the needed checks if you want (to avoid pushing empty values, or that matches some regular expression, etc) and return a new Set to remove the duplicates.
Then, you only need to loop through the slugs to create pages using createPage API and pass the needed data via context:
context: {
name
}
Because of redundancy, this is the same than doing:
context: {
name: name
}
So, in your template, you will get the name in pageContext props. Replace it with the slug if needed, depending on your situation and your use case, the approach is exactly the same.
Github Issue Posted Here
"apollo-boost": "^0.1.13",
"apollo-link-context": "^1.0.8",
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"react-apollo": "^2.1.11",
Current Code Structure
<div>
<Query
query={FETCH_CATEGORIES_AUTOCOMPLETE}
variables={{ ...filters }}
fetchPolicy="no-cache"
>
{({ loading, error, data }) => {
console.log('category', loading, error, data); // _______Label_( * )_______
if (error) return 'Error fetching products';
const { categories } = data;
return (
<React.Fragment>
{categories && (
<ReactSelectAsync
{...this.props.attributes}
options={categories.data}
handleFilterChange={this.props.handleCategoryFilterChange}
loading={loading}
labelKey="appendName"
/>
)}
</React.Fragment>
);
}}
</Query>
<Mutation mutation={CREATE_CATEGORY}>
{createCategory => (
<div>
// category create form
</div>
)}
</Mutation>
</div>
Behavior
Initially, the query fetches data and I get list of categories inside data given in Label_( * ) .
After entering form details, the submission occurs successfully.
Issue: Then, suddenly, in the Label_( * ), the data object is empty.
How can I solve this?
Edit
These are the response:
Categories GET
{
"data": {
"categories": {
"page": 1,
"rows": 2,
"rowCount": 20,
"pages": 10,
"data": [
{
"id": "1",
"appendName": "Category A",
"__typename": "CategoryGETtype"
},
{
"id": "2",
"appendName": "Category B",
"__typename": "CategoryGETtype"
}
],
"__typename": "CategoryPageType"
}
}
}
Category Create
{
"data": {
"createCategory": {
"msg": "success",
"status": 200,
"category": {
"id": "21",
"name": "Category New",
"parent": null,
"__typename": "CategoryGETtype"
},
"__typename": "createCategory"
}
}
}
(I came across this question while facing a similar issue, which I have now solved)
In Apollo, when a mutation returns data that is used in a different query, then the results of that query will be updated. e.g. this query that returns all the todos
query {
todos {
id
description
status
}
}
Then if we mark a todo as completed with a mutation
mutation CompleteTodo {
markCompleted(id: 3) {
todo {
id
status
}
}
}
And the result is
{
todo: {
id: 3,
status: "completed"
__typename: "Todo"
}
}
Then the todo item with id 1 will have its status updated. The issue comes when the mutation tells the query it's stale, but doesn't provide enough information for the query to be updated. e.g.
query {
todos {
id
description
status
owner {
id
name
}
}
}
and
mutation CompleteTodo {
assignToUser(todoId: 3, userId: 12) {
todo {
id
owner {
id
}
}
}
}
Example result:
{
todo: {
id: 3,
owner: {
id: 12,
__typename: "User"
},
__typename: "Todo"
}
}
Imagine your app previously knew nothing about User:12. Here's what happens
The cache for Todo:3 knows that it now has owner User:12
The cache for User:12 contains just {id:12}, since the mutation didn't return the name field
The query cannot give accurate information for the name field for the owner without refetching (which doesn't happen by default). It updates to return data: {}
Possible solutions
Change the return query of the mutation to include all the fields that the query needs.
Trigger a refetch of the query after the mutation (via refetchQueries), or a different query that includes everything the cache needs
manual update of the cache
Of those, the first is probably the easiest and the fastest. There's some discussion of this in the apollo docs, but I don't see anything that describes the specific behavior of the data going empty.
https://www.apollographql.com/docs/angular/features/cache-updates.html
Tip of the hat to #clément-prévost for his comment that provided a clue:
Queries and mutations that fetch the same entity must query the same
fields. This is a way to avoid local cache issues.
After changing fetchPolicy to cache-and-network. It solved the issue. Link to fetchPolicy Documentation
Doing so, I also had to perform refetch query.
return Cache::remember('districts.all',60*60, function (){
return District::all();
});
cache collection is
[
{
"id": 1,
"title": "Dhaka"
},
{
"id": 2,
"title": "Rajshahi"
},
{
"id": 3,
"title": "Barishal"
}
]
now how to add new in districts.all cache
"id":4 ,
"title":"New District"
and also how to update this cache
"id":3,
"title":"Khulna"
Laravel doesn't have a special function to update cache directly, but you still can clear districts.all and redefine it again with the new value:
Cache::forget('districts.all');
$district = new District();
$district->id = 4;
$district->name = "New District";
Cache::remember('districts.all', 60*60, function () use ($district) {
return District::all()->push($district);
});
Pretty straightforward (I hope). I'd like to be able to use the API endpoint and have it only return specified fields. I.E. something like this
http://localhost:1337/api/reference?select=["name"]
Would ideally return something of the form
[{"name": "Ref1"}]
Unfortunately that is not the case, and in actuality it returns the following.
[
{
"contributors": [
{
"username": "aduensing",
"email": "standin#gmail.com",
"lang": "en_US",
"template": "default",
"id_ref": "1",
"provider": "local",
"id": 1,
"createdAt": "2016-07-28T19:39:09.349Z",
"updatedAt": "2016-07-28T19:39:09.360Z"
}
],
"createdBy": {
"username": "aduensing",
"email": "standin#gmail.com",
"lang": "en_US",
"template": "default",
"id_ref": "1",
"provider": "local",
"id": 1,
"createdAt": "2016-07-28T19:39:09.349Z",
"updatedAt": "2016-07-28T19:39:09.360Z"
},
"updatedBy": {
"username": "aduensing",
"email": "standin#gmail.com",
"lang": "en_US",
"template": "default",
"id_ref": "1",
"provider": "local",
"id": 1,
"createdAt": "2016-07-28T19:39:09.349Z",
"updatedAt": "2016-07-28T19:39:09.360Z"
},
"question": {
"createdBy": 1,
"createdAt": "2016-07-28T19:41:33.152Z",
"template": "default",
"lang": "en_US",
"name": "My Question",
"content": "Cool stuff, huh?",
"updatedBy": 1,
"updatedAt": "2016-07-28T19:45:02.893Z",
"id": "579a5ff83af4445c179bd8a9"
},
"createdAt": "2016-07-28T19:44:31.516Z",
"template": "default",
"lang": "en_US",
"name": "Ref1",
"link": "Google",
"priority": 1,
"updatedAt": "2016-07-28T19:45:02.952Z",
"id": "579a60ab5c8592c01f946cb5"
}
]
This immediately becomes problematic in any real world context if I decide to load 10, 20, 30, or more records at once, I and end up loading 50 times the data I needed. More bandwidth is used up, slower load times, etc.
How I solved this:
Create custom controller action (for example, 'findPaths')
in contributor/controllers/contributor.js
module.exports = {
findPaths: async ctx => {
const result = await strapi
.query('contributor')
.model.fetchAll({ columns: ['slug'] }) // here we wait for one column only
ctx.send(result);
}
}
Add custom route (for example 'paths')
in contributor/config/routes.json
{
"method": "GET",
"path": "/contributors/paths",
"handler": "contributor.findPaths",
"config": {
"policies": []
}
},
Add permission in admin panel for Contributor entity, path action
That's it. Now it shows only slug field from all contributor's records.
http://your-host:1337/contributors/paths
Here is how you can return specific fields and also exclude the relations to optimize the response.
async list (ctx) {
const result = await strapi.query('article').model.query(qb => {
qb.select('id', 'title', 'link', 'content');
}).fetchAll({
withRelated: []
}).catch(e => {
console.error(e)
});
if(result) {
ctx.send(result);
} else {
ctx.send({"statusCode": 404, "error": "Not Found", "message": "Not Found"});
}
}
I know this is old thread but I just run into exactly same problem and I could not find any solution. Nothing in the docs or anywhere else.
After a few minutes of console logging and playing with service I was able to filter my fields using following piece of code:
const q = Post
.find()
.sort(filters.sort)
.skip(filters.start)
.limit(filters.limit)
.populate(populate);
return filterFields(q, ['title', 'content']);
where filterFields is following function:
function filterFields(q, fields) {
q._fields = fields;
return q;
}
It is kinda dirty solution and I haven't figured out how to apply this to included relation entites yet but I hope it could help somebody looking for solution of this problem.
I'm not sure why strapi does not support this since it is clearly capable of filtering the fields when they are explicitly set. it would be nice to use it like this:
return Post
.find()
.fields(['title', 'content'])
.sort(filters.sort)
.skip(filters.start)
.limit(filters.limit)
.populate(populate);
It would be better to have the query select the fields rather than relying on node to remove content. However, I have found this to be useful in some situations and thought I would share. The strapi sanitizeEntity function can include extra options, one of which allows you only include fields you need. Similar to what manually deleting the fields but a more reusable function to do so.
const { sanitizeEntity } = require('strapi-utils');
let entities = await strapi.query('posts').find({ parent: parent.id })
return entities.map(entity => {
return sanitizeEntity(entity, {
model: strapi.models['posts'],
includeFields: ['id', 'name', 'title', 'type', 'parent', 'userType']
});
});
This feature is not implemented in Strapi yet. To compensate, the best option for you is probably to use GraphQL (http://strapi.io/documentation/graphql).
Feel free to create an issue or to submit a pull request: https://github.com/wistityhq/strapi
You can use the select function if you are using MongoDB Database:
await strapi.query('game-category').model.find().select(["Code"])
As you can see, I have a model called game-category and I just need the "Code" field so I used the Select function.
In the current strapi version (3.x, not sure about previous ones) this can be achieved using the select method in custom queries, regardless of which ORM is being used.
SQL example:
const restaurant = await strapi
.query('restaurant')
.model.query((qb) => {
qb.where('id', 1);
qb.select('name');
})
.fetch();
not very beautiful,but you can delete it before return.
ref here:
https://strapi.io/documentation/developer-docs/latest/guides/custom-data-response.html#apply-our-changes
const { sanitizeEntity } = require('strapi-utils');
module.exports = {
async find(ctx) {
let entities;
if (ctx.query._q) {
entities = await strapi.services.restaurant.search(ctx.query);
} else {
entities = await strapi.services.restaurant.find(ctx.query);
}
return entities.map(entity => {
const restaurant = sanitizeEntity(entity, {
model: strapi.models.restaurant,
});
if (restaurant.chef && restaurant.chef.email) {
**delete restaurant.chef.email;**
}
return restaurant;
});
},
};
yeah,I remember another way.
you can use the attribute in xx.settings.json file.
ref:
model-options
{
"options": {
"timestamps": true,
"privateAttributes": ["id", "created_at"], <-this is fields you dont want to return
"populateCreatorFields": true <- this is the system fields,set false to not return
}
}
You can override the default strapi entity response of:-
entity = await strapi.services.weeklyplans.create(add_plan);
return sanitizeEntity(entity, { model: strapi.models.weeklyplans });
By using:-
ctx.response.body = {
status: "your API status",
message: "Your own message"
}
Using ctx object, we can choose the fields we wanted to display as object.
And no need to return anything. Place the ctx.response.body where the response has to be sent when the condition fulfilled.
It is now 2023, and for a little while it has been possible to do this using the fields parameter:
http://localhost:1337/api/reference?fields[0]=name&fields[1]=something
I am trying to send data to my controller from an ajax function that needs to have multiple levels, so something like this:
{
"lob": {
"TESTING": [
{
"name": "color",
"value": "1"
},
{
"name": "time",
"value": "2"
},
{
"name": "jeremy",
"value": "3"
},
{
"name": "fourtytwo",
"value": "4"
},
{
"name": "owl",
"value": "5"
},
{
"name": "why",
"value": "6"
},
{
"name": "derp",
"value": "7"
},
{
"name": "where",
"value": "8"
}
]
}
}
but when it sends to grails I am getting this when I print out the params
[lob[TESTING][4][value]:5,
lob[TESTING][3][name]:fourtytwo,
lob[TESTING][6][name]:derp,
lob[TESTING][5][name]:why,
lob[TESTING][3][value]:4,
lob[TESTING][1][value]:2,
lob[TESTING][2][value]:3,
lob[TESTING][5][value]:6,
lob[TESTING][1][name]:time,
lob[TESTING][0][value]:1,
lob[TESTING][6][value]:7,
lob[TESTING][0][name]:color,
lob[TESTING][7][value]:8,
lob[TESTING][4][name]:owl,
lob[TESTING][7][name]:where,
lob[TESTING][2][name]:jeremy,
action:save,
controller:LOB]
The data I am sending from JavaScript:
{
lob: {
TESTING: $form.serializeArray()
}
}
I have been reading multiple forums saying using JSON.parse or request.JSON but these solutions do not seem to be fixing my problems. I want to be able to access the data like
params.lob.testing.each{ a->
println a
}
I will be doing alot more than just that but it would be nice to be able to access the data in that fashion. I am using Grails 2.1 and Jquery 1.7.2
Actually Grails makes it very easy. I've taken your test data and ran it through the following:
import grails.converters.JSON
class LobController {
def save = {
def json = request.JSON
json.lob.TESTING.each {item->
println "Name: ${item.name} - Value: ${item.value}"
}
//render something back if you need to here
}
}
And it outputs:
Name color - Value: 1
Name time - Value: 2
Name jeremy - Value: 3
Name fourtytwo - Value: 4
Name owl - Value: 5
Name why - Value: 6
Name derp - Value: 7
Name where - Value: 8
I created a UrlMapping entry like this (you probably already have this):
"/myApi"(controller: "lob", parseRequest: true) {
action = [POST: "save"]
}
The parseRequest: true will automatically parse the incoming JSON.
I found a `serializeJSON' function that might replace the serializeArray() to format this for JSON. The following was provided by Arjen Oosterkamp on the jQuery serializeArray page:
(function( $ ){
$.fn.serializeJSON=function() {
var json = {};
jQuery.map($(this).serializeArray(), function(n, i){
json[n['name']] = n['value'];
});
return json;
};
})( jQuery );
Simply use as $('form').serializeJSON();
All credit for that function goes to Arjen Oosterkamp...