How to send non-persistent value to Cloud Firestore? - javascript

I want to send some value for a field to Cloud Firestore, but I dont want to be persist(saved) in Cloud Firestore.
Code:
const message = {
persistentData: {
id: 'dSXYdieiwoDUEUWOssd',
text: 'Hi dear how are you',
date: new Date();
},
nonPersistentData: {
securityCode: 393929949
}
};
db.collection('messages').doc(message.persistentData.id).set(message).catch(e => {});
In above code I want to persit (save) persistentData, but I dont want to save nonPersistentData online nor offline, because I only need them to check real data in Firestore rule. So I dont want they should be accessible in cache(offline) or server(online)...

This is simply not possible with firestore. There is a similar question here. You need to separate the data into public (persistent) and private data (non-persistent). One possible solution will be-
From the client, push the private data which contains the securityCode to a new collection called securityCodes and store the id of the new entry.
Because you don't want this info to be available to anyone, you can add a security rule
match /securityCodes/{securityCode} {
// No one can read the value from this collection, but only create
allow create: true;
}
In your public data, add the id of the previously added document
data = {
id: 'dSXYdieiwoDUEUWOssd',
text: 'Hi dear how are you',
date: new Date(),
securityId: <id of the secretCode entry>
}
In your security rules, get the secret code using the securityId you are sending with the public data. Example-
match /collectionId/documentId {
allow create: if get(/secretCodes/$(request.resource.data.secretId)) == 'someknowncode'
}

Related

Atomic update of data structure in Firebase Realtime Database

I am using typescript and the Firebase Realtime Database (cannot use Firestore), and I have data in the form on an interface MyData as follows:
enum RunStatus {
RUNNING,
ENDED
}
interface IResults {
firstItem: string;
secondItem: number;
}
interface MyData {
status: RunStatus;
results: IResults;
}
Suppose I have 10 clients that might end up writing this data simultaneously to (1) change status from RUNNING to ENDED and (2) set the results field.
What I want is for only the first client to be able to do this, so I need to use a transaction of some sort.
My data is stored at this path: "/some/path/here/mydata".
As near as I can tell from the limited documentation, my writes should look something like this:
class MyDatabase {
db: Database;
constructor(db_: Database) {
this.db = db_
}
writeMyData(newData: MyData) {
const path="/some/path/here/mydata";
const reference=child(ref(this.db),path);
runTransaction(reference, (currentData) => {
if (currentData) {
if (currentData.status!=RunStatus.ENDED && newData.status==RunStatus.ENDED) {
currentData.status=newData.status;
currentData.results=newData.results;
// Or, I could have just set currentData=newData
}
}
return currentData;
});
}
}
Is this correct? And what exactly does it do if multiple clients try to run this at the same time? The documentation says something about runTransaction being automatically rerun if currentData is updated by another client while the first client is writing. Can someone please explain to me if this is correct, and what is happening here?

TypeORM AfterSave() triggers after creation but when queried, it returns NULL

