I'm trying to change the result that apollo returns with the data on the client side. I've read in the docs of version 3 that maybe this is possible. I currently have a graphql query that returns data within an EDGES array, and then each item is in a "node" before you get to the actual data.
The query looks like:
export const QUERY_ALL_POSTS = gql`
query AllPosts {
posts(first: 20) {
__typename
edges {
__typename
node {
__typename
author {
node {
avatar {
height
url
width
}
id
name
slug
}
}
id
categories {
edges {
node {
databaseId
id
name
slug
}
}
}
content
date
excerpt
featuredImage {
node {
altText
caption
sourceUrl
srcSet
sizes
id
}
}
modified
databaseId
title
slug
isSticky
}
}
}
}
`;
And the response looks like:
I know that I can write the typePolicy this way to change the date string for example:
Post:{
fields:{
date(){
return 'date edit'
}
}
},
So I wanted to see if I could change the response of the entire array that gets returned to something like this because I'll be needing to check for edges and nodes in every component which is kinda of annoying:
posts:[
{...item 1},
{...item 2},
{...item 3},
...etc
]
I've tried to write a typePolicy like this, and it returns the correct data, but when I query it on the frontend, nothing has changed. The data is still contained in an Edges object and each item is still in a NODE from the wpGraphql backend.
typePolicies: {
Post:{
fields:{
date(){
return 'date edit test' // returns a modified date object
}
}
},
Query: {
fields: {
// posts: flatten(),
posts:{
read(posts, { readField }) {
if(posts){
const newItem = posts.edges.map(post => {
return post.node
})
console.log('newItem', newItem)
return newItem
}
return posts // does not return a modified posts object
},
}
},
},
},
Can I modify the response like this or do I just have to write a custom helper function to modify the data every time I query it before using the data inside a component?
Related
I am using Next JS 13 and Sanity 3 GraphQL API to build a simple portfolio site. My problem is to get a single post/project by slug instead of id.
The id url ends up looking like:
http://127.0.0.1:3000/projects/093e9421-3dec-4b22-a106-63dd31b0e685
but I want it to use the slug instead.
This is the getStaticFunction function
export async function getStaticProps({ params }: any) {
const GET_PROJECTS = gql`
query SingleProject($slug: String) {
Project(slug: $slug) {
title
_id
slug {
current
}
bodyRaw
summary
category {
title
}
projectImage {
asset {
url
}
}
}
}
`;
const response = await client.query({
query: GET_PROJECTS,
variables: {
slug: params.slug.current,
},
});
const project = response.data?.Project;
return {
props: {
project,
},
};
}
but this gives me:
ApolloError: Unknown argument "where" on field "Project" of type "RootQuery".
Field "Project" argument "id" of type "ID!" is required, but it was not provided.
I also tried to query using the where like so:
query SingleProject($slug: String!) {
Project(where: { slug: { current: { eq: $slug } } }) {
title
}
}
but I get a Unkown arugment "where" on field "Project" of type "RootQuery"
I just want to Publish the relational Data for a Publication to client, but the issue is my Relational Data field is array of ID's of a Different Collection, I tried Different Packages but all works with single Relational ID but not working with Array of relational ID's, let assume I have two Collection Companies and Meteor.users below is my Company Document Looks like
{
_id : "dYo4tqpZms9j8aG4C"
owner : "yjzakAgYWejmJcuHz"
name : "Labbaik Waters"
peoples : ["yjzakAgYWejmJcuHz", "yjzakAgYWejmJcuHz"],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
here you can see peoples field contains the user ID's as Array, so How I publish this userId's as user Documents, as for example I tried the most popular meteor package named publishComposit, when I tried Loop in Children's find, I got undefined in children i.e below
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
find(company) {
let cursors = company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
and if I implement async loop in children's find I got error like below code
publishComposite('compoundCompanies', {
find() {
// Find top ten highest scoring posts
return Companies.find({
owner: this.userId
}, {sort: {}});
},
children: [
{
async find(company) {
let cursors = await company.peoples.forEach(peopleId => {
console.log(peopleId)
return Meteor.users.find(
{ _id: peopleId },
{ fields: { profile: 1 } });
})
//here cursor undefined
console.log(cursors)
return cursors
}
}
]
});
the error occured in above code is Exception in callback of async function: TypeError: this.cursor._getCollectionName is not a function
I don't know what I am exactly doing wrong here, or implementing package function not as intended any help will be greatly appropriated
EDIT: my desired result should be full user documents instead of ID no matter it mapped in same peoples array or as another fields I just want as below
{
_id: "dYo4tqpZms9j8aG4C",
owner: "yjzakAgYWejmJcuHz",
name: "Labbaik Waters",
peoples: [
{
profile: {firstName: "Abdul", lastName: "Hameed"},
_id: "yjzakAgYWejmJcuHz"
}
],
createdAt: "2019-09-18T15:33:29.952+00:00"
}
I ran into a similar problem couple of days ago. There are two problems with the provided code. First, using async; it's not needed and rather complicates things. Second, publishComposite relies on receiving one cursor not multiple within its children to work properly.
Below is a snippet of the code used to solve the problem I had, hopefully you can replicate it.
Meteor.publishComposite("table.conversations", function(table, ids, fields) {
if (!this.userId) {
return this.ready();
}
check(table, String);
check(ids, Array);
check(fields, Match.Optional(Object));
return {
find() {
return Conversation.find(
{
_id: {
$in: ids
}
},
{ fields }
);
},
children: [
{
find(conversation) {
// constructing one big cursor that entails all of the documents in one single go
// as publish composite cannot work with multiple cursors at once
return User.find(
{ _id: { $in: conversation.participants } },
{ fields: { profile: 1, roles: 1, emails: 1 } }
);
}
}
]
};
});
I can't access a primaryTag variable in my GraphQL page-query.
What I want to achieve is on a blog Post page:
display the post content
display the related posts (based on the first tag)
In my gridsome.server.js
api.createPages(async ({ graphql, createPage }) => {
// Use the Pages API here: https://gridsome.org/docs/pages-api
const { data } = await graphql(`{
allPost {
edges {
node {
id
path
tags {
id
}
}
}
}
}`)
data.allPost.edges.forEach(({ node }) => {
createPage({
path: `${node.path}`,
component: './src/templates/Post.vue',
context: {
id: node.id,
path: node.path,
primaryTag: (node.tags[0] && node.tags[0].id) || '',
}
})
})
})
then in my Post.vue
<page-query>
query Post ($path: String!, $primaryTag: String!) {
post: post (path: $path) {
title
path
content
}
related: allPost(
filter: { tags: { contains: [$primaryTag] }, path: { ne: $path } }
) {
edges {
node {
id
title
path
}
}
}
}
</page-query>
Unfortunately I get the following error: `Variable "$primaryTag" of non-null type "String!" must not be null.
Also, as a side note (and that might be the bug issue) I'm using #gridsome/source-filesystem and #gridsome/transformer-remark to create my Post collection.
If you know how to solve this or have a better approach for getting the related posts, comment below.
Libs:
- gridsome version: 0.6.3
- #gridsome/cli version: 0.1.1`
I have the following document structure:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
"user1id",
"user2id",
"user3id",
]
}
For security, I want to ensure that a given user cannot see the other user's starredByUserIds by performing a query through a client console.
i.e. user3 should only be able to see his own respective entry:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
"user3id",
]
}
whilst a non-logged-in user should only be able to see:
ProductDocument {
_id: "a",
price: 12,
starredByUserIds: [
]
}
I can't seem to define the right Publish command. I'd like to be able to do something like:
Meteor.publish('Products', function() {
return Products.find( {}, { fields: { starredByUserIds: this.userId }} );
})
but 'fields' doesn't accept/match arbitrary string values.
How can this be achieved?
I think the below query should answer your requirement:
Products.find({}, {starredByUserIds: {$elemMatch:{$eq:this.userId}});
Following snippet would work:
Meteor.publish('Products', function() {
return Products.find( {starredByUserIds: this.UserId}, { fields: { starredByUserIds: 0 }} );
})
Explanation:
Here the query selector starredByUserIds: this.UserId will return documents only which has current user's Id in its starredByUserIds array.
I'm omitting starredByUserIds array while sending it to the client, because you it will either contain the current user's Id if the user is logged in or empty if the user is not logged in, you can regenerate it.
With those security concerns, maybe you should change your data model and put the 'stars' in a different collection. If you don't want to do that, then you must change the code of your publish functions to something like this:
Meteor.publish('Products', function () {
return Products.find().fetch().map(function (product) {
if (this.userId && product.starredByUserIds.indexOf(this.userId) != -1) {
product.starredByUserIds = [this.userId];
} else {
product.starredByUserIds = [];
}
return product;
});
});
I have probably overlooked something in the docs, but I have seem to run into a problem with being able to get a single object from my graphql queries.
Here is the schema:
type Query {
product(name: String!): Product
}
type Product {
_id: String
name: String
}
Here is the resolver:
Query: {
product (_, args) {
return Products.find({where: args})
},
products () {
return Products.find().fetch()
}
}
Here is the Query:
query {
product(name: "burgers") {
name
}
}
I get a result of this:
{
"data": {
"product": {
"name": null
}
}
}
Am I just forgetting to add something to this, and if so could you point me the right direction.
If Products is a Meteor Collection, then .find returns a cursor, so the right thing to return would be Products.findOne({name: args.name})
http://docs.meteor.com/api/collections.html#Mongo-Collection-findOne