Unique property fails in Sails.js - node.js

The following code represents an Account Model in Sails.js v0.9.4 .
module.exports = {
attributes: {
email: {
type: 'email',
unique: true,
required: true
},
password:{
type: 'string',
minLength: 6,
maxLength: 15,
required:true
}
}
};
When I send two POSTS and a PUT request via Postman to localhost:8080/account, the unique property of the email fails.
Specifically, I send the following HTTP requests from Postman:
POST http://localhost:8080/account?email=foo#gmail.com&password=123456
POST http://localhost:8080/account?email=bar#gmail.com&password=123456
PUT http://localhost:8080/account?id=1&email=bar#gmail.com
GET http://localhost:8080/account
The last GET request shows me:
[
{
"email": "bar#gmail.com",
"password": "123456",
"createdAt": "2013-09-30T18:33:00.415Z",
"updatedAt": "2013-09-30T18:34:35.349Z",
"id": 1
},
{
"email": "bar#gmail.com",
"password": "123456",
"createdAt": "2013-09-30T18:33:44.402Z",
"updatedAt": "2013-09-30T18:33:44.402Z",
"id": 2
}
]
Should this happen?
*For those who don't know, Waterline generates by default an id which automatically increments in every insertion.

This is because your schema is not updated in your disk database (".tmp/disk.db").
You need to shutdown sails, drop your DB and restart sails.
The DB will be reconstruct with your good schema.
Attention : the data will be drop too !
If you want keep your data, you can just update the schema part of ".tmp/disk.db".
What I have doing to keep data and rebuild schema by sails.js :
copy ".tmp/disk.db"
clean ".tmp/disk.db"
shutdown sails.js
start sails.js
-> the database is empty and the schema is updated
copy old "counters" part
copy old "data" part
You must have this in your schema (file ".tmp/disk.db" -> "schema" part) for the unique field :
"xxx": {
"type": "string",
"unique": true
},
I hope this help you.

I ran into this same issue. To solve it, you have to avoid using the 'disk' ORM adapter. For some reason it appears that it doesn't support uniqueness checks.
Other adapters such as mongo and mysql should support uniqueness checks, so this shouldn't be an issue outside of development.
For the course of development, change the default adapter in config/adapters.js from 'disk' to 'memory'. Should look like this:
module.exports.adapters = {
// If you leave the adapter config unspecified
// in a model definition, 'default' will be used.
'default': 'memory',
// In-memory adapter for DEVELOPMENT ONLY
memory: {
module: 'sails-memory'
},
...
};

I'm not certain this is the issue, but have you added schema:true to your models and adapters?
My mongo adapter config looks like this:
module.exports.adapters = {
'default': 'mongo',
mongo: {
module: 'sails-mongo',
url: process.env.DB_URL,
schema: true
}
};
And my User model looks like this (trimmed a bit):
module.exports = {
schema: true,
attributes: {
username: {
type: 'string',
required: true,
unique: true
}
//...
}
};

There is no need to delete current database to solve this, in stead change the waterline migrate option from safe to alter. This way the underlying database will adapt this setting.
I wouldn't recommend migrate: alter in a production environment, though. ;)
Here is my /config/local.js:
module.exports = {
...
models: {
migrate: 'alter'
},
}

According to the official documentation of sails
You should configure the option "migrate" in "alter" to create the schemas with their indexes
There's nothing wrong with adding or removing validations from your
models as your app evolves. But once you go to production, there is
one very important exception: unique. During development, when your
app is configured to use migrate: 'alter', you can add or remove
unique validations at will. However, if you are using migrate: safe
(e.g. with your production database), you will want to update
constraints/indices in your database, as well as migrate your data by
hand.
http://sailsjs.com/documentation/concepts/models-and-orm/validations

var InvoiceSchema = new Schema({
email: {type: 'email', required: true}
name : {type: String}
});
InvoiceScheme({email: 1}, {unique: true});
Set Uniquee In Nodejs

Related

When sails default model attributes get their values?

