How to send a notification to all users that meet conditions? - javascript

I have an app with two types of users - managers and employees. Also my app has groups. I have the following structure of the firebase database:
groups (collection) -> group (document)
Each group contains two collections managers and employees. Each document in both of the collections contains a field birthday. I want to send two types of notifications:
One to the employee to tell him happy birthday.
One to the manager to tell him that his employee/manager has a birthday today.
What I did:
exports.scheduledBirthday = functions.pubsub.schedule('0 8 * * *').onRun((context) => {
return admin.firestore().doc(`groups/{groupUid}/{usersCollection}/{user}`).get().then(userDoc => {
const userData = userData.data();
const birthday = userData.birthday;
const userFullName = userData.full_name
const birthdayDate = pasrseBirthday(birthday);
const currentDate = new Date();
if (currentDate.getDay() === birthdayDate.getDay() && currentDate.getMonth() === birthdayDate.getMonth()) {
console.log("Creating a birthday notification for: " + userFullName);
let tokens = userData.tokens;
const payload = {
data: {
notification_type: "USER_BIRTHDAY",
user_type: usersCollection,
title: "Happy birthday " + userFullName + "!"
}
};
return admin.messaging().sendToDevice(tokens, payload);
}
return null;
});
});
The code does as following: Set a scheduled crontab to 08:00 each day. It checks if the user's birthday is today. If it is, it will send a "Happy birthday" notification to that user (by executing the Android Java code).
As I mentioned before, I would like to send a notification to the manager if some employee or manager in his group has a birthday today. If there are some users with a birthday, it should send only one push notification (instead of sending for each one). In order to do it I thought of keeping an array variable usersBirthdays and push userData if the user has a birthday. In the last iterator of the loop I made, check if usersBirthdays is empty and if it's not, iterate over the managers and send the push notification. But I have some technical difficulties:
Is it the best way to do it? If not, how should I do it instead?
How can I know if we are in our last iteration of the firebase loop?
How should I iterate over the managers and send each one a push notifcation?

Related

Array is not showing last element in the output

I am working on a project where there are 2 databases (firestore) at same level.
One having user's profile and other has list of schedules.
Profile Db - user's email (stored as an array in the Db) , name, age.
Dates Db - user's email, time and date of schedule.
The function below matches user's email from the profile db with every email given in dates db and gives the list of dates having the same email.
DESIRED OUTPUT
startDateArr pushes the date records from dates Db so that the last date can be filtered and user can be informed of the last schedule.
ISSUE
I am unable to find the last date that is stored of the array.
let startDateArr = [];
db.collection('dates').onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {
db.collection('profiles').get().then((querySnapshot) => {
querySnapshot.forEach((profileDoc) => {
if (profileDoc.data().userEmail[profileDoc.data().userEmail.length - 1] === doc.data().userEmail) {
let finalDate = `${ doc.data().startDate[ doc.data().startDate.length - 1 ] }
${ doc.data().startMonth[ doc.data().startMonth.length - 1 ] }`
startDateArr.push(finalDate);
}
})
})
})
})

Computed property in realm. How to update a different realm from a realm collection listener?

