Transform publish MeteorJS with count of show - node.js

I try to use limit : count with transform observer in Meteor and don't understand how to do it without "dirty" solutions.
Code I have on Client (not all, but main part)
var self = this;
self.autorun(function() {
self.subscribe('posts', Session.get('count')); // some Number like 10
}); // client
And on server where I try to use it
Meteor.publish('posts', function(count){
check(count, Number);
let i = 0;
var transform = function(doc) {
console.log(i,count);
if (i < count){ // I try something here
doc.description = new Date();
i++;
return doc;
}
else self.ready();
}
var self = this;
var observer = Posts.find().observe({
added: function (document) {
self.added('posts', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('posts', document._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('posts', oldDocument._id);
}
});
self.onStop(function () {
observer.stop();
});
self.ready();
});
Any idea how to limit count of shown documents with transform in publish ?

Just use Posts.find({},{limit:count}) in your query.

Related

How to connect my electron app using PouchDB (leveldb) with Cloudant or any other database that support couchDB and sync

I'm creating an electron app using pouchDB and I want the app to be able to diferents customers and sync the data between them. As an example I'm making the tutorial: https://github.com/nolanlawson/pouchdb-getting-started-todo, I adapt the code to electron and I created a noSQL database at cloudant.
At the moment I can save data but I cannot sync with my remote db that is in cloudant. Here is the endpoint I'm using to sync data between both database.
Here is the error that I'm getting.
Here is the code of my script.js
(function() {
'use strict';
var $ = document.querySelector.bind(document);
var ENTER_KEY = 13;
var newTodoDom = document.getElementById('new_todo');
var syncDom = document.getElementById('sync-wrapper');
// EDITING STARTS HERE (you dont need to edit anything above this line)
var NodePouchDB = require('pouchdb');
var db = new NodePouchDB('todos');
var couchdb = require('felix-couchdb')
var remoteCouch = couchdb.createClient(5984, 'https://ac725f4e-29ec-4614-8e96-02ebc74a529b-bluemix.cloudant.com/')
db.info(function(err, info) {
console.log("is working", info)
db.changes({
since: info.update_seq,
live: true
}).on('change', showTodos);
});
// We have to create a new todo document and enter it in the database
function addTodo(text) {
var todo = {
_id: new Date().toISOString(),
title: text,
completed: false
};
db.put(todo).then(function (result) {
console.log("everything is A-OK");
console.log(result);
}).catch(function (err) {
console.log('everything is terrible');
console.log(err);
});
}
// Show the current list of todos by reading them from the database
function showTodos() {
db.allDocs({include_docs: true, descending: true}).then(function(doc) {
redrawTodosUI(doc.rows);
}).catch(function (err) {
console.log(err);
});
}
function checkboxChanged(todo, event) {
todo.completed = event.target.checked;
console.log(todo);
db.put(todo);
}
// User pressed the delete button for a todo, delete it
function deleteButtonPressed(todo) {
db.remove(todo);
}
// The input box when editing a todo has blurred, we should save
// the new title or delete the todo if the title is empty
function todoBlurred(todo, event) {
var trimmedText = event.target.value.trim();
if (!trimmedText) {
db.remove(todo);
} else {
todo.title = trimmedText;
db.put(todo);
}
}
// Initialise a sync with the remote server
function sync() {
syncDom.setAttribute('data-sync-state', 'syncing');
var opts = {live: true};
db.sync(remoteCouch, opts, syncError);
}
// EDITING STARTS HERE (you dont need to edit anything below this line)
// There was some form or error syncing
function syncError() {
syncDom.setAttribute('data-sync-state', 'error');
}
// User has double clicked a todo, display an input so they can edit the title
function todoDblClicked(todo) {
var div = document.getElementById('li_' + todo._id);
var inputEditTodo = document.getElementById('input_' + todo._id);
div.className = 'editing';
inputEditTodo.focus();
}
// If they press enter while editing an entry, blur it to trigger save
// (or delete)
function todoKeyPressed(todo, event) {
if (event.keyCode === ENTER_KEY) {
var inputEditTodo = document.getElementById('input_' + todo._id);
inputEditTodo.blur();
}
}
// Given an object representing a todo, this will create a list item
// to display it.
function createTodoListItem(todo) {
var checkbox = document.createElement('input');
checkbox.className = 'toggle';
checkbox.type = 'checkbox';
checkbox.addEventListener('change', checkboxChanged.bind(this, todo));
var label = document.createElement('label');
label.appendChild( document.createTextNode(todo.title));
label.addEventListener('dblclick', todoDblClicked.bind(this, todo));
var deleteLink = document.createElement('button');
deleteLink.className = 'destroy';
deleteLink.addEventListener( 'click', deleteButtonPressed.bind(this, todo));
var divDisplay = document.createElement('div');
divDisplay.className = 'view';
divDisplay.appendChild(checkbox);
divDisplay.appendChild(label);
divDisplay.appendChild(deleteLink);
var inputEditTodo = document.createElement('input');
inputEditTodo.id = 'input_' + todo._id;
inputEditTodo.className = 'edit';
inputEditTodo.value = todo.title;
inputEditTodo.addEventListener('keypress', todoKeyPressed.bind(this, todo));
inputEditTodo.addEventListener('blur', todoBlurred.bind(this, todo));
var li = document.createElement('li');
li.id = 'li_' + todo._id;
li.appendChild(divDisplay);
li.appendChild(inputEditTodo);
if (todo.completed) {
li.className += 'complete';
checkbox.checked = true;
}
return li;
}
function redrawTodosUI(todos) {
var ul = document.getElementById('todo-list');
ul.innerHTML = '';
todos.forEach(function(todo) {
ul.appendChild(createTodoListItem(todo.doc));
});
}
function newTodoKeyPressHandler( event ) {
if (event.keyCode === ENTER_KEY) {
addTodo(newTodoDom.value);
newTodoDom.value = '';
}
}
function addEventListeners() {
newTodoDom.addEventListener('keypress', newTodoKeyPressHandler, false);
}
addEventListeners();
showTodos();
if (remoteCouch) {
sync();
}
})();
To get to where the problem sits, can you verify that you can speak to the Cloudant database normally, that is using curl from the command-line? Using curl, fetch a document by its _id, perhaps a document you created manually using the Cloudant dashboard. That should shake out any problems with authentication: I note you're using IAM, which isn't always straight-forward -- and to my knowledge, not supported by PouchDB (or wasn't, last time I looked).
If that is the problem, create a new Cloudant instance with IAM+Legacy credentials.

Proper way to make callbacks async by wrapping them using `co`?

It is 2016, Node has had nearly full ES6 support since v4, and Promises have been around since 0.12. It's time to leave callbacks in the dust IMO.
I'm working on a commander.js-based CLI util which leverages a lot of async operations - http requests and user input. I want to wrap the Commander actions in async functions so that they can be treated as promises, and also to support generators (useful for the co-prompt library I'm using for user input).
I've tried wrapping the CB with co in two ways:
1)
program.command('myCmd')
.action(program => co(function* (program) {...})
.catch(err => console.log(err.stack)) );
and
2) program.command('myCmd').action(co.wrap(function* (program) { .. }));
The problem with 1) is that the program parameter isn't passed
The problem with 2) is that errors are swallowed...
I'd really like to get this working as it yields much nicer code in my use case - involving a lot of http requests and also waiting for user input using the co-prompt library..
Is it a better option altogether perhaps to wrap program.Command.prototype.action somehow?
thanks!
I've used a bespoke version of something like co to get a db.exec function which uses yield to do database request. You can pass parameters into a generator function (I pass in a connection object - see the comment where I do it).
Here is by db.exec function that is very similar to what co does
exec(generator) {
var self = this;
var it;
debug('In db.exec iterator');
return new Promise((accept,reject) => {
debug('In db.exec Promise');
var myConnection;
var onResult = lastPromiseResult => {
debug('In db.exec onResult');
var obj = it.next(lastPromiseResult);
if (!obj.done) {
debug('db.exec Iterator NOT done yet');
obj.value.then(onResult,reject);
} else {
if (myConnection) {
myConnection.release();
debug('db.exec released connection');
}
accept(obj.value);
debug('db.exec Promise Resolved with value %d',obj.value);
}
};
self._connection().then(connection => {
debug('db.exec got a connection');
myConnection = connection;
it = generator(connection); //This passes it into the generator
onResult(); //starts the generator
}).catch(error => {
logger('database', 'Exec Function Error: ' + error.message);
reject(error);
});
});
}
the connection object also wraps by database connection object and provides a generator function ability to process the rows of the results from the database, but I won't post that here (although the example below is using it to process the rows).
Here is an example of using the exec function to run a sequence of sql
db.exec(function*(connection) {
if (params.name === ADMIN_USER) {
debug('Admin Logon');
user.name = ADMIN_DISPLAY;
user.keys = 'A';
user.uid = 0;
let sql = 'SELECT passwordsalt FROM Admin WHERE AdminID = 0';
connection.request(sql);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.nopass = (row[0].value === null);
} else {
user.nopass = false;
}
debug('Admin Password bypass ' + user.nopass.toString());
});
} else {
debug('Normal User Logon');
let sql = `SELECT u.UserID,PasswordSalt,DisplayName,AccessKey,l.LogID FROM Users u
LEFT JOIN UserLog l ON u.userID = l.userID AND DATEDIFF(D,l.LogDate,GETDATE()) = 0
WHERE u.UserName = #username`;
let request = connection.request(sql);
request.addParameter('username',db.TYPES.NVarChar,params.name);
let count = yield connection.execSql(function*() {
let row = yield;
if (row) {
user.uid = row[0].value;
user.name = row[2].value;
user.keys = (row[3].value === null) ? '' : row[3].value;
user.nopass = (row[1].value === null) ;
user.lid = (row[4].value === null) ? 0 : row[4].value;
debug('Found User with uid = %d and lid = %d, keys = %s',
user.uid, user.lid, user.keys);
}
});
if (count === 0) {
debug('Not Found User');
// couldn't find name in database
reply(false,false);
return;
}
}
if (!user.nopass) {
debug('Need a Password');
//user has a password so we must check it
passGood = false; //assume false as we go into this
let request = connection.request('CheckPassword');
request.addParameter('UserID',db.TYPES.Int,user.uid);
request.addParameter('password',db.TYPES.VarChar,params.password);
yield connection.callProcedure(function*() {
let row = yield;
if (row) {
//got a valid row means we have a valid password
passGood = true;
}
});
} else {
passGood = true;
}
if (!passGood) {
debug('Not a Good Pasword');
reply(false,true);
} else {
if (user.uid !== 0 && user.lid === 0) {
let sql = `INSERT INTO UserLog(UserID,LogDate,TimeOn,UserName) OUTPUT INSERTED.logID
VALUES(#uid,GETDATE(),GETDATE(),#username)`;
let request = connection.request(sql);
request.addParameter('uid',db.TYPES.Int,user.uid);
request.addParameter('username',db.TYPES.NVarChar,user.name);
yield connection.execSql(function*() {
let row = yield;
if (row) {
user.lid = row[0].value;
debug('Users Log Entry = %d',user.lid);
}
});
}
reply(true,user);
}
})
.catch((err) => {
logger('database','Error on logon: ' + err.message);
reply(false,false);
});
});
There is a quite simple way to do async function in Commander.js
async function run() {
/* code goes here */
}
program
.command('gettime')
.action(run);
program.parse(process.argv);

