diff --git a/app.js b/app.js index fe392932b..15a7a5442 100644 --- a/app.js +++ b/app.js @@ -3,10 +3,10 @@ const bodyParser = require('body-parser'); const morgan = require('morgan'); const path = require('path'); const helmet = require('helmet'); -const passport = require('./passport'); +const passport = require('./services/passport'); const session = require('express-session'); const RedisStore = require('connect-redis')(session); -const redis = require('./redis'); +const redis = require('./services/redis'); const app = express(); diff --git a/bin/cli-assets b/bin/cli-assets index 9ab36685a..4b0d65c2a 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -15,7 +15,7 @@ const pkg = require('../package.json'); const parseDuration = require('parse-duration'); const Table = require('cli-table'); const Asset = require('../models/asset'); -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const scraper = require('../services/scraper'); const util = require('../util'); diff --git a/bin/cli-jobs b/bin/cli-jobs index bc320d14b..91a0a6e59 100755 --- a/bin/cli-jobs +++ b/bin/cli-jobs @@ -13,8 +13,8 @@ process.env.DEBUG = process.env.TALK_DEBUG; const program = require('commander'); const scraper = require('../services/scraper'); const util = require('../util'); -const mongoose = require('../mongoose'); -const kue = require('../kue'); +const mongoose = require('../services/mongoose'); +const kue = require('../services/kue'); util.onshutdown([ () => mongoose.disconnect() diff --git a/bin/cli-serve b/bin/cli-serve index dd682f5ad..c22920eaa 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -11,7 +11,7 @@ const debug = require('debug')('talk:server'); const http = require('http'); const init = require('../init'); const scraper = require('../services/scraper'); -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const util = require('../util'); /** diff --git a/bin/cli-settings b/bin/cli-settings index e7ba30151..e38249199 100755 --- a/bin/cli-settings +++ b/bin/cli-settings @@ -11,7 +11,7 @@ process.env.DEBUG = process.env.TALK_DEBUG; */ const program = require('commander'); -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const Setting = require('../models/setting'); const util = require('../util'); diff --git a/bin/cli-users b/bin/cli-users index adfe3bf23..e212a477f 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -14,7 +14,7 @@ const program = require('commander'); const pkg = require('../package.json'); const prompt = require('prompt'); const User = require('../models/user'); -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const util = require('../util'); const Table = require('cli-table'); diff --git a/client/coral-configure/translations.json b/client/coral-configure/translations.json index e597c9c9a..5aa7f72b6 100644 --- a/client/coral-configure/translations.json +++ b/client/coral-configure/translations.json @@ -3,7 +3,7 @@ "configureCommentStream": { "apply": "Apply", "title": "Configure Comment Stream", - "description": "As an admin you may customize the settings for the comment stream for this article", + "description": "As an admin you may customize the settings for the comment stream for this asset", "enablePremod": "Enable Premoderation", "enablePremodDescription": "Moderators must approve any comment before its published.", "enablePremodLinks": "Pre-Moderate Comments Containing Links", diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js index f85e609eb..c88138534 100644 --- a/client/coral-framework/actions/config.js +++ b/client/coral-framework/actions/config.js @@ -6,14 +6,6 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -export const updateOpenStatus = status => (dispatch, getState) => { - const assetId = getState().items.get('assets') - .keySeq() - .toArray()[0]; - return coralApi(`/asset/${assetId}/status?status=${status}`, {method: 'PUT'}) - .then(() => dispatch({type: status === 'open' ? actions.OPEN_COMMENTS : actions.CLOSE_COMMENTS})); -}; - const updateConfigRequest = () => ({type: actions.UPDATE_CONFIG_REQUEST}); const updateConfigSuccess = config => ({type: actions.UPDATE_CONFIG_SUCCESS, config}); const updateConfigFailure = () => ({type: actions.UPDATE_CONFIG_FAILURE}); @@ -31,3 +23,31 @@ export const updateConfiguration = newConfig => (dispatch, getState) => { }) .catch(error => dispatch(updateConfigFailure(error))); }; + +export const updateOpenStream = closedBody => (dispatch, getState) => { + const assetId = getState().items.get('assets') + .keySeq() + .toArray()[0]; + + dispatch(updateConfigRequest()); + + coralApi(`/asset/${assetId}/status`, {method: 'PUT', body: closedBody}) + .then(() => { + dispatch(addNotification('success', lang.t('successUpdateSettings'))); + dispatch(updateConfigSuccess(closedBody)); + }) + .catch(error => dispatch(updateConfigFailure(error))); +}; + +const openStream = () => ({type: actions.OPEN_COMMENTS}); +const closeStream = () => ({type: actions.CLOSE_COMMENTS}); + +export const updateOpenStatus = status => dispatch => { + if (status === 'open') { + dispatch(openStream()); + dispatch(updateOpenStream({closedAt: null})); + } else { + dispatch(closeStream()); + dispatch(updateOpenStream({closedAt: new Date().getTime()})); + } +}; diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js index 8266fbfa1..362608d6d 100644 --- a/client/coral-framework/reducers/config.js +++ b/client/coral-framework/reducers/config.js @@ -22,7 +22,7 @@ export default (state = initialState, action) => { return state .set('status', 'closed'); case actions.ADD_ITEM: - return action.item_type === 'assets' ? state.set('status', action.item.status) : state; + return action.item_type === 'assets' ? state.set('status', (action.item && action.item.closedAt && new Date(action.item.closedAt).getTime() <= new Date().getTime()) ? 'closed' : 'open') : state; default: return state; } diff --git a/architecture.png b/docs/architecture.png similarity index 100% rename from architecture.png rename to docs/architecture.png diff --git a/architecture.xml b/docs/architecture.xml similarity index 100% rename from architecture.xml rename to docs/architecture.xml diff --git a/ data-model.png b/docs/data-model.png similarity index 100% rename from data-model.png rename to docs/data-model.png diff --git a/ data-model.xml b/docs/data-model.xml similarity index 100% rename from data-model.xml rename to docs/data-model.xml diff --git a/moderation-flow-post.png b/docs/moderation-flow-post.png similarity index 100% rename from moderation-flow-post.png rename to docs/moderation-flow-post.png diff --git a/moderation-flow-pre.png b/docs/moderation-flow-pre.png similarity index 100% rename from moderation-flow-pre.png rename to docs/moderation-flow-pre.png diff --git a/moderation-flow.xml b/docs/moderation-flow.xml similarity index 100% rename from moderation-flow.xml rename to docs/moderation-flow.xml diff --git a/swagger.yaml b/docs/swagger.yaml similarity index 100% rename from swagger.yaml rename to docs/swagger.yaml diff --git a/models/action.js b/models/action.js index ed18e48c5..45f828226 100644 --- a/models/action.js +++ b/models/action.js @@ -1,4 +1,4 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const uuid = require('uuid'); const _ = require('lodash'); const Schema = mongoose.Schema; diff --git a/models/asset.js b/models/asset.js index 3c68b7c13..09c06a09d 100644 --- a/models/asset.js +++ b/models/asset.js @@ -1,4 +1,4 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const Schema = mongoose.Schema; const Setting = require('./setting'); @@ -19,7 +19,7 @@ const AssetSchema = new Schema({ }, type: { type: String, - default: 'article' + default: 'assets' }, scraped: { type: Date, @@ -64,13 +64,6 @@ 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). @@ -83,6 +76,13 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id}); */ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url}); +/** + * Returns true if the asset is closed, false else. + */ +AssetSchema.virtual('isClosed').get(function() { + return this.closedAt && this.closedAt.getTime() <= new Date().getTime(); +}); + /** * Retrieves the settings given an asset query and rectifies it against the * global settings. diff --git a/models/comment.js b/models/comment.js index 9cfee2ee7..b3b3bc52f 100644 --- a/models/comment.js +++ b/models/comment.js @@ -1,4 +1,4 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const Schema = mongoose.Schema; const _ = require('lodash'); const uuid = require('uuid'); diff --git a/models/setting.js b/models/setting.js index 687c28959..967b5fd7e 100644 --- a/models/setting.js +++ b/models/setting.js @@ -1,7 +1,7 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const Schema = mongoose.Schema; const _ = require('lodash'); -const cache = require('../cache'); +const cache = require('../services/cache'); /** * SettingSchema manages application settings that get used on front and backend. diff --git a/models/user.js b/models/user.js index 857eb1260..216345cbd 100644 --- a/models/user.js +++ b/models/user.js @@ -1,4 +1,4 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); const uuid = require('uuid'); const _ = require('lodash'); const bcrypt = require('bcrypt'); diff --git a/package.json b/package.json index c54bd6723..53af9910b 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,26 @@ "main": "app.js", "scripts": { "start": "./bin/cli serve --jobs", - "build": "NODE_ENV=production webpack --config webpack.config.js --bail", - "build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch", - "lint": "eslint bin/* .", - "lint-fix": "eslint . --fix", - "test": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive tests", - "test-watch": "NODE_ENV=test mocha --compilers js:babel-core/register --recursive -w tests", - "pree2e": "NODE_ENV=test ./pree2e.sh", - "e2e": "NODE_ENV=test node_modules/.bin/nightwatch", + "build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.config.js --bail", + "build-watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.config.dev.js --watch", + "lint": "./node_modules/.bin/eslint bin/* .", + "lint-fix": "./node_modules/.bin/eslint bin/* . --fix", + "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive tests", + "test-watch": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-core/register --recursive -w tests", + "pree2e": "NODE_ENV=test ./scripts/pree2e.sh", + "e2e": "NODE_ENV=test ./node_modules/.bin/nightwatch", "embed-start": "NODE_ENV=development npm run build && ./bin/cli serve --jobs" }, "config": { "pre-git": { "commit-msg": [], - "pre-commit": ["npm run lint", "npm test"], - "pre-push": ["npm test"], + "pre-commit": [ + "npm run lint", + "npm test" + ], + "pre-push": [ + "npm test" + ], "post-commit": [], "post-merge": [] } @@ -28,7 +33,12 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": ["talk", "coral", "coralproject", "ask"], + "keywords": [ + "talk", + "coral", + "coralproject", + "ask" + ], "author": "", "license": "Apache-2.0", "bugs": { @@ -59,7 +69,6 @@ "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", "prompt": "^1.0.0", - "react-linkify": "^0.1.3", "redis": "^2.6.3", "uuid": "^2.0.3" }, @@ -84,9 +93,10 @@ "copy-webpack-plugin": "^4.0.0", "css-loader": "^0.25.0", "dialog-polyfill": "^0.4.4", - "eslint": "^3.9.1", + "eslint": "^3.12.1", "eslint-config-postcss": "^2.0.2", "eslint-config-standard": "^6.2.1", + "eslint-module-utils": "^2.0.0", "eslint-plugin-flowtype": "^2.25.0", "eslint-plugin-import": "^2.2.0", "eslint-plugin-mocha": "^4.7.0", @@ -113,6 +123,7 @@ "pym.js": "^1.1.1", "react": "15.3.2", "react-dom": "15.3.2", + "react-linkify": "^0.1.3", "react-mdl": "^1.7.2", "react-mdl-selectfield": "^0.2.0", "react-onclickoutside": "^5.7.1", diff --git a/pree2e.sh b/pree2e.sh deleted file mode 100755 index 8857e5745..000000000 --- a/pree2e.sh +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/selenium-standalone/bin/selenium-standalone install -npm start & diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 2ddc3ea23..5aa9b8cc6 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -90,11 +90,28 @@ router.put('/:asset_id/settings', (req, res, next) => { }); router.put('/:asset_id/status', (req, res, next) => { - // Update the asset status + + const id = req.params.asset_id; + + const { + closedAt, + closedMessage + } = req.body; + Asset - .update({id: req.params.asset_id}, {status: req.query.status}) - .then(() => res.status(204).end()) - .catch((err) => next(err)); + .update({id}, { + $set: { + closedAt, + closedMessage + } + }) + .then(() => { + + res.status(204).json(); + }) + .catch((err) => { + next(err); + }); }); module.exports = router; diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 369e5ec77..bcbfcc27e 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -1,5 +1,5 @@ const express = require('express'); -const passport = require('../../../passport'); +const passport = require('../../../services/passport'); const authorization = require('../../../middleware/authorization'); const router = express.Router(); diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 3ca2f4877..4141c64df 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -87,7 +87,6 @@ router.post('/', wordlist.filter('body'), (req, res, next) => { // 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}`)); } diff --git a/routes/api/index.js b/routes/api/index.js index 7863b0cd0..6a7f6ff10 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -19,6 +19,6 @@ router.use('/stream', require('./stream')); router.use('/users', require('./users')); // Bind the kue handler to the /kue path. -router.use('/kue', authorization.needed('admin'), require('../../kue').kue.app); +router.use('/kue', authorization.needed('admin'), require('../../services/kue').kue.app); module.exports = router; diff --git a/scripts/pree2e.sh b/scripts/pree2e.sh new file mode 100755 index 000000000..dc3266dd2 --- /dev/null +++ b/scripts/pree2e.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# install selenium +../node_modules/selenium-standalone/bin/selenium-standalone install + +# start the app server +npm start & diff --git a/cache.js b/services/cache.js similarity index 100% rename from cache.js rename to services/cache.js diff --git a/kue.js b/services/kue.js similarity index 100% rename from kue.js rename to services/kue.js diff --git a/mongoose.js b/services/mongoose.js similarity index 100% rename from mongoose.js rename to services/mongoose.js diff --git a/passport.js b/services/passport.js similarity index 98% rename from passport.js rename to services/passport.js index 2e084eafd..f709970dc 100644 --- a/passport.js +++ b/services/passport.js @@ -1,5 +1,5 @@ const passport = require('passport'); -const User = require('./models/user'); +const User = require('../models/user'); const LocalStrategy = require('passport-local').Strategy; const FacebookStrategy = require('passport-facebook').Strategy; diff --git a/redis.js b/services/redis.js similarity index 100% rename from redis.js rename to services/redis.js diff --git a/services/scraper.js b/services/scraper.js index 10e0e4aa8..c75a12750 100644 --- a/services/scraper.js +++ b/services/scraper.js @@ -1,4 +1,4 @@ -const kue = require('../kue'); +const kue = require('./kue'); const debug = require('debug')('talk:services:scraper'); const Asset = require('../models/asset'); const JOB_NAME = 'scraper'; diff --git a/tests/mongoose.js b/tests/mongoose.js index 74a0b04f7..7544072ec 100644 --- a/tests/mongoose.js +++ b/tests/mongoose.js @@ -1,4 +1,4 @@ -const mongoose = require('../mongoose'); +const mongoose = require('../services/mongoose'); beforeEach(function (done) { function clearDB() { diff --git a/tests/routes/api/assets/index.js b/tests/routes/api/assets/index.js index c56e5b1ba..64e820722 100644 --- a/tests/routes/api/assets/index.js +++ b/tests/routes/api/assets/index.js @@ -17,7 +17,8 @@ describe('/api/v1/assets', () => { { url: 'https://coralproject.net/news/asset1', title: 'Asset 1', - description: 'term1' + description: 'term1', + id: '1' }, { url: 'https://coralproject.net/news/asset2', @@ -82,4 +83,32 @@ describe('/api/v1/assets', () => { }); + describe('#put', () => { + it('should close the asset', function() { + + const today = Date.now(); + + return Asset.findOrCreateByUrl('http://test.com') + .then((asset) => { + expect(asset).to.have.property('isClosed', null); + expect(asset).to.have.property('closedAt', null); + + return chai.request(app) + .put(`/api/v1/asset/${asset.id}/status`) + .set(passport.inject({roles: ['admin']})) + .send({closedAt: today}); + }) + .then((res) => { + + expect(res).to.have.status(204); + + return Asset.findByUrl('http://test.com'); + }) + .then((asset) => { + expect(asset).to.have.property('isClosed', true); + expect(asset).to.have.property('closedAt').and.to.not.equal(null); + }); + }); + }); + }); diff --git a/tests/utils/e2e-mongoose.js b/tests/utils/e2e-mongoose.js index 1bb7e695b..0b630c0e3 100644 --- a/tests/utils/e2e-mongoose.js +++ b/tests/utils/e2e-mongoose.js @@ -1,4 +1,4 @@ -const mongoose = require('../../mongoose'); +const mongoose = require('../../services/mongoose'); // Ensure the NODE_ENV is set to 'test', // this is helpful when you would like to change behavior when testing.