From b82f1304542c1ab148bf8ce1f22dfa231682b6dc Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 6 Dec 2016 14:44:15 -0500 Subject: [PATCH 1/3] Adds a new closedAt time for assets to prevent new comments from being created --- models/asset.js | 15 +++++++ models/setting.js | 10 +++++ routes/api/comments/index.js | 15 ++++++- tests/routes/api/comments/index.js | 63 ++++++++++++++++++++++++++---- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/models/asset.js b/models/asset.js index a0619576f..7b0717674 100644 --- a/models/asset.js +++ b/models/asset.js @@ -29,6 +29,14 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, + closedAt: { + type: Date, + default: null + }, + closedMessage: { + type: String, + default: null + }, title: String, description: String, image: String, @@ -60,6 +68,13 @@ AssetSchema.index({ background: true }); +/** + * Returns true if the asset is closed, false else. + */ +AssetSchema.virtual('isClosed').get(function() { + return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); +}); + /** * Finds an asset by its id. * @param {String} id identifier of the asset (uuid). diff --git a/models/setting.js b/models/setting.js index 22b2b103f..5d71b8c97 100644 --- a/models/setting.js +++ b/models/setting.js @@ -28,6 +28,16 @@ const SettingSchema = new Schema({ type: String, default: '' }, + closedTimeout: { + type: Number, + + // Two weeks default expiry. + default: 60 * 60 * 24 * 7 * 2 + }, + closedMessage: { + type: String, + default: '' + }, wordlist: [String] }, { timestamps: { diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index b82c1faf3..748f64f5b 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -80,7 +80,20 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { status = Promise.resolve('rejected'); } else { status = Asset - .rectifySettings(Asset.findById(asset_id)) + .rectifySettings(Asset.findById(asset_id).then((asset) => { + if (!asset) { + return Promise.reject(new Error('asset referenced is not found')); + } + + // Check to see if the asset has closed commenting... + if (asset.isClosed) { + + // They have, ensure that we send back an error. + return Promise.reject(new Error(`asset has commenting closed because: ${asset.closedMessage}`)); + } + + return asset; + })) // Return `premod` if pre-moderation is enabled and an empty "new" status // in the event that it is not in pre-moderation mode. diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index bf7a06ff6..70912eac7 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -19,6 +19,14 @@ const settings = {id: '1', moderation: 'pre'}; describe('/api/v1/comments', () => { + // Ensure that the settings are always available. + beforeEach(() => Promise.all([ + Setting.init(settings), + wordlist.insert([ + 'bad words' + ]) + ])); + describe('#get', () => { const comments = [{ body: 'comment 10', @@ -75,11 +83,7 @@ describe('/api/v1/comments', () => { return Action.create(actions); }), - User.createLocalUsers(users), - wordlist.insert([ - 'bad words' - ]), - Setting.init(settings) + User.createLocalUsers(users) ]); }); @@ -142,11 +146,19 @@ describe('/api/v1/comments', () => { describe('#post', () => { + let asset_id; + + beforeEach(() => Asset.findOrCreateByUrl('https://coralproject.net/section/article-is-the-best').then((asset) => { + + // Update the asset id. + asset_id = asset.id; + })); + it('should create a comment', () => { return chai.request(app) .post('/api/v1/comments') .set(passport.inject({roles: []})) - .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) + .send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset_id, 'parent_id': ''}) .then((res) => { expect(res).to.have.status(201); expect(res.body).to.have.property('id'); @@ -157,7 +169,7 @@ describe('/api/v1/comments', () => { return chai.request(app) .post('/api/v1/comments') .set(passport.inject({roles: []})) - .send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) + .send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': asset_id, 'parent_id': ''}) .then((res) => { expect(res).to.have.status(201); expect(res.body).to.have.property('id'); @@ -188,6 +200,43 @@ describe('/api/v1/comments', () => { expect(res.body.status[0]).to.have.property('type', 'premod'); }); }); + + it('shouldn\'t create a comment when the asset has expired commenting', () => { + return Asset.create({ + closedAt: new Date().setDate(0), + closedMessage: 'tests said expired!' + }) + .then((asset) => { + return chai.request(app) + .post('/api/v1/comments') + .set(passport.inject({roles: []})) + .send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''}); + }) + .then((res) => { + expect(res).to.have.status(500); + }) + .catch((err) => { + expect(err.response.body).to.not.be.null; + expect(err.response.body).to.have.property('message'); + expect(err.response.body.message).to.contain('tests said expired!'); + }); + }); + + it('should create a comment when the asset has not expired yet', () => { + return Asset.create({ + closedAt: new Date().setDate(32), + closedMessage: 'tests said expired!' + }) + .then((asset) => { + return chai.request(app) + .post('/api/v1/comments') + .set(passport.inject({roles: []})) + .send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''}); + }) + .then((res) => { + expect(res).to.have.status(201); + }); + }); }); }); From fb392422eae88c96be5016c1e8d26bcd21a53e30 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Thu, 8 Dec 2016 12:44:02 -0500 Subject: [PATCH 2/3] Added textarea for closed message on admin --- .../src/containers/Configure/Configure.js | 16 ++++++++++++++++ client/coral-admin/src/translations.json | 8 ++++++-- client/coral-embed-stream/src/CommentStream.js | 4 ++-- client/coral-framework/actions/config.js | 1 + client/coral-framework/reducers/config.js | 3 ++- models/setting.js | 2 +- 6 files changed, 28 insertions(+), 6 deletions(-) diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js index a9e2f3ef2..e171fc27d 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/containers/Configure/Configure.js @@ -28,6 +28,7 @@ class Configure extends React.Component { // InfoBox has two settings. Enable or not and the content of it if it is enable. this.updateInfoBoxEnable = this.updateInfoBoxEnable.bind(this); this.updateInfoBoxContent = this.updateInfoBoxContent.bind(this); + this.updateClosedMessage = this.updateClosedMessage.bind(this); this.saveSettings = this.saveSettings.bind(this); } @@ -51,6 +52,11 @@ class Configure extends React.Component { this.props.dispatch(updateSettings({infoBoxContent})); } + updateClosedMessage (event) { + const closedMessage = event.target.value; + this.props.dispatch(updateSettings({closedMessage})); + } + saveSettings () { this.props.dispatch(saveSettingsToServer()); } @@ -78,6 +84,16 @@ class Configure extends React.Component {

+ + + {lang.t('configure.closed-comments-desc')} + + + @@ -114,7 +114,7 @@ class CommentStream extends Component { author={user} /> - :

Comments are closed for this thread.

+ :

{this.props.config.closedMessage}

} {!loggedIn && } { diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index 6dc880434..e004fa1eb 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -5,6 +5,7 @@ import coralApi from '../helpers/response'; * Action name constants */ +export const UPDATE_SETTINGS = 'UPDATE_SETTINGS'; export const OPEN_COMMENTS = 'OPEN_COMMENTS'; export const CLOSE_COMMENTS = 'CLOSE_COMMENTS'; export const ADD_ITEM = 'ADD_ITEM'; diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js index 9620f5b97..7fbccaa49 100644 --- a/client/coral-framework/reducers/config.js +++ b/client/coral-framework/reducers/config.js @@ -5,7 +5,8 @@ import * as actions from '../actions/config'; const initialState = Map({ features: Map({}), - status: 'open' + status: 'open', + closedMessage: '' }); export default (state = initialState, action) => { diff --git a/models/setting.js b/models/setting.js index 5d71b8c97..2414381fa 100644 --- a/models/setting.js +++ b/models/setting.js @@ -99,7 +99,7 @@ SettingService.update = (settings) => Setting.findOneAndUpdate(selector, { * @param {Object} settings the source settings object * @return {Object} the filtered settings object */ -SettingService.public = (settings) => _.pick(settings, ['moderation', 'infoBoxEnable', 'infoBoxContent']); +SettingService.public = (settings) => _.pick(settings, ['moderation', 'infoBoxEnable', 'infoBoxContent', 'closedMessage']); /** * This is run once when the app starts to ensure settings are populated. From fed41e1fba80250545f8192ca1c7e7fb7ec7eb5d Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Thu, 8 Dec 2016 16:59:29 -0500 Subject: [PATCH 3/3] Linted code --- client/coral-embed-stream/src/CommentStream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 69fc5f73f..7f3b1b30f 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -114,7 +114,7 @@ class CommentStream extends Component { author={user} /> - :

{this.props.config.closedMessage}

+ :

{closedMessage}

} {!loggedIn && } {