From b82f1304542c1ab148bf8ce1f22dfa231682b6dc Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 6 Dec 2016 14:44:15 -0500 Subject: [PATCH 1/6] 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/6] 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/6] 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 && } { From c8d0fae0970fa0bb9fe0639c9077ce125b272856 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 8 Dec 2016 12:50:12 -1000 Subject: [PATCH 4/6] Fix formatting --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6589cddb9..51c0d1ab9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ You can view product ideas and our longer term roadmap here: https://trello.com/ ### Local Dependencies Node + Mongo ### Getting Started @@ -26,17 +27,17 @@ inside the environment in order to run, those variables are listed here: - `TALK_SESSION_SECRET` (*required*) - a random string which will be used to secure cookies. -`TALK_FACEBOOK_APP_ID` (*required*) - the Facebook app id for your Facebook +- `TALK_FACEBOOK_APP_ID` (*required*) - the Facebook app id for your Facebook Login enabled app. -`TALK_FACEBOOK_APP_SECRET` (*required*) - the Facebook app secret for your +- `TALK_FACEBOOK_APP_SECRET` (*required*) - the Facebook app secret for your Facebook Login enabled app. -`TALK_ROOT_URL` (*required*) - root url of the installed application externally +- `TALK_ROOT_URL` (*required*) - root url of the installed application externally available in the format: `://` without the path. -`TALK_SMTP_PROVIDER` (*required*) - SMTP provider name. -`TALK_SMTP_USERNAME` (*required*) - username of the SMTP provider you are using. -`TALK_SMTP_PASSWORD` (*required*) - password for the SMTP provider you are using. -`TALK_SMTP_HOST` (*required*) - SMTP host url with format `smtp.domain.com`. -`TALK_SMTP_PORT` (*required*) - SMTP port. +- `TALK_SMTP_PROVIDER` (*required*) - SMTP provider name. +- `TALK_SMTP_USERNAME` (*required*) - username of the SMTP provider you are using. +- `TALK_SMTP_PASSWORD` (*required*) - password for the SMTP provider you are using. +- `TALK_SMTP_HOST` (*required*) - SMTP host url with format `smtp.domain.com`. +- `TALK_SMTP_PORT` (*required*) - SMTP port. ### Running with Docker Make sure you have Docker running first and then run `docker-compose up -d` From 7ce44ccd8c40d7098d2283a2d8bed836a232239b Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 8 Dec 2016 12:53:39 -1000 Subject: [PATCH 5/6] More formatting --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 51c0d1ab9..0b77e95b7 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,11 @@ Make sure you have Docker running first and then run `docker-compose up -d` `npm run lint` ### Helpful URLs -Bare comment stream: http://localhost:5000/client/coral-embed-stream/ -Comment stream embedded on sample article: http://localhost:5000/client/coral-embed-stream/samplearticle.html -Moderator view: http://localhost:5000/admin/ +Comment stream: http://localhost:3000/ + +Comment stream embedded on sample article: http://localhost:3000/assets/samplearticle.html + +Moderator view: http://localhost:3000/admin ### Docs `swagger.yaml` From dd5c114dc2ea82bd521ff6327db1ee129328ca32 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 8 Dec 2016 18:36:20 -1000 Subject: [PATCH 6/6] don't use well-known email settings --- docker-compose.yml | 1 + services/mailer.js | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index d7a180277..16df60095 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: restart: always ports: - "5000:5000" + - "2525:2525" environment: - "TALK_PORT=5000" - "TALK_MONGO_URL=mongodb://mongo" diff --git a/services/mailer.js b/services/mailer.js index b7d621954..fe07f063b 100644 --- a/services/mailer.js +++ b/services/mailer.js @@ -3,7 +3,7 @@ const nodemailer = require('nodemailer'); const smtpRequiredProps = [ 'TALK_SMTP_USERNAME', 'TALK_SMTP_PASSWORD', - 'TALK_SMTP_PROVIDER' + 'TALK_SMTP_HOST' ]; smtpRequiredProps.forEach(prop => { @@ -13,9 +13,7 @@ smtpRequiredProps.forEach(prop => { }); const options = { - // list of providers here: - // https://github.com/nodemailer/nodemailer-wellknown#supported-services - service: process.env.TALK_SMTP_PROVIDER, + host: process.env.TALK_SMTP_HOST, auth: { user: process.env.TALK_SMTP_USERNAME, pass: process.env.TALK_SMTP_PASSWORD @@ -24,10 +22,8 @@ const options = { if (process.env.TALK_SMTP_PORT) { options.port = process.env.TALK_SMTP_PORT; -} - -if (process.env.TALK_SMTP_HOST) { - options.host = process.env.TALK_SMTP_HOST; +} else { + options.port = 25; } const defaultTransporter = nodemailer.createTransport(options);