Cannot insert to collection from NPM using Meteor 1.3 - javascript

I am using the imap-simple NPM package to check emails, and I am having trouble getting the insert to work properly.
I have already read through this page: https://guide.meteor.com/using-npm-packages.html#async-callbacks - and I have tried the suggestions but none of them are working!
I've also simplified the code a bit just to try to get it working, but still have no luck.
The problem should be very easy to reproduce - meteor npm install imap-simple, throw the above code on the server, add some email credentials, and call the method.
Here is my code:
var imaps = require('imap-simple');
var config = {
imap: {
user: '<removed>',
password: '<removed>',
host: 'imap.gmail.com',
port: 993,
tls: true,
authTimeout: 3000
}
};
Meteor.methods({
api_connectEmail: function () {
console.log('Received call to connect email');
imaps.connect(config).then(function (connection) {
return connection.openBox('INBOX').then(function () {
var searchCriteria = [
'UNSEEN'
];
var fetchOptions = {
bodies: ['HEADER', 'TEXT'],
markSeen: true
};
return connection.search(searchCriteria, fetchOptions).then(function (results) {
results.map(function (res) {
var subject = res.parts.filter(function (part) {return part.which === 'HEADER';})[0].body.subject[0];
console.log("Subject: " + subject);
// insert
var attributes = {
subject: subject
};
console.log("Attempting to insert to collection...");
var newData = TempEmailCollection.insert(attributes);
console.log("New Database Entry ID: " + newData);
});
});
});
})
}
});
The console.log with the subject is working. The insert is not working. No error, no console.log post insert, nothing.
I've tried both strategies recommended in the guide, neither work.

