From 74d749fa638ce81c734c6d0ac0a633ea968684ca Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Tue, 11 Oct 2022 13:21:31 +0200 Subject: [PATCH] Added `members_feedback` table (#15581) fixes https://github.com/TryGhost/Team/issues/2041 --- .../core/server/data/exporter/table-lists.js | 3 +- ...-10-10-10-05-add-members-feedback-table.js | 10 ++++ ghost/core/core/server/data/schema/schema.js | 8 +++ .../core/server/models/member-feedback.js | 22 ++++++++ .../integration/exporter/exporter.test.js | 1 + .../unit/server/data/schema/integrity.test.js | 2 +- .../server/models/member-feedback.test.js | 56 +++++++++++++++++++ 7 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 ghost/core/core/server/data/migrations/versions/5.19/2022-10-10-10-05-add-members-feedback-table.js create mode 100644 ghost/core/core/server/models/member-feedback.js create mode 100644 ghost/core/test/unit/server/models/member-feedback.test.js diff --git a/ghost/core/core/server/data/exporter/table-lists.js b/ghost/core/core/server/data/exporter/table-lists.js index d7a997b51d..7f6db6bb6a 100644 --- a/ghost/core/core/server/data/exporter/table-lists.js +++ b/ghost/core/core/server/data/exporter/table-lists.js @@ -40,7 +40,8 @@ const BACKUP_TABLES = [ 'comment_reports', 'jobs', 'redirects', - 'members_click_events' + 'members_click_events', + 'members_feedback' ]; // NOTE: exposing only tables which are going to be included in a "default" export file diff --git a/ghost/core/core/server/data/migrations/versions/5.19/2022-10-10-10-05-add-members-feedback-table.js b/ghost/core/core/server/data/migrations/versions/5.19/2022-10-10-10-05-add-members-feedback-table.js new file mode 100644 index 0000000000..0ad885be08 --- /dev/null +++ b/ghost/core/core/server/data/migrations/versions/5.19/2022-10-10-10-05-add-members-feedback-table.js @@ -0,0 +1,10 @@ +const {addTable} = require('../../utils'); + +module.exports = addTable('members_feedback', { + id: {type: 'string', maxlength: 24, nullable: false, primary: true}, + score: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}, + member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true}, + post_id: {type: 'string', maxlength: 24, nullable: false, references: 'posts.id', cascadeDelete: true}, + created_at: {type: 'dateTime', nullable: false}, + updated_at: {type: 'dateTime', nullable: true} +}); diff --git a/ghost/core/core/server/data/schema/schema.js b/ghost/core/core/server/data/schema/schema.js index 52a5b36dce..1802e7b7c5 100644 --- a/ghost/core/core/server/data/schema/schema.js +++ b/ghost/core/core/server/data/schema/schema.js @@ -850,5 +850,13 @@ module.exports = { member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true}, redirect_id: {type: 'string', maxlength: 24, nullable: false, references: 'redirects.id', cascadeDelete: true}, created_at: {type: 'dateTime', nullable: false} + }, + members_feedback: { + id: {type: 'string', maxlength: 24, nullable: false, primary: true}, + score: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0}, + member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true}, + post_id: {type: 'string', maxlength: 24, nullable: false, references: 'posts.id', cascadeDelete: true}, + created_at: {type: 'dateTime', nullable: false}, + updated_at: {type: 'dateTime', nullable: true} } }; diff --git a/ghost/core/core/server/models/member-feedback.js b/ghost/core/core/server/models/member-feedback.js new file mode 100644 index 0000000000..b29fc0fe0b --- /dev/null +++ b/ghost/core/core/server/models/member-feedback.js @@ -0,0 +1,22 @@ +const errors = require('@tryghost/errors'); +const ghostBookshelf = require('./base'); + +const MemberFeedback = ghostBookshelf.Model.extend({ + tableName: 'members_feedback', + + post() { + return this.belongsTo('Post', 'post_id', 'id'); + }, + + member() { + return this.belongsTo('Member', 'member_id', 'id'); + } +}, { + async destroy() { + throw new errors.IncorrectUsageError({message: 'Cannot destroy MemberFeedback'}); + } +}); + +module.exports = { + MemberFeedback: ghostBookshelf.model('MemberFeedback', MemberFeedback) +}; diff --git a/ghost/core/test/integration/exporter/exporter.test.js b/ghost/core/test/integration/exporter/exporter.test.js index c6f139c452..e8f0f8331c 100644 --- a/ghost/core/test/integration/exporter/exporter.test.js +++ b/ghost/core/test/integration/exporter/exporter.test.js @@ -40,6 +40,7 @@ describe('Exporter', function () { 'members', 'members_cancel_events', 'members_email_change_events', + 'members_feedback', 'members_labels', 'members_click_events', 'members_login_events', diff --git a/ghost/core/test/unit/server/data/schema/integrity.test.js b/ghost/core/test/unit/server/data/schema/integrity.test.js index fa811d5068..a3c8fc4a55 100644 --- a/ghost/core/test/unit/server/data/schema/integrity.test.js +++ b/ghost/core/test/unit/server/data/schema/integrity.test.js @@ -35,7 +35,7 @@ const validateRouteSettings = require('../../../../../core/server/services/route */ describe('DB version integrity', function () { // Only these variables should need updating - const currentSchemaHash = '67009be314428b02976e75281de37b14'; + const currentSchemaHash = 'c7f45ba40b60a81ce92d2d277009c681'; const currentFixturesHash = '8cf221f0ed930ac1fe8030a58e60d64b'; const currentSettingsHash = '2978a5684a2d5fcf089f61f5d368a0c0'; const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01'; diff --git a/ghost/core/test/unit/server/models/member-feedback.test.js b/ghost/core/test/unit/server/models/member-feedback.test.js new file mode 100644 index 0000000000..1f656d6f7b --- /dev/null +++ b/ghost/core/test/unit/server/models/member-feedback.test.js @@ -0,0 +1,56 @@ +const should = require('should'); +const sinon = require('sinon'); +const errors = require('@tryghost/errors'); +const models = require('../../../../core/server/models'); + +describe('Unit: models/MemberFeedback', function () { + before(function () { + models.init(); + }); + + afterEach(function () { + sinon.restore(); + }); + + describe('validation', function () { + it('throws if member_id is missing', function () { + return models.MemberFeedback.add({score: 1, post_id: 'post'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_feedback\.member_id/); + }); + }); + + it('throws if post_id is missing', function () { + return models.MemberFeedback.add({score: 1, member_id: '123'}) + .then(function () { + throw new Error('expected ValidationError'); + }) + .catch(function (err) { + should(err).lengthOf(1); + (err[0] instanceof errors.ValidationError).should.eql(true); + err[0].context.should.match(/members_feedback\.post_id/); + }); + }); + }); + + it('Delete is disabled', function () { + return models.MemberFeedback.destroy({id: 'any'}) + .then(function () { + throw new Error('expected IncorrectUsageError'); + }) + .catch(function (err) { + (err instanceof errors.IncorrectUsageError).should.eql(true); + }); + }); + + it('Has post and member relations', function () { + const model = models.MemberFeedback.forge({id: 'any'}); + model.post(); + model.member(); + }); +});