I am attempting to set up a REST API on a Ubuntu 16.04 digital ocean VPS. I am running into this error 'TypeError: Model.create(...).meta is not a function'. After checking through the stack trace (posted below), the function is in fact there.
A couple of things to note:
1) I have successfully been able to do a 'sails lift' command which did create the 'testauthdb' along with its associated schema (shown below)
2) Using postman (shown below), I was able to successfully hit the API on my remote server, but it returned a 500 error with the stack trace (shown below).
3) I've tried several means of updating all packages (@beta, @latest), npm cache clearing, reinstalling node_modules, nginx server restart, full on project deletion and re-installation, sail's upgrade command, etc.
4) https://github.com/balderdashy/sails/issues/4177 , was a similar issue but they were using mongodb and mongoose. I tried using sails-mysql@beta and sails-mysql@latest with no such luck.
5) There were no syntax errors caught by the compiler, so any small mistakes you may see in this document where most likely translation issues. The only real error here I believe is the one posted in the title/stack trace.
Any guidance would be much appreciated, I've spent a lot of time trying to figure this bug out.
Versions
node -v: 9.10.1
npm -v: 5.8.0
sails -v: 1.0.0
package.json
{
"name": "blah",
"private": true,
"version": "0.0.0",
"description": "a Sails application",
"keywords": [],
"dependencies": {
"@sailshq/connect-redis": "^3.2.1",
"@sailshq/lodash": "^3.10.2",
"@sailshq/socket.io-redis": "^5.2.0",
"@sailshq/upgrade": "^1.0.9",
"ajv": "^6.4.0",
"async": "^2.6.0",
"bcryptjs": "^2.4.3",
"coffeescript": "^2.2.4",
"grunt": "1.0.1",
"jsonwebtoken": "^8.2.0",
"lodash": "^4.17.5",
"sails": "^1.0.0",
"sails-hook-grunt": "^3.0.2",
"sails-hook-orm": "^2.0.0",
"sails-hook-sockets": "^1.5.2",
"sails-hook-validation": "^0.4.7",
"sails-mysql": "^1.0.0",
"sprint-js": "^0.1.0",
"waterline": "^0.13.3"
},
"devDependencies": {
"@sailshq/eslint": "^4.19.3"
},
"scripts": {
"start": "NODE_ENV=production node app.js",
"test": "npm run lint && npm run custom-tests && echo 'Done.'",
"lint": "eslint . --max-warnings=0 --report-unused-disable-directives && echo '✔ Your .js files look good.'",
"custom-tests": "echo \"(No other custom tests yet.)\" && echo"
},
"main": "app.js",
"repository": {
"type": "git",
"url": "git://github.com/root/dwms-api.git"
},
"author": "root",
"license": "",
"engines": {
"node": ">=9.10"
}
}
api/controller/UserController.js
var jwt = require("jsonwebtoken");
var bcrypt = require("bcryptjs");
module.exports = {
/**
* this is used to authenticate user to our api using either email and password
* POST /login
* @param req
* @param res
*/
login: function (req, res) {
/**
* this is param checking if they are provided
*/
if (!_.has(req.body, 'email') || !_.has(req.body, 'password')) {
return res.serverError("No field should be empty.");
}
/**
* check if the username matches any email or phoneNumber
*/
User.findOne({
email: req.body.email
}).exec(function callback(err, user) {
if (err) return res.serverError(err);
if (!user) return res.serverError("User not found, please sign up.");
//check password
bcrypt.compare(req.body.password, user.password, function (error, matched) {
if (error) return res.serverError(error);
if (!matched) return res.serverError("Invalid password.");
//save the date the token was generated for already inside toJSON()
var token = jwt.sign(user.toJSON(), "this is my secret key", {
expiresIn: '10m'
});
//return the token here
res.ok(token);
});
});
},
/**
* this is used to request for another token when the other token is about
* expiring so for next request call the token can be validated as true
* GET /token
* @param req
* @param res
*/
token: function (req, res) {
User.findOne(req.user.id).exec(function callback(error, user) {
if (error) return res.serverError(error);
if (!user) return res.serverError("User not found");
var token = jwt.sign(user.toJSON(), "this is my secret key", {
expiresIn: '10m'
});
res.ok(token);
});
}
};
api/models/Users.js
var bcrypt = require("bcryptjs");
module.exports = {
attributes: {
name: {
type: 'string',
required: true
},
roles: {
type: 'string',
defaultsTo: "DEFAULT_USER"
},
email: {
type: 'string',
unique: true,
required: true
},
password: {
type: 'string',
required: true
},
lastlogout: {
type:'string'
},
},
//attributes methods
customToJSON: function() {
return _.omit(this, ['password'])
},
/**
* this holds our validation message by
* sails-hook-validation dependency
*/
validationMessages: { //hand for i18n & l10n
names: {
required: 'Name is required'
},
email: {
email: 'Provide valid email address',
required: 'Email is required',
unique: 'This email is already existing'
},
password: {
required: 'Password is required'
}
},
/**
* this is called so we can create our password hash for us
*
* before saving
* @param values
* @param cb
*/
beforeCreate: function(user, cb){
bcrypt.genSalt(10, function(err, salt){
bcrypt.hash(user.password, salt, null, function(err, hash){
if(err) return cb(err);
user.password = hash;
return cb();
});
});
}
};
config/model.js
module.exports.models = {
/***************************************************************************
* *
* Your app's default connection. i.e. the name of one of your app's *
* connections (see `config/connections.js`) *
* *
***************************************************************************/
/***************************************************************************
* *
* Whether the `.create()` and `.update()` model methods should ignore *
* (and refuse to persist) unrecognized data-- i.e. properties other than *
* those explicitly defined by attributes in the model definition. *
* *
* To ease future maintenance of your code base, it is usually a good idea *
* to set this to `true`. *
* *
* > Note that `schema: false` is not supported by every database. *
* > For example, if you are using a SQL database, then relevant models *
* > are always effectively `schema: true`. And if no `schema` setting is *
* > provided whatsoever, the behavior is left up to the database adapter. *
* > *
* > For more info, see: *
* > https://sailsjs.com/docs/concepts/orm/model-settings#?schema *
* *
***************************************************************************/
schema: true,
/***************************************************************************
* *
* How and whether Sails will attempt to automatically rebuild the *
* tables/collections/etc. in your schema. *
* *
* > Note that, when running in a production environment, this will be *
* > automatically set to `migrate: 'safe'`, no matter what you configure *
* > here. This is a failsafe to prevent Sails from accidentally running *
* > auto-migrations on your production database. *
* > *
* > For more info, see: *
* > https://sailsjs.com/docs/concepts/orm/model-settings#?migrate *
* *
***************************************************************************/
migrate: 'alter',
/***************************************************************************
* *
* Base attributes that are included in all of your models by default. *
* By convention, this is your primary key attribute (`id`), as well as two *
* other timestamp attributes for tracking when records were last created *
* or updated. *
* *
* > For more info, see: *
* > https://sailsjs.com/docs/concepts/orm/model-settings#?attributes *
* *
***************************************************************************/
attributes: {
id: { type: 'number', autoIncrement: true, },
createdAt: { type: 'string', autoCreatedAt: true, },
updatedAt: { type: 'string', autoUpdatedAt: true, },
},
/******************************************************************************
* *
* The set of DEKs (data encryption keys) for at-rest encryption. *
* i.e. when encrypting/decrypting data for attributes with `encrypt: true`. *
* *
* > The `default` DEK is used for all new encryptions, but multiple DEKs *
* > can be configured to allow for key rotation. In production, be sure to *
* > manage these keys like you would any other sensitive credential. *
* *
* > For more info, see: *
* > https://sailsjs.com/docs/concepts/orm/model-settings#?dataEncryptionKeys *
* *
******************************************************************************/
dataEncryptionKeys: {
default: 'blahblah'
},
/***************************************************************************
* *
* Whether or not implicit records for associations should be cleaned up *
* automatically using the built-in polyfill. This is especially useful *
* during development with sails-disk. *
* *
* Depending on which databases you're using, you may want to disable this *
* polyfill in your production environment. *
* *
* (For production configuration, see `config/env/production.js`.) *
* *
***************************************************************************/
cascadeOnDestroy: true
};
Postman: remotehost:xxxx/user?______________________
{
"id": 1,
"createdAt": "2017-12-30T12:51:10Z",
"updatedAt": "2017-12-30T12:51:10Z",
"name": "Drake Bell",
"roles": "DEFAULT_USER",
"email": "[email protected]",
"password": "Youll never know :D",
"lastlogout": "2017-12-30T12:51:10Z"
}
SQL Schema
describe testauthdb.user;
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| createdAt | bigint(20) | YES | | NULL | |
| updatedAt | bigint(20) | YES | | NULL | |
| name | varchar(255) | YES | | NULL | |
| roles | varchar(255) | YES | | NULL | |
| email | varchar(255) | YES | UNI | NULL | |
| password | varchar(255) | YES | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
node_modules/sails/lib/hooks/blueprints/actions/create.js
/**
* Module dependencies
*/
var _ = require('@sailshq/lodash');
var async = require('async');
var formatUsageError = require('../formatUsageError');
/**
* Create Record
*
* http://sailsjs.com/docs/reference/blueprint-api/create
*
* An API call to crete a single model instance using the specified attribute values.
*
*/
module.exports = function createRecord (req, res) {
var parseBlueprintOptions = req.options.parseBlueprintOptions || req._sails.config.blueprints.parseBlueprintOptions;
// Set the blueprint action for parseBlueprintOptions.
req.options.blueprintAction = 'create';
var queryOptions = parseBlueprintOptions(req);
var Model = req._sails.models[queryOptions.using];
// Get the new record data.
var data = queryOptions.newRecord;
// Look for any many-to-one collections that are being set.
// For example, User.create({pets: [1, 2, 3]}) where `pets` is a collection of `Pet`
// via an `owner` attribute that is `model: 'user'`.
// We need to know about these so that, if any of the new children already had parents,
// those parents get `removedFrom` notifications.
async.reduce(_.keys(Model.attributes), [], function(memo, attrName, nextAttrName) {
var attrDef = Model.attributes[attrName];
if (
// Does this attribute represent a plural association.
attrDef.collection &&
// Is this attribute set with a non-empty array?
_.isArray(data[attrName]) && data[attrName].length > 0 &&
// Does this plural association have an inverse attribute on the related model?
attrDef.via &&
// Is that inverse attribute a singular association, making this a many-to-one relationship?
req._sails.models[attrDef.collection].attributes[attrDef.via].model
) {
// Create an `in` query looking for all child records whose primary keys match
// those in the array that the new parent's association attribute (e.g. `pets`) is set to.
var criteria = {};
criteria[req._sails.models[attrDef.collection].primaryKey] = data[attrName];
req._sails.models[attrDef.collection].find(criteria).exec(function(err, newChildren) {
if (err) {return nextAttrName(err);}
// For each child, see if the inverse attribute already has a value, and if so,
// push a new `removedFrom` notification onto the list of those to send.
_.each(newChildren, function(child) {
if (child[attrDef.via]) {
memo.push({
id: child[attrDef.via],
removedId: child[req._sails.models[attrDef.collection].primaryKey],
attribute: attrName
});
}
});
return nextAttrName(undefined, memo);
});
}
else {
return nextAttrName(undefined, memo);
}
}, function (err, removedFromNotificationsToSend) {
if (err) {return res.serverError(err);}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: Use a database transaction here, if supported by the datastore.
// e.g.
// ```
// Model.getDatastore().transaction(function during(db, proceed){ ... })
// .exec(function afterwards(err, result){}));
// ```
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Create new instance of model using data from params
Model.create(data).meta(queryOptions.meta).exec(function created (err, newInstance) {
// Differentiate between waterline-originated validation errors
// and serious underlying issues. Respond with badRequest if a
// validation error is encountered, w/ validation info, or if a
// uniqueness constraint is violated.
if (err) {
switch (err.name) {
case 'AdapterError':
switch (err.code) {
case 'E_UNIQUE': return res.badRequest(err);
default: return res.serverError(err);
}//•
case 'UsageError': return res.badRequest(formatUsageError(err, req));
default: return res.serverError(err);
}
}//-•
// If we didn't fetch the new instance, just return 'OK'.
if (!newInstance) {
return res.ok();
}
// Look up and populate the new record (according to `populate` options in request / config)
Model
.findOne(newInstance[Model.primaryKey], queryOptions.populates)
.exec(function foundAgain(err, populatedRecord) {
if (err) { return res.serverError(err); }
if (!populatedRecord) { return res.serverError('Could not find record after creating!'); }
// If we have the pubsub hook, use the model class's publish method
// to notify all subscribers about the created item
if (req._sails.hooks.pubsub) {
if (req.isSocket) {
Model.subscribe(req, [populatedRecord[Model.primaryKey]]);
Model._introduce(populatedRecord);
}
Model._publishCreate(populatedRecord, !req.options.mirror && req);
if (removedFromNotificationsToSend.length) {
_.each(removedFromNotificationsToSend, function(notification) {
Model._publishRemove(notification.id, notification.attribute, notification.removedId, !req.options.mirror && req, {noReverse: true});
});
}
}//>-
// Send response
res.ok(populatedRecord);
}); // </foundAgain>
});
});
};
STACK TRACE
TypeError: Model.create(...).meta is not a function at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:89:24 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:421:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2494:9 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:421:16 at replenish (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:941:25) at iterateeCallback (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:931:17) at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:906:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2491:13 at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:71:14 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2489:9 at replenish (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:946:17) at iterateeCallback (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:931:17) at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:906:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2491:13 at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:71:14 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2489:9 at replenish (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:946:17) at iterateeCallback (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:931:17) at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:906:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2491:13 at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:71:14 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2489:9 at replenish (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:946:17) at iterateeCallback (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:931:17) at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:906:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2491:13 at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:71:14 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2489:9 at replenish (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:946:17) at iterateeCallback (/home/username/api/node_modules/sails/node_modules/async/dist/async.js:931:17) at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:906:16 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2491:13 at /home/username/api/node_modules/sails/lib/hooks/blueprints/actions/create.js:71:14 at /home/username/api/node_modules/sails/node_modules/async/dist/async.js:2489:9