How to read and update the same document using angularfire2? - javascript

I am using angularfire2 to try and update my Firestore document. I have a reference that I use valueChanges and then subscribe to get the data, but then since I'm updating the data inside the function it will keep calling it since the values change. I figured a 'hack' solution using first() from rxjs/operators, however, there has to be a better way to do this. Below is the code I am trying to implement.
this.storeCollectionRef = this.afs.collection('storeInfo');
this.storeDocumentRef= this.storeCollectionRef.doc<StoreInfo>(window.localStorage.getItem("uid"));
this.storeDocumentRef.valueChanges().subscribe(data=>{
console.log(data.itemcount);
var pictures = storage().ref(`${userid}/${data.itemcount+1}`);
pictures.putString(image, "data_url").then(()=>{
pictures.getDownloadURL().then(url => {
this.storeInfo.itemcount = data.itemcount+1;
this.storeInfo.image = url;
this.storeDocumentRef.update(this.storeInfo);
})
});

Annoyingly, it's not included in the official AngularFire2 docs for working with Firestore documents. However, as you might know, AngularFire2 is just a wrapper around the original Firebase package - intended to make it easier to work with Firebase in an Angular environment. Checking out the docs for the original Firebase package provides this example of how to accomplish this goal with the basic Firebase package:
var docRef = db.collection("cities").doc("SF");
docRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
... which is pretty simple. If you were using the original Firebase package you could just get the reference to your document and call .get()... if only AngularFire2 included that functionality! Digging a little deeper, it DOES appear to wrap that method too. Looking at the source code for AngularFire2 Firestore documents, waaayyyyyy at the bottom, the very last method, after valueChanges(), is get()! So it looks like it IS SUPPORTED just not documented.
/**
* Retrieve the document once.
* #param options
*/
get(options?: firestore.GetOptions) {
return from(this.ref.get(options)).pipe(
runInZone(this.afs.scheduler.zone)
);
}
I'm not in a position to test this myself, so I can't promise it will work. Try it and let me know. As a last resort if you can't get AF2 to behave, you could skip using the AngularFire2 package for that particular feature and also import the original Firebase package to that component, and just reference the document using the Firebase package's method instead.
EDIT:
Incase it wasn't clear, I'm suggesting you try something like:
this.storeCollectionRef = this.afs.collection('storeInfo');
this.storeDocumentRef= this.storeCollectionRef.doc<StoreInfo>(window.localStorage.getItem("uid"));
this.storeDocumentRef.get().then( data => {
console.log(data.itemcount);
// ... etc...
// continue doing your picture uploading stuff here
// ...
});

Related

Mongoose Pre and Post hook: stopping a query from execution

I am working on versioning changes for an application. I am making use of the mongoose pre-hook to alter the queries before processing according to the versioning requirements, I came across a situation where I need to do a separate query to check whether the other document exists and if it is I don't have to execute the current query as shown below,
schema.pre('find', { document: false, query: true }, async function (next) {
const query = this.getQuery();
const doc = await model.find(query).exec();
if (!doc) {
const liveVersion = { ...query, version: "default" };
this.setQuery(liveVersion);
} else {
return doc;
}
});
In the above find pre-hook, I am trying to
check the required doc exists in the DB using the find query and return if does exist and
if the document does not exist, I am executing the query by setting the default version based query.
The problem here is mongoose will execute the set query no matter what and the result its returning is also the one which I got for the this.setQuery, not the other DB query result(doc).
Is there a way to stop the default query execution in mongoose pre-hook?
Any help will be appreciated.
The only way to stop the execution of the subsequent action would be to throw an error, so you can throw a specific error in else, with your data in the property of the error object, something like:
else {
let err = new Error();
err.message = "not_an_error";
err.data = doc;
}
but that would mean wrapping all your find calls with a try/catch, and in the catch deal with this specific error in the way of extracting your data, or throw for the main error checking if it's an actual error. In the end you'll be having a very ugly code and logic.
This is specifically for the way you ask it, but normally you can just define another method, like findWithCheck(), and do your checks of the pre hook above in this custom method.
Of course you could try also overriding the actual find(), but that would be overkill, and in this case it means pretty much breaking the whole thing more for test purposes rather than development.

How to do pagination on channels in twilio-programmable-chat?

I am using twilio javascript sdk for twilio-programmable-chat.
And I want to apply pagination to my channels result but I am not able to figure it out.
Here is my current code.
this.chatClient.getUserChannelDescriptors().then(paginator => {
// All channels are fetched
})
I tried to pass a pageSize similar to how getMessages(10) work but it didn't work.
this.chatClient.getUserChannelDescriptors(10).then(paginator => {
// The result was same, it fetched all the channels instead of just 10
})
I am looking for a example that how pagination can be done on channels.
Thanks.
I've finally found a way how to do it.
It should be done recursively since we get the initial list by calling getUserChannelDescriptors() but then the rest of the records can be fetched by calling nextPage();
async function processChannels(paginator) {
// Now, if hasNextPage is true
// call nextPage() to get the records instead of getUserChannelDescriptors()
if (paginator.hasNextPage) {
const nextPaginator = paginator.nextPage();
processChannels(nextPaginator);
} else {
console.log("END OF RECORDS");
}
}
async function getChannels() {
const paginator = await chatClient.getUserChannelDescriptors();
// Initiate the recursive function
if (paginator.items) {
await processChannels(paginator);
}
}
And this is what you will get in each call.
According to the documentation, the getUserChannelDescriptors method doesn't take any arguments.
But you shouldn't have to do pagination manually, since the method returns a Promise.<Paginator.<ChannelDescriptor>> type..which means you should be able to get access to the Pagination features twilio provides.
Your paginator.items should have only the items in a single page.
EDIT: Basically the point is that your first snippet is correct. Unfortunately, twilio is not open source so I cannot check where they have defined the page_size exactly. But I would encourage you to create say a hundred mock channels, and then check the size of the paginator.items array.
Try this:
this.chatClient.getUserChannelDescriptors().then(paginator => {
console.log(paginator.items, paginator.hasNextPage(), paginator.hasPrevPage());
})
The documentation for the Paginator class is here

Firebase function .onWrite not working?

Ok, I have looked at similar questions like Firebase function onWrite not being called and thought it was my fault getting the reference, but I have no idea what is happening here with my Firebase functions.
I am just trying to get a function to write to my database when a write has been made to database. I followed the firebase tutorial exactly:
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
//https://firebase.google.com/docs/functions/database-events
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// const gl = require('getlocation');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
exports.enterLocation = functions.database.ref('/Users/{name}') //brackets is client param
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
// const original = event.data.val();
console.log('SKYLAR HERE:', event.params.name);
// You must return a Promise when performing asynchronous tasks inside a Functions such as
return firebase.database().ref('/Users/{name}').set({ location: 'test loc' });
});
The function is being run, yet in my logs I get a pretty unhelpful error that it is getting the {name} param, and data is definitely written to my database, however my SERVER code is not writing:
I get -
ReferenceError: firebase is not defined at
exports.enterLocation.functions.database.ref
Which makes no sense as it is defined. I just want to add an extra child under the user I create, like I do already with "password"
What am I doing wrong?
Two problems here. First, you haven't defined firebase anywhere in your code. I think you meant to use admin instead to use the Admin SDK.
Second, it looks like you're trying to do variable interpolation into a string to build the name of the ref. Your syntax is wrong here.
I imagine you're trying to say this instead in your last line of code:
return admin.database().ref(`/Users/${name}`).set({ location: 'test loc' });
Note the backticks on the string quotes. That JavaScript syntax lets you use ${exp} to insert the contents of some expression in the string.
It turns out you don't even need to use the admin SDK here. Since you're trying to write back to the same location that triggered the function, you can just use the ref that comes from the event object:
return event.data.adminRef.set({ location: 'test loc' });
instead of this:
return firebase.database().ref('/Users/{name}').set({ location: 'test loc' });
use this:
return admin.database().ref('/Users/{name}').set({ location: 'test loc' });

