Loopback discoverAndBuildModels not generating models - node.js

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));

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.

Sequelize create belongTo instance with reference

I'm using Sequelize for my new NodeJs project
I defined two models: BusinessUnit and Store with this association: Store.belongsTo(BusinessUnit);
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
models.Store.create({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
businessUnitId: element.id
});
});
});
};
I don't find to right way to reference the element in my Store, I would like something like this where I don't have to reference an id field directly
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
models.Store.create({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
businessUnit: element
});
});
});
};
It should be simple but I don't see it. Thanks
Assuming your association are setup correctly, you are looking for something like this:
module.test_data_insertion = function() {
models.BusinessUnit.findOne({
where: {
BUID: "001"
}
}).then(element => {
fs.readdirSync(dir).forEach(function(file) {
var contents = fs.readFileSync(dir + file);
var jsonContent = JSON.parse(contents);
element.setStore({
StoreId: jsonContent.StoreId,
StoreName: jsonContent.StoreName,
});
});
});
};
please read belongsTo from doc which enables a set method,
However please note if this is a one to many relationship(one business unit with many stores) you may need to switch to belongsToMany it has add method. because setStore would override previous set.
Also, i'm not sure if any of those method would work inside .forEach correctly since they are promise, you may need to switch to a traditional for loop.

umzug down method not running

I am trying to use umzug/sequelize but I can't run the down method at all. I am following this tutorial. I want to basically create migration files with up and down methods. The up method gets executed successfully but calling down does not all the umzug.down() method at all.
"use strict";
const Promise = require("bluebird");
const sqlite3 = require("sqlite3");
const path = require('path');
module.exports = {
up: function() {
return new Promise(function(resolve, reject) {
/* up is to commit migrations to the database */
let db = new sqlite3.Database('./database/db.db');
db.run(`PRAGMA foreign_keys = ON`);
db.serialize(function() {
db.run(`CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT
)`);
});
db.close();
});
},
down: function() {
return new Promise(function(resolve, reject) {
/* roll back database changes made by this migration */
console.log('in down')
let db = new sqlite3.Database("./database/db.db");
db.serialize(function() {
db.run(`DROP TABLE users`);
});
db.close();
});
}
};
My migrate file looks like this as well:
const path = require("path");
const Umzug = require("umzug");
let umzug = new Umzug({
logging: function() {
console.log.apply(null, arguments);
},
migrations: {
path: "./database/migrations",
pattern: /\.js$/
},
upName: "up",
downName: "down"
});
const cmd = process.argv[2].trim();
// this will run your migrations
if(cmd=='up')
{
umzug.up().then(console.log("Migrations committed"));
}
else if (cmd=='down'){
umzug.down().then(console.log("Migrations revereted"));
}
When I do node migrate.js up it works. but down never gets executed. Am I missing something?
At some point, you will need to call the resolve() or reject() on the Promise for both your up and down functions.

Update variable value in module