I'm building a chat app. Each channel has many messages. I am building the channel list view where I want to display all the channels sorted by the most recent message sent at per channel.
Each time a message is sent or received I would like to keep the channel.latestMessageUpdatedAt up to date so that I can sort channels later.
I would like to separate concerns and not have to remember to update channels each time messages are updated.
My strategy is to update the channel inside the listener to the message realm, but I get the following error
Error: Wrong transactional state (no active transaction, wrong type of transaction, or transaction already in progress)
const ChannelSchema = {
name: "channel",
primaryKey: "id",
properties: {
id: "string",
latestMessageUpdatedAt: "date",
},
};
const MessageSchema = {
name: "message",
primaryKey: "id",
properties: {
id: "string",
channelId: "string",
text: "string",
updatedAt: "date",
},
};
const realm = await Realm.open({
path: "default.realm",
schema: [ChannelSchema, MessageSchema],
schemaVersion: 0,
});
const messagesRealm = realm.objects("message");
messagesRealm.addListener((messages, changes) => {
for (const index of changes.insertions) {
const channel = realm.objectForPrimaryKey(
"channel",
messages[index].channelId
);
if (!channel) continue;
const message = messages[index];
channel.latestMessageUpdatedAt = new Date();
}
});
I've checked the docs there seems to be no reason why this wouldn't be possible.
Perhaps there is a better way of having this computed field.
Note I thought about having embedded objects / a list of messages on the channel but the number of messages could be up to 10k, I don't want that all returned at once into memory.
I've also tried doing
realm.write(() => {
channel.latestMessageUpdatedAt = new Date();
});
but I get the error that a transaction is already in progress.
The OP requested a Swift solution so I have two: which one is used depends on the data set and coding preference for relationships. There is no automatic inverse relationship in the question but wanted to include that just in case.
1 - Without LinkingObjects: a manual inverse relationship
Let's set up the models with a 1-Many relationship from a channel to messages and then a single inverse relationship from a message back to it's parent channel
class ChannelClass: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var channelName = ""
#Persisted var messageList: List<MessageClass>
}
class MessageClass: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var msg = ""
#Persisted var msgDate = ""
#Persisted var myChannel: ChannelClass!
}
Then after we populate Realm we have some objects that look like this - keeping in mind that different channels will have had messages added at different times
channel 0
message 1
message 3
message 4
channel 1
message 5
message 9
message 10
channel 2
message 2
message 6
message 7
message 8
It would look like this because: suppose a user posts a message to channel 0, which would be message 1. Then a day later another user posts a message to to channel 2, which would be message 2. Then, on another day, a user posts a message to channel 0 which would be message 3. Etc etc
Keeping in mind that while Realm objects are unsorted, List objects always maintain their order. So the last element in each channels list is the most current message in that channel.
From there getting the channels sorted by their most recent message is a one liner
let messages = realm.objects(MessageClass.self).sorted(byKeyPath: "msgDate").distinct(by: ["myChannel._id"])
If you now iterate over messages to print the channels, here's the output. Note: This is ONLY for showing the data has already been retrieved from Realm and would not be needed in an app.
Channel 0 //the first message
Channel 2 //the second message
Channel 1 //the third message? Not quite
Then you say to yourself "self, wait a sec - the third message was in channel 0! So why output channel 1 as the last item?"
The reasoning is that the OP has a requirement that channels should only be listed once - therefore since channel 0 was already listed, the remaining channel is channel 1.
2 - With LinkingObjects: Automatic inverse relationship
Take a scenario where LinkingObjects are used to automatically create the backlink from the messages object back to the channel e.g. reverse transversing the object graph
class MessageClass: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var msg = ""
#Persisted var msgDate = ""
#Persisted(originProperty: "messageList") var linkedChannels: LinkingObjects<ChannelClass>
}
the thought process is similar but we have to lean on Swift a little to provide a sort. Here's the one liner
let channels = realm.objects(ChannelClass.self).sorted { $0.messageList.last!.msgDate < $1.messageList.last!.msgDate }
What were doing here is querying the channels and using the msgDate property from the last message object in each channels list to sort the channels. and the output is the same
Channel 0 //the first message
Channel 2 //the second message
Channel 1 //see above
The only downside here is this solution will have larger memory impact but adds the convenience of automatic reverse relationships through LinkingObjects
3 Another option
Another options to add a small function and a property to the Channel class that both adds a message to the channels messagesList and also populates the 'lastMsgDate' property of the channel. Then sorting the channels is a snap. So it would look like this
class ChannelClass: Object {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var channelName = ""
#Persisted var messageList: List<MessageClass>
#Persisted var lastMsgDate: String
func addMessage(msg: MessageClass) {
self.messageList.append(msg)
self.lastMsgDate = msg.msgDate
}
}
When ever a message is added to the channel, the last message date is updated. Then sort channels by lastMsgDate
someChannel.addMessage(msg: someMessage)
Note: I used Strings for message dates for simplicity. If you want to do that ensure it's a yyyymmddhhmmss format, or just use an actual Date property.

Node-PostgreSQL time format

I have two tables named in users and card_info in my Postgres database. I have created an endpoint for registering new users and I have given a field called dateCreated as shown in the code below.
Register.js
const handleRegister=(req,res,db,bcrypt,saltRounds)=>{
const {name,phno,dob,email,username,password,aadharCard}=req.body;
if(!name || !email|| !phno || !dob || !username || !password || !aadharCard){
return res.status(400).json('Wrong Form Submission');
}
const hash=bcrypt.hashSync(password,saltRounds);
const accNo=Math.floor(2E9+Math.random()*1E9);
const dateCreated=new Date().toJSON().slice(0,10);
db('users')
.insert({
username:username,
name:name,
email:email,
password:hash,
dob:dob,
phno:phno,
aadhar:aadharCard,
acc_no:accNo,
created_date:dateCreated
})
.returning('*')
.then(users=>{
res.json(users[0]);
})
.catch(err=>res.status(400).json("Unable to register"));
}
module.exports={
handleRegister:handleRegister
}
In the card endpoint, I am selecting the user's created year whose id is sent as a request. I then store these values into the card_info table using calculated values. The expiration date is supposed to be 4 years more than the created_year. The id of the table users is a foreign key of the table card_info
Card.js
const genCard=(req,res,db)=>{
const {id,cardType} =req.body;
db.select('created_date').from('users').where("id","=",id)
.then(data=>{
const createdDate=data[0].created_date.toString();
db('card_info')
.insert({
card_number:Math.floor(Math.random()*1E17),
owner_id:id,
expiration_date: createdDate.replace(parseInt(createdDate),parseInt(createdDate)+4),
cvv:Math.floor(Math.random()*1E3),
card_type:cardType
})
.returning('*')
.then(users=>{
console.log(res.json(users[0]));
})
.catch(err=>res.json(err.message))
})
.catch(err=>res.json(err));
}
module.exports={
genCard
}
This is the error that I recieve: "insert into "card_info" ("card_number", "card_type", "cvv", "expiration_date", "owner_id") values ($1, $2, $3, $4, $5) returning * - time zone "gmt+0300" not recognized".
I have tried various date formatting methods but nothing seems to work. Please let me know if someone has a solution to this.
I would avoid all the attempts to manipuate strings that represent dates–it only results in tears and often doesn't consider edge cases. Rather manipulate the date directly. I would recommend Luxon that provides a battled-tested wrapper around Date and has convenience functions that make dealing with dates easier.
// convert to DateTime object
const createdDate = DateTime.fromJSDate(data[0].created_date);
// add 3 months
const futureDate = createdDate.plus({months: 3});
// insert a date object
db('card_info').insert({
card_number:Math.floor(Math.random()*1E17),
...
created: futureDate, // or futureDate.toJSDate()
})
Demo:
const {
DateTime
} = luxon;
// convert a saved date from the DB to DateTime
const dt = DateTime.fromJSDate(new Date());
// add 3 months
const futureDate = dt.plus({
months: 3
});
console.info(futureDate.toFormat('yyyy-MM-dd'), futureDate.toJSDate());
<script src="https://cdn.jsdelivr.net/npm/luxon#2.0.2/build/global/luxon.min.js"></script>

Parse.com match all pointers on array in given relation column

I have Conversation classes with a members relation attribute pointing to User class.
This members attribute consists of people belong to a particular conversation.
Now I want to query if given array of pointers User is part of particular conversation given that all elements must match.
I tried to use containsAll("members", users) but instead got undefined.
containedIn() worked but it returned all matching conversation that has at least one matching User in array.
equalTo("members", users) was not working as well and note that the users variable is array of pointers and not just array of strings objectId, but I also tried that one but got me nowhere.
Here's what I tried:
* Created AND queries where userRelationQuery.equalTo('member', ParseUser1) up to N number of users and still didn't work
Here's my solution but feel free to correct this for improvement
const members = getMembers();
let query = new Parse.Query("Conversation").equalTo(
"members",
members[0]
);
for (let i = 0; i < members.length; i++) {
query = new Parse.Query("Conversation")
.matchesKeyInQuery("objectId", "objectId", query)
.equalTo(
"members",
members[i]
);
}
const chat = await query.includeAll().first();
This should work for you
var conversationClass = Parse.Object.extend('Conversation');
var conversationQuery = new Parse.Query(conversationClass);
return conversationQuery.first()
.then(queryResult => {
var userRelationQuery = queryResult.relation('members').query(); //You can get all users releated to this conversation
//userRelationQuery.equalTo('username', 'Blank0330') // Or you can add more conditions
return userRelationQuery.find()
.then(users => {
return users; //This is your releated users
});
});
For more information about Parse-JS-SDK Relation

Sorting data in Firebase with JavaScript, how to compare all register in an array of objects?

I found this method, with which I have ordered the hours what I store in my records, I wanted them to be sorted from highest to lowest, but when I test the code, I notice that only two values of the array are compared, which are the first registers.
I've seen other methods of comparison and the logic is the same, what am I doing wrong? I group the messages per user, using the id of the user as key of the array , then I save the rest of data. I do this for retrieve the current messages, since I want show a list of the last currently messages sent.
This is the code:
var ref = new Firebase('https://chatfbexample.firebaseio.com/all-messages');
ref.on("value", function (snapshot) {
var Messages = [];
var value = 0;
snapshot.forEach(function (snap) {
value = snap.val().id;
fecha = snap.val().id;
Messages[value] = [];
Messages[value]['fecha'] = snap.val().fechahora; //I receive a date with the format HH:MM:SS
Messages[value]['texto'] = snap.val().texto;
});
function compare(a, b) {
var time1 = a['fecha'].replace(/:/gi, '1');
var time2 = b['fecha'].replace(/:/gi, '1');
var data1 = parseInt(time1);
var data2 = parseInt(time2);
// alert(time1);
if (data1 > data2)
return -1;
if (data1 < data2)
return 1;
return 0;
}
Messages.sort(compare);
for (var i in Messages) {
console.log("Hour: " + Messages[i]['fecha'] + ' ' + ' Message: ' + Messages[i]['texto']);
}
});
the result is something like this
Hour: 12:11:13 Message: whats'up?
Hour: 11:38:44 Message: p
Hour: 11:49:01 Message: hey?
the second and the third messages are not being compared
an image of my Firebase database
First off it probably would be better for you to save a timestamp from Firebase instead of an actual time, which was probably created on the client side, inside your database. This way you could easily let Firebase handle the sorting.
You would need to use firebase.database.ServerValue.TIMESTAMP to save the exact time at which Firebase received your message inside fechahora.
Following you could simply query all of your messages ordered by their child fechahora.
ref.orderByChild("fechahora").on("value", function (snapshot) {
//Do stuff with your already sorted data
}
If you want your query to have a better performance set the .indexOn property of your node containing all the messages inside your Firebase Database rules to fechahora.
Bonus: If you want your data to be ordered newest to oldest instead of oldest to newest, you just need to use the negative value of the timestamp created by Firebase.
Instead of ordering client-side, you'll be better off asking the server to sort the messages:
ref.orderByChild("fechahora").on(...
Make sure that you define the proper index in your Firebase security rules:
{
"rules": {
"all-messages": {
".indexOn": "fechahora"
}
}
}
Without that the sorting will still work, but will happen on the client instead of the server.

Categories

Resources