Combined search in sails js - node.js

I have game collection:
{
"name": "Play RoadRash",
"version": "1.0.0",
"icon": "image-md-two-thirds.png",
"id": "6dc41c3fa0e7"
}
and platform collection:
{
"name": "PlayStation",
"version": "7",
"icon": "playstation.jpg",
"id": "55eaf322f1a16"
}
I'm trying to create a search query who searches in both collection based on name parameters. Does anyone have any idea how to search on multiple collection in sails waterline?

We've wrote a controller with full-text search within all models. All what it does is search within all models and their attributes by q parameter from request. Here is the full code of this controller:
var _ = require('lodash');
var Promise = require('bluebird');
module.exports = {
index: function (req, res) {
var models = [];
if (!req.param('q')) {
return res.badRequest(null, null, 'You should specify a "q" parameter!');
}
var q = req.param('q');
if (req.param('model')) {
var modelStr = req.param('model').toString().toLowerCase();
if (!(modelStr in sails.models)) {
return res.badRequest(null, null, 'Cannot find model: ' + modelStr);
}
models.push({name: modelStr, model: sails.models[modelStr]});
} else {
_.forEach(sails.models, function (model, modelStr) {
models.push({name: modelStr, model: model});
});
}
Promise.map(models, function (modelObj) {
var model = modelObj.model;
var modelStr = modelObj.name;
var where = _.transform(model.definition, function (result, val, key) {
result.or.push(_.set({}, key, {contains: q}));
}, {or: []});
return model
.find(where)
.then(function (queryRes) {
var resObj = {};
resObj[modelStr] = queryRes;
return Promise.resolve(resObj)
});
})
.then(function (searchRes) {
return _.transform(searchRes, function (result, val) {
result = _.merge(result, val);
}, {});
})
.then(res.ok)
.catch(res.serverError)
}
};
You can just copy-paste it in your api/controllers/SearchController.js and that's it. It still need to refactor this code, but it works.

Related

collection.find() for 2 collections and pushing the information to a view - mongoose

I am trying to extract all documents from 2 collections using collection.find() and pushing all the information to an ejs view using res.render using mongoose (node.js).
My code:
var eduvents = {
en: [],
ar: []
};
Eduvent.find({}, function(err, allEduvents) {
allEduvents.forEach(function(eduvent) {
eduvents.en.push(eduvent);
});
});
EduventAr.find({}, function(err, allEduvents) {
allEduvents.forEach(function(eduvent) {
eduvents.ar.push(eduvent);
});
});
console.log(eduvents);
when this is executed, I get this:
{ en: [], ar: [] }
Shouldn't I get all the Eduvent and EduventAr queries in the eduvent variable?
Because mongoose find is synchronous, your console will run instantly before the response from query is returned, hence its empty
You can do something like this if you want to stick with this callbacks approach
var eduvents = {
en: [],
ar: []
};
Eduvent.find({}, function(err, allEduvents) {
allEduvents.forEach(function(eduvent) {
eduvents.en.push(eduvent);
});
EduventAr.find({}, function(err, allEduvents) {
allEduvents.forEach(function(eduvent) {
eduvents.ar.push(eduvent);
});
console.log(eduvents);
});
});
Another approach would be to use async await as mongoose already has support for Promises
Using Async / AWAIT
async function GetEvents(){
try {
var eduvents = {
en: [],
ar: []
};
var allEvents = await Eduvent.find();
// Here you can simply assign array to the `eduevents.en` if you are not altering anything from `allEduvents`
eduvents.en = allEduvents;
var otherEvents = await EduventAr.find();
eduvents.ar = otherEvents;
console.log(eduvents);
} catch(err) {
console.log("ERROR",err);
}
}

Alexa Test Response does not contain outputSpeech