How to get table after indexedDB was just openned via Dexie.js?

I need to check if some table already exists into IndexedDB just after it was openned. But I don't know how to get DexieDB object inside 'then' statement.
this.db = new Dexie("DBNAME");
if (!this.db.isOpen()) {
this.db.open().then(function () {
//how to get this.db.table(storeName) here?
}).catch(function (error) {
console.log(error)
});
}
So this.db doesn't exist inside 'then' statement. How to get it?
In Dexie
In Dexie in particular you don't have to call isOpen and open() like that, you can just .open and things will work like this:
// Declare db instance
var db = new Dexie("MyDatabase");
// Define Database Schema
//...
// Open Database
db.open();
db.trasnaction(...
In General
This is a classic JS context value. The way this works in JavaScript is different - here is the canonical reference about it which you should read.
In addition - about passing parameters in then chains you should refer to this excellent Q&A which covers the more general approach
The workarounds described there (with context) generally apply and contain more library specific code which could help you here.

What is the recommended way to drop indexes using Mongoose?

I need to create several deployment scripts like data migration and fixtures for a MongoDB database and I couldn't find enough information about how to drop indexes using Mongoose API. This is pretty straight-forward when using the official MongoDB API:
To delete all indexes on the specified collection:
db.collection.dropIndexes();
However, I would like to use Mongoose for this and I tried to use executeDbCommand adapted from this post, but with no success:
mongoose.connection.db.executeDbCommand({ dropIndexes: collectionName, index: '*' },
function(err, result) { /* ... */ });
Should I use the official MongoDB API for Node.js or I just missed something in this approach?
To do this via the Mongoose model for the collection, you can call dropAllIndexes of the native collection:
MyModel.collection.dropAllIndexes(function (err, results) {
// Handle errors
});
Update
dropAllIndexes is deprecated in the 2.x version of the native driver, so dropIndexes should be used instead:
MyModel.collection.dropIndexes(function (err, results) {
// Handle errors
});
If you want to maintain your indexes in your schema definitions with mongoose (you probably do if you're using mongoose), you can easily drop ones not in use anymore and create indexes that don't exist yet. You can just run a one off await YourModel.syncIndexes() on any models that you need to sync. It will create ones in the background with .ensureIndexes and drop any that no longer exist in your schema definition. You can look at the full docs here:
https://mongoosejs.com/docs/api.html#model_Model.syncIndexes
It looks like you're attempting to drop all of the indexes on a given collection.
According to the MongoDB Docs, this is the correct command.
... I tried to use executeDbCommand adapted from this post, but with no success:
To really help here, we need more details:
What failed? How did you measure "no success"?
Can you confirm 100% that the command ran? Did you output to the logs in the callback? Did you check the err variable?
Where are you creating indexes? Can you confirm that you're not re-creating them after dropping?
Have you tried the command while listing specific index names? Honestly, you should not be using "*". You should be deleting and creating very specific indexes.
This might not be the best place to post this, but I think its worth posting anyway.
I call model.syncIndexes() every time a model is defined/created against the db connection, this ensures the indexes are current and up-to-date with the schema, however as it has been highlighted online (example), this can create issues in distributed architectures, where multiple servers are attempting the same operation at the same time. This is particularly relevant if using something like the cluster library to spawn master/slave instances on multiple cores on the same machine, since they often boot up in close proximity to each other when the whole server is started.
In reference to the above 'codebarbarian' article, the issue is highlighted clearly when they state:
Mongoose does not call syncIndexes() for you, you're responsible for
calling syncIndexes() on your own. There are several reasons for this,
most notably that syncIndexes() doesn't do any sort of distributed
locking. If you have multiple servers that call syncIndexes() when
they start, you might get errors due to trying to drop an index that
no longer exists.
So What I do is create a function which uses redis and redis redlock to gain a lease for some nominal period of time to prevent multiple workers (and indeed multiple workers in multiple servers) from attempting the same sync operation at the same time.
It also bypasses the whole thing unless it is the 'master' that is trying to perform the operation, I don't see any real point in delegating this job to any of the workers.
const cluster = require('cluster');
const {logger} = require("$/src/logger");
const {
redlock,
LockError
} = require("$/src/services/redis");
const mongoose = require('mongoose');
// Check is mongoose model,
// ref: https://stackoverflow.com/a/56815793/1834057
const isMongoModel = (obj) => {
return obj.hasOwnProperty('schema') && obj.schema instanceof mongoose.Schema;
}
const syncIndexesWithRedlock = (model,duration=60000) => new Promise(resolve => {
// Ensure the cluster is master
if(!cluster.isMaster)
return resolve(false)
// Now attempt to gain redlock and sync indexes
try {
// Typecheck
if(!model || !isMongoModel(model))
throw new Error('model argument is required and must be a mongoose model');
if(isNaN(duration) || duration <= 0)
throw new Error('duration argument is required, and must be positive numeric')
// Extract name
let name = model.collection.collectionName;
// Define the redlock resource
let resource = `syncIndexes/${name}`;
// Coerce Duration to Integer
// Not sure if this is strictly required, but wtf.
// Will ensure the duration is at least 1ms, given that duration <= 0 throws error above
let redlockLeaseDuration = Math.ceil(duration);
// Attempt to gain lock and sync indexes
redlock.lock(resource,redlockLeaseDuration)
.then(() => {
// Sync Indexes
model.syncIndexes();
// Success
resolve(true);
})
.catch(err => {
// Report Lock Error
if(err instanceof LockError){
logger.error(`Redlock LockError -- ${err.message}`);
// Report Other Errors
}else{
logger.error(err.message);
}
// Fail, Either LockError error or some other error
return resolve(false);
})
// General Fail for whatever reason
}catch(err){
logger.error(err.message);
return resolve(false);
}
});
I wont go into setting up Redis connection, that is the subject of some other thread, but the point of this above code is to show how you can use syncIndexes() reliably and prevent issues with one thread dropping an index and another trying to drop the same index, or other distributed issues with attempting to modify indexes concurrently.
to drop a particular index you could use
db.users.dropIndex("your_index_name_here")

Categories

Resources