How to update the only changed fields in Firestore? - node.js

In the users collection in Firestore, I have all users' uid as documents, inside each user document I am storing user preferences.
For example, here's a sample of user preferences I am saving in a specific user document:
{
"preferences": {
"settings": {
"themeColorMode": "light-mode",
"debugMode": false
},
"filterChips": {
"pathName": {
"filterChipsPreferences": true
}
}
}
}
I want to update user document with the data sent in the body of an API
I want that API to be compatible in a way such that
I should be able to add another root node other than preferences
I should be able to customize and add new nodes in preferences.settings & preferences.filterChips
I should be able to update a specific node - examples: preferences.settings.themeColorMode& preferences.filterChips.filterChipsPreferences
For example, in the request body I am sending this info:
{
"preferences": {
"settings": {
"themeColorMode": "dark-mode",
"isSoundNotificationOn": false,
"isAppListeningToStream": true
},
"filterChips": {
"pathName": {
"filterChipsPreferences": false
},
"FirstUsedSearch": "23/12/2021"
},
"columnDefs": {
"pathName": {
"ColumnDefsPreferences": true
}
},
},
"search": {
"savedSearches":["searchName"]
}
}
I am expecting this result to be saved in the user's document
{
"preferences": {
"settings": {
"themeColorMode": "dark-mode",
"isSoundNotificationOn": false,
"isAppListeningToStream": true,
"debugMode": false
},
"filterChips": {
"pathName": {
"filterChipsPreferences": false
},
"FirstUsedSearch": "23/12/2021"
},
"columnDefs": {
"pathName": {
"ColumnDefsPreferences": true
}
},
"search": {
"savedSearches":["searchName"]
}
}
}
How could I approach that?

Since you are using the Node.js Admin SDK on the server that calls Firestore, it is actually quite straightforward: you need to use the update() method of the DocumentReference.
The update() method accepts either an object with field paths encoded
as keys and field values encoded as values, or a variable number of
arguments that alternate between field paths and field values.
More precisely:
// 1/ Define your DocumentReference
const userId = ...;
const docRef = admin.firestore().doc(`users/${userId}`);
// 2/ Get the desired elements from the payload received by your API and use them to build the object to pass to the update method
// For example
const updateObj = {
preferences.settings.debugMode: false,
preferences.settings.themeColorMode: "myColor",
preferences.filterChips.filterChipsPreferences: "myPref",
aNewRootNode: {
foo: 'bar',
bar: 'foo'
}
}
await docRef.update(updateObj);
More info on how to update fields in nested objects can be found in the doc of the JS SDK.

Related

CouchDB count all documents and return a number?