I'm new to Alexa, and have followed the airportinfo tutorial and I have copied the code from github https://github.com/bignerdranch/alexa-airportinfo and when i test it using npm and input an airport code e.g. SFO, Theres no "outputSpeech:" and i tried making a similar skill with the same issue, I'm not sure what I'm doing wrong. I have both index.js and FAADataInfo.js Thanks in advance for your help.
This is the index.js file
'use strict';
module.change_code = 1;
var _ = require('lodash');
var Alexa = require('alexa-app');
var skill = new Alexa.app('airportinfo');
var FAADataHelper = require('./faa_data_helper');
skill.launch(function(req, res) {
var prompt = 'For delay information, tell me an Airport code.';
res.say(prompt).reprompt(prompt).shouldEndSession(false);
});
skill.intent('airportInfoIntent', {
'slots': {
'AIRPORTCODE': 'FAACODES'
},
'utterances': [
'{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}'
]
},
function(req, res) {
var airportCode = req.slot('AIRPORTCODE');
var reprompt = 'Tell me an airport code to get delay information.';
if (_.isEmpty(airportCode)) {
var prompt = 'I didn\'t hear an airport code. Tell me an airport code.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
var faaHelper = new FAADataHelper();
console.log(airportCode);
faaHelper.getAirportStatus(airportCode).then(function(airportStatus) {
console.log(airportStatus);
res.say(faaHelper.formatAirportStatus(airportStatus)).send();
}).catch(function(err) {
console.log(err.statusCode);
var prompt = 'I didn\'t have data for an airport code of ' +
airportCode;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
});
return false;
}
}
);
module.exports = skill;
and heres FAADataInfo.js
'use strict';
var _ = require('lodash');
var requestPromise = require('request-promise');
var ENDPOINT = 'http://services.faa.gov/airport/status/';
function FAADataHelper() {
}
FAADataHelper.prototype.getAirportStatus = function(airportCode) {
var options = {
method: 'GET',
uri: ENDPOINT + airportCode,
json: true
};
return requestPromise(options);
};
FAADataHelper.prototype.formatAirportStatus = function(aiportStatusObject) {
if (aiportStatusObject.delay === 'true') {
var template = _.template('There is currently a delay for ${airport}. ' +
'The average delay time is ${delay_time}.');
return template({
airport: aiportStatusObject.name,
delay_time: aiportStatusObject.status.avgDelay
});
} else {
//no delay
var template =_.template('There is currently no delay at ${airport}.');
return template({
airport: aiportStatusObject.name
});
}
};
module.exports = FAADataHelper;
This is the response that I get
{
"version": "1.0",
"response": {
"directives": [],
"shouldEndSession": true
},
"sessionAttributes": {},
"dummy": "text"
}
The alexa-app version that the tutorial is using is out of date. When using the latest alexa-app npm version (4.0.0), the return value for the .intent() function should be a Promise and not a boolean if you are running asynchronous functions.
In your index.js, add:
return faaHelper.getAirportStatus(....) {}.catch(){}
and remove the return false; after the catch.
Here's the full skill.intent() code
skill.intent('airportInfoIntent', {
'slots': {
'AIRPORTCODE': 'FAACODES'
},
'utterances': [
'{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}'
]
},
function(req, res) {
var airportCode = req.slot('AIRPORTCODE');
var reprompt = 'Tell me an airport code to get delay information.';
if (_.isEmpty(airportCode)) {
var prompt = 'I didn\'t hear an airport code. Tell me an airport code.';
res.say(prompt).reprompt(reprompt).shouldEndSession(false);
return true;
} else {
var faaHelper = new FAADataHelper();
console.log(airportCode);
return faaHelper.getAirportStatus(airportCode).then(function(airportStatus) {
console.log(airportStatus);
res.say(faaHelper.formatAirportStatus(airportStatus)).send();
}).catch(function(err) {
console.log(err.statusCode);
var prompt = 'I didn\'t have data for an airport code of ' +
airportCode;
res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
});
//return false;
}
}
);

Fetch multiple documents in a stored procedure (Azure DocumentDB)