I have an Entity called Trip. The structure is:
What I want is whenever a new trip is created, the room column should be populated with ${tripId}_${someRandomStringHere}. So for example, I just created a new trip using this body:
The response should be:
The newly created trip has the id of 15. So, the response has the room valued at 15_4gupvdo0ea408c25ia0qsbh because again: ${tripId}_${someRandomStringHere}.
This is working as expected whenever I POST the request and create the trip. BUT whenever I query all the trips created, the room property of each trip objects shows null!
Look at the /api/trips:
room property is NULL. So what the heck I dont understand what is happening.
My Trip Entity code is:
import { PrimaryGeneratedColumn, Column, CreateDateColumn,
UpdateDateColumn, Entity, Unique, ManyToOne, AfterInsert, JoinColumn, getConnection } from 'typeorm'
import { DriverEntity } from 'src/driver/driver.entity';
#Entity('trips')
export class TripEntity {
#PrimaryGeneratedColumn()
id: number
#Column()
destination: string
#Column('decimal')
destination_lat: number
#Column('decimal')
destination_long: number
#Column()
maxPassenger: number
#Column()
totalPassenger: number
#Column({ nullable: true })
room: string
#CreateDateColumn()
created_at: Date
#UpdateDateColumn()
updated_at: Date
// --------->HERE: The after insert
#AfterInsert()
async createSocketRoom(): Promise<void> {
const randomString = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
this.room = `${this.id}_${randomString}`
}
// Trip belongs to driver
// Adds driver_id to trips table
#ManyToOne(type => DriverEntity, driver => driver.trips)
#JoinColumn({ name: 'driver_id' })
driver: DriverEntity
}
My Trip Service Code is:
async create(data: CreateTripDTO) {
const { driver_id } = data
const driver = await this.driverRepository.findOne({ where: { id: driver_id } })
const trip = await this.tripRepository.create(data)
trip.driver = driver
await this.tripRepository.save(trip)
return trip
}
I dont think I need to include the Trip Controller code but anyway..
I don't know why it is happening because I have my User Entity with #BeforeUpdate and works fine...
After reading alot of similar github issues, watched youtube tutorials [Hi Ben Awad! :D], I found a somewhat fix.. by using Subscribers
Actually, I don't know what is the difference of the Listener/Subscriber. Maybe I am doing the wrong usage. Can someone enlighten me please? For example the difference of AfterSave of Entity Listener vs AfterSave of Entity Subscriber. When/Best case to use? something like that. Anyway back with the "fix..."
I created a Trip Subscriber:
import { EventSubscriber, EntitySubscriberInterface, InsertEvent } from "typeorm";
import { TripEntity } from "src/trip/trip.entity";
#EventSubscriber()
export class TripSubsriber implements EntitySubscriberInterface<TripEntity> {
// Denotes that this subscriber only listens to Trip Entity
listenTo() {
return TripEntity
}
// Called after entity insertion
async afterInsert(event: InsertEvent<any>) {
console.log(`AFTER ENTITY INSERTED: `, event.entity);
const randomString = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
// query the trip with given event.entity
const trip = await event.manager.getRepository(TripEntity).findOne(event.entity.id)
// populate the room with desired format
trip.room = `${trip.id}_${randomString}`
// save it!
await event.manager.getRepository(TripEntity).save(trip)
}
}
At first it is not working but after digging for hours again, I need to add a subscriber property with the value of the path of my subscribers at the ormconfig.json for it to work!
e.g: "subscribers": [
"src/subscriber/*.ts"
]
Again, the Trip Subscriber code seems spaghetti to me because I already have the event.entity object but I do not know how to update it without the need of querying and updating it using event.manager.getRepository(). Please can someone fix this code for me? the proper way of doing it?
NOW, It is working!
the request body:
the /api/trips res:
My questions are:
Why whenever I use that method methoud subscriber, it is not working. Is it not the proper way to do it? The why is it in the docs? Or for other use case?
Do I really have to use subscriber for it to achieve? Thats so many steps.
I came from Rails. So having to create files/subscribers just to do it somewhat tiring. Unlike ActiveRecord's after_save callback it is very easy..
PS. I'm new to nest-js and typeorm
#AfterInsert method will just modify your JS object after inserting into DB is done. So thats reason why is your code not working. You have to use #BeforeInsert decorator. BeforeInsert will modify your JS entity/object before inserting/saving into DB.
What it looks like is happening with your AfterInsert is that you are creating the random room string just fine, but you are not saving the value to the database, only using the return of the id so that you can create the string. What you could do in your AfterInsert is run the save() function from the EntityManager or RepositoryManger once more and commit the value to the database, similar to what you have happening in you Subscriber. I haven't dived too deep into the Subscriber/Listener vs Before-/AfterInsert decorators, so I can't give a deeper answer to your questions.
If you'd rather not make two commits to the database, you can always do a query for the most recent id and increment it by 1 (thus, matching what the new objects id should be) with something like
const maxId = await this.tripRepository.findOne({select: ['id'], order: {id: "DESC"} });
const trip = await this.tripRepository.create(data);
const randomString = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
this.room = `${maxId + 1}_${randomString}`
trip.driver = driver
await this.tripRepository.save(trip)
It's a little clunky to look at, but it doesn't require two writes to the database (though you'll definitely need to ensure that after creation room and trip have the same id).
Your last option would be to create a Trigger in your database that does the same thing as your JavaScript code.
You just use "this.save()" after all in createSocketRoom() function
https://i.stack.imgur.com/oUY8n.png my query after use that!!!

Algolia Instant Search Firebase Cloud Function - how to get the other value?

I don't have much idea about JavaScript, so I used Algolia's Instant Search for Firebase Github Repository to build my own function.
My function:
exports.indexentry = functions.database.ref('/posts/{postid}/text').onWrite(event => {
const index = client.initIndex(ALGOLIA_POSTS_INDEX_NAME);
const firebaseObject = {
text: event.data.val(),
timestamp: event.data.val(),
objectID: event.params.postid
};
In Algolia indices, with timestamp as the key, I get the same value as in text field, but in Firebase backend timestamp is different. How to fix this?
I tried different statements to get timestamp value but couldn't.
Edit
Expected Outcome:
{
text: "random rext",
timestamp: "time stamp string",
author: "author name",
object ID: "object ID"
}
Actual Outcome
{
text: "entered text",
object ID: "object ID"
}
I'm not real clear about your goal. Event has a timestamp property. Have you tried:
const firebaseObject = {
text: event.data.val(),
timestamp: event.timestamp, // <= CHANGED
objectID: event.params.postid
};
If you want a long instead of string, use Date.parse(event.timestamp)
EDIT 2: Answer can be found here.
Original Answer: What Bob Snyder said about the timestamp event is correct.
There may be other fields as well, for example, author_name that we may need to index, is there a generalized way to do that or do I write separate functions for every field?
If you want a general way to add all fields, I think what you are looking for can be found here. This should give you the right guidance to get what you want, i.e save your whole object into the Algolia index.
EDIT:
index.saveObject(firebaseObject, function(err, content) {
if (err) {
throw err;
}
console.log('Firebase object indexed in Algolia', firebaseObject.objectID);
});
event.data.val() returns the entire firebase snapshot. If you want a specific value in your data you add it after .val() for example if every post has an author stored in your firebase database under they key "author" you can get this value using var postAuthor = event.data.val().author
I've included some samples from my code for those interested. A sample post looks like this:
Then inside my cloud functions I can access data like this:
const postToCopy = event.data.val(); // entire post
const table = event.data.val().group;
const category = event.data.val().category;
const region = event.data.val().region;
const postKey = event.data.val().postID;

get record `id` before create/save | Sails Js

I am trying to create a notification for user.
with notification url in description like this http://localhost:1337/invited/accept?referrer=msgIcon&id=this-notification-id the url has id of this newly created notification.
AppUserNotification.create({
projectId: projectId,
appuser: appusers.id,
notificationTitle: 'You are invited in project',
isRead: 0,
description: 'You are invited in project collaboration, '
+ 'please accept invitation by following the link.\nHave a good day !\n'
+ 'Accept Invitation http://localhost:1337/invited/accept?referrer=msgIcon&id=this-notification-id',
}).exec(function (err, appuserNotifications) {
apiStatus = 1; // heading toward success
if (err){
return false;
}else if(appuserNotifications){
return true;
}
});//</after AppUserNotification.create()>
what I want to do is to save a link in description with this newly created notification. but couldn't manage to do so.
please help me.
By default, id is generated by database during record creating. So can be accessed only after creation.
Here are some ways in which your objective can be achieved:
New attribute: Add another unique attribute which is used in description URL. It can be generated randomly before creation (e.g. can use UUID)
Use custom id: Set autoPK: false in Model, generate id yourself; (I have done it for MySQL in beforeCreate hook using UUID as primary key id, not sure about MongoDB)
Update after create: Use afterCreate hook to update description with id
New Model method: Define a method say getDescription() in the Model which returns something like this.description + this.id.
Override toJSON(): http://sailsjs.com/documentation/reference/waterline-orm/records/to-json

Mongoose: Adding an element to array

I'm using Drywall to create a website.
I'm trying to add a dashboard element to the accounts section of the admin site. The dashboard element is to store an array of dashboards (strings) that the user has access to.
I've managed to successfully add the "dashboards" into the schema and store data in it.
Here's the problem:
I need to be able to add elements to the array. The way the code stands currently replaces the contents of dashboards in the database.
I know I can use $addToSet, but I'm not sure how I'd do that since the fieldsToSet variable is sent to the findByIdAndUpdate() method as a single object.
Here's the snippet of my code:
workflow.on('patchAccount', function() {
var fieldsToSet = {
name: {
first: req.body.first,
middle: req.body.middle,
last: req.body.last,
full: req.body.first +' '+ req.body.last
},
company: req.body.company,
phone: req.body.phone,
zip: req.body.zip,
search: [
req.body.dashboards,
req.body.first,
req.body.middle,
req.body.last,
req.body.company,
req.body.phone,
req.body.zip,
]
};
req.app.db.models.Account.findByIdAndUpdate(req.params.id, fieldsToSet, function(err, account) {
if (err) {
return workflow.emit('exception', err);
}
workflow.outcome.account = account;
return workflow.emit('response');
});
});
Here's a link to the original file: (lines 184-203)
Thanks!
fieldsToSet is a bad name (at least misleading in this case), the parameter is actually update which can take $actions like $addToSet
I don't think you want to set (only) the search field with dashboards. I'm guessing that field is used to index users for a search. So you'll probably wind up doing something like this:
fieldsToSet = {
....all the regular stuff,
$addToSet: {dashboard: req.body.dashboardToAdd}
//I'm not sure that you can add multiple values at once
}
Since this is setting all of the values each time I'm not sure you actually will want to add single dashboard items. Instead you might want to get the full set of dashboards the user has and set the whole array again anyway (what if they removed one?)
fieldsToSet = {
....all the regular stuff,
dashboards: req.body.dashboards
//In this case you'd want to make sure dashboards is an appropriate array
}

Categories

Resources