Cloud Functions for Firebase: write to database on fileupload - firebase

I have a cloud function (modified version of generateThumbnail sample function). I want to create thumbnail, but I also want to get image width and height, and update size value in the database.
To break up this problem:
I need to get snapshot of current database
Navigate to /projects of database
Find correct key using filename (project.src == fileName)
Get size of image (done)
Update project.size to new value
I did some research, but I only found the functions.database.DeltaSnapshot interface, that is given, when you listen on functions.database().ref().onwrite(snapshot => {})
projects.json:
[{
"name": "lolipop",
"src": "lolipop.jpg",
"size": ""
},{
"name": "cookie",
"src": "cookie.jpg",
"size": ""
}]

Database interaction can be done using the firebase-admin package. Check out this sample to see how a function not triggered by a database write accesses the database.
Accessing child nodes by the value of one of their keys in Firebase is a bit clunky, more on that at the end.
For each concern:
1 & 2: create a reference to the projects key in your DB
3: Find the project you're looking for by its src key
5: Update the project
// create reference
const projectsRef = admin.database().ref('projects');
// create query
const srcProjectQuery = projectsRef.orderByChild('src').equalTo(fileName);
// read objects that fit the query
return srcPojectQuery.once('value').then(snapshot => {
const updates = {};
snapshot.forEach(childSnapshot => {
updates[`${childSnapshot.key}/size`] = fileSize;
});
return projectsRef.update(updates);
});
Since it looks like you're treating the src values as unique, a lot of headache can be avoided by using the src as the key for each project object. This would simplify things to:
const projectsRef = admin.database().ref(`projects/${src}`);
projectsRef.update({'size': fileSize});

Related

Firebase storage | how to update customMetaData with merge true

When uploading a file.. I have set the following custom meta data
const metadata = {
customMetadata: {
user: userId,
disabled: 'false'
},
};
and upload it like
uploadBytes(ref(this.str, invoicePath), invoiceFile, metadata),
Now some time later I would like to set disabled to true. Doing smth like this
const metadata = {
customMetadata: {
disabled: 'true',
},
};
updateMetadata(ref(this.str, invoicePath), metadata)
will remove the user key in the customMetaData
Is it possible to update it without setting the user key again??
As far as I know the metadata you pass always completely replaces the existing metadata for that object. If you want to retain values from the previous metadata, you will have to perform a read-modify-write sequence.
Update: Interestingly enough the documentation on updating metadata says:
You can update file metadata at any time after the file upload completes by using the updateMetadata() method. Refer to the full list for more information on what properties can be updated. Only the properties specified in the metadata are updated, all others are left unmodified. updateMetadata() returns a Promise containing the complete metadata, or an error if the Promise rejects.

Been trying to set Custom time on a file using Firebase?

I'm trying to set the Custom time attribute in firebase on the front end. Everything is possible to set, like contentDisposition, custom Metadata etc, just can't find any way or any info about setting Custom time.
You can see it referenced here https://cloud.google.com/storage/docs/metadata#custom-time
You can set the custom time on the file manually in the Storage cloud console, but even when you do and you load the file in firebase on the front end, it's missing from the returned object! (makes me feel like it's not possible to achieve this)
var storage = this.$firebase.app().storage("gs://my-files");
var storage2 = storage.ref().child(this.file);
//// Tried this
var md = {
customTime: now.$firebase.firestore.FieldValue.serverTimestamp()
};
//// & Tried this
var md = {
Custom-Time: now.$firebase.firestore.FieldValue.serverTimestamp()
};
storage2.updateMetadata(md).then((metadata) => {
console.log(metadata);
}).catch((err) => {
console.log(err);
});
The reason I ask is I'm trying to push back the lifecycle delete date (which will be based on the custom time) every time the file is loaded. Does anyone know the answer or an alternative way of doing it?
Thanks in advance
The CustomTime metadata is not possible to update using Firebase JavaScript SDK since it is not included in the file metadata properties list mentioned in the documentation. So even if you specify it as customTime: or Custom-Time: the updateMetadata() method does not perform any changes.
I suggest you as a better practice, set the CustomTime metadata from the cloud console and modify the CustomTimeBefore Lifecycle condition from the back-end each time you load the file using the addLifeCycleRule method of the GCP Node.js Client.
// Imports the Google Cloud client library
const {Storage} = require('#google-cloud/storage');
// Creates a client
const storage = new Storage();
//Imports your Google Cloud Storage bucket
const myBucket = storage.bucket('my_bucket');
//-
// Delete object that has a customTime before 2021-05-25.
//-
myBucket.addLifecycleRule({
action: 'delete',
condition: {
customTimeBefore: new Date('2021-05-25')
}
}, function(err, apiResponse) {});

Metascraper Consolidated Data

I'm using metascraper in a project I'm working on. I'm passing in custom rules into the contructor. It's actually scraping actual content from the page its scraping. The problem is, is that it appears to be finding every tag that matches the CSS selector, and combining all of the text() content from every tag on the page. I checked metascraper website and github and couldn't find any information about an option that changes this kind of mode/behavior. I made sure that each scrape request creates a new instance of metascraper in case it was just using the same member variables across multiple uses of the object, but that didn't seem to do anything. Any thoughts?
Edit: Also, ideally, metascraper would return an array of arrays of sets of selectors it finds. I have 4 selectors in a group that appear in groups throughout a page. I need it to iterate over the selectors in order, until it cannot find any more instances of the 1st selector (aka the groups have stopped appearing on the page).
type4: async (page: Page): Promise<Extract[]> => {
const html = await page.content()
const url = await page.url()
const type4MetascraperInstance = createType4MetaScraperInstance()
const metadata = await type4MetascraperInstance({ html: html, url: url })
console.log('metadata: ', metadata)
const extract: Extract[] = [{
fingerprint: 'type4',
author: metadata.author,
body: metadata.description,
images: null,
logo: null,
product: null,
rating: null,
title: metadata.title,
videos: null
}]
return extract
}
The function for creating the Type4 metascraper instance is:
function createType4MetaScraperInstance() {
const toDescription = toRule(description)
const toAuthor = toRule(author)
const toTitle = toRule(title, { removeSeparator: false })
const type4MetaScraperInstance = metaScraper([ {
author: [
toAuthor($ => $('.a-profile-name').text()),
],
title: [
toTitle($ => $('a[data-hook="review-title"] > span').text()),
],
description: [
toDescription($ => $('.review-text-content').text()),
]
} ])
return type4MetaScraperInstance
}
I decided to architect a different solution here that uses a python script to properly parse reviews, and it will need to read/write to google cloud datastore. Some suggestions people provided were to write my own calls to cheeriojs (https://cheerio.js.org/), instead of using metascraper at all.

Firebase query download the whole database. Why?

I try to download and show only specific data from the Realtime Database. I have the following code:
getUserPlatformIos() {
this.dataRef = this.afDatabase.list('data/users', ref => ref.orderByChild('meta/platform').equalTo('ios'));
this.data = this.dataRef.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
});
return this.data;
}
My firebase database structure
Firebase rules
Why firebase does download the whole database if I query before? This causes very long loading times and a lot of downloaded data....
Indexes need to be defined at the place where you the query. Since you run the query on data/users, that's where you need to define your index:
"users": {
".indexOn": "meta/platform"
}
This defines an index on users, which has the value of the meta/platform property of each user.
Note that the log output of your app should be showing an error message with precisely this information. I highly recommend checking log output whenever something doesn't work the way you expect it to work.

Meteor Framework Subscribe/Publish according to document variables

I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.

Resources