I have two document types, Listing and Products. A Listing object contains a list of Products for certain countries, like this:
Listing:
{
"Name": "Default",
"Countries": {
"_default": [
"4QlxAPFcCAAPAAAAAAAAAA==",
"4QlxAPFcCAAHAAAAAAAAAA=="
],
"US": [
"4QlxAPFcCAAIAAAAAAAAAA==",
"4QlxAPFcCAAHAAAAAAAAAA=="
]
},
"Type": "Listing",
"id": "dfed1839-07c5-482b-81c5-669b1dbcd0b6",
"_rid": "4QlxAPFcCAAEAAAAAAAAAA=="
}
Product:
{
"Name": "Widget",
"Price": 3.45,
"Type": "Product",
"_rid": "4QlxAPFcCAAHAAAAAAAAAA=="
}
My goal was to create a stored procedure in the Azure DocumentDB collection taking two parameters, ridand country, which would essentially fetch the Listing document, and the documents for that country, in the most efficient manner possible. My presumption is that loading a Document by its resource Id using getContext().getCollection().readDocument(...) would be the fastest way, thus attempting to create a stored procedure for this.
My attempts have been to nest the consecutive calls (callback hell?), using generator/iterators with yield and then with a pure Promise approach. All of the attempts have given the same result:
It will fetch the first document, but will end quite abruptly after the document has been received.
For reference, here's my latest attempt:
function test(rid, country) {
var collection = getContext().getCollection();
var collectionSelfLink = collection.getSelfLink();
var docsLink = collectionSelfLink + "docs/";
var body = getContext().getResponse().setBody;
function getDocument(rid) {
return new Promise(function(resolve, reject) {
var accepted = collection.readDocument(docsLink + rid, (err, doc, opts) => {
resolve(doc);
});
if (!accepted)
reject("Not accepted");
});
}
getDocument(rid)
.then(doc => {
body("0. First step"); // set test body
// Countries is a Dictionary<string, string[]> with resource ids
return doc.Countries[country] || doc.Countries["_default"];
})
// This is how far it gets, resulting in response "1. Documents to fetch: 2"
.then(a => body("1. Documents to fetch: " + a.length))
.then(a => a.map(function(productId) { return getDoument(productId); }))
.then(a => body("2. It should come this far, right?"))
.then(a => Promise.all(a))
.then(a => body(a))
.catch(function(e) { throw new Error(JSON.stringify(e)); });
}
It turns out that nesting the calls do in fact work, if you alter the response body frequently(?)
The following procedure worked as expected:
function test(rid, country) {
var collection = getContext().getCollection();
var collectionSelfLink = collection.getSelfLink();
var docsLink = collectionSelfLink + "docs/";
var body = getContext().getResponse().setBody;
var accepted = collection.readDocument(docsLink + rid, (err, doc, opts) => {
if (err) throw new Error(err.message);
// Countries is a Dictionary<string, string[]> with resource ids
var offerIds = doc.Countries[country] || doc.Countries["_default"];
var result = [];
for (var docId of offerIds) {
var subAccepted =
collection.readDocument(docsLink + docId, (err, doc, opts) => {
if (err) throw new Error(err.message);
result.push(doc);
});
if (!subAccepted)
throw new Error("A subsequent request was not accepted");
body(result); // <-- Note, setting body in each iteration.
}
});
if (!accepted)
throw new Error("The request was not accepted");
}

Loopback discoverAndBuildModels not generating models

I'm trying to get Loopback to discover and build my first table. I've used the simple example on their page at the bottom here:
http://docs.strongloop.com/display/LB/Database+discovery+API#DatabasediscoveryAPI-Exampleofbuildingmodelsviadiscovery
and I see the output of the table I'm discovering, but the API Explorer doesn't show the table or any newly generated endpoints. Also, the model-config.js file is not updated with the new table object. Here is the basic section of the code done on server start:
var loopback = require('loopback');
var boot = require('loopback-boot');
var DataSource = require('loopback-datasource-juggler').DataSource;
var mysqlSource = require('./datasources.json');
var dataSource = new DataSource('mssql', mysqlSource.mysqlserver);
var app = module.exports = loopback();
// Set up the /favicon.ico
app.use(loopback.favicon());
// request pre-processing middleware
app.use(loopback.compress());
// -- Add your pre-processing middleware here --
dataSource.discoverAndBuildModels('CATS', {owner: 'mamacat'}, function (err, models) {
models.Cat.find(function (err, cat) {
if (err) {
console.error(err);
} else {
console.log(cat);
}
dataSource.disconnect();
});
});
// boot scripts mount components like REST API
boot(app, __dirname);
To summarize, this runs, no errors. But no new models show on http://localhost:3000/explorer
Seems that discovery scripts only shows the output and doesn't create the model files. I found some instructions on loopback docs:
http://docs.strongloop.com/display/public/LB/Discovering+models+from+relational+databases
In section Basic Procedure, the second step:
2. Use fs.writeFile() to save the output in common/models/model-name.json.
So you can try the following approach:
Setup your mysql data in yourloopbackproject/server/datasources.json file:
{
"db": {
"name": "db",
"connector": "memory"
},
"accountDs": {
"host": "mysqlServerName",
"port": 3306,
"database": "databaseName",
"username": "username",
"password": "password!",
"name": "accountDs",
"connector": "mysql"
}
}
Create the models folder if doesn't exist: yourloopbackproject/common/models.
Create discovery-and-build.js script on yourloopbackproject/server/bin folder:
var path = require('path');
var fs = require('fs');
var app = require(path.resolve(__dirname, '../server'));
var outputPath = path.resolve(__dirname, '../../common/models');
var dataSource = app.dataSources.accountDs;
function schemaCB(err, schema) {
if(schema) {
console.log("Auto discovery success: " + schema.name);
var outputName = outputPath + '/' +schema.name + '.json';
fs.writeFile(outputName, JSON.stringify(schema, null, 2), function(err) {
if(err) {
console.log(err);
} else {
console.log("JSON saved to " + outputName);
}
});
}
if(err) {
console.error(err);
return;
}
return;
};
dataSource.discoverSchema('tableName',{schema:'schemaName'},schemaCB);
This script is based on: http://www.reddit.com/r/strongloop/comments/2upy76/autodiscoveryjs_recipe/
After the script execution you will find a .json file on models folder. Go to step 3 on Basic Procedure section:
http://docs.strongloop.com/display/public/LB/Discovering+models+from+relational+databases
Follow these steps to expose your model over REST:
http://docs.strongloop.com/display/public/LB/Exposing+models+over+REST
I hope this helps!
Use Arc for this.
Run slc arc from the project folder and it will show up the gui tool called arc in default browser. If you've not already registered, sign up and log in. You will be directed to GUI tool of StrongLoop, the Arc. Select your model from list on the left pane. You'll be able to see save and migrate button. Just click the migrate button and your table will be created into model.(within millisecs!)
Cheers!
discovery api is used to only discover the schema not to create models for now.
please use the following project to create models with one to one and one to many relationships and all the models.
https://github.com/savsharma2/loopback-sql-create-model-with-relation/
Building off of #Underskay's answer, I did something like
var fs = require('fs');
var app = require(__dirname + '/server/server');
function makePromise(f, parent) {
return function(...args) {
return new Promise((resolve, reject) => {
f.call(parent, ...args, (err, ...data) => {
if (err) return reject(err);
resolve(data.length === 1 ? data[0] : data);
});
});
};
}
var readFile = makePromise(fs.readFile, fs);
var writeFile = makePromise(fs.writeFile, fs);
function writeSchemas(schemas) {
return Promise.all(schemas.map(data => {
var schema = data[Object.keys(data)[0]];
return writeFile('common/models/' + schema.name + '.json', JSON.stringify(schema, null, '\t'));
}))
.then(() => readFile('server/model-config.json'))
.then(JSON.parse)
.then(conf => {
for (let schema of schemas)
conf[schema[Object.keys(schema)[0]].name] = { "dataSource": "mysql" };
return conf;
})
.then(conf => writeFile('server/model-config.json', JSON.stringify(conf, null, '\t')));
}
function getSchemas(ds) {
var discoverSchemas = makePromise(ds.discoverSchemas, ds);
return makePromise(ds.discoverModelDefinitions, ds)({})
.then(tables => Promise.all(tables.map(t => discoverSchemas(t.name, { relations: true }))))
.then(data => { ds.disconnect(); return data; });
}
Promise.resolve(app.datasources.mysql)
.then(ds => getSchemas(ds))
.then(schemas => writeSchemas(schemas))
.catch(err => log.error(err));

