Firebase Update resultset NodeJS - node.js

Why is it that I can't seem to update a result set. I have 1000 items and I query Firebase and get back a DataSnapShot array. I want to check each element of the array and if a condition exists, then change the "type" to a "C". I get no errors, yet the update does not appear to have been applied. I'm doing this in the promise that is returned and yet the update is "ignored".
var db = admin.database();
var ref = db.ref("/calendars");
ref.once("value", function(snapshot) {
snapshot.forEach(function(data) {
var obj = data.val();
if (someconditionIsTrue) {
var objToUpdate = `calendars\\${data.key}`;
console.log(`Should update ${objToUpdate}`);
var entryRef = db.ref("/calendars").child(data.key).child('type');
entryRef.transaction(function(t) {
console.log(`TYPE: ${t}`); // DISPLAYS NULL EVEN THOUGH THERE IS A CHAR IN DB
return "C"; // Should change TYPE in the record to a "C"
});
}
Also tried just to do a set inside the forEach at well but that does not work either (which is why I thought i had to use a transaction).

Frank answered this the best way:
Firebase Database transactions are compare-and-set. The callback will immediately be invoked with the best guess that the client has about the current value. Often this will be null. You tell it what the new value would be based on that, and your callback will eventually be invoked again with the updated guess to the current value. See stackoverflow.com/questions/33578887/… – Frank van Puffelen Nov 21 '18 at 16:21

Related

Retrieve a Reference value from one Firestore document then write it to another document reference field

I want to (1) retrieve a reference field value from a doc in collection_a and write it to a doc in collection_b. However, I get a full DocumentReference object with lots of info rather than just the document reference path/string I want.
// Function executes {onCreate} of a new Firestore document in {collection_a}
exports.createNotifications = functions.firestore
.document(`collection_a/{docId}`)
.onCreate(async (snap: any, context: any) => {
const newRecord = snap.data();
// Retrieve {myReferenceField} from {collection_a} collection.
const myReferenceField: string = newRecord.myReferenceField
// Query {collection_b} collection for docs where {myReferenceField} = the previously retrieved reference (above).
const collection_b_ref = db.collection('collection_b');
const collection_b_snapshot = await collection_b_ref.where('myReferenceField', '==', myReferenceField).get();
// Loop through retrieved documents from {collection_b}, writing the retrieved reference to the {myTargetReferenceField}.
collection_b_snapshot.forEach((doc: any) => {
var newDocumentWrite = {
myTargetReferenceField: db.doc('target_collection/' + myReferenceField),
}
return admin.firestore().collection('notifications').add(newDocumentWrite)
});
}
});
The myTargetReferenceField: db.doc('target_collection/' + myReferenceField) line returns an object of myTargetReferenceField: target_collection/[object Object] rather than just the reference path of target_collection/123456sample_ref123456 I want.
My console shows myTargetReferenceField as a full object (as below) rather than just the slash-delimited reference which is what I want.
Console Output for {MyTargetReferenceField} - I only want the ref path
** See the {_path: ResourcePath} in output below (replaced {teams} for purpose of this ticket **
I'm not exactly sure of your question, so will answer the two most likely asks below.
If you want to get the path to the DocumentReference from a field, you can call the path property on it.
Firestore's get() operation always reads complete documents, not just some fields from it.
To get only some fields from a document, the Node.js SDK has a select method that you can call on either a Query or a CollectionReference.

CosmosDb Delete in Loop fails after one deletion

I have created a console app to delete documents from CosmosDB based on a partition that we have set up.
using (var client = new DocumentClient(new Uri(Config["CosmosEndpoint"]), Config["CosmosAuthkey"]))
{
var db = Options.Env.ToUpper().Equals("CI") ? Config["CiDatabase"] : Config["QaDatabase"];
var tenantString = $"{Options.Tenant}-{Options.Language}";
Log.Information($"Deleting {tenantString} from {db}-{Config["CosmosCollection"]}");
var query = client.CreateDocumentQuery<Document>(
UriFactory.CreateDocumentCollectionUri(db, Config["CosmosCollection"]),
"SELECT * FROM c",
new FeedOptions()
{
PartitionKey = new PartitionKey(tenantString)
}
).ToList();
Log.Information($"Found {query.Count} records for Tenant: {tenantString}");
if (query.Count > 0)
{
foreach (var doc in query)
{
Log.Information($"Deleting document {doc.Id}");
await client.DeleteDocumentAsync(doc.SelfLink,
new RequestOptions { PartitionKey = new PartitionKey(tenantString) });
Log.Information($"Deleted document {doc.Id}");
}
}
else
{
Log.Information($"Tenant: {tenantString}, No Search Records Found");
}
}
This never reaches the line Log.Information($"Deleted document {doc.Id}"); but also does not seem to throw an exception. I have wrapped the call in a try/catch for DocumentClient/ArgumentNull Exception in an attempt to see what it was but it just bombs on the delete call. It does, however, always delete one document.
This tells me that my config must be correct as I am connecting and querying and even deleting but not for all documents in the query. Even more strange is that I have copied this from another application that I wrote earlier where this code works.
Is there an upper limit on connecting meaning I need to delay my loop
some?
Why do I not see an exception when using a try/catch?
Or is there another reason that I only seem to be able to delete one document at a time with this code?

Firebase node.js remove item from dictionary

I can successfully add and fetch values from my firebase database.
firebase.initializeApp(config);
// Get a reference to the database service
var database = firebase.database();
var ref = database.ref();
var refer = database.ref('todo');
I have a todo list in my database, I wanted use an array but from what I understand firebase doesnt prefer arrays.
So I just add values to my todo list like
function handleAddTodoIntent(intent, session, response) {
refer.push(todoSlot.value);
}
and it works
But when I try to remove the items , it seems it doesnt do anything
refer.remove(); removes entire todo dictionary and refer.child(todoSlot.value).remove(); seems not deleting the value from database.
What is the correct way of doing this?
Your mistake is refer.child(todoSlot.value).
You are referencing the wrong path. child() should take the name of the field, not a value.
When you push a new child, you can get its key by following:
var todoSlotKey = refer.push(todoSlot.value);
And if you just want to remove the pushed todoSlot.value, you can simply do: refer.child(todoSlotKey).remove();

Field index for queries not updating when value set programmatically

My module creates a custom content item through the controller:
private ContentItem createContentItem()
{
// Add the field
_contentDefinitionManager.AlterPartDefinition(
"TestType",
cfg => cfg
.WithField(
"NewField",
f => f
.OfType(typeof(BooleanField).Name)
.WithDisplayName("New Field"))
);
// Not sure if this is needed
_contentDefinitionManager.AlterTypeDefinition(
"TestType",
cfg => cfg
.WithPart("TestType")
);
// Create new TestType item
var newItem = _contentManager.New("TestType");
_contentManager.Create(TestItem, VersionOptions.Published);
// Set the added boolean field to true
BooleanField newField = ((dynamic)newItem).TestType.NewField as BooleanField;
newField.Value = true;
// Set title (as date created, for convenience)
var time = DateTime.Now.ToString("MM-dd-yyyy h:mm:ss tt", CultureInfo.InvariantCulture).Replace(':', '.');
newItem.As<TitlePart>().Title = time;
return newItem;
}
The end result of this is a new TestType item with a field that's set to true. Viewing the content item in the dashboard as well as examining ContentItemVersionRecord in the database confirms that the value was set correctly.
However, queries don't seem to work properly on fields that are set in this manner. I found the record IntegerFieldIndexRecord, which is what I assume projections use to fill query result pages. On this, the value of TestField remains at 0 (false), instead of 1 (true).
Going to the content item edit page and simply clicking 'save' updates IntegerFieldIndexRecord correctly, meaning that the value is now picked up by the query. How can the record be updated for field values set programmatically?
Relevant section of migration:
SchemaBuilder.CreateTable(typeof(TestTypePartRecord).Name, table => table
.ContentPartRecord()
);
ContentDefinitionManager.AlterTypeDefinition(
"TestType",
cfg => cfg
.DisplayedAs("Test Type")
.WithPart(typeof(TitlePart).Name)
.WithPart(typeof(ContainablePart).Name)
.WithPart(typeof(CommonPart).Name)
.WithPart(typeof(IdentityPart).Name)
);
Edit: The fix for this is to manually change the projection index record whenever changing a field value, using this call:
_fieldIndexService.Set(testResultItem.As<FieldIndexPart>(),
"TestType", // Resolves as TestTypePart, which holds the field
"newField",
"", // Not sure why value name should be empty, but whatever
true, // The value to be set goes here
typeof(bool));
In some cases a simple contentManager.Publish() won't do.
I've had a similar problem some time ago and actually implemented a simple helper service to tackle this problem; here's an excerpt:
public T GetStringFieldValues<T>(ContentPart contentPart, string fieldName)
{
var fieldIndexPart = contentPart.ContentItem.As<FieldIndexPart>();
var partName = contentPart.PartDefinition.Name;
return this.fieldIndexService.Get<T>(fieldIndexPart, partName, fieldName, string.Empty);
}
private void SetStringFieldValue(ContentPart contentPart, string fieldName, IEnumerable<int> ids)
{
var fieldIndexPart = contentPart.ContentItem.As<FieldIndexPart>();
var partName = contentPart.PartDefinition.Name;
var encodedValues = "{" + string.Join("},{", ids) + "}";
this.fieldIndexService.Set(fieldIndexPart, partName, fieldName, string.Empty, encodedValues, typeof(string));
}
I've actually built this for use with MediaLibrary- and ContentPicker fields (they encode their value as string internally), so it might not be suitable for the boolean field in your example.
But it can't be that hard to implement, just look at the existing drivers and handlers for those fields.
There are 2 ways to fix this:
1) Ensure the newly created item is getting published by calling ContentManager.Publish() as Orchard.Projections.Handlers.FieldIndexPartHandler listens to the publish event to update the FieldIndexPartRecord
2) use IFieldIndexService to update FieldIndexPartRecord manually, see implementation of Orchard.Projections.Handlers.FieldIndexPartHandler to get in idea how to do this
Hope this helps.
:edit
Due to calling Create(...Published) the ContentManager.Published() won't do anything as the item is already considered published.
You can do the following to force the publish logic to run:
bool itemPublished = newItem.VersionRecord.Published;
// unpublish item first when it is already published as ContentManager.Publish() internally first checks for published flag and when set it aborts silently
// -> this behaviour prevents calling publish listeners
if (itemPublished)
_contentManager.Unpublish(newItem);
// the following call will result in calls to IContentHandler.Publishing() / IContentHandler.Published()
_contentManager.Publish(newItem);
or just create the item as a draft and publish it when everything is setup correctly.

MongoDB update object and remove properties?

I have been searching for hours, but I cannot find anything about this.
Situation:
Backend, existing of NodeJS + Express + Mongoose (+ MongoDB ofcourse).
Frontend retrieves object from the Backend.
Frontend makes some changes (adds/updates/removes some attributes).
Now I use mongoose: PersonModel.findByIdAndUpdate(id, updatedPersonObject);
Result: added properties are added. Updated properties are updated. Removed properties... are still there!
Now I've been searching for an elegant way to solve this, but the best I could come up with is something like:
var properties = Object.keys(PersonModel.schema.paths);
for (var i = 0, len = properties.length; i < len; i++) {
// explicitly remove values that are not in the update
var property = properties[i];
if (typeof(updatedPersonObject[property]) === 'undefined') {
// Mongoose does not like it if I remove the _id property
if (property !== '_id') {
oldPersonDocument[property] = undefined;
}
}
}
oldPersonDocument.save(function() {
PersonModel.findByIdAndUpdate(id, updatedPersonObject);
});
(I did not even include trivial code to fetch the old document).
I have to write this for every Object I want to update. I find it hard to believe that this is the best way to handle this. Any suggestions anyone?
Edit:
Another workaround I found: to unset a value in MongoDB you have to set it to undefined.
If I set this value in the frontend, it is lost in the REST-call. So I set it to null in the frontend, and then in the backend I convert all null-values to undefined.
Still ugly though. There must be a better way.
You could use replaceOne() if you want to know how many documents matched your filter condition and how many were changed (I believe it only changes one document, so this may not be useful to know). Docs: https://mongoosejs.com/docs/api/model.html#model_Model.replaceOne
Or you could use findOneAndReplace if you want to see the document. I don't know if it is the old doc or the new doc that is passed to the callback; the docs say Finds a matching document, replaces it with the provided doc, and passes the returned doc to the callback., but you could test that on your own. Docs: https://mongoosejs.com/docs/api.html#model_Model.findOneAndReplace
So, instead of:
PersonModel.findByIdAndUpdate(id, updatedPersonObject);, you could do:
PersonModel.replaceOne({ _id: id }, updatedPersonObject);
As long as you have all the properties you want on the object you will use to replace the old doc, you should be good to go.
Also really struggling with this but I don't think your solution is too bad. Our setup is frontend -> update function backend -> sanitize users input -> save in db. For the sanitization part, we use a helper function where we integrate your approach.
private static patchModel(dbDocToUpdate: IModel, dataFromUser: Record<string, any>): IModel {
const sanitized = {};
const properties = Object.keys(PersonModel.schema.paths);
for (const key of properties) {
if (key in dbDocToUpdate) {
sanitized[key] = data[key];
}
}
Object.assign(dbDocToUpdate, sanitized);
return dbDocToUpdate;
}
That works smoothly and sets the values to undefined. Hence, they get removed from the document in the db.
The only problem that remains for us is that we wanted to allow partial updates. With that solution that's not possible and you always have to send everything to the backend.
EDIT
Another workaround we found is setting the property to an empty string in the frontend. Mongo then also removes the property in the database

Resources