diff --git a/app.js b/app.js index e84eb4b79..dd58e9883 100644 --- a/app.js +++ b/app.js @@ -22,7 +22,10 @@ if (app.get('env') !== 'test') { //============================================================================== app.set('trust proxy', 1); -app.use(helmet()); +// We disable frameward on helmet to allow crossdomain injection of the embed +app.use(helmet({ + frameguard: false +})); app.use(bodyParser.json()); app.use('/client', express.static(path.join(__dirname, 'dist'))); app.set('views', path.join(__dirname, 'views')); diff --git a/bin/cli-assets b/bin/cli-assets index 99965c6d9..9ab36685a 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -12,9 +12,11 @@ process.env.DEBUG = process.env.TALK_DEBUG; const program = require('commander'); 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 scraper = require('../services/scraper'); const util = require('../util'); // Register the shutdown criteria. @@ -55,6 +57,37 @@ function listAssets() { }); } +function refreshAssets(ageString) { + const now = new Date().getTime(); + const ageMs = parseDuration(ageString); + const age = new Date(now - ageMs); + + Asset.find({ + $or: [ + { + scraped: { + $lte: age + } + }, + { + scraped: null + } + ] + }) + + // Queue all the assets for scraping. + .then((assets) => Promise.all(assets.map(scraper.create))) + + .then(() => { + console.log('Assets were queued to be scraped'); + util.shutdown(); + }) + .catch((err) => { + console.error(err); + util.shutdown(1); + }); +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== @@ -67,6 +100,11 @@ program .description('list all the assets in the database') .action(listAssets); +program + .command('refresh ') + .description('queues the assets that exceed the age requested') + .action(refreshAssets); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/bin/cli-jobs b/bin/cli-jobs index f14276d38..bc320d14b 100755 --- a/bin/cli-jobs +++ b/bin/cli-jobs @@ -14,11 +14,15 @@ const program = require('commander'); const scraper = require('../services/scraper'); const util = require('../util'); const mongoose = require('../mongoose'); +const kue = require('../kue'); util.onshutdown([ () => mongoose.disconnect() ]); +/** + * Starts the job processor. + */ function processJobs() { // Start the processor. @@ -31,6 +35,65 @@ function processJobs() { ]); } +/** + * Removes a single job. + * @param {Object} job the job to be removed + * @return {Promise} + */ +function removeJob(job) { + return new Promise((resolve, reject) => job.remove((err) => { + if (err) { + return reject(err); + } + + return resolve(job); + })); +} + +/** + * Removes the jobs passed in and returns a promise. + * @param {Array} jobs array of jobs + * @return {Promise} + */ +function removeJobs(jobs) { + return Promise.all(jobs.map(removeJob)); +} + +/** + * Get the top n jobs with a specific state. + * @param {String} [state='complete'] state to list jobs by + * @param {Number} limit limit of jobs to load + * @return {Promise} + */ +function rangeJobsByState(state = 'complete', limit) { + return new Promise((resolve, reject) => { + kue.Job.rangeByState(state, 0, limit, 'asc', (err, jobs) => { + if (err) { + return reject(err); + } + + resolve(jobs); + }); + }); +} + +/** + * Cleans up the jobs that are in the queue. + */ +function cleanupJobs(options) { + const n = 100; + + Promise.all([ + rangeJobsByState('complete', n), + options.stuck ? rangeJobsByState('failed', n) : false + ]) + .then((joblists) => joblists.filter((jobs) => jobs).map(removeJobs)) + .then(() => { + util.shutdown(); + console.log('Removed old jobs'); + }); +} + //============================================================================== // Setting up the program command line arguments. //============================================================================== @@ -40,6 +103,12 @@ program .description('starts job processing') .action(processJobs); +program + .command('cleanup') + .option('-s, --stuck', 'cleans up jobs that have been stuck', false) + .description('cleans up inactive jobs') + .action(cleanupJobs); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 40d249088..be40ecff6 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -3,7 +3,7 @@ import { itemActions, Notification, notificationActions, - authActions + authActions, } from '../../coral-framework'; import {connect} from 'react-redux'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; @@ -19,6 +19,8 @@ import LikeButton from '../../coral-plugin-likes/LikeButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; import UserBox from '../../coral-sign-in/components/UserBox'; +import {TabBar, Tab, TabContent} from '../../coral-ui'; +import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -46,11 +48,27 @@ const mapDispatchToProps = (dispatch) => ({ }, appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), - logout: () => dispatch(logout()) + logout: () => dispatch(logout()), }); class CommentStream extends Component { + constructor (props) { + super(props); + + this.state = { + activeTab: 0 + }; + + this.changeTab = this.changeTab.bind(this); + } + + changeTab (tab) { + this.setState({ + activeTab: tab + }); + } + static propTypes = { items: PropTypes.object.isRequired, addItem: PropTypes.func.isRequired, @@ -90,137 +108,148 @@ class CommentStream extends Component { const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; const {actions, users, comments} = this.props.items; const {loggedIn, user, showSignInDialog} = this.props.auth; + const {activeTab} = this.state; return
{ rootItem ?
-
- - - {loggedIn && } - - {!loggedIn && } -
- { - rootItem.comments && rootItem.comments.map((commentId) => { - const comment = comments[commentId]; - return
-
- - - -
- - -
-
- - -
- - { - comment.children && - comment.children.map((replyId) => { - let reply = this.props.items.comments[replyId]; - return
-
- - - -
- + + Settings + + + +
+ + {loggedIn && } + + {!loggedIn && } +
+ { + rootItem.comments && rootItem.comments.map((commentId) => { + const comment = comments[commentId]; + return
+
+ + + +
+ + +
+
+ + +
+ + { + comment.children && + comment.children.map((replyId) => { + let reply = this.props.items.comments[replyId]; + return
+
+ + + +
+ + +
+
+ + +
+ - -
-
- - -
- -
; - }) - } -
; - }) - } - +
; + }) + } +
; + }) + } + + + + +
: 'Loading' } diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 174f3ef16..2da0c09c4 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -154,11 +154,6 @@ hr { color: #F00; } -/* Comment count styles */ -.coral-plugin-comment-count-text { - margin-bottom: 15px; -} - .coral-plugin-pubdate-text { color: #CCC; display: inline-block; diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 4587b174b..00deba5ef 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -1,4 +1,4 @@ -import {getInit, base, handleResp} from '../../coral-framework/helpers/response'; +import {getInit, base, handleResp} from '../helpers/response'; import {fromJS} from 'immutable'; /* Item Actions */ @@ -95,7 +95,7 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) => */ export function getStream (assetUrl) { return (dispatch) => { - return fetch(`${base}/stream?asset_url=${encodeURIComponent(assetUrl)}`) + return fetch(`${base}/stream?asset_url=${encodeURIComponent(assetUrl)}`, getInit('GET')) .then(handleResp) .then((json) => { diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js index 837c8956a..7b721badc 100644 --- a/client/coral-framework/index.js +++ b/client/coral-framework/index.js @@ -11,5 +11,5 @@ export { itemActions, I18n, notificationActions, - authActions + authActions, }; diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index b38de61ae..90eaa7cb7 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -14,5 +14,5 @@ export default combineReducers({ config, items, notification, - auth + auth, }); diff --git a/client/coral-settings/components/Bio.css b/client/coral-settings/components/Bio.css new file mode 100644 index 000000000..8c36b989d --- /dev/null +++ b/client/coral-settings/components/Bio.css @@ -0,0 +1,21 @@ +.bio textarea { + width: 100%; + box-sizing: border-box; + border-radius: 2px; + min-height: 100px; + margin: 10px 0; + border: solid 1px #d8d8d8; +} + +.bio h1 { + font-size: 16px; + margin: 3px 0; +} + +.bio p { + margin: 3px 0; +} + +.actions { + text-align: right; +} diff --git a/client/coral-settings/components/Bio.js b/client/coral-settings/components/Bio.js new file mode 100644 index 000000000..b82587322 --- /dev/null +++ b/client/coral-settings/components/Bio.js @@ -0,0 +1,16 @@ +import React from 'react'; +import styles from './Bio.css'; +import {Button} from '../../coral-ui'; + +export default () => ( +
+

Bio

+

Tell the community about yourself

+