diff --git a/core/server/data/validation/index.js b/core/server/data/validation/index.js index 60c7f23f61..9b2c70dca0 100644 --- a/core/server/data/validation/index.js +++ b/core/server/data/validation/index.js @@ -220,6 +220,11 @@ validateSchema = function validateSchema(tableName, model, options) { context: tableName + '.' + columnKey })); } + + // CASE: ensure we transform 0|1 to false|true + if (!validator.empty(strVal)) { + model.set(columnKey, !!model.get(columnKey)); + } } // TODO: check if mandatory values should be enforced diff --git a/core/server/models/post.js b/core/server/models/post.js index 770b515405..013c7056d8 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -21,18 +21,31 @@ Post = ghostBookshelf.Model.extend({ tableName: 'posts', /** - * ## NOTE: - * We define the defaults on the schema (db) and model level. - * When inserting resources, the defaults are available **after** calling `.save`. - * But they are available when the model hooks are triggered (e.g. onSaving). - * It might be useful to keep them in the model layer for any connected logic. + * @NOTE * - * e.g. if `model.get('status') === draft; do something; + * We define the defaults on the schema (db) and model level. + * + * Why? + * - when you insert a resource, Knex does only return the id of the created resource + * - see https://knexjs.org/#Builder-insert + * - that means `defaultTo` is a pure database configuration (!) + * - Bookshelf just returns the model values which you have asked Bookshelf to insert + * - it can't return the `defaultTo` value from the schema/db level + * - but the db defaults defined in the schema are saved in the database correctly + * - `models.Post.add` always does to operations: + * 1. add + * 2. fetch (this ensures we fetch the whole resource from the database) + * - that means we have to apply the defaults on the model layer to ensure a complete field set + * 1. any connected logic in our model hooks e.g. beforeSave + * 2. model events e.g. "post.published" are using the inserted resource, not the fetched resource */ defaults: function defaults() { return { uuid: uuid.v4(), - status: 'draft' + status: 'draft', + featured: false, + page: false, + visibility: 'public' }; }, diff --git a/core/server/models/user.js b/core/server/models/user.js index 665cc6de7a..8f6ea16422 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -25,7 +25,9 @@ User = ghostBookshelf.Model.extend({ defaults: function defaults() { return { - password: security.identifier.uid(50) + password: security.identifier.uid(50), + visibility: 'public', + status: 'active' }; }, diff --git a/core/test/unit/data/validation/index_spec.js b/core/test/unit/data/validation/index_spec.js index deb8dd0a14..a9c729b252 100644 --- a/core/test/unit/data/validation/index_spec.js +++ b/core/test/unit/data/validation/index_spec.js @@ -79,6 +79,30 @@ describe('Validation', function () { {method: 'insert'} ); }); + + it('transforms 0 and 1', function () { + const post = models.Post.forge(testUtils.DataGenerator.forKnex.createPost({slug: 'test', featured: 0, page: 1})); + post.get('featured').should.eql(0); + post.get('page').should.eql(1); + + return validation.validateSchema('posts', post, {method: 'insert'}) + .then(function () { + post.get('featured').should.eql(false); + post.get('page').should.eql(true); + }); + }); + + it('keeps true or false', function () { + const post = models.Post.forge(testUtils.DataGenerator.forKnex.createPost({slug: 'test', featured: true, page: false})); + post.get('featured').should.eql(true); + post.get('page').should.eql(false); + + return validation.validateSchema('posts', post, {method: 'insert'}) + .then(function () { + post.get('featured').should.eql(true); + post.get('page').should.eql(false); + }); + }); }); describe('models.edit', function () { diff --git a/core/test/unit/models/post_spec.js b/core/test/unit/models/post_spec.js index cd77bf8b3c..0b172d665d 100644 --- a/core/test/unit/models/post_spec.js +++ b/core/test/unit/models/post_spec.js @@ -56,6 +56,10 @@ describe('Unit: models/post', function () { _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { should.exist(post.hasOwnProperty(key)); + + if (['page', 'status', 'visibility', 'featured'].indexOf(key) !== -1) { + events.post[0].data[key].should.eql(schema.tables.posts[key].defaultTo); + } }); should.not.exist(post.authors); @@ -67,6 +71,10 @@ describe('Unit: models/post', function () { _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { should.exist(events.post[0].data.hasOwnProperty(key)); + + if (['page', 'status', 'visibility', 'featured'].indexOf(key) !== -1) { + events.post[0].data[key].should.eql(schema.tables.posts[key].defaultTo); + } }); should.exist(events.post[0].data.authors); @@ -76,6 +84,29 @@ describe('Unit: models/post', function () { }); }); + it('with page:1', function () { + const events = { + post: [] + }; + + sandbox.stub(models.Post.prototype, 'emitChange').callsFake(function (event) { + events.post.push({event: event, data: this.toJSON()}); + }); + + return models.Post.add({ + title: 'My beautiful title.', + page: 1 + }, testUtils.context.editor) + .then((post) => { + post.get('title').should.eql('My beautiful title.'); + post = post.toJSON(); + + // transformed 1 to true + post.page.should.eql(true); + events.post[0].data.page.should.eql(true); + }); + }); + it('use `withRelated=tags`', function () { const events = { post: [] @@ -97,21 +128,12 @@ describe('Unit: models/post', function () { post.get('title').should.eql('My beautiful title.'); post = post.toJSON(); - _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { - should.exist(post.hasOwnProperty(key)); - }); - should.not.exist(post.authors); should.not.exist(post.primary_author); should.exist(post.tags); should.exist(post.primary_tag); events.post[0].event.should.eql('added'); - - _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { - should.exist(events.post[0].data.hasOwnProperty(key)); - }); - should.exist(events.post[0].data.authors); should.exist(events.post[0].data.primary_author); should.exist(events.post[0].data.tags); @@ -140,10 +162,6 @@ describe('Unit: models/post', function () { post.get('title').should.eql('My beautiful title.'); post = post.toJSON(); - _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { - should.exist(post.hasOwnProperty(key)); - }); - should.exist(post.authors); should.exist(post.primary_author); should.exist(post.tags); @@ -151,10 +169,6 @@ describe('Unit: models/post', function () { events.post[0].event.should.eql('added'); - _.each(_.keys(_.omit(schema.tables.posts, ['mobiledoc', 'amp', 'plaintext'])), (key) => { - should.exist(events.post[0].data.hasOwnProperty(key)); - }); - should.exist(events.post[0].data.authors); should.exist(events.post[0].data.primary_author); should.exist(events.post[0].data.tags); diff --git a/core/test/unit/models/user_spec.js b/core/test/unit/models/user_spec.js index 3d33cb3623..519a1f3ed5 100644 --- a/core/test/unit/models/user_spec.js +++ b/core/test/unit/models/user_spec.js @@ -1,5 +1,7 @@ const should = require('should'), sinon = require('sinon'), + _ = require('lodash'), + schema = require('../../../server/data/schema'), models = require('../../../server/models'), validation = require('../../../server/data/validation'), common = require('../../../server/lib/common'), @@ -334,4 +336,41 @@ describe('Unit: models/user', function () { }); }); }); + + describe('Add', function () { + const events = { + user: [] + }; + + before(function () { + models.init(); + + sandbox.stub(models.User.prototype, 'emitChange').callsFake(function (event) { + events.user.push({event: event, data: this.toJSON()}); + }); + }); + + after(function () { + sandbox.restore(); + }); + + it('defaults', function () { + return models.User.add({slug: 'joe', name: 'Joe', email: 'joe@test.com'}) + .then(function (user) { + user.get('name').should.eql('Joe'); + user.get('email').should.eql('joe@test.com'); + user.get('slug').should.eql('joe'); + user.get('visibility').should.eql('public'); + user.get('status').should.eql('active'); + + _.each(_.keys(schema.tables.users), (key) => { + should.exist(events.user[0].data.hasOwnProperty(key)); + + if (['status', 'visibility'].indexOf(key) !== -1) { + events.user[0].data[key].should.eql(schema.tables.users[key].defaultTo); + } + }); + }); + }); + }); });