How to get nested objects using Firestore REST API? - javascript

I'm sending REST API requests using axios package.
I can get a single document (for example, cities/cityId):
axios.get(`https://firestore.googleapis.com/v1/projects/<PROJECTIDHERE>/databases/(default)/documents/<COLLECTIONNAME>/<DOCID>`)
What I can't do is to get a nested document (for example, cities/cityId/streetId)
The database structure is very simple.
cities: { // COLLECTION
cityId: { // DOCUMENT
streetId: { // MAP
buildingId: '...', // STRING
...
},
...
},
}
This article Google Firestore REST API examples suggests that it's possible to get nested objects using structured queries. Unfortunately, I've been trying to do it without any success.
Here's my not working code:
getQuery ({ path, token }) {
const url = 'https://firestore.googleapis.com/v1/projects/big-boobs/databases/(default)/documents:runQuery'
const params = {
from: [ { collectionId: 'cities' } ],
select: {
fields: [
{ fieldPath: 'cityId1.streetId' }
]
}
}
const options = {
method: 'get',
url,
params,
paramsSerializer: function (params) {
return Qs.stringify(params, { arrayFormat: 'brackets' })
}
}
if (token) options.headers['Authorization'] = `Bearer ${token}`
return axios(options)
}
I'm getting an error:
{
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"from[][collectionId]\": Cannot bind query parameter. Field 'from[][collectionId]' could not be found in request message.\nInvalid JSON payload received. Unknown name \"select[fields][][fieldPath]\": Cannot bind query parameter. Field 'select[fields][][fieldPath]' could not be found in request message.",
}

You can select individual fields from a document, but you can't select individual properties from a map field. I think you'll have to settle for getting the entire map called streetId.
If it's unacceptable to pull down the entire map, you can reorganize your data such that each streetId exists in its own document.

Related

How do I stream Product objects in object mode with the fetch API

I am processing data from a server which returns JSON objects. I read that it is possible to process data in different modes, byob and object mode. Unfortunately, I cannot seem to find any examples demonstrating that scenario. I don't want to have to read a byte buffer, decode and then process the result JSON if there is a way to get the chunks as objects instead? I can see the data in my browser but when I assign it to a Product object in my UI, no products show. Here is my sample code:
interface Product {
productId: string,
productName: string,
barcode: string,
quantityOnHold: number
}
Here is how I am attempting to use the Fetch API. I'd like to use the highWaterMark that I read about but don't know how to use to return objects.
const retrieveProducts = async () => {
const products = [];
const productBody ={
category: 'fragrance',
productCodes: [122222, 555555, 444444444]
}
const response = await fetch('/products', {
method: 'POST',
body: JSON.stringify(productBody)
})
const readableStream = response.body.pipeThrough(new TextDecoderStream());
readableStream.pipeTo(new WritableStream({
write(chunk) {
console.log("Chunk received", chunk);
products.push(JSON.parse(chunk)); // does not work!!
// I want to be able add products
// i.e. products.push(....)
},
close() {
console.log("All products successfully processed!");
},
abort(e) {
console.error("An error occurred!", e);
}
}));
}

Modify deeply nested data in Apollo client query result

I'm using #apollo/client to fetch data from Storyblok's API.
I want to iterate over all the fields in the returned data to potentially sanitize its content (it contains raw HTML). Because Apollo makes the result data immutable, I'm not able to do this:
const client = new ApolloClient({
uri: "https://gapi.storyblok.com/v1/api",
cache: new InMemoryCache(),
headers: {
token: "EXAMPLE",
},
});
function sanitizeFields(obj) {
for (const field of obj) {
if (field?.type === "custom-rich-text") {
field.content = sanitizeContent(field.content); // 💥 This generates an error because I'm trying to modify a read only object
}
if (Array.isArray(field) || isPlainObject(field)) {
sanitizeFields(field);
}
}
}
const postData = await client.query({ query: MY_QUERY });
sanitizeFields(postData?.data?.Page);
How can I modify deeply nested data returned by #apollo/client without knowing the particular field name beforehand?

Apollo GraphQL merge cached data

I have a page that consists of 2 components and each of them has its own request for data
for example
<MovieInfo movieId={queryParamsId}/>
const GET_MOVIE_INFO = `gql
query($id: String!){
movie(id: $id){
name
description
}
}`
Next component
<MovieActors movieId={queryParamsId}/>
const GET_MOVIE_ACTORS = `gql
query($id: String!){
movie(id: $id){
actors
}
}`
For each of these queries I use apollo hook
const { data, loading, error } = useQuery(GET_DATA, {variable: {id: queryParamsId}}))
Everything is fine, but I got a warning message:
Cache data may be lost when replacing the movie field of a Query object.
To address this problem (which is not a bug in Apollo Client), either ensure all objects of type Movie have IDs, or define a custom merge function for the Query.movie field, so InMemoryCache can safely merge these objects: { ... }
It's works ok with google chrome, but this error affects Safari browser. Everything is crushing. I'm 100% sure it's because of this warning message. On the first request, I set Movie data in the cache, on the second request to the same query I just replace old data with new, so previous cached data is undefined. How can I resolve this problem?
Here is the same solution mentioned by Thomas but a bit shorter
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
// shorthand
merge: true,
},
},
},
},
});
This is same as the following
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing, incoming, { mergeObjects }) {
return mergeObjects(existing, incoming);
},
},
},
},
},
});
Solved!
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing = [], incoming: any) {
return { ...existing, ...incoming };
// this part of code is depends what you actually need to do, in my
case i had to save my incoming data as single object in cache
}
}
}
}
}
})
});
The other answers still work, but as of Apollo Client >= 3.3 there's an easier option that doesn't require specifying specific fields or a custom merge function. Instead, you only have to specify the type and it will merge all fields for that type:
const cache = new InMemoryCache({
typePolicies: {
YOUR_TYPE_NAME: {
merge: true,
}
}
});
From your example query, I'd guess that an id field should be available though? Try requesting the ID in your query, that should solve the problem in a much more ideal way.
Had same issue with inconsistency of data values vs. our schema. A value type within an entity was missing the id value. Caused by an incomplete data migration.
Temporary solution:
const typePolicies = {
PROBLEM_TYPE: {
keyFields: false as false,
},
PARENT_TYPE: {
fields: {
PROBLEM_FIELD: {
merge: true
}
}
}
}