I'm a real newbie in node.js so pls understand my possible stupidity
I'm trying to use a external file to serve as a module so I can use it in other files. The project is bigger than this but let's say my module is:
var Tools = module.exports = {
result_arr: [],
object_data: {
times : [],
temps1 : [],
temps2 : [],
temps3 : [],
temps4 : [],
levels : [],
flows : []
},
getLastNRows: function (whereIsData, DB_info, table, NRows) {
if (whereIsData == "MySQL") {
function setValue (value) {
Tools.result_arr = value;
}
function dataArray2Object (array_data) {
Tools.object_data.times = array_data.map(row => row.timestamp);
Tools.object_data.temps1 = array_data.map(row => row.temp1);
Tools.object_data.temps2 = array_data.map(row => row.temp2);
Tools.object_data.temps3 = array_data.map(row => row.temp3);
Tools.object_data.temps4 = array_data.map(row => row.temp4);
Tools.object_data.levels = array_data.map(row => row.level_ice_bank);
Tools.object_data.flows = array_data.map(row => row.flow);
}
var queryString = "SELECT timestamp, temp1, temp2, temp3, temp4, level_ice_bank, flow FROM " +
table + " ORDER BY id DESC LIMIT " + NRows + ";";
var connnection = mysql.createConnection(DB_info);
connnection.connect(function(err) {
console.log("connected");
if (err) throw err;
});
connnection.query(queryString, function (err, rows) {
console.log("queried");
if (err) throw err;
setValue(rows);
dataArray2Object(Tools.result_arr);
console.log(Tools.result_arr);
console.log(Tools.object_data);
});
}
else {
console.log("Function only accepts data stored in MySQL.\n(u still have to improve...)");
return;
}
};
The variable object_data is supposed to be used in a main file. This way, whenever I call getLastNRows, I expect object_data to be updated by the operations in getLastNRows. The main file would be:
var tools = require('./tools');
var where2save = "MySQL";
var info_db = {
host : "127.0.0.1",
user : "root",
password: "xxxx",
database: "mydb",
port : 3306
};
var table = "tempdata";
var NRows = 4;
tools.getLastNRows(where2save, info_db, table, NRows);
console.log(tools.object_data);
What is observed is that, in fact, tools.object_data is not updated by getLastNRows in the main file, although console.log(Tools.object_data); from the tools.js (module) file logs the updated values. So my question is:
How can I make getLastNRows update tools.object_data (which is empty when created) in the main file?
Is getLastNRows asynchronous? Cuz it seems to me that is the cause of the problem.
It calls the getLastNRows in main which then calls connection.query, which gets put on a worker thread then immediately continues to the console.log where tools.object_data has not been updated.
Try:
getLastNRows: function (whereIsData, DB_info, table, NRows, cb) {
// ...
connnection.query(queryString, function (err, rows) {
console.log("queried");
if (err) throw err;
setValue(rows);
dataArray2Object(Tools.result_arr);
console.log(Tools.result_arr);
console.log(Tools.object_data);
cb()
});
// ...
}
// in main
tools.getLastNRows(where2save, info_db, table, NRows, function() {
console.log(tools.object_data);
});

How to serve a PDF file stored using CollectionFS in Meteor?

I am working on a Meteor application.
Currently, I have a few PDFs on my server. To serve these already existing PDFs directly to the client, I do it this way and it works very well:
Router.route("/file/:fileName", function() {
var fileName = this.params.fileName;
// console.log(process.env.PWD);
var filePath = process.env.PWD + '/' + fileName;
var fs = Meteor.npmRequire('fs');
var data = fs.readFileSync(filePath);
this.response.writeHead(200, {
"Content-Type": "application/pdf",
"Content-Length": data.length
});
this.response.write(data);
this.response.end();
}, {
where: "server"
});
I save these PDFs to Mongo using CollectionFS (Later, I shall generate PDFs and save them. For now, I am just directly saving these already existing PDFs to Mongo as I first want to get the Mongo part to work.).
testCollection = new FS.Collection("testCollection", {
stores: [new FS.Store.GridFS("testCollection")]
});
testCollection.allow({
'insert': function () {
return true;
}
});
var file = new FS.File(process.env.PWD + '/PDFKitExampleServerSide.pdf');
file.encoding = 'binary';
file.name('myPDF.pdf');
var document = testCollection.insert(file);
console.log(document._id);
My question is, after I save these PDFs to Mongo using CollectionFS (like I do above), how do I retrieve and serve these PDFs?
Router.route("/database/:pdfId", function() {
//need help here
}, { where: "server"});
After a lot of searching and trying, I've finally gotten it to work.
Router.route("/database/:pdfId", function() {
var pdfId = this.params.pdfId;
var file = testCollection.findOne({_id: pdfId});
var readable = file.createReadStream("tmp");
var buffer = new Buffer(0);
readable.on("data", function(b) {
buffer = Buffer.concat([buffer, b]);
});
var response = this.response;
readable.on("end", function() {
response.writeHead(200, {
"Content-Type": "application/pdf",
"Content-Length": buffer.length
});
response.write(buffer);
response.end();
});
}, {
where: "server"
});
I know that this question is old, but I found an easier way to store and retrieve PDFs. Apparently if you store your PDFs in the database and they are smaller than 16MB (which is likely in this type of files) the performance is way slower than if you store the files in your server file system.
For doing that, you can use FS.Store.FileSystem instead of FS.Store.GridFS. The following code works for me:
// Client
var pdfStore = new FS.Store.FileSystem("uploadedFiles");
UploadedFiles = new FS.Collection("uploadedFiles", {
stores: [pdfStore],
filter: {
allow: {
extensions: ['pdf','doc','docx','xls','xlsx','ppt','pptx''jpg','png','jpeg']
}
}
});
// Server
var pdfStore = new FS.Store.FileSystem("uploadedFiles", {path: uploadFilesPath});
UploadedFiles = new FS.Collection("uploadedFiles", {
stores: [pdfStore],
filter: {
maxSize: 5242880, // 5MB in bytes
allow: {
extensions: ['pdf','doc','docx','xls','xlsx','ppt','pptx''jpg','png','jpeg']
},
deny: {
extensions: ['exe']
},
onInvalid: function (message) {
if (Meteor.isClient) {
alert(message);
} else {
console.log(message);
}
}
}
});
And then just use this little helper to retrieve the url to the file:
get_uploaded_link: function(id){
console.log(id);
var file = UploadedFiles.findOne({_id: id});
return file.url();}

Resources