When sails fill default global attributes which we added on config/models.js ,
default settings looks like :
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
}
Now if we add sth like creatorId to this default attributes , how we should fill it once for all our models ?
attributes: {
id: { type: 'number', autoIncrement: true },
createdAt: { type: 'number', autoCreatedAt: true },
updatedAt: { type: 'number', autoUpdatedAt: true },
creatorId: { type: 'number'}
}
After this change , all models have creatorId with 0 value , how I can set userId to all of my models creatorId before save without repeating my self?
In the controller you are creating the entry in the database this should be quite straight forward. Let's assume that you have two models, User, which comes with Sails built-in authentication, and a Thing, something that someone can own.
In the Thing model, I'd change the ownerId to be owner and associate it with the User model like so:
attributes: {
id: { ... },
createdAt: { ... },
updatedAt: { ... },
owner: {
model: 'User',
required: yes // Enable this when all the stuff in the db has this set
},
}
This creates an association or one-to-many relationship if you know database terminology.
Now in the controller where you create your object to be inserted:
Thing.create({
someAttribute: inputs.someValue,
someOtherAttribute: inputs.someOtherValue,
owner: this.req.me.id
});
If you want to use the created object right away, append .fetch() to the chain after .create({...}) like so:
var thing = await Thing.create({ ... }).fetch();
Let me know if something is unclear.
I'd actually recommend you invest the $9 in buying the SailsJS course. It's an official course, taught by the creator of SailsJS, Mike McNeil. It takes you from npm i sails -g to pushing to production on the Heroku cloud platform. It teaches basic Vue (parasails flavour), using MailGun, Stripe payments, and more. They link to the course on the site here
Update
Did some further digging, and was inspired by a couple of similar cases.
What you can do is expand your model with a custom method that wraps the .create() method. This method can receive the request object from your controllers, but doing this, rather than the previous suggestion, will probably be more work than just adding ownerId: this.req.me.id, to existing calls. I1ll demonstrate anyway.
// Your model
module.exports = {
attributes: { ... },
proxyCreate(req, callback) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id // or req.user.id, cant remember
// which works here
}
Thing.create(request.body, callback);
}
}
And in your controller:
...
// Change from:
Thing.create(req.body);
// To:
Thing.proxyCreate(req);
...
Update #2
Another idea I had was adding the middleware on a per-route basis. I don't know the complexity of your routes, but you can create a custom middleware for only those routes.
In router.js you edit your routes (I'll show one for brevity):
....
'POST /api/v1/things/upload-thing': [
{ action: 'helpers/add-userid-to-ownerid' },
{ action: 'new-thing' }
],
....
In helpers/add-userid-to-ownerid:
module.exports: {
fn: function(req, res) {
if(!req.body.ownerId){
req.body.ownerId = req.me.id;
}
}
}

Mongoose Schema validation issues when using Array of Mixed [{ }]

I am building a schema in mongoose (v4.13.8) with an Array of Mixed values. I have come up with the following Schema:
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validator: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validator: [isValidServices, "Not a valid service array"]
}
});
The problem is that I get a validation error saying that services: Path 'services' is required. when I try to submit data. What is strange is that the data I send for the 'capabilities' works fine and the only difference is that I specify a schema explicitly.
Removing the required: true from services causes there to be an empty array object in the returned values.
I am submitting the data using an API POST request with the data in the body of the request. I am using Postman to submit the request, with x-www-form-urlencoded checked. This is copied from the body key-value input
capabilities[0][field_map][field]:pressure
capabilities[0][field_map][type]:float
capabilities[0][field_map][format]:hPa
services[0][name]:rest
services[0][receive][0][capability_id]:0
services[0][receive][0][path]:/api/relay/0
Update:
I'd like to apologise as this was a mistake on my part. I dynamically create a configuration based on the request and at one point the copied services were being made null, doh!
However, having got the required: true validation to pass, the custom validator is still not being executed. I also can't find any documentation about the order in which validators and are executed which would be very useful. Below is the validator snippet for reference:
function isValidServices(services) {
for (const service of services) {
if (typeof service.name !== 'string') return false;
}
return true;
}
Having experimented with various approaches and looking in more detail on the mongoose API docs, I found that there is a validate option for schemas too. http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate
I changed my Schema from this:
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validator: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validator: [isValidServices, "Not a valid service array"]
}
});
To this [notice the validate instead of validator]...
var deviceConfigSchema = new mongoose.Schema({
capabilities: {
type: [capabilitySchema],
required: true,
validate: [isValidCapabilities, "Not a valid capability array"]
},
services: {
type: [{}],
required: true,
validate: [isValidServices, "Not a valid service array"]
}
});
After this my validator functions were being executed without any issues. Hopefully this helps someone.