passing in json variable into sendgrid's dynamic_template_data

I'm having some trouble passing into a variable that holds a json object into sendgrid's dynamic_template_data. My setup looks like this:
const send = async (address, mentions) => {
console.log('mentions json obj', mentions)
let name = "john"
try {
let config = {
headers: {
Authorization: `Bearer ${process.env.sendgridKey}`,
}
}
let data = {
personalizations: [
{
to: [
{
email: `${address}`,
},
],
dynamic_template_data: {
name: name,
allMentions: mentions
}
}
],
from: {
email: "email#email.com",
name: "Mentionscrawler Team"
},
template_id: process.env.template_id,
}
await axios.post("https://api.sendgrid.com/v3/mail/send", data, config)
} catch (error) {
console.error(error, 'failing here>>>>>>>')
}
}
when I console.log mentions, which is json, and paste the code I get from the terminal directly into the allMentions key, it works. but when I just pass in mentions itself, nothing shows up on the sent email. I've been very confused the last few hours why this is happening. Any advice appreciated.
edit: i should also note that allmentions is an object with keys that hold arrays. So I'm looking to iterate over those arrays. Again, this totally all works if I just paste in directly what mentions is, but passing in mentions is giving me an issue.
Thank you very much,
just realized what was wrong. sendgrid's template requires a json object, so I assumed that I needed to use json.stringify on my mentions obj. Turns out I didn't need to do that, as long as all values are in string format.

fastify and ajv schema validation

I am trying to validate the querystring parameter 'hccid' as shown below. Seems like the validation is not working for me. Can some one see what I am missing?
const fastify = require('fastify')({
ajv: {
removeAdditional: true,
useDefaults: true,
coerceTypes: true
}
});
const schema = {
querystring: {
hccid: { type: 'string' }
}
};
// Declare a route
fastify.get('/hello', {schema}, function (request, reply) {
const hccid = request.query.hccid;
reply.send({ hello: 'world' })
});
// Run the server!
fastify.listen(3000, function (err) {
if (err) throw err
console.log(`server listening on ${fastify.server.address().port}`)
});
So with that code, I should get a schema validation exception when I call the service with a total new queryparam abc just like I shown below
http://localhost:3000/hello?abc=1
but there was no error. I got the response back {"hello":"world"}
I also tried removing the queryparam all together http://localhost:3000/hello
and I still got {"hello":"world"}
so obviously the validation is not working. What is missing in my code? any help would be appreciated.
this schema structure solved my problem. Just in case if someone wants to check it out if they run into similar issue.
const querySchema = {
schema: {
querystring: {
type: 'object',
properties: {
hccid: {
type: 'string'
}
},
required: ['hccid']
}
}
}
According to the docs, you can use the querystring or query to validate the query string and params to validate the route params.
Params would be:
/api/garage/:id where id is the param accessible request.params.id
/api/garage/:plate where plate is the param accessible at request.params.plate
Example for param validation would be:
const getItems = (req, reply) => {
const { plate } = req.params;
delete req.params.plate;
reply.send(plate);
};
const getItemsOpts = {
schema: {
description: "Get information about a particular vehicle present in garage.",
params: {
type: "object",
properties: {
plate: {
type: "string",
pattern: "^[A-Za-z0-9]{7}$",
},
},
},
response: {
200: {
type: "array",
},
},
},
handler: getItems,
};
fastify.get("/garage/:plate", getItemsOpts);
done();
Query / Querystring would be:
/api/garage/id?color=white&size=small where color and size are the two querystrings accessible at request.query.color or request.query.size .
Please refer to the above answer as an example fro query validation.
Validation
The route validation internally relies upon Ajv v6 which is a
high-performance JSON Schema validator. Validating the input is very
easy: just add the fields that you need inside the route schema, and
you are done!
The supported validations are:
body: validates the body of the request if it is a POST, PUT, or PATCH method.
querystring or query: validates the query string.
params: validates the route params.
headers: validates the request headers.
[1] Fastify Validation: https://www.fastify.io/docs/latest/Validation-and-Serialization/#validation
[2] Ajv#v6: https://www.npmjs.com/package/ajv/v/6.12.6
[3] Fastify Request: https://www.fastify.io/docs/latest/Request/

Categories

Resources