The problem is that you are calling a Meteor function inside asynchronously called Promise handlers.
However, all Meteor functions that are called on the server have to run in a fiber.
Meteor actually throws an error in this case but you are ignoring it because you haven't specified catch functions for the Promises.
Consider the following simplified example (it just connects to the server and tries to insert a new document):
import { Meteor } from 'meteor/meteor';
import imaps from 'imap-simple';
const Storage = new Mongo.Collection('storage');
const config = {
imap: {
…
}
};
Meteor.methods({
connect() {
console.log('Method called');
imaps.connect(config).then(function(connection) {
console.log('Connected');
Storage.insert({
value: 'success'
});
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
The following message will arrive in the catch function:
[Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.]
You could do something like this to wrap the insert call:
Meteor.methods({
connect() {
console.log('Method called');
const insert = Meteor.bindEnvironment(function() {
Storage.insert({
value: 'success'
});
});
imaps.connect(config).then(function(connection) {
console.log('Connected');
insert();
console.log('Document inserted');
})
.catch(function(err) {
console.error(err);
});
}
});
Then the document will be inserted as expected.

Related

My Node Script Hangs after functions are finished

I'm calling three functions, after the completion of these functions I want my script to close on it's own but it just hangs.
I've tried making the functions async/promise based, closing the database after each 'mongodb' type function, and using process.exit() within a function as a callback to the last called function.
Connecting to the (local - not Atlas) Database:
MongoClient.connect(local, {useNewUrlParser: true, useUnifiedTopology: true}, function(err, db) {
if (err) {
console.log(err)
}
else {
console.log('Connected to MongoDB...')
//Read in data from jsonfiles and store each file's contents into the database : This is where the functions are being called... within a successful connect to the MongoDB
insertJSON(db, jsonfiles, 'requests', jsonfilesSource)
insertJSON(db, issuedfiles, 'issuedLicenses', isssuedfilesSource)
insertLicenses(db)
}
db.close()
})
Function 1:
function insertJSON(db, dirBuf,collection, sourceFolder) {
var database = db.db('license-server')
var collection = database.collection(collection)
fs.readdir(dirBuf, function(err, files) {
if (err) {
console.log(err.message)
}
else {
files.forEach(function(filename) {
var text = fs.readFileSync(sourceFolder + filename);
var filecontents = JSON.parse(text)
//collection.insertOne(filecontents)
collection.findOne({"DisplayTitle" : filecontents.DisplayTitle, "NodeInformation" : filecontents.NodeInformation, "Date": filecontents.Date})
.then(function(result) {
if(result) {
console.log(`An Item could already be in the database: A file is unique if its display title, nodeinformation, and date are different.
the items display title is ${result.DisplayTitle}`)
return
}
else {
collection.insertOne(filecontents)
console.log(`Added ${filecontents.DisplayTitle} to database`)
}
})
.catch(function(error) {
console.log(error)
})
})
}
})
}
Function 2:
function insertLicenses(db) {
// Set up GridFS to import .lic and .licx files into the database
var database = db.db('license-server')
var collection = database.collection('fs.files')
var bucket = new mongodb.GridFSBucket(database);
var dirBuf = Buffer.from('../license-server/private/licenses')
fs.readdir(dirBuf, function(err, files) {
if (err) {
console.log(err.message)
}
else {
files.forEach(function(filename) {
collection.findOne({"filename": filename}).
then(function(result) {
if(result) {
console.log(`The file ${filename} is already in the database`)
return
}
else {
fs.createReadStream('./private/licenses/' + filename).
pipe(bucket.openUploadStream(filename)).
on('error', function(error) {
assert.ifError(error)
}).
on('finish', function() {
console.log(`Uploaded ${filename}`)
})
}
})
})
}
})
// I tried calling db.close() here since this is the last function to be called. No luck.
}
I'm guessing it has something to do with the mongodb functions having their own way to close themselves but I couldn't seem to find what I was looking for in previous attempts to resolve this issue.
The expected result should be the script closing itself, the actual result is a handing script.
All of these database calls are asynchronous -- the result of this code running is to immediately call db.close and then do the work in insertJSON and insertLicenses. If you were to rewrite this to use async/await (and you'd need to update your other functions as well) the db.close call would close the db, and that would allow the script to exit:
await insertJSON(db, jsonfiles, 'requests', jsonfilesSource)
await insertJSON(db, issuedfiles, 'issuedLicenses', isssuedfilesSource)
await insertLicenses(db)
db.close()
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

How to deal with async calls in nodejs

I'm new to node.js and I'm having a hard time trying to understand the concept of event-based async programming.
I'm implementing a restful API web service, so consider the following simple (synchronous!) API method addStuff(), which inserts stuff to an elasticsearch db:
var client = new elasticsearch.Client({ host: 'localhost:9200' });
function indexStuff(stuff) {
return client.index({
index: 'test_idx',
type: 'test',
id: stuff.id,
body: stuff
});
}
function addStuff(req, res, next) {
let stuff = processRequest(req);
indexStuff(stuff).then(
function (body) {
return true;
},
function (error) {
res.status(error.status).send({ message: error.message });
}
);
}
So far, so good.
Now during testing I wanted to avoid inserting already existing stuff to the db.
So I'd like to add something like:
function stuffAlreadyInDB(id) {
... // returns true/false
}
function addStuff(req, res, next) {
if (stuffAlreadyInDB(req.id))
{
res.status(409).send({ message: 'stuff with id ' + req.id + ' already in DB' });
return;
}
var stuff = processRequest(req);
...
}
Unfortunately, the call to the elasticsearch db is asyncronous, which means, I can't just return a boolean in a sync function. Instead, I have to refactor the whole shabang to something (argueably less easy to read) like this:
function getStuffByID(id) {
return client.get({
id: id,
index: 'test_idx',
type: 'test',
ignore: 404
});
}
function addStuff(req, res, next) {
getStuffByID(req.id).then(
function(resp) {
if (resp.found) {
res.status(409).send({ message: 'stuff with id ' + req.id + ' already in DB' });
return;
}
else {
var stuff = processRequest(req);
indexStuff(stuff).then(
function (body) {
res.writeHead(200, {'Content-Type': 'application/json' });
res.end();
},
function (error) {
res.status(error.status).send({ message: error.message });
}
);
}
},
function(error) {
res.status(error.status).send({ message: error.message });
}
);
}
At least, I haven't found any better solution. I tried to find out how to make the async call to the db a sync call, but basically everybody was saying: just don't do it.
So how am I supposed to do it right if I don't want to refactor everything and back-factor it when I finished testing and don't need this extra db check anymore?
Oh... and if you downvote my question: leave a comment why you do so.
Because I have the feeling that many people struggle with this issue, but I haven't found a satisfying answer yet.
you could use async\await syntax to make your code readable.
for example you could do this:
async function getStuffById(){
//return true or false; }
and in the "add stuff" function you could write:
if ( await getStuffById() ){
//do some more stuff }
please notice that you have to make "add stuff" async as well in order to use await syntax.
more on async \ await can be found here

Call Magento SOAP inside Meteor method invoked by the client

I'm using zardak:soap package in Meteor to connect with Magento SOAP v2 API. I've created a file inside the 'server' folder where I create a soap connection on Meteor.startup. Then I run a ticker that invokes random soap method every 30sec just to keep the connection up.
let soapConnection;
Meteor.startup(() => {
soapConnection = createAPIConnection('http://magento.site.com/api/v2_soap/?wsdl=1', {username: 'user', apiKey: 'password'});
});
function createAPIConnection(url, credentials) {
try {
let client = Soap.createClient(url);
let loginResult = client.login(credentials);
let sessionId = loginResult.loginReturn.$value;
return {
conn: client,
sessionId: sessionId
};
} catch (e) {
if (e.error === 'soap-creation') {
console.log('SOAP Client creation failed');
}
return null;
}
}
function tick() {
try {
soapConnection.conn.catalogCategoryInfo({
sessionId: soapConnection.sessionId,
categoryId: 1
}, (err, result) => { });
} catch (e) { }
}
Then I have a Meteor method that is called from the client. When it is called, the soap method call fails and I'm getting a 'soap error' message in console.
Meteor.methods({
'createMagentoCustomer'(customer) {
try {
soapConnection.conn.customerCustomerCreate({
sessionId: soapConnection.sessionId,
customerData: customer
}, (err, res) => {
if (err)
console.log('soap error');
else
console.log(res);
});
} catch (e) {
console.log('SOAP Method <customerCustomerCreate> call failed');
}
},
});
So, the ticker works well with no problems, but when I try to call soap via Meteor method, it fails. Notice that the soapConnection method is not null and I do receive error in the soap method callback.
Any suggestions?
Meteor version 1.3.4.1

Meteor Collection not displaying in Mongo show collections

I have created a collection called Nodes and have defined it as follows:
(located in project/lib so it has access to both client and server)
Nodes = new Mongo.Collection('nodes');
Nodes.allow({
insert: function (userId, node) {
return !!userId;
}
});
I have an insert method that lies within the same file:
Meteor.methods({
nodeInsert: function(nodeAttributes) {
var node = _.extend(nodeAttributes, {
userId: user._id, author: user.username, submitted: new Date()
});
var nodeIdentity = Nodes.insert(node, function(error,result){
if (error)
console.log(error);
if (result)
console.log(result);
});
}
});
However, there is no error that is logged on the console.
I have also published this collection on the server:
(located in project/server)
Meteor.publish('nodes', function(){
return Nodes.find();
});
I have also subscribed to this collection in the template where I use my insert method:
(located in project/lib so it has access to both client and server)
Router.route('/workflow',{
waitOn: function () {
return [Meteor.subscribe('uploads'),Meteor.subscribe('nodes')];
},
action: function () {
if (this.ready())
this.render('workflow');
else
this.render('Loading');
}
});
Finally, this is how I call my nodeInsert function from my client side template:
(located in project/client)
var node = {
title: d.title
};
Meteor.call('nodeInsert', node, function(error, result) {
if (error)
return throwError(error.reason);
});
The node appears to insert into the collection, but when I use the "show collections" command in mongo, the nodes collection does not come up.I also get an internal server error when I do the insert. What may be the reason for this error?

Why is my Meteor app logging to server but not client?

I'm building a meteor app that hooks into the twitter api and I've had no luck so far getting it to work. I'm using the twit package to make the call on the server side, and it logs the data to the server console, but when the client console goes to log it there is no data.
The client doesn't throw an error, it runs the console.log in the else statement for the result parameter, but it comes through as undefined. It's as if the result callback runs before the data comes back, but my understanding of the Meteor.call method is that it's supposed to wait until it hears back from the server before it runs.
What am I doing wrong here?
if (Meteor.isClient) {
Template.hello.greeting = function () {
return "Welcome to testing.";
};
Template.hello.recentFollows = function () {
return Session.get("recentFollows");
};
Template.hello.events({
'click #fetchButton': function () {
console.log("Recent tweets from stream!");
userName = "josiahgoff";
Meteor.call('getBananaTweets', function(err, result) {
if(err) {
console.log("error occurred on receiving data on server. ", err);
} else {
console.log("result: ", result);
Session.set("recentFollows", result);
}
});
}
});
}
if (Meteor.isServer) {
Meteor.startup(function () {
Twit = new TwitMaker({
consumer_key: '******',
consumer_secret: '******',
access_token: '******',
access_token_secret: '******'
});
});
Meteor.methods({
getBananaTweets: function () {
Twit.get('search/tweets', { q: 'banana since:2011-11-11', count: 1 }, function(err, result) {
if (err) {
console.log("Error", err);
return err;
} else {
console.log(result);
return result;
}
});
}
});
}
You are using return in your server code in a place where it must not be used: in an asynchronous call-back. The Twit.get call returns immediately and the function ends (with no return value). So the client doesn't receive anything. Some time later the Twit.get comes back, but the return in that case goes nowhere.
This is a pretty common question. The solution is to wrap your Twit.get call into a fiber in some shape or form to make it synchronous. See for instance this answer: Iron Router Server Side Routing callback doesn't work

Categories

Resources