Node.js Express PUT Functionality - Saving data

I am setting up a server with Node and Express for the first time and am having trouble saving the response I am retrieving in my PUT call. This is a survey - I need to update the model with the "responded" object entered in the survey.
I do see the correct response outputting in the console but receive "Object [object Object],[object Object],[object Object],[object Object],[object Object] has no method 'findById'" from my "save" function.
Thank you in advance.
kitty-questions.json
[
{
"id": "favorite-food",
"number": "1",
"url": "favorite-food",
"name": "Favorite Food",
"question": "Which of the following best describes your kitty's palatte?",
"responded" : "default response",
"query": "Which of the following best describes your kitty's palatte?",
"answers": {
"Grumpy" : "Fresh Water Salmon, no bones, served on china",
"Hipster" : "Nothing - trying to fit into newer, tighter jeans",
"Pudge" : "Anything and everything my owner is eating",
"Bub" : "Mice",
"Meow" : "Roaches"
}
},
{
"id": "favorite-band",
"number": "2",
"url": "favorite-band",
"name": "Favorite Band",
"question": "Your kitty claws at you desperatly when it wants to listen to:",
"responded" : "default response",
"query": "Which of the following best describes your kitty's palatte?",
"answers": {
"Bub" : "Country",
"Grumpy" : "Mozart. Popular music is for the plebs.",
"Pudge" : "z100",
"Meow" : "Very heavy metal",
"Hipster" : "something long winded"
}
}
Server.js
var express = require('express'),
http = require('http'),
questions = require('./data/kitty-questions');
var app = express()
.use(express.bodyParser())
.use(express.static('public'));
app.get('/questions', function (req, res) {
res.json(questions);
});
app.post('/questions', function (req, res) {
var matches = questions.filter(function (question) {
return question.url === req.body.url;
});
if (matches.length > 0) {
res.json(409, {status: 'question already exists'});
} else {
req.body.id = req.body.url;
questions.push(req.body);
res.json(req.body);
}
});
app.put('/questions/:question_name', function (req, res) {
var matches = questions.filter(function (question) {
return question.url === req.params.question_name;
});
var catResponse = req.body.responded;
console.log(JSON.stringify(catResponse));
return questions.findById(req.params.question_name, function (err, question) {
question.catResponse = req.body.responded;
return question.save(function (err) {
if (!err) {
console.log("updated");
} else {
console.log(err);
}
return res.send(question);
});
});
});
app.get('/questions/:question_name', function (req, res) {
var matches = questions.filter(function (question) {
return question.url === req.params.question_name;
});
if (matches.length > 0) {
res.json(matches[0]);
} else {
res.json(404, {status: 'invalid survey question'});
}
});
app.delete('/questions/:question_name', function (req, res) {
var found = false;
items.forEach(function (question, index) {
if (question.url === req.params.question_name) {
found = index;
}
});
if (found) {
items.splice(found, 1);
res.json(200, {status: 'deleted'});
} else {
res.json(404, {status: 'invalid survey question deletion'});
}
});
app.get('/*', function (req, res) {
res.json(404, {status: 'not found'});
});
http.createServer(app).listen(3000, function () {
console.log("Server ready at http://localhost:3000");
});
STRING FROM THE TERMINAL AFTER MAKING PUT CALL:
Server ready at http://localhost:3000
TypeError: Object [{"id":"favorite-food","number":"1","url":"favorite-food","name":"Favorite Food","question":"Which of the following best describes your kitty's palatte?","responded":"default response","query":"Which of the following best describes your kitty's palatte?","answers":{"Grumpy":"Fresh Water Salmon, no bones, served on china","Hipster":"Nothing - trying to fit into newer, tighter jeans","Pudge":"Anything and everything my owner is eating","Bub":"Mice","Meow":"Roaches"}},{"id":"favorite-band","number":"2","url":"favorite-band","name":"Favorite Band","question":"Your kitty claws at you desperatly when it wants to listen to:","responded":"default response","query":"Which of the following best describes your kitty's palatte?","answers":{"Bub":"Country","Grumpy":"Mozart. Popular music is for the plebs.","Pudge":"z100","Meow":"Very heavy metal","Hipster":"something long winded"}},{"id":"favorite-hideout","number":"3","url":"favorite-hideout","name":"Favorite Hideout","question":"You are most likely to find your beast perched here:","responded":"","answers":{"Bub":"On your shoulder","Grumpy":"Alone. Anywhere, just alone.","Pudge":"In the fridge","Meow":"Herding other cats","Hipster":"Outside, smoking."}},{"id":"favorite-friends","number":"4","url":"favorite-friends","name":"Favorite Friends","question":"Your kitty generally gets along with:","responded":"","answers":{"Bub":"Other cats","Grumpy":"No one.","Pudge":"Humans, animals, whoever.","Meow":"Obedient animals","Hipster":"dogs"}},{"id":"favorite-celebrity","number":"5","url":"favorite-celebrity","name":"Favorite Celebrity","question":"Your feline cannot get enough of this red carpet walker:","responded":"","answers":{"Bub":"Meg Ryan","Grumpy":"Jack Nicholson","Pudge":"John Candy","Meow":"Does General McArthur count?","Hipster":"Zooey Deschanel"}}] has no method 'update'
3/19 UPDATE:
app.put('/questions/:question_name', function (req, res) {
var question = questions.filter(function (question) {
return question.url === req.params.question_name;
});
var defaultResponse = question[0].responded;
res.json(defaultResponse);
var catResponse = req.body.responded;
questions.update({id: req.params.question_name}, function (err, question) {
question.catResponse = catResponse;
question.save(function (err) {
if (!err) {
res.send(catResponse);
} else {
res.send(400); //or something
}
});
});
});
There are a lot of unnecessary returns going on here, and at the very least, they make the code confusing to read.
Removing some stuff and ignoring the matches variable, since that isn't used in the PUT itself, something like this may be more what you're looking for:
app.put('/questions/:question_name', function (req, res) {
var catResponse = req.body.responded;
questions.update({id: req.params.question_name}, function (err, question) {
question.catResponse = catResponse;
question.save(function (err) {
if (!err) {
res.send(question);
} else {
res.send(400); //or something
}
});
});
});
*EDIT*
I assumed that questions = require('./data/kitty-questions'); was your mongoose model. You need questions to be a mongoose model for update to work.
Like:
var mongoose = require('mongoose')
, Questions = mongoose.model('Question')
Then your questions model file probably looks like:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
QuestionSchema = new Schema({
//keys and stuff in here
});
mongoose.model('Question', QuestionSchema);

Resources