Transactions With Mongoose and NodeJs, calling an external function - node.js

I have some code that have implemented Transaction and it works perfectly, but now I need to call a function inside this code(it has a comment in my code example), both parts have to have success or then nothing have to be done. It is not working properly. How can I integrate the "sendCashback, function" to work perfectly with my Transaction?
This is my code:
var transaction: any
let cashback_sent: any
try{
const transactiontimectrl = Number(process.env.TRANSACTION_TIME_STOP)
let dt = new Date();
dt.setSeconds(dt.getSeconds() - transactiontimectrl);
const timectrl = dt.toISOString();
console.log('teste transaction1')
console.log(timectrl)
const session = await mongoose.startSession();
await session.withTransaction(async () => {
const transactionRes = await Transaction.findOne(
{
from: from,
//to: to,
//value: valuetodebit,
createdAt: {$gt: timectrl}
}).session(session)
if(!transactionRes){
transaction = await Transaction.create([
{
from,
to,
value: value,
division_factor: destinationUserGroup.division_factor,
title: title || null,
description: description || null,
type: type || null,
hasCashback: hasCashback,
realmoney: realmoney,
valuetodebit: valuetodebit
}],{ session })
}else{
throw new Error('Erro, uma transação semelhante foi realizada recentemente')
}
if (!transaction) {
throw new Error('Erro, tente novamente mais tarde')
}
console.log('#sadihjaisvq3')
let fromBalance
//Saldo real
if (realmoney) {
fromBalance = await Balance.findOne({
type: IBalanceType.GENERAL,
user: from
}).session(session)
}
//Saldo cashback
else {
fromBalance = await Balance.findOne({
type: IBalanceType.CASHBACK,
user: from
}).session(session)
}
console.log('****criar balance2')
let toBalance = await Balance.findOne({
type: IBalanceType.GENERAL,
user: to
}).session(session)
console.log('****criar balance1', toBalance)
if (!toBalance) {
console.log('****criar balance')
toBalance = await Balance.create(
{
user: to
})
}
let toLivetPay = await Balance.findOne({
type: IBalanceType.GENERAL,
user: toLivet
}).session(session)
if (!fromBalance || !toBalance || !toLivetPay) throw new Error()
if (!credit) {
fromBalance.value = fromBalance.value + parseFloat(valuetodebit) * -1
}
// fromBalance.value = fromBalance.value + parseFloat(valuetodebit) * -1
toBalance.value = toBalance.value + parseFloat(value)
let valorCash = Number(value) / 0.75
let valorLivet = Number(valorCash) * 0.25
toLivetPay.value = toLivetPay.value + parseFloat(String(valorLivet))
await fromBalance.save()
await toBalance.save()
await toLivetPay.save()
if (hasCashback) {
if (!saque) {
//EXTERNAL FUNCTION---------------------------------------------------------
cashback_sent = await sendCashback(
transaction[0],
body.originUser!,
body.destinationUser!
)
//------------------------------------------------------------------------
}
if (!cashback_sent) {
throw new Error('Erro ao distribuir cashback')
}
console.log('depois do cashback')
}
})

The transaction advanced usage is what you need. To work with async await, checkout this link.
As described in the linked mongoose documentation, you can call session.startTransaction() method to start a transaction, and whenever you wish to abort, call session.abortTransaction() to abort the transaction, after all the operations in the transaction are success, call session.commitTransaction() to commit the transaction.

Related

NodeJs parallel request in a loop too slow

so I have this feature that I am working on that takes an object (Over 10k items) picks an item from this object, sends it to an api, which process it then give a response, then proceeds to the next item.
Currently using the async library, the mapLimit method and it works as aspected.
But my problem is that it’s takes too long to loop through the entire dataset cause of the length.
This feature is suppose to be a continual process, once the entire object is iterated, wait for few seconds then do the same thing again.
I tried forking a child_process for this, broke the objects into chunks and created a process for each chunks till it was completed. This worked well as intended but the memory consumption was massive other processes on the server failed as a result of lack of available memory, even after exiting the process after it was completed.
Please how do I achieve this at a faster rate?
I use this to get the list of wallets.
getListofWallet = async () => {
try {
const USDT = await usdt.query(sql`
SELECT * FROM USDT ORDER BY id DESC;
`);
let counter = 0;
let completion = 0;
async.mapLimit(USDT, 6, async (user) => {
let userDetail = {
email: user.email,
id: user.user_id,
address: user.address
}
try {
await this.getWalletTransaction(userDetail);
completion++;
} catch (TronGridException) {
completion++;
console.log(":: A TronGrid Exception Occured");
console.log(TronGridException);
}
if (USDT.length == completion || USDT.length == (completion-5)) {
setTimeout(() => {
this.getListofWallet();
}, 60000);
console.log('~~~~~~~ Finished Wallet Batch ~~~~~~~~~');
}
}
);
} catch (error) {
console.log(error);
console.log('~~~~~~~Restarting TronWallet File after Crash ~~~~~~~~~');
this.getListofWallet();
}
}
Then I use this to process to data sent and perform the neccessary action.
getWalletTransaction = async (walletDetail) => {
const config = {
headers: {
'TRON-PRO-API-KEY': process.env.api_key,
'Content-Type': 'application/json'
}
};
const getTransactionFromAddress = await axios.get(`https://api.trongrid.io/v1/accounts/${walletDetail.address}/transactions/trc20`, config);
const response = getTransactionFromAddress.data;
const currentTimeInMillisecond = 1642668127000; //1632409548000
response.data.forEach(async (value) => {
if (value.block_timestamp >= currentTimeInMillisecond && value.token_info.address == "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t") {
let doesHashExist = await transactionCollection.query(sql`SELECT * FROM transaction_collection WHERE txhash=${value.transaction_id};`);
if (doesHashExist.length == 0) {
if (walletDetail.address == value.to) {
const checkExistence = await CryptoTransactions2.query(sql`
SELECT * FROM CryptoTransaction2 WHERE txHash=${value.transaction_id};
`);
if (checkExistence.length == 0) {
const xCollection = {
collection: "CryptoTransaction2",
queryObject: {
currency: "USDT",
sender: ObjectID("60358d21ec2b4b33e2fcd62e"),
receiver: ObjectID(walletDetail.id),
amount: parseFloat(tronWeb.fromSun(value.value)),
txHash: value.transaction_id,
description: "New wallet Deposit " + "60358d21ec2b4b33e2fcd62e" + " into " + value.to,
category: "deposit",
createdAt: new Date(),
updatedAt: new Date(),
},
};
await new MongoDbService().createTransaction(xCollection);
//create record inside cryptotransactions db.
await CryptoTransactions2.query(sql`INSERT INTO CryptoTransaction2 (txHash) VALUES (${value.transaction_id})`)
});
}

Get number of requisitions made by the user in a day

I have a POST ENDPOINT in my API where i want to register all work journals a worker made.
export const registerPoint = async (req: Request, res: Response) => {
const user = res.locals.decoded;
const {id} = user;
const ponto = new Ponto;
ponto.datePoint = new Date();
ponto.user_id = id;
//this numPoint should restart every day for that user...
ponto.numPoint = 1;
res.json({ msg: "Register point Route", user, id });
return;
}
how can i control how many times this requisition was made by a worker?
I want to control the variable numPoint, when the user makes this requisition it should increase by 01 and then in the final of the day returns it to 0.
Anyone knows about a solution or about a npm package that can handle this?
EDIT: Im storing all the data with SEQUELIZE + MYSQL.
As a starting point you could use a simple database for storing the data such as https://www.npmjs.com/package/sqlite3 or MySQL.
Running jobs daily you could consider https://www.npmjs.com/package/node-cron , for example having a daily job (outside of an API call function);
var cron = require('node-cron');
cron.schedule('0 0 1 * *', () => {
console.log('running every minute to 1 from 5');
});
From what I understand, you need a logging/audit trail mechanism. Since you are using MySQL you can create a new table with columns like (datetime, user_id, action). Every time the user does any action, it will be logged here. Then you can easily find out the count by aggregation. You won't need to reset any count for any user, if data doesn't exist for a given date then it's count will be 0.
I've made a solution that it worked for what i want.
Here is the solution below:
export const registerPoint = async (req: Request, res: Response) => {
const decodedUser = res.locals.decoded;
const user = await User.findByPk(decodedUser.id);
const TODAY_START = new Date().setHours(0, 0, 0, 0);
const NOW = new Date();
const markedPointsOfDay = await Ponto.findAll({
where: {
datePoint: {
[Op.gt]: TODAY_START,
[Op.lt]: NOW
},
}
});
const markedPointsOfDayByUser = await Ponto.findAll({
where: {
datePoint: {
[Op.gt]: TODAY_START,
[Op.lt]: NOW
},
UserId: (user !== null) ? user.id : decodedUser.id
}
})
if (!markedPointsOfDay || markedPointsOfDay.length === 0) {
const ponto = new Ponto;
ponto.datePoint = new Date();
if (user) {
console.log(user)
ponto.UserId = user.id as number;
console.log(ponto.UserId);
}
if (markedPointsOfDayByUser) {
ponto.numPoint = markedPointsOfDayByUser.length + 1;
}
const newPoint = await ponto.save();
res.json({ msg: "Ponto registrado com sucesso", msg2: "Caiu no IF de quando nao encontrou ponto do DIA", newPoint })
return;
}
if (markedPointsOfDay) {
const ponto = new Ponto;
ponto.datePoint = new Date();
if (user) {
ponto.UserId = user.id as number;
}
if (markedPointsOfDayByUser) {
ponto.numPoint = markedPointsOfDayByUser.length + 1;
}
const newPoint = await ponto.save();
res.json({ msg: "ponto registrado", markedPoint: newPoint, markedPointsOfDayByUser });
return;
}
return;
}

Music freezes for 1 second when adding a song to the queue then continues playing

My music bot works great but there is a problem that when I add another song to the queue, the currently playing song will freeze for 0.70s - 1s, then will continue playing and add the song earlier in the queue, i want when i add a song to the queue, the song currently playing will continue playing, here is my code in play.js (i don't know where the error comes from)
const { Util, MessageEmbed } = require("discord.js");
const ytdl = require("ytdl-core");
const ytdlDiscord = require("ytdl-core-discord");
const yts = require("yt-search");
const fs = require('fs');
const sendError = require("../util/error")
const sendfact = require("../unti/fact")
module.exports = {
info: {
name: "play",
description: "To play songs",
usage: "<YouTube_URL> | <song_name>",
aliases: ["p"],
},
run: async function (client, message, args) {
let channel = message.member.voice.channel;
if (!channel)return sendError("You need to be in voice channel to use this command!", message.channel);
const permissions = channel.permissionsFor(message.client.user);
if (!permissions.has("CONNECT"))return sendError("I cannot connect to your voice channel, make sure I have [CONNECT] permissions to play!", message.channel);
if (!permissions.has("SPEAK"))return sendError("I cannot speak in this voice channel, make sure I have the [SPEAK] permissions to play!", message.channel);
var searchString = args.join(" ");
if (!searchString)return sendError("There are no songs for me to play yet", message.channel);
const url = args[0] ? args[0].replace(/<(.+)>/g, "$1"): " ";
var serverQueue = message.client.queue.get(message.guild.id);
let songInfo = null;
let song = null;
if (url.match(/^(https?:\/\/)?(www\.)?(m\.)?(youtube\.com|youtu\.?be)\/.+$/gi)) {
try {
songInfo = await ytdl.getInfo(url)
if(!songInfo)return sendError("Can't find the song you want on youtube - is it real?", message.channel);
song = {
id: songInfo.videoDetails.videoId,
title: songInfo.videoDetails.title,
url: songInfo.videoDetails.video_url,
img: songInfo.player_response.videoDetails.thumbnail.thumbnails[0].url,
duration: songInfo.videoDetails.lengthSeconds,
ago: songInfo.videoDetails.publishDate,
views: String(songInfo.videoDetails.viewCount).padStart(10, ' '),
req: message.author
};
} catch (error) {
console.error(error);
return message.reply(error.message).catch(console.error);
}
}else {
try {
var searched = await yts.search(searchString);
if(searched.videos.length === 0)return sendError("Can't find the song you want on youtube - is it real?", message.channel)
songInfo = searched.videos[0]
song = {
id: songInfo.videoId,
title: Util.escapeMarkdown(songInfo.title),
views: String(songInfo.views).padStart(5, ' '),
url: songInfo.url,
ago: songInfo.ago,
duration: songInfo.duration.toString(),
img: songInfo.image,
req: message.author
};
} catch (error) {
console.error(error);
return message.reply(error.message).catch(console.error);
}
}
sendfact("If you want to play a playlist on youtube , try to use `playlist <Playlist url>`", message.channel)
if (serverQueue) {
let thing = new MessageEmbed()
.setAuthor("Song has been added to queue", "https://cdn.discordapp.com/emojis/943352487723827281.gif?size=96&quality=lossless")
.setThumbnail(song.img)
.setColor("WHITE")
.addField("Title", song.url, true)
.addField("Duration", song.duration, true)
.addField("Requested by", song.req.tag, true)
.setFooter(`Views: ${song.views} # ${song.ago}`)
serverQueue.songs.push(song);
return message.channel.send(thing);
}
const queueConstruct = {
textChannel: message.channel,
voiceChannel: channel,
connection: true,
songs: [],
volume: 80,
playing: true,
loop: false
};
message.client.queue.set(message.guild.id, queueConstruct);
queueConstruct.songs.push(song);
const play = async (song) => {
const queue = message.client.queue.get(message.guild.id);
let afk = JSON.parse(fs.readFileSync("./afk.json", "utf8"));
if (!afk[message.guild.id]) afk[message.guild.id] = {
afk: false,
};
var online = afk[message.guild.id]
if (!song){
if (!online.afk) {
sendError("Nothing to play ! If you want to loop , please use `loop`", message.channel)
message.client.queue.delete(message.guild.id);
}
return message.client.queue.delete(message.guild.id);
}
let stream = null;
if (song.url.includes("youtube.com")) {
stream = await ytdl(song.url);
stream.on('error', function(er) {
if (er) {
if (queue) {
queue.songs.shift();
play(queue.songs[0]);
return sendError(`An unexpected error has occurred.\nPossible type \`${er}\``, message.channel)
}
}
});
}
queue.connection.on("disconnect", () => message.client.queue.delete(message.guild.id));
const dispatcher = queue.connection
.play(ytdl(song.url, {quality: 'highestaudio', highWaterMark: 1 << 1 ,type: "opus"}))
.on("finish", () => {
const shiffed = queue.songs.shift();
if (queue.loop === true) {
queue.songs.push(shiffed);
};
play(queue.songs[0])
})
dispatcher.setVolumeLogarithmic(queue.volume / 100);
let thing = new MessageEmbed()
.setAuthor('Started Playing Music!', "https://cdn.discordapp.com/emojis/943352487723827281.gif?size=96&quality=lossless", message.author.name)
.setThumbnail(song.img)
.setColor("WHITE")
.addField("Title", song.title, true)
.addField("Duration", song.duration, true)
.addField("Requested by", song.req.tag, true)
.setFooter(`Views: ${song.views} # ${song.ago}`)
queue.textChannel.send(thing);
};
try {
const connection = await channel.join();
play(queueConstruct.songs[0]);
connection.voice.setSelfDeaf(true);
queueConstruct.connection = connection;
} catch (error) {
console.error(`I could not join the voice channel: ${error}`);
message.client.queue.delete(message.guild.id);
await channel.leave();
return sendError(`I could not join the voice channel: ${error}`, message.channel);
}
},
};

How to handle scarce resource disputes in a procurement system

In the code below, when you have a purchase order for a product with 1 unit and two users try to buy at the same time, both are able to buy. I'm using transactions in this process and I believed that the transaction idea would handle this case.
I'm using DB postgres and I've already tested using ISOLATION LEVEL for this transaction, but without success.
try {
//I tried this, but doesn't work
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
let order_total = 0;
const products = [];
const sales = [];
for (const pBought of data.products) {
let prod = await trx('tb_product')
.select('tb_product.id', 'tb_product.title', 'tb_product.price', 'tb_product.price_total', 'tb_product.quantity', 'tb_product.id_salesman', 'tb_product.width', 'tb_product.height', 'tb_product_discount.value', 'tb_product_discount.is_deleted')
.leftJoin('tb_product_discount', 'tb_product_discount.id', 'tb_product.id_discount')
.where({ 'tb_product.id': pBought.id_product, 'tb_product.is_active': true, 'tb_product.is_deleted': false });
prod = prod[0];
if (!prod) throw new Error('Produto inexistente!');
let salesman = await trx('tb_salesman')
.select('*')
.where({ 'id': prod.id_salesman, 'is_deleted': false });
salesman = salesman[0];
if (!salesman) throw new Error('Vendedor inexistente!');
if (salesman.id !== prod.id_salesman)
throw new Error('Vendedor inconsistente!');
if (!prod || !salesman)
throw new Error('Não foi possível recuperar as informações!');
if (pBought.quantity <= 0)
throw new Error(`Quantidade informada de ${prod.title} menor que 1!`);
console.log(prod.quantity, pBought.quantity);
if (prod.quantity < pBought.quantity)
throw new Error(`Quantidade de ${prod.title} maior que a disponível!`);
if (prod.price !== pBought.price)
throw new Error(`Preço de produtos inválido, atualize a página!`);
let aux;
if (prod.value && !prod.is_deleted) {
aux = (prod.price - prod.value) * prod.width * prod.height;
order_total += roundToTwo(aux * pBought.quantity);
} else {
aux = prod.price_total;
order_total += roundToTwo(aux * pBought.quantity);
}
sales.push({
id_salesman: salesman.id,
id_product: prod.id,
product_title: prod.title,
quantity: prod.quantity,
unit_price: parseInt(aux.toFixed(0)),
price: pBought.quantity * aux
});
products.push({ id_product: prod.id, price: aux, discount: prod.value, quantity: pBought.quantity, available_quantity: prod.quantity, tax: process.env.PERCENTAGE_TAX });
}
let order = await trx('tb_order').insert({ order_total, id_client: client.client.id_client, id_address: address.address.id, portions: data.portions, method: data.method }).returning('*');
order = order[0];
for (const prod of products) {
if (prod.available_quantity - prod.quantity)
await trx('tb_product').update('quantity', prod.available_quantity - prod.quantity)
.where({ 'id': prod.id_product });
else
await trx('tb_product').update({ 'quantity': 0, 'is_active': false })
.where({ 'id': prod.id_product });
delete prod['available_quantity'];
prod.id_order = order.id;
}
await trx('tb_order_product').insert(products);
const wasPaid = await Payment.execute(sales, { portions: data.portions, method: data.method, card_hash: data.card_hash, document: data.document, name: data.name, total: parseInt((order_total * 100)).toFixed(0) }, client, address, order.id);
if (!wasPaid.success) throw new Error(wasPaid.message);
trx.commit();
return { success: true, order };
} catch (error) {
Message.warning(error);
return { success: false, message: error.message }
}
I'm using Node.js and Knex.
I want know how can I handle this scenario for allow only one user take a product at once.
Using serializable for isolation level I get error, because even non-scarce products cannot be acquired simultaneously, because it locks the table and, if another user tries to buy, any product that is at the same instant, will receive an error.

Sequelize Transaction inside forEach loop

I'm trying to use transaction inside forEach loop using async/await syntax of Node 7.0+
When I try to print committed transaction response in console, I'm able to see the values but those same values are not committed in to DB.
Below is the code :
documentInfo.forEach(async (doc) => { // array of documentInfo
var frontImgName = await module.exports.uploadImage(docFiles, doc.front, req, res )
var backImgName = await module.exports.uploadImage(docFiles, doc.back, req, res )
var checkKycDoc = await KYCDocument.findOne({
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
}
})
if (checkKycDoc) { //update
var updateDocument = await KYCDocument.update({
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {
where: {
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id
},
}, {transaction})
log('updateDocument', updateDocument.dataValues)
} else { // insert
var newKycDocument = await new KYCDocument({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName,
status: true
}, {transaction})
log('newKycDocument', newKycDocument.dataValues)
}
if (rowCount === documentInfo.length) {
await transaction.commit() // transaction is printed on this line
log('KYC has been uploaded successfully')
helpers.createResponse(res, constants.SUCCESS,
messages.KYC_UPLOAD_SUCCESS,
{'error': messages.KYC_UPLOAD_SUCCESS}
)
} else {
rowCount++
}
})
The issue was in the create method.
To resolve the issue I had to create a new row using:
var newKycDocument = await KYCDocument.create({
kyc_id: checkUserKyc.dataValues.kyc_id,
user_id: checkUserKyc.dataValues.user_id,
document_name: doc.document_name,
front_image: frontImgName,
back_image: backImgName
}, {transaction})
I was missing the .create method.

Resources