We have a college project in CouchDB and I'm using node, I want to create a view that returns a number of all my documents by email.
I cannot find anything that works and I'm not sure what I'm missing, I tried a lot of different reduce functions and emit methods.
Thanks for any answers.
The documents have 2 fields, name and email
Do not use the db endpoint because the response field doc_count includes design documents along with other documents that may not have an email field.
A straight forward way to do this is with a view. The code snippet demonstrates the difference between db info doc_count and a view's total_rows using PouchDB. I'd guess there's probably more interesting uses for the index.
The design doc is trivial
{
_id: '_design/my_index',
views: {
email: {
map: function(doc) {
if (doc.email) emit(doc.email);
}.toString()
}
}
}
And the view query is very efficient and simple.
db.query('my_index/email', {
include_docs: false,
limit: 0
})
const gel = id => document.getElementById(id);
let db;
function setJsonToText(elId, json) {
gel(elId).innerText = JSON.stringify(json, undefined, 3);
}
async function view() {
// display db info
setJsonToText('info', await db.info());
// display total number or rows in the email index
const result = await db.query('my_index/email', {
include_docs: false,
limit: 0
});
setJsonToText('view', result);
}
// canned test documents
function getDocsToInstall() {
return [{
email: 'jerry#garcia.com',
},
{
email: 'bob#weir.com',
},
{
email: 'phil#lesh.com'
},
{
email: 'wavy#gravy.com'
},
{
email: 'samson#delilah.com'
},
{
email: 'cosmic#charlie.com'
},
// design doc
{
_id: '_design/my_index',
views: {
email: {
map: function(doc) {
if (doc.email) emit(doc.email);
}.toString()
}
}
}
]
}
// init example db instance
async function initDb() {
db = new PouchDB('test', {
adapter: 'memory'
});
await db.bulkDocs(getDocsToInstall());
};
(async() => {
await initDb();
await view();
})();
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb-7.1.1.min.js"></script>
<script src="https://github.com/pouchdb/pouchdb/releases/download/7.1.1/pouchdb.memory.min.js"></script>
<pre>Info</pre>
<pre id='info'></pre>
<div style='margin-top:2em'></div>
<pre>email view</pre>
<pre id='view'>
</pre>
You can use GET /{db}, which returns information about the specified database. This is a JSON object that contains the property doc_count.
doc_count (number) – A count of the documents in the specified database.
With Angular for example, this could be done with the following method:
async countDocuments(database: string): Promise<number> {
return this.http.get<any>(this.url('GET', database), this.httpOptions).toPromise()
.then(info => info['doc_count']);
}
Assumption:
Assuming that following documents are present in the Customers database:
[
{
"_id": "93512c6c8585ab360dc7f535ff00bdfa",
"_rev": "1-299289ee89275a8618cd9470733035f4",
"name": "Tom",
"email": "tom#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff00c930",
"_rev": "1-a676883d6f1b5bce3b0a9ece92da6964",
"name": "Tom Doe",
"email": "tom#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff00edc0",
"_rev": "1-09b5bf64cfe66af7e1134448e1a328c3",
"name": "John",
"email": "john#domain.com"
},
{
"_id": "93512c6c8585ab360dc7f535ff010988",
"_rev": "1-88e347af11cfd1e40e63920fa5806fd2",
"name": "Alan",
"email": "alan#domain.com"
}
]
If I understand your query correctly, then based on above data, You need below given result set.
{
"tom#domain.com": 2,
"alan#domain.com": 1,
"john#domain.com": 1
}
Solution:
In order to achieve above, Consider following design document containing a View which has Map and Reduce functions.
{
"_id": "_design/Customers",
"views": {
"by-email": {
"map": "function (doc) {
if(doc.email){
emit(doc.email, doc._id);
}
}",
"reduce": "_count"
}
},
"language": "javascript"
}
The above view function emits value of the key email of the document if the key exists in the document.
The reduce function _count is a built in reducer (provided by CouchDB) that does the counting logic.
Executing View Query:
In order to query this view, you need to: select the view function, mark reduce to be executed (as it is optional to run reduce) and set 1 as group level.
Here is how you can do it through the UI:
Result:
Here is the result given by above query:
[![result of map reduce query
Hope this helped.
For more details about other reduce functions and group level, please refer CouchDB documentation.
Cheers.

How to upsert a Hashmap into MongoDB Document?

I'm trying to structure a friendslist using MongoDB.
This is how I currently have it structured:
{
"_id": {
"$oid": "601c570da04b75fabcd2705d"
},
"user_id": 1,
"friendslist": {
3 : true
}
}
How can I create a query that does a put on my Hashmap (The hashmap is "friendslist") and the key is 3 for the friend's id and the true flag is for the status of the pending friend request. (True meaing they are friends and false would be pending friend request)
This is what I currently have:
const upsertFriendId = db.collection('friends')
.updateOne(
{ user_id: userId },
{ $set: { ???? : ????} },
{ upsert: true }
);
I can't figure out the syntax that I need to put in the $set object.
In-order to ensure that you do not override the rest of the keys inside the friendlist, you can do an update like
const mongoFriendKey = `friendlist.${/* FRIEND_ID_VARIABLE_GOES_HERE */}`;
const upsertFriendId = db.collection('friends')
.updateOne(
{
user_id: userId
},
{
$set:
{
[mongoFriendKey] : true
}
},
{ upsert: true }
);
In the above, replace the /* FRIEND_ID_VARIABLE_GOES_HERE */ with the friend id variable. Doing that, the query will look for a friend with that specific Id, and then it will try to set it to true if not found, without overriding the entire friendlist object of yours.
Also, I hope you know what upsert does, if the update won't find the doc, then it will end up inserting one.

how to push value in to object's property dynamically in mongoose?

I want to push the value into array based on the property value recieved from frontend using mongoose.
Schema:
authencity: {
fake: [],
notSure: [],
authentic: [],
}
Now I want to push the value into array based on property value received from frontend
const result = await News.findOneAndUpdate({ name: newsName }, { $push: { `authencity[${authencityType}]`: email } });
authencityType could be anything like fake, notSure, authentic based on this value I want to insert the value into respective array.
Sample Data:
Sample Document :{
"_id": {
"$oid": "5f0af1e09a724b06c86bfaec"
},
"authencity": {
"fake": [],
"notSure": [],
"authentic": []
},
"name": "Rajasthan deputy CM Sachin Pilot\u2019s supporters come to Gurugram resort, call police summon a joke",
"description": "",
"url": "https://news.google.com/__i/rss/rd/articles/CBMiqwFodHRwczovL3d3dy5oaW5kdXN0YW50aW1lcy5jb20vaW5kaWEtbmV3cy9yYWphc3RoYW4tZGVwdXR5LWNtLXNhY2hpbi1waWxvdC1zLXN1cHBvcnRlcnMtY29tZS10by1ndXJ1Z3JhbS1yZXNvcnQtY2FsbC1wb2xpY2Utc3VtbW9uLWEtam9rZS9zdG9yeS1ZQ1BQVDFlandtb1RoQXJaRXVabmNOLmh0bWzSAa0BaHR0cHM6Ly9tLmhpbmR1c3RhbnRpbWVzLmNvbS9pbmRpYS1uZXdzL3JhamFzdGhhbi1kZXB1dHktY20tc2FjaGluLXBpbG90LXMtc3VwcG9ydGVycy1jb21lLXRvLWd1cnVncmFtLXJlc29ydC1jYWxsLXBvbGljZS1zdW1tb24tYS1qb2tlL3N0b3J5LVlDUFBUMWVqd21vVGhBclpFdVpuY05fYW1wLmh0bWw?oc=5",
"category": "general",
"language": "en",
"country": "in",
"__v": 0
}
Received Data : email : abcd#gmail.com authencityType could be fake or notSure or authentic
I want to push value in specific array based on authencityType recieved
You need to define your path using the dot notation instead of square brackets:
`authencity.${authencityType}`
For instance authenticity.fake
try:
const result = await News.findOneAndUpdate({ name: newsName }, { $push: { `authencity.${authencityType}`: email } }, { new: true });
Also note that in mongoose's findOneAndUpdate version you need to pass new: true parameter in order to return modified document
const newColumn = `authencity.${authencityType}`;
const result = await News.findOneAndUpdate({ name: newsName }, { $push: { [newColumn]: email } }, { new: true });
Here, I have created a dynamic string to construct a column name using template literal and then enclosed it with [], so mongo will consider this a column name

Node.js, mongodb and filtered queries

I'm using the native API of mongodb and I'm trying to query the data on my collection.
This is my filter object:
{
email: 'admin#email.it',
login: { '$exists': true }
}
and this is one document that it should find:
{
"_id": "5829cd89a48a7813f0cc7429",
"timestamp": "2016-11-14T14:43:18.705Z",
"login": {
"clientIPaddr": "::1",
"clientProxy": "none"
},
"userData": {
"sessdata": {
"sessionID": "CRTZaqpaUs-ep0J6rvYMBlQTdDakGwle",
"email": "admin#email.it",
"token": "3PlfQBVBoftlIpl-FizeCW5TbYMgcYTl4ZPTkHMVyxqv-TldWb_6U3eusJ27gtI64v7EqjT-KPlUUwkJK7hPnQ"
}
}
}
But the query doesn't return anything! Why?
It doesn't return anything because the email field is in an embedded document within the userData field, hence it tries to look for an email field at a higher level within the document that does not exist.
To make this work, you need to modify the filter or create a new query object which includes the embedded field, albeit the key will be in dot notation field i.e. the query should resemble
{
"userData.sessdata.email": "admin#email.it",
"login": { "$exists": true }
}
You can use the bracket notation to create the required field. For example:
var filter = {
email: 'admin#email.it',
login: { '$exists': true }
},
query = {};
Object.keys(filter).forEach(function(key){
if (key === "email") {
query["userData.sessdata."+key] = filter[key];
} else {
query[key] = filter[key];
}
});
console.log(JSON.stringify(query, null, 4));
Output
{
"userData.sessdata.email": "admin#email.it",
"login": {
"$exists": true
}
}
You can then use the query object in your find() query
collection.find(query).toArray(function(err, docs) {
// access the docs array here
})

Set field if doesn't exist, push to array, then return document

I was hoping to do this in one operation, with just hitting the database once... but I don't know if it's possible with the api's.....
what I want is to:
find the document by id(which always will exist)
add object if it doesn't already exist { dayOfYear: 3, dataStuff: [{time:
Date(arg), data: 123] }
push {time: Date(arg), data: 123] } to dataStuff array
return modified document
I cooked up something along the lines of
return this.collection.findOneAndUpdate(dataDoc,
{ $set: { dayOfYear: reqBody.dayOfYear ,
$addToSet: { dataStuff: { time: Date(reqBody.date), data: reqBody.data }
}
but no success
The update object needs "seperate" top level keys for each atomic operation:
return this.collection.findOneAndUpdate(
dataDoc,
{
"$set": { dayOfYear: reqBody.dayOfYear },
"$addToSet": {
"dataStuff": { "time": Date(reqBody.date), "data": reqBody.data }
}
},
{ "returnOriginal": false }
)
With .findOneAndUpdate() from the core API you also need to set the "returnOrginal" option to false in order to return the modified document. With the mongoose API, it is { "new": true } instead.
In this syntax, both calls are returning a "Promise" to be resolved, and not just a direct response.
If you want to check if the whole object exists, you'll have to compare all the properties, like
this.collections.update({
_id: dataDoc,
dayOfYear: {
$ne: reqBody.dayOfYear
},
dataStuff: {
$elemMatch: {
time: {
$ne: Date(arg)
},
data: {
$ne: reqBody.data
}
}
}
}, {
$set: {
dayOfYear: reqBody.dayOfYear,
},
$addToSet: {
dataStuff: {
time: Date(arg),
data: reqBody.data
}
}
});
This way, you ensure that you always update one or zero collection items. The first argument is the query that either returns no elements or a single element (because _id is there), and if it returns one element, it gets updated. Which is, I believe, exactly what you need.

Resources