Meteor, MongoDb: Double inserting to collection on few occurences

I noticed this strange behaviour, when for few users only on production, it inserts every item multiple times to collection on asynchronous Meteor call. I tried multiple things, but nothing worked. I can't test on localhost, bc it never happens to me on localhost or in production.
I spent the whole night solving this, but didn't find any solution. I suppose it's caused by new Date(), but I have to call it somewhere. The production server is in Amsterdam and it seems like it happens only for users located further outside of Europe.
I found this to be similar issue, but can't really wrap my head on how to implement - https://github.com/meteor/meteor/issues/4263
Slug is what same songs are supposed to have the same.
This is the workflow, user clicks on song that triggers addNewSong function:
addNewSong = function (track) {
Globals.current_track = track;
checkIfSongAlreadySaved();
}
I need to check if song is already in collection, if it's -> route to it, else create the new song and route to it.
checkIfSongAlreadySaved = function() {
loadPrimaryGlobalItems();
Meteor.call('checkIfSongAlreadySaved', Globals.current_song_item_slug, function(error, result) {
if( result.item ) {
Globals.current_song_item_id = result.item._id;
Globals.current_song_item_slug = result.item.slug;
routeToSongPage();
if (! (result.item.download && result.item.mp3) ) {
downloadSong();
}
}
else {
loadSecondaryGlobalItems();
var item = {
slug:Globals.current_song_item_slug,
duration:Globals.current_duration,
thumbnail:Globals.current_song_thumbnail,
title:Globals.current_cleaned_song,
album:Globals.current_track.album,
artist:Globals.current_track.artists[0],
track:Globals.current_track.name,
date:result.date,
}
Globals.current_song_item_id = Songs.insert(item);
routeToSongPage();
downloadSong();
recentSongItem(result.date);
}
});
}
Add recent song
recentSongItem = function (date) {
Recentsongs.insert({
slug:Globals.current_song_item_slug,
songId:Globals.current_song_item_id,
title:Globals.current_cleaned_song,
duration:Globals.current_duration,
date:date,
});
}
If creating new song,
downloadSong = function() {
Meteor.call('findSong', Globals.current_song, function(error, result) {
console.log(result);
if (result) {
Globals.current_song_mp3 = true;
updateSongItemDownload(result.itemDetails);
}
else {
alert('not found')
}
});
}
and update song, to add download and mp3 values.
updateSongItemDownload = function(link) {
Songs.update({
_id: Globals.current_song_item_id
},
{
$set: {
download: link,
mp3: Globals.current_song_mp3,
}
});
}
Server methods:
Meteor.methods({
checkIfSongAlreadySaved: function(slug) {
return {item: Songs.findOne({slug:slug}), date: new Date()};
},
findSong:function(song) {
ServerGlobals.current_song = song;
var result = searchSite();
return result;
},
});
EDIT:
This is my subscription, just in case it might be causing the problem:
Template.songPage.onCreated(function() {
Session.set('processing', true);
var self = this;
self.autorun(function() {
var id = Router.current().params.id;
self.subscribe('singleSong', id);
var item = Songs.findOne({_id: id});
if (item) {
if (item.download) {
createSong(item.download);
}
else if( item.download === false ) {
console.log('item not found');
}
Session.set('loader', false);
Session.set('processing', false);
}
});
});
Meteor.publish('singleSong', function(id) {
check(id, String);
return Songs.find({_id: id});
});
You can apply a unique index on the slug field to ensure the same slug can only exist once and the second operation to insert will fail and show up as an error in your callback which you can discard or alert user as you desire.
db.collection.createIndex( { slug: 1 }, { unique: true } )
You will need to clear or modify the slug name on the dups from the db before applying the index though

What is the best way to loop bluebird promises

Now I've working on NodeJS and Sequelize to query and process an database data.
I've call findAll from Table1 and I want to query each rows to apply some data to Table2 then I want to add all data to array before send output, I did like this
var last_promise;
var output_results = {};
Table1Model.findAll()
.then(function(results1)
{
for (var i = 0; i < results1.length; ++i)
{
var result1 = results1[i];
output_results[result1.id] = result1;
var add_promise = Table2Model
.create({
id_from_table1: result1.id,
data_from_table1: result1.data
});
.then(function(result2) {
output_results[result2.id_from_table1].data2 = result2;
});
if (last_promise)
{
last_promise.then(function()
{
return add_promise;
});
} else {
last_promise = add_promise;
}
}
}
}
last_promise.then(function() {
return output_results;
}
I want to know that there any better way to execute promises sequentially in a loop like this ?
It looks like you can do that with .all() method:
Table1Model
.findAll()
.then(function(results1) {
return Promise.all(results1.map(function(result) {
return Table2Model
.create({
id_from_table1: result1.id,
data_from_table1: result1.data
})
.then(function(result2) {
...
});
}));
})
.then(function(output_results) {
});

mongodb data async in a for-loop with callbacks?

Here is a sample of the working async code without mongodb. The problem is, if i replace the vars (data1_nodb,...) with the db.collection.find(); function, all needed db vars received at the end and the for()-loop ends not correct. Hope someone can help. OA
var calc = new Array();
function mach1(callback){
error_buy = 0;
// some vars
for(var x_c99 = 0; x_c99 < array_temp_check0.length;x_c99++){
// some vars
calc[x_c99] = new Array();
calc[x_c99][0]= new Array();
calc[x_c99][0][0] = "dummy1";
calc[x_c99][0][1] = "dummy2";
calc[x_c99][0][2] = "dummy3";
calc[x_c99][0][3] = "dummy4";
calc[x_c99][0][4] = "dummy5";
function start_query(callback) {
data1_nodb = "data1";
data2_nodb = "data2";
data3_nodb = "data3";
data4_nodb = "data4";
calc[x_c99][0][0] = data1_nodb;
calc[x_c99][0][1] = data2_nodb;
calc[x_c99][0][2] = data3_nodb;
callback(data1_nodb,data2_nodb,etc..);
}
start_query(function() {
console.log("start_query OK!");
function start_query2(callback) {
data4_nodb = "data5";
data5_nodb = "data6";
data6_nodb = "data7";
calc[x_c99][0][3] = data4_nodb;
calc[x_c99][0][4] = data5_nodb;
callback(data5_nodb,data6_nodb,etc..);
}
start_query2(function() {
console.log("start_query2 OK!");
function start_query3(callback) {
for(...){
// do something
}
callback(vars...);
}
start_query3(function() {
console.log("start_query3 OK!");
});
});
});
}
callback(calc);
};
function mach2(callback){
mach1(function() {
console.log("mach1 OK!");
for(...){
// do something
}
});
callback(calc,error_buy);
};
mach2(function() {
console.log("mach2 OK 2!");
});
You need to work with the async nature of the collection.find() method and wait for all of them to be done. A very popular approach is to use the async module. This module allows you run several parallel tasks and wait for them to finish with its async.parallel() method:
async.parallel([
function (callback) {
db.foo.find({}, callback);
},
function (callback) {
db.bar.find({}, callback);
},
function (callback) {
db.baz.find({}, callback);
}
], function (err, results) {
// results[0] is the result of the first query, etc
});

Resources