How do I dynamically update the fields of a document? - node.js

I'm building a basic crud app for storing movie/tv titles. I am using version 2.4.4 of elasticsearch because my project manager chose this version. I am using the express framework along with the native elasticsearch nodejs driver.
Currently this is what my mappings look like for the "titles" index for a "title" type.
client.indices.create({
index: "titles",
body: {
"mappings": {
"title": {
"properties": {
"seriesName": { "type": "string", "index": "not_analyzed" },
"seriesEpisodeNumber": { "type": "long", "index": "not_analyzed" },
"seriesEpisodeTitle": { "type": "string", "index": "not_analyzed" },
"isDeleted": { "type": "boolean", "index": "not_analyzed" }
}
}
}
}
}).
I want to have an endpoint on my server that updates the document by its id. This is what I have so far for this endpoint " POST /api/titles/:titleID/updateTitleByID"
titlesController.updateTitleByID = function(req,res){
const titleID = req.params.titleID;
es.updateByQuery({
index: "titles",
type: "title",
body: {
query: {
bool: {
must: [{
term: {
_id: titleID
}},{
term: {
isDeleted: false
}}]
}
},
script: {
inline: "ctx._source.seriesName = seriesName;ctx._source.seriesEpisodeNumber = seriesEpisodeNumber; ctx._source.seriesEpisodeTitle = seriesEpisodeTitle;",
params: { ...req.body }
}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
What I want to achieve is to update the document with what ever I get from my req.body. For example if I get a seriesName I from the request body. That is only what I want to update and leave the rest untouched. How do I achieve this?
Edit: Here is the full stack trace
Error: [remote_transport_exception] [Agon][127.0.0.1:9300][indices:data/write/update[s]]
at respond (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:289:15)
at checkRespForFailure (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/transport.js:248:7)
at HttpConnector.<anonymous> (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/elasticsearch/src/lib/connectors/http.js:164:7)
at IncomingMessage.wrapper (/home/natealcedo/Projects/digitalfolks.hooq/node_modules/lodash/lodash.js:4968:19)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:186:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:74:11)
at process._tickDomainCallback (internal/process/next_tick.js:122:9)

You don't need to use the update-by-query endpoint if you want to update a single document, the Update API is more than sufficient since it supports partial updates, which is exactly what you're after. You need to use the update call instead:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
doc: partialFields
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you need to only update the document if the isDeleted flag is false, then you can use the same API call but with a script instead, like this:
titlesController.updateDocByID = function(req,res){
const titleID = req.params.titleID;
const partialFields = req.body;
// TODO do some validations on the received fields
es.update({
index: "titles",
type: "title",
id: titleID,
body: {
script: "if (!ctx._source.isDeleted) { ctx._source.putAll(params.partialFields) }",
params: {partialFields: partialFields}
}}).then(results => {
logger.info(results);
res.status(200).json({
message: "OK"
});
}).catch(err => {
logger.error(err);
res.status(500).json({
error: "Internal server error"
});
});
};
If you use this second method, you'll need to enable dynamic scripting in your elasticsearch.yml configuration file.

Related

AJV validation doesn't return multiple errors when different values are missing in the fastify request body

I have a fastify server with a post endpoint. It accepts a JSON request body with a validation. Server code below:
const fastify = require("fastify");
const server = fastify({
ajv: {
customOptions: {
allErrors: true,
},
},
logger: true,
});
const schema = {
schema: {
body: {
type: "object",
properties: {
data: {
type: "array",
items: {
type: "object",
properties: {
foo: {
type: "string",
},
bar: {
type: "string",
},
},
required: ["foo", "bar"],
},
},
},
required: ["data"],
},
},
};
server.post("/", schema, function (request, reply) {
console.log({
request: {
body: JSON.stringify(request.body),
},
});
reply.send({ message: "hello" });
});
server.listen(3000, function (err, address) {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`server listening on ${address}`);
});
Below is a valid request body.
{ "data":[{ "bar": "bar exists", "foo": "foo exists" }]}
When I try to access the same server with multiple values in input missing i.e.,
{ "data":[{ "bar": "bar exists, foo missing" }, {}] }
I am getting the below response.
{
"statusCode": 400,
"error": "Bad Request",
"message": "body.data[0] should have required property 'foo', body.data[1] should have required property 'foo', body.data[1] should have required property 'bar'"
}
I want to get each error separately, instead of getting a single large error message as this request can go very large. I have tried a bit of trial around the ajv options but couldn't find anything.
Any help is appreciated. Cheers :)
You need to have a custom parser after the error is caught.
In order to achieve this approach, there is a method called setErrorHandler.
According to Fastify documentation:
Set a function that will be called whenever an error happens.
This is a simple parser, but you may need to change it to your taste:
server.setErrorHandler(function (error, request, reply) {
if (error.validation) {
reply.status(400).send({
statusCode: error.statusCode,
error: 'Bad Request',
message: error.validation.map(err => `${err.instancePath} ${err.message}`)
});
}
})
// rest of code

How to query a json in postgres using Op.iLike in sequelize?

My table called 'notifications' has the following columns/attributes :
targetPage -> integer
notificationDescription -> JSONB
Here notificationDescription has 3 attributes in it i.e title, body and media.
So I need to query data that is in title and body so I did the below code:
await this.customerNotificationRepository.findAndCountAll({
where: {
notificationDescription: {
title: {
[Op.iLike]: `%${searchKey}%`,
},
body: {
[Op.iLike]: `%${searchKey}%`,
},
},
}
});
The 'searchKey' in the above code is taken from queryParams.
I always get the empty as result even if I search with the key the is present in the database
{
"code": 200,
"message": "Notifications fetched successfully",
"data": []
}
You can use [Op.contains] on jsonb in postgres. Try something like,
await this.customerNotificationRepository.findAndCountAll({
where: {
notificationDescription: {
[Op.contains]: {
title: {
[Op.iLike]: `%${searchKey}%`,
},
body: {
[Op.iLike]: `%${searchKey}%`,
},
}
},
}
});

Firebase database collection returns empty array when trying to get all documents

I'm trying to get all documents from my database collection "posts" but I'm getting an empty array instead.
The strange thing is that I'm able to get all documents from another collection called "users" that has the same structure and using the exact same code.
I've spent days looking for an answer but I haven't been able to find the solution.
This is the request:
const db = admin.firestore();
exports.getAllPosts = (req, res) => {
db.collection('posts')
.orderBy('createdAt', 'desc')
.get()
.then(snapshot => {
let posts = [];
snapshot.forEach((doc) => {
posts.push({
id: doc.id,
body: doc.data().body,
author: doc.data().author,
createdAt: doc.data().timestamp,
voteScore: doc.data().voteScore
});
});
return res.json(posts);
})
.catch(err => {
console.error(err);
res.status(500).json({ error: err.code });
});
}
And this is the response:
[]
This is what my current collection looks like:
Posts collection screenshot
This the response that I get when I return "snapshot":
{
"_query": {
"_firestore": {
"_settings": {
"projectId": "readable-bf7a6",
"firebaseVersion": "9.6.0",
"libName": "gccl",
"libVersion": "4.10.0 fire/9.6.0"
},
"_settingsFrozen": true,
"_serializer": {
"allowUndefined": false
},
"_projectId": "readable-bf7a6",
"registeredListenersCount": 0,
"bulkWritersCount": 0,
"_backoffSettings": {
"initialDelayMs": 100,
"maxDelayMs": 60000,
"backoffFactor": 1.3
},
"_clientPool": {
"concurrentOperationLimit": 100,
"maxIdleClients": 1,
"activeClients": {},
"failedClients": {},
"terminated": false,
"terminateDeferred": {
"promise": {}
}
}
},
"_queryOptions": {
"parentPath": {
"segments": []
},
"collectionId": "posts",
"converter": {},
"allDescendants": false,
"fieldFilters": [],
"fieldOrders": [
{
"field": {
"segments": [
"createdAt"
]
},
"direction": "DESCENDING"
}
],
"kindless": false
},
"_serializer": {
"allowUndefined": false
},
"_allowUndefined": false
},
"_readTime": {
"_seconds": 1622395245,
"_nanoseconds": 513743000
},
"_size": 0,
"_materializedDocs": null,
"_materializedChanges": null
}
Notice how the request for the collection "users" works successfully:
const db = admin.firestore();
exports.getAllUsers = (req, res) => {
db.collection('users')
.orderBy('createdAt', 'desc')
.get()
.then(snapshot => {
let users = [];
snapshot.forEach((doc) => {
let users = [];
snapshot.forEach((doc) => {
users.push({
id: doc.data().userId,
email: doc.data().email,
handle: doc.data().handle
});
});
return res.json(users);
})
.catch(err => {
console.error(err);
res.status(500).json({ error: err.code });
});
}
And the response:
[
{
"id": "EPoHBxhQFUXbcL3TCVx1LdUG2nO2",
"email": "ruben#gmail.com"
},
{
"id": "RqEa3dEq8TSDcZYeolXafju67rB2",
"email": "user10#gmail.com"
},
{
"id": "dxveb4n2iMQej5Q14uprsKRxFp23",
"email": "user4#gmail.com",
"handle": "user4"
},
{
"id": "YQPzBPcsqlVZk9iJEuZTHKUNuVG2",
"email": "user2#gmail.com",
"handle": "user2"
},
{
"id": "CZ05BJxi3TUOpIrmBaz539OWlbC3",
"email": "user#gmail.com",
"handle": "user"
},
{
"id": "t0t83BVwt4gVgJkDv7HL1r1MaKr1",
"email": "userJose2#gmail.com",
"handle": "Jose"
}
]
This is what the users collection looks like in Firebase:
Users collection screenshot
Why is one collection failing when the other works fine and I'm using the same code? What am I missing here?
Thanks in advance and I hope I've made it as clear as possible. Please let me know if you need me to provide anything else.
Very simple my friend, your posts documents can't be ordered like this:
.orderBy('createdAt', 'desc')
Because the post documents does not have the createdAt property, but they have a timestamp property, you should use that property to order your posts like this:
.orderBy('timestamp', 'desc')
I hope that helps 👍
I am not answering directly to #Ruben Garcia Bri, but for future firebase developers who may run into the problem of getting empty documents, I also ran into the same problem, but I solved it by adding a field to the particular document I am trying to retrieve.
Sometimes the cause is because the documents you are trying to get have no field in them.
I mean that a document must have a field before the server can recognize it as an existing document.
So if you run into this problem, consider adding a field to that document before you can successfully retrieve it.

How to add values from coinmarketcap API to telegram message?

I'm trying to finish telegram bot that will after several commands respond with message... Lost any hope of trying that i can solve this alone. Those commands with predefined message are done and working like a charm.. But now im stucked on the /price command which should show coin value in the message from coinmarket API respond..
I tried many variants but following results always called for API Call error: or message like [object Object]..
ALQO: $0.0443407142 | 9.73% 🙂
ETH: 0.000313592 | 10.14% 🙂
BTC: 0.0000107949 | 9.5% 🙂
Cap: $2,545,718
This text above is correct respond from bot... Unfortunately with free API from CMC i can do only price with USD so correct answer should be
Coinname: Price | Change%
Cap: Marketcap
My code of /price command
//This is /price command code
'use strict';
const Telegram = require('telegram-node-bot');
const rp = require('request-promise');
const requestOptions = {
method: 'GET',
uri: 'https://pro-
api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?
id=3501&convert=USD',
headers: {
'X-CMC_PRO_API_KEY': 'MYFREEAPIKEYFROMCMC'
},
json: true,
gzip: true
};
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
}).catch((err) => {
console.log('API call error:', err.message);
});
class PriceController extends Telegram.TelegramBaseController {
PriceHandler($) {
rp(requestOptions).then(response => {
console.log('API call response:', response['data'][3501]);
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501]);
}).catch((err) => {
$.sendMessage('API call error:', err.message);
});
}
get routes() {
return {
'priceCommand': 'PriceHandler'
};
};
}
module.exports = PriceController;
Respond from API after node index.js (turning bot on, (message from visual studio terminal)
API call response: { id: 3501,
name: 'CryptoSoul',
symbol: 'SOUL',
slug: 'cryptosoul',
circulating_supply: 143362580.31,
total_supply: 499280500,
max_supply: null,
date_added: '2018-10-25T00:00:00.000Z',
num_market_pairs: 3,
tags: [],
platform:
{ id: 1027,
name: 'Ethereum',
symbol: 'ETH',
slug: 'ethereum',
token_address: '0xbb1f24c0c1554b9990222f036b0aad6ee4caec29' },
cmc_rank: 1194,
last_updated: '2019-04-01T23:03:07.000Z',
quote:
{ USD:
{ price: 0.000188038816143,
volume_24h: 11691.5261174775,
percent_change_1h: 0.29247,
percent_change_24h: 0.0222015,
percent_change_7d: 4.69888,
market_cap: 26957.72988069816,
last_updated: '2019-04-01T23:03:07.000Z' } } }
The messages that appears after /price command triggered
"API call error:"
"[object Object]"
"Error while running node index.js (bad code)"Chat with Bot
As I could see, you are incorrectly accessing the resulting json response object here:
$.sendMessage('Cryptosoul: price', response['data']['USD']['price']
[3501])
Just pretty printing that response object gives a correct way to access certain properties.
{
"status": {
"timestamp": "2019-04-02T08:38:09.230Z",
"error_code": 0,
"error_message": null,
"elapsed": 14,
"credit_count": 1
},
"data": {
"3501": {
"id": 3501,
"name": "CryptoSoul",
"symbol": "SOUL",
"slug": "cryptosoul",
"circulating_supply": 143362580.31,
"total_supply": 499280500,
"max_supply": null,
"date_added": "2018-10-25T00:00:00.000Z",
"num_market_pairs": 3,
"tags": [],
"platform": {
"id": 1027,
"name": "Ethereum",
"symbol": "ETH",
"slug": "ethereum",
"token_address": "0xbb1f24c0c1554b9990222f036b0aad6ee4caec29"
},
"cmc_rank": 1232,
"last_updated": "2019-04-02T08:37:08.000Z",
"quote": {
"USD": {
"price": 0.000201447607597,
"volume_24h": 12118.3983544441,
"percent_change_1h": 1.48854,
"percent_change_24h": 6.88076,
"percent_change_7d": 12.4484,
"market_cap": 28880.04882238228,
"last_updated": "2019-04-02T08:37:08.000Z"
}
}
}
}
}
So we could see that price field is located under USD object wich itself located under quote object, which is missing in your code.
Proper way to get it would be:
const price = response["data"][3501]["quote"]["USD"]["price"];
PriceHandler code:
PriceHandler($) {
rp(requestOptions)
.then((response) => {
const price = response["data"][3501]["quote"]["USD"]["price"];
$.sendMessage("Cryptosoul: price", price);
})
.catch((err) => {
console.error("API call error:", err.message);
});
}

FCM iOS: Push notifications throw invalid argument

I am attempting to implement pushNotifications using Cloud Functions and FCM for iOS but I am consistently thrown this error:
2018-05-21T13:04:00.087Z I sendPushNotifications: Error sending
message: { Error: Request contains an invalid argument.
at FirebaseMessagingError.Error (native)
at FirebaseMessagingError.FirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:39:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/user_code/node_modules/firebase-admin/lib/utils/error.js:85:28)
at new FirebaseMessagingError (/user_code/node_modules/firebase-admin/lib/utils/error.js:241:16)
at Function.FirebaseMessagingError.fromServerError (/user_code/node_modules/firebase-admin/lib/utils/error.js:271:16)
at /user_code/node_modules/firebase-admin/lib/messaging/messaging-api-request.js:149:50
at process._tickDomainCallback (internal/process/next_tick.js:135:7) errorInfo: { code:
'messaging/invalid-argument',
message: 'Request contains an invalid argument.' }, codePrefix: 'messaging' }
My implementation in cloud functions are as follows:
exports.sendPushNotifications = functions.database.ref('/conversations/{userUid}/').onWrite((snap, context) => {
const userUid = context.params.userUid
console.log("Triggered user ", userUid)
return admin.database().ref('/fcmToken/' + userUid).once('value', snapshot => {
const values = snapshot.val()
const fcmToken = values.fcmToken
var message = {
"token": fcmToken,
"notification": {
"body": "New message"
},
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": "1",
"sound": "default"
}
}
}
};
return admin.messaging().send(message)
.then((response) => {
return console.log('Successfully sent message:', response);
})
.catch((error) => {
return console.log('Error sending message:', error);
});
})
})
The frustrating thing is that when I remove the whole "apns" node the code actually works, ie I can receive the push notifications. I suppose this means my setup are all done properly. Once I included the "apns", it starts throwing the above error. I also reference these three posts, this, this and this, and made sure that I have carefully followed the code and instructions. For some reasons I cannot get it to work.
I also attempted to remove the "notification" node as the docs did mention that only use common keys when targetting all platforms. Since I am targetting only iOS for now, I suppose I should remove the "notification" key. But again it also throws the same error.
Ok, so it was a rookie mistake. It is correct that common keys should not be used if I am targetting iOS only. In addition to that, the badge should be an Int and not String.
This code worked:
var message = {
"token": fcmToken,
"apns": {
"headers": {
"apns-priority": "5"
},
"payload": {
"aps": {
"alert": {
"body": "New message"
},
"badge": 1,
"sound": "default"
}
}
}
}
Hope it helps anyone out there facing the same problem.
Just to add to this answer. If you are using both IOS and android while having custom sounds for both, the code below will work cross platform and avoid this issue.
const payload = {
token,
notification: {
title: `title text`,
body: `body text`,
},
android: {
// android
priority: "high", // legacy HTTP protocol (this can also be set to 10)
notification: {
channel_id: "call1",
priority: "high", // HTTP v1 protocol
notification_priority: "PRIORITY_MAX",
sound: "sound",
default_sound: true,
visibility: "PUBLIC",
},
},
apns: {
// apple
headers: { "apns-priority": "10" },
payload: {
aps: {
// If present with notification: {...}, this will create errors
// "alert": {
// "title": `title text`,
// "body": `body text`,
// },
badge: 1,
sound: {
critical: 1,
name: "sound.aiff",
volume: 1,
},
category: "call1",
},
},
},

Resources