Using only JsonSchema with Mongoose possible?

I have an api using express, mongodb and I use AJV validation to validate the incoming requests.
//JSONSchema
var recordJsonSchema = {
type: "object",
properties: {
name: { type: "string" },
idNumber: { type: "number" },
content: { type: "string" }
},
required: ['name','idNumber']
}
And I'd use this JSON schema to validate incoming requests like so.
app.post('/record', (req,res) => {
let errors = ajv.inspect(req.body, recordJsonSchema)
return errors ? res.send(errors) : res.send(this.handler(req));
})
This works fine and is very fast. I also like JsonSchema since it follows OpenAPI standards.
Unfortunately, in order to read/write to mongo via mongoose I also need to create a MongoSchema for Record. They are very similar but a bit different in how they handle required fields etc.
var recordSchema = new Schema({
name: { type: "string", required: true },
idNumber: { type: "number", required: true },
content: { type: "string" }
})
So for my model of Record I have two schemas now. One for JSONschema and one for handling Mongo read/writes.
I'm looking for a way to cut MongoSchema, any suggestions?
Maybe this, seems like it imports your ajv schema from the entry and place it in the mongoose schema. https://www.npmjs.com/package/mongoose-ajv-plugin
I have faced with same problem. I think in new mongo 4.4 we can load ajv schema directly to mongodb https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/

sails js many to many record insertion error

I have two models: Users and InstalledApps. Models are like this:
//Users Model
attributes:{
name: 'string',
age: 'string',
installedApps:{
collection: 'installedApps',
via: 'users'
}
}
My InstalledApps model is like this:
attributes:{
deviceID : 'string',
users:{
collection:'users',
via:'installedApps'
}
}
Now I have already created the user with id 1 and 2.
But when I insert data in InstalledApps via postman like this:
{
"users": [1,2],
"deviceID": "123456",
}
It pops an error: Unknown rule: default. I don't know where I am wrong?
Your models are fine. Have you made sure you're posting the data as JSON? In the Body tab in Postman, select raw and then change from Text to JSON (application/json). Remove the surplus comma after "123456" and your request should go through without problems. Also, make sure you're doing a POST request to the /installedApps route, e.g. http://localhost:1337/installedApps.
I tested this with the model definitions you posted in a fresh Sails.js app, getting installedApps entries inserted correctly with associations to users entries.
I think you made your models wrong.
You should use http://sailsjs.com/documentation/concepts/models-and-orm/associations/through-associations .
Then you should have 3 models. USER, APP, INSTALLED_APPS.
Your user model will get:
//Users Model
attributes:{
name: 'string',
age: 'string',
installedApps:{
collection: 'APP',
via: 'user',
through: 'installedApps'
}
}
and your APP model:
//APP Model
attributes:{
device: 'string',
installedApps:{
collection: 'APP',
via: 'app',
through: 'installedApps'
}
}
and finally your INSTALLED_APPS:
//INSTALLED_APPS Model
attributes:{
user: {
model: 'user'
},
app: {
model: 'app'
}
}
Then you will just use simple create of INSTALLED APP for every user/app or blueprint http://sailsjs.com/documentation/reference/blueprint-api/add-to
Hope it helps.

Mongoose field how to auto increment

I have a mongoose Schema like below:
var userSchema = new Shema({
userName: {type: String, default: "ABC"},
lastLoginTime: {type: Date, default: Date.now},
loginTimes: {type: Number, default: 1},
......
});
And I want to update lastLoginTime and let loginTimes plus one when user login every time.
It's easy to update lastLoginTime, just give it a new time string.
But how can I make loginTimes plus one every time.
Well you can always use the $inc operator with a form of "update"
Model.update(
{ _id: docId },
{
"$set": { "lastLoginTime": new Date() },
"$inc": { "loginTimes": 1 }
},
function(err,numaffected) {
}
)
That is a general MongoDB preferred way of doing things as there is minimal traffic sent for each actual update.
Also see the .findByIdAndUpdate() method for mongoose since this is likely restricted to one document.
An alternate is to use "pre save" hooks to be very mongoose about it, but it seems sort of cumbersome to me to retrieve and modify a document when you don't really need things like validation for this sort of update.

Resources