I have a function that deletes matches from users if the match is not played after 15 minutes. if the user that create the match add coins to the match, the function retrieves that coins to the user profile.
here is the code
function deleteMatch(key,user,bet){
MatchesRef.child(key).remove();
UsersRef.child(user).once('value').then(userSnapshot => {
const credits = userSnapshot.val().credits || 0;
return UsersRef.child(user).child('credits').set(credits + parseInt(bet, 10));
})
}
The problem is if the function recieve in the parameters a user that create 2 matchs in the same minute, only add 1 coin.
User1 have 9 coins and user2 have 8 coins.
Example parameters: match1(key1, user1, 1), match2(key2,user2,1)
with this parameters the code work, and now user1 have 10 coins and user2 have 9.
But with differente paramenters
user1 have 9 coins, user2 have 8 coins.
Example parameters with problems:
match1(key1,user1,1), match2(key2,user2,1) match3(key3,user1,1).
With this the code read all the matches, but after the code the user1 have 10 coins, and user 2 have 9 coins.
The code is not giving 2 coins, 1 for every match that user1 create.
I think this is because firebase are trying to write to the user node 2 times at once. But no idea how to solve
The code that starts the action
module.exports = () =>
MatchesRef.once('value').then(snap => {
const now = new Date().getTime();
const records = [];
snap.forEach(row => {
const { date, hour, adversary1, bet } = row.val();
console.log("el adversario es "+ adversary1);
if (!date || !hour) return;
const [day, month] = date.split('/').map(p => parseInt(p, 10));
const [h, min] = hour.split(':').map(p => parseInt(p, 10));
const scheduledTime = (new Date((new Date()).getFullYear(), month - 1, day, h, min, 0, 0))
.getTime() + expireMinutes * 60 * 1000;
if (scheduledTime <= now) {
records.push({ key: row.key, user: adversary1, bet });
deleteMatch(row.key,adversary1,bet);
//records.map(({key,user,bet}) => deleteMatch({key,user, bet}))
}
});
return Promise.all(
// records.map(({key, user, bet}) => expireMatch(key, user, bet))
);
});
The problem was firebase try to write 2 times at the same location and to avoid this problem I implement transactions, who guarantee that firebase wait until the previous write was finished.
UsersRef.child(user).child('credits').transaction(function(currentCredits){
console.log(currentCredits);
return currentCredits + parseInt(bet, 10);
})
Related
I am trying to modify an existing code that adds credits to a user's account based on the amount they paid. Currently, the code adds an unclear number of credits by dividing the amount paid by 12. I would like to add 500 credits for every $29 paid. How can I modify the code to achieve this?
Here is the code that I am working with
const models = require("../models")
const User = models.user;
const invoice = async (eventType,data) => {
if (!eventType.includes("invoice")) {
return // not a subscription event
}
paid(eventType,data)
}
const paid = async (eventType,data) => {
if (!eventType.includes("invoice.paid")) {
return // not a subscription event
}
const { object } = data
console.log(eventType)
let credits = 0
// 500 credits for $30
if(object.amount_paid > 2900){
credits += (object.amount_paid / 12)
}
if(object.amount_paid > 8900){
credits += (object.amount_paid / 12)
}
let user = await User.findOne({
customerId: object.customer
})
I changed the code to add 500 credits for every $29 paid by using the following logic:
if (object.amount_paid >= 2900) {
credits += Math.floor(object.amount_paid / 29) * 500;
}
This checks if the "amount_paid" property of the "object" variable is greater than or equal to 2900 (since 2900 cents is equal to $29). If it is, it calculates the number of times 29 can go into the "amount_paid" by using the JavaScript Math.floor() function which rounds down to the nearest integer. This result is then multiplied by 500 to get the number of credits to be added to the user's account.
Regarding the value of 2900, in order to get 500 by dividing 2900, I would need to divide it by 5.8333 approximately, but as I said before, this is not the right way to get the number of credits that the user should get.
I am trying to currently create a Discord bot which does temporary banning and for the most part I know how to handle this. The only issue is that I can't figure out how I would use an argument like 3w/2w/1y/etc to convert to a new time to create a timer. I've crawled all over Google to find an answer and I can't find even a slight hint or tip on how to accomplish this, maybe you guys might be able to point me in the right direction.
I would use a regex to parse the argument, and then map it to a date via milliseconds:
const mapping = {
w: 7 * 24 * 60 * 60 * 1000,
d: 24 * 60 * 60 * 1000,
// whatever other units you want
};
const toDate = (string) => {
const match = string.match(/(?<number>[0-9]*)(?<unit>[a-z]*)/);
if (match) {
const {number, unit} = match.groups;
const offset = number * mapping[unit];
return new Date(Date.now() + offset);
}
}
Examples:
> toDate('3w')
2020-09-08T19:04:15.743Z
> toDate('2d')
2020-08-20T19:04:20.622Z
You can use date-fns libaray
npm install date-fns
And then use the formatDistance function
formatDistance( new Date(1986, 3, 4, 11, 32, 0), new Date(1986, 3, 4, 10, 32, 0), { addSuffix: true } ) //=> 'in about 1 hour'
You could convert the argument into milliseconds, log the current Date.now() and check the difference to a new Date.now() with a certain update rate.
If the time difference is less, the user is still banned, else the user is to be unbanned.
To convert that format, replace the h,d,w,m and y with x<number of seconds>, then split it and then times the first by the second, giving you the sum in seconds.
Presuming you don't want to use a library. (which may be more robust).
Below is a few test, you would obviously need to add validation or NaN is likely.
const shortSinceToSeconds = input => {
var p = input
.replace('h', 'x3600')
.replace('d', 'x86400')
.replace('w', 'x604800')
.replace('m', 'x2.628e+6')
.replace('y', 'x3.154e+7').split('x')
return (p[0] || 0) * (p[1] || 0)
}
const test = [
'1h', '13h', '1d', '100d', '1w', '100w', '2m', '1y'
]
//
for (let i of test) {
console.log(`${i} = ${shortSinceToSeconds(i)} seconds`)
}
I need a code showing user's server join position. For example:
User: !userinfo
Bot: You're 23rd member of server.
Here is a very basic example. I've tried to explain it with comments as much as I can.
// Import the Client class from Discord.js
const {Client} = require('discord.js')
// Initialise a new client
const client = new Client()
// This will be used later on to determine the ordinal suffix to use
// for 1st, 2nd, and 3rd
const digitToOrdinalSuffix = {
1: 'st',
2: 'nd',
3: 'rd'
}
// Add a listener for the 'message' event (which is emitted when a message is
// sent)
// The {author, channel, content, guild} destructures the message object.
// author is the message's author, channel is the message's channel etc.
client.on('message', async ({author, channel, content, guild}) => {
try {
// If the message content is '!userinfo'...
if (content === '!userinfo') {
// Send a message if it's a DM
if (!guild) return await channel.send('This command can’t be used in DMs!')
// This is the actual part that gets the user's 'join position'
// Get the message server's members...
const result = guild.members.cache
// sort them in ascending order by when they joined...
.sorted((a, b) => a.joinedAt - b.joinedAt)
// convert the Collection to an array so we can use findIndex...
.array()
// and find the position of the member in the array.
// For example, the first member to join the server will be the first
// member of the array (index 0), so they will be the
// 0 + 1 = 1st member to join.
.findIndex(member => member.id === author.id) + 1
// The last digit of the result (0-9)
const lastDigit = result % 10
// The last 2 digits of the result (0-99)
const last2Digits = result % 100
// Send the message
await channel.send(
`You’re the ${result}${
// 4th, 5th, 6th, 7th, 8th, 9th, 10th, 14th, 15th...
lastDigit === 0 || lastDigit > 3 ||
// 11th, 12th, 13th
last2Digits >= 11 && last2Digits <= 13
? 'th'
// 1st, 2nd, 3rd, 21st, 22nd, 23rd...
: digitToOrdinalSuffix[lastDigit]
} member of the server.`
)
}
} catch (error) {
// Log any errors
console.error(error)
}
})
// Login to Discord using your bot token. It is highly recommended that you don't
// literally put your token here as a string; store it somewhere else like a
// config or .env file.
client.login('your token')
If you are planning on adding a lot more commands, I recommend using a better command handler that trims the user input and handles arguments, instead of using if/else if to check if the input matches a string. A good example of this is on the Discord.js guide, which is a very good resource for learning how to code a bot using Discord.js.
I have an InfluxDB (version 2), where an entry is written every second into my bucket, together with an identifier (uuid of the source) and a side value (some domain-specific measurement from 1 to 6). Instead of having a long list of such by-second logs:
2020-05-18T15:57:18 side=1
2020-05-18T15:57:19 side=1
2020-05-18T15:57:20 side=3
2020-05-18T15:57:21 side=3
2020-05-18T15:57:22 side=3
2020-05-18T15:57:23 side=2
2020-05-18T15:57:24 side=2
I'd like to condense those entries, so that I can calculate the duration, for how long side=x held true:
from 2020-05-18T15:57:18 to 2020-05-18T15:57:19 side=1 duration=2s
from 2020-05-18T15:57:20 to 2020-05-18T15:57:22 side=3 duration=3s
from 2020-05-18T15:57:23 to 2020-05-18T15:57:24 side=2 duration=2s
I also restrict the time period by a from/to range. This is my current query:
from(bucket: "mybucket")
|>range(start:2020-01-01T00:00:00.000Z, stop:2020-12-31T00:00:00.000Z
|>filter(fn:(r) => r.identifier == "3c583530-0152-4ed1-b15f-5bb0747e771e")
My approach is to read the raw data, and then iterate over it, detect changes of the side (something like current.side != last.side), and then report that as a new entry to my logbook. This approach is very inefficient, of course (JavaScript):
const data = fetchFromInfluxDB(from, to):
const terminator = { 'time': new Date(), 'identifier': '', 'side': -1 };
data.push(terminator); // make sure last item is also reported
const logbook = new Array();
let lastSide = -1;
let from = undefined;
for (let e of data) {
if (e.side != lastSide) {
if (from != undefined) {
let to = e.time;
let diff = Math.floor((new Date(to) - new Date(from)) / 1000);
if (diff > 0) {
logbook.push({'from': from, 'to': to, 'side': lastSide, 'seconds': diff});
}
}
lastSide = e.side;
from = e.time;
}
}
Is there a way to group and sum up that data using the InfluxDB query language?
I am adding entries to a schema every hour in order to track growth over the course of days while maintaining a current score for the current day. Now I would like to be able to pull the most recent record for each day for the past week. The results would be 6 records at or around midnight for 6 days previous and the 7th being the latest for the current day.
Here is my schema:
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
, createdAt: { type: Date, default: Date.now() }
})
Edit
I've tried using this static, but it pulls the exact same record 7 times
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 1; i <= 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (i * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
}
If I were using SQL I would do a subquery selecting MAXDATE and join it to my main query in order to retrieve the results I want. Anyway to do this here?
Kristina Chodorow gives a detailed recipe for this exact task in her book MongoDB: The Definitive Guide:
Suppose we have a site that keeps track of stock prices. Every few
minutes from 10 a.m. to 4 p.m., it gets the latest price for a stock,
which it stores in MongoDB. Now, as part of a reporting application,
we want to find the closing price for the past 30 days. This can be
easily accomplished using group.
I'm not familiar with Mongoose, however I've tried to adapt her example to your case below. Note I changed the createdAt default property from a value to a function and added an extra field datestamp to your schema:
var oneday = 24 * 60 * 60;
var schema = new Schema({
aid: { type: Number }
, name: { type: String }
, score: { type: Number }
// default: is a function and called every time; not a one-time value!
, createdAt: { type: Date, default: Date.now }
// For grouping by day; documents created on same day should have same value
, datestamp: { type: Number
, default: function () { return Math.floor(Date.now() / oneday); }
}
});
schema.statics.getLastWeek = function(name, fn) {
var oneweekago = Date.now() - (7 * oneday);
ret = this.collection.group({
// Group by this key. One document per unique datestamp is returned.
key: "datestamp"
// Seed document for each group in result array.
, initial: { "createdAt": 0 }
// Update seed document if more recent document found.
, reduce: function(doc, prev) {
if (doc.createdAt > prev.createdAt) {
prev.createdAt = doc.createdAt;
prev.score = doc.score;
// Add other fields, if desired:
prev.name = doc.name;
}
// Process only documents created within past seven days
, condition: { "createdAt" : {"$gt": oneweekago} }
}});
return ret.retval;
// Note ret, the result of group() has other useful fields like:
// total "count" of documents,
// number of unique "keys",
// and "ok" is false if a problem occurred during group()
);
A solution is to use group() to groups records by day. It's fancy, slow and can be blocking (meaning nothing else can run at the same time), but if your record set isn't too huge it's pretty powerful.
Group: http://www.mongodb.org/display/DOCS/Aggregation#Aggregation-Group
As for mongoose, I'm not sure if it supports group() directly, but you can use the node-mongodb-native implementation, by doing something like this (pseudo-code, mostly):
schema.statics.getLastWeek = function(name, cb) {
var keys = {} // can't remember what this is for
var condition = {} // maybe restrict to last 7 days
var initial = {day1:[],day2:[],day3:[],day4:[],day5:[],day6:[],day7:[]}
var reduce = function(obj, prev) {
// prev is basically the same as initial (except with whatever is added)
var day = obj.date.slice(0,-10) // figure out day, however it works
prev["day" + day].push(obj) // create grouped arrays
// you could also do something here to sort by _id
// which is probably also going to get you the latest for that day
// and use it to replace the last item in the prev["day" + 1] array if
// it's > that the previous _id, which could simplify things later
}
this.collection.group(keys, condition, initial, reduce, function(err, results) {
// console.log(results)
var days = results // it may be a property, can't remember
var lastDays = {}
days.forEach(function(day) {
// sort each day array and grab last element
lastDays[day] = days[day].sort(function(a, b) {
return a.date - b.date // check sort syntax, you may need a diff sort function if it's a string
}).slice(-1) // i think that will give you the last one
})
cb(lastDays) // your stuff
})
}
Some more comparisons between groups and map reduce from my blog:
http://j-query.blogspot.com/2011/06/mongodb-performance-group-vs-find-vs.html
There are no docs about the group command in the native driver, so you'll have to peer through the source code here:
https://github.com/christkv/node-mongodb-native/blob/master/lib/mongodb/collection.js
Also for sort, check check https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort for exact syntax
Edit: Better Idea!!!
Just have a special collection called "lastRequestOfDay" and make the _id the day.
Overwrite the value with each new request. It will be super easy to query and fast to write and will always have the last value written each day!
Add another property to the schema named dateAdded or something.
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
for (var i = 0; i < 7; i++) {
this.where('name', new RegExp(name, 'i'))
.where('createdAt')
.lt(now - (i * oneday))
.gte(now - ((i + 1) * oneday))
.desc('createdAt')
.findOne(function(err,doc){
// might not always find one
docs.push(doc)
})
}
return fn(null, docs)
}
Try something like this:
schema.statics.getLastWeek = function(name, fn) {
var oneday = 60 * 60 * 24
, now = Date.now()
, docs = []
, that = this
function getDay(day){
that.where('name', new RegExp(name, 'i'))
.where('createdAt')
.gte(now - (day * oneday))
.desc('createdAt')
.findOne(function(err,doc){
docs.push(doc)
})
}
for (var i = 1; i <= 7; i++) {
getDay(i);
}
}
Nobody seems to be trying for "close to midnight". :) The issue I saw with the original code was that it checked for a time greater than or equal to x days ago... which will always return the most recent time. I'm confused as to why DeaDEnD's solution returns the same record 7 times, though. Also, you never called fn, but that's not really the biggest of your concerns, is it?
Try adding on .lt(now - (now % oneday) - (i - 1) * oneday) (assuming 0-indexed; if it's 1-indexed, change that to say i - 2)