{children}
diff --git a/client/coral-admin/src/components/ui/Logo.css b/client/coral-admin/src/components/ui/Logo.css
index e764af627..f89bf3d5d 100644
--- a/client/coral-admin/src/components/ui/Logo.css
+++ b/client/coral-admin/src/components/ui/Logo.css
@@ -1,7 +1,9 @@
.logo h1 {
color: #272727;
font-size: 20px;
- padding: 0 30px;
+ margin: 0;
+ line-height: 60px;
+ padding: 0 20px;
}
.logo span {
@@ -13,6 +15,7 @@
.logo {
background: #E5E5E5;
+ height: 100%;
}
diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js
index 5f3cb0cff..f263c33f5 100644
--- a/client/coral-admin/src/containers/LayoutContainer.js
+++ b/client/coral-admin/src/containers/LayoutContainer.js
@@ -1,37 +1,31 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Layout} from '../components/ui/Layout';
-import {checkLogin} from '../actions/auth';
-import {NotFound} from '../components/NotFound';
+import {checkLogin, logout} from '../actions/auth';
+import {FullLoading} from '../components/FullLoading';
import {PermissionRequired} from '../components/PermissionRequired';
class LayoutContainer extends Component {
componentWillMount () {
- this.props.checkLogin();
+ const {checkLogin} = this.props;
+ checkLogin();
}
render () {
- const {isAdmin, loggedIn} = this.props.auth;
-
- if (!loggedIn) {
- return
;
- }
-
- if (!isAdmin && loggedIn) {
- return
;
- }
-
- return
;
+ const {isAdmin, loggedIn, loadingUser} = this.props.auth;
+ if (loadingUser) { return
; }
+ if (!isAdmin) { return
; }
+ if (isAdmin && loggedIn) { return
; }
+ return
;
}
}
-LayoutContainer.propTypes = {};
-
const mapStateToProps = state => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin()),
+ handleLogout: () => dispatch(logout())
});
export default connect(
diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js
index 59dccac5e..f897c1bae 100644
--- a/client/coral-admin/src/reducers/auth.js
+++ b/client/coral-admin/src/reducers/auth.js
@@ -9,19 +9,25 @@ const initialState = Map({
export default function auth (state = initialState, action) {
switch (action.type) {
+ case actions.CHECK_LOGIN_REQUEST:
+ return state
+ .set('loadingUser', true);
case actions.CHECK_LOGIN_FAILURE:
return state
.set('loggedIn', false)
+ .set('loadingUser', false)
.set('user', null);
case actions.CHECK_LOGIN_SUCCESS:
return state
.set('loggedIn', true)
+ .set('loadingUser', false)
.set('isAdmin', action.isAdmin)
.set('user', action.user);
case actions.LOGOUT_SUCCESS:
return state
.set('loggedIn', false)
- .set('user', null);
+ .set('user', null)
+ .set('isAdmin', false);
default :
return state;
}
diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js
index eeb799452..6b872d12d 100644
--- a/client/coral-admin/src/services/talk-adapter.js
+++ b/client/coral-admin/src/services/talk-adapter.js
@@ -1,4 +1,4 @@
-import {base, handleResp, getInit} from '../helpers/response';
+import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
/**
* The adapter is a redux middleware that interecepts the actions that need
diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js
index f2a584ed1..de14edbc7 100644
--- a/client/coral-embed-stream/src/index.js
+++ b/client/coral-embed-stream/src/index.js
@@ -2,9 +2,7 @@ import React from 'react';
import {render} from 'react-dom';
import CommentStream from './CommentStream';
import {Provider} from 'react-redux';
-import {fetchConfig, store} from '../../coral-framework';
-
-store.dispatch(fetchConfig());
+import {store} from '../../coral-framework';
render(
diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js
deleted file mode 100644
index d8fd886be..000000000
--- a/client/coral-framework/actions/config.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import {fromJS} from 'immutable';
-
-/**
- * Action name constants
- */
-
-export const FETCH_CONFIG_REQUEST = 'FETCH_CONFIG_REQUEST';
-export const FETCH_CONFIG_FAILED = 'FETCH_CONFIG_FAILED';
-export const FETCH_CONFIG_SUCCESS = 'FETCH_CONFIG_SUCCESS';
-
-/**
- * Action creators
- */
-
-export function fetchConfig () {
- return (dispatch) => {
-
- dispatch({type: FETCH_CONFIG_REQUEST});
-
- return fetch('/api/v1/settings')
- .then(
- response => {
- return response.ok ? response.json()
- : Promise.reject(`${response.status} ${response.statusText}`);
- }
- )
- .then((json) => {
- return dispatch({type: FETCH_CONFIG_SUCCESS, config: fromJS(json)});
- })
- .catch((error) => {
- dispatch({type: FETCH_CONFIG_FAILED, error});
- });
-
- };
-}
diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js
index 4af6dd1b1..4587b174b 100644
--- a/client/coral-framework/actions/items.js
+++ b/client/coral-framework/actions/items.js
@@ -1,3 +1,5 @@
+import {getInit, base, handleResp} from '../../coral-framework/helpers/response';
+import {fromJS} from 'immutable';
/* Item Actions */
/**
@@ -6,28 +8,9 @@
export const ADD_ITEM = 'ADD_ITEM';
export const UPDATE_ITEM = 'UPDATE_ITEM';
+export const UPDATE_SETTINGS = 'UPDATE_SETTINGS';
export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY';
-const getInit = (method, body) => {
- const headers = {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- };
-
- const init = {method, headers};
- if (body) {
- init.body = JSON.stringify(body);
- }
-
- return init;
-};
-
-const responseHandler = response => {
- if (response.status === 204) {
- return;
- }
- return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
-};
/**
* Action creators
*/
@@ -61,6 +44,7 @@ export const addItem = (item, item_type) => {
* id - the id of the item to be posted
* property - the property to be updated
* value - the value that the property should be set to
+* item_type - the type of the item being updated (users, comments, etc)
*
*/
export const updateItem = (id, property, value, item_type) => {
@@ -73,6 +57,18 @@ export const updateItem = (id, property, value, item_type) => {
};
};
+/*
+* Appends data to an array in an item in the local store without posting it to the server
+* Useful for adding a recently posted reply to a comment, etc.
+*
+* @params
+* id - the id of the item to be posted
+* property - the property to be updated (should be an array)
+* value - the value that should be added to the array
+* add_to_front - boolean that defines whether value is added at the beginning (unshift) or end (push)
+* item_type - the type of the item being updated (users, comments, etc)
+*
+*/
export const appendItemArray = (id, property, value, add_to_front, item_type) => {
return {
type: APPEND_ITEM_ARRAY,
@@ -99,8 +95,8 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) =>
*/
export function getStream (assetUrl) {
return (dispatch) => {
- return fetch(`/api/v1/stream?asset_url=${encodeURIComponent(assetUrl)}`)
- .then(responseHandler)
+ return fetch(`${base}/stream?asset_url=${encodeURIComponent(assetUrl)}`)
+ .then(handleResp)
.then((json) => {
/* Add items to the store */
@@ -110,6 +106,8 @@ export function getStream (assetUrl) {
action.id = `${action.action_type}_${action.item_id}`;
dispatch(addItem(action, 'actions'));
});
+ } else if (type === 'settings') {
+ dispatch({type: UPDATE_SETTINGS, config: fromJS(json[type])});
} else {
json[type].forEach(item => {
dispatch(addItem(item, type));
@@ -168,8 +166,8 @@ export function getStream (assetUrl) {
export function getItemsArray (ids) {
return (dispatch) => {
- return fetch(`/v1/item/${ids}`, getInit('GET'))
- .then(responseHandler)
+ return fetch(`${base}/item/${ids}`, getInit('GET'))
+ .then(handleResp)
.then((json) => {
for (let i = 0; i < json.items.length; i++) {
dispatch(addItem(json.items[i]));
@@ -198,8 +196,8 @@ export function postItem (item, type, id) {
if (id) {
item.id = id;
}
- return fetch(`/api/v1/${type}`, getInit('POST', item))
- .then(responseHandler)
+ return fetch(`${base}/${type}`, getInit('POST', item))
+ .then(handleResp)
.then((json) => {
dispatch(addItem({...item, id:json.id}, type));
return json.id;
@@ -229,8 +227,8 @@ export function postAction (item_id, action_type, user_id, item_type) {
user_id
};
- return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('POST', action))
- .then(responseHandler);
+ return fetch(`${base}/${item_type}/${item_id}/actions`, getInit('POST', action))
+ .then(handleResp);
};
}
@@ -251,7 +249,7 @@ export function postAction (item_id, action_type, user_id, item_type) {
export function deleteAction (action_id) {
return () => {
- return fetch(`/api/v1/actions/${action_id}`, {method: 'DELETE'})
- .then(responseHandler);
+ return fetch(`${base}/actions/${action_id}`, {method: 'DELETE'})
+ .then(handleResp);
};
}
diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/response.js
index bccfc5a04..83f51e3ec 100644
--- a/client/coral-framework/helpers/response.js
+++ b/client/coral-framework/helpers/response.js
@@ -3,10 +3,10 @@ export const base = '/api/v1';
export const getInit = (method, body) => {
let init = {
method,
- headers: new Headers({
+ headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
- }),
+ },
credentials: 'same-origin'
};
diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js
index c11510cf4..7b721badc 100644
--- a/client/coral-framework/index.js
+++ b/client/coral-framework/index.js
@@ -1,6 +1,5 @@
import Notification from './modules/notification/Notification';
import store from './store';
-import {fetchConfig} from './actions/config';
import * as itemActions from './actions/items';
import I18n from './modules/i18n/i18n';
import * as notificationActions from './actions/notification';
@@ -9,7 +8,6 @@ import * as authActions from './actions/auth';
export {
Notification,
store,
- fetchConfig,
itemActions,
I18n,
notificationActions,
diff --git a/client/coral-framework/reducers/config.js b/client/coral-framework/reducers/config.js
index cbc131fe6..6521d92a3 100644
--- a/client/coral-framework/reducers/config.js
+++ b/client/coral-framework/reducers/config.js
@@ -1,7 +1,7 @@
/* @flow */
import {Map} from 'immutable';
-import * as actions from '../actions/config';
+import * as actions from '../actions/items';
const initialState = Map({
features: Map({})
@@ -9,15 +9,10 @@ const initialState = Map({
export default (state = initialState, action) => {
switch(action.type) {
- case actions.FETCH_CONFIG_REQUEST:
- return state.set('loading', true);
- case actions.FETCH_CONFIG_FAILED:
- return state.set('loading', false);
-
- // Override config if worked
- case actions.FETCH_CONFIG_SUCCESS:
- return action.config.set('loading', false);
+ // Override config if worked
+ case actions.UPDATE_SETTINGS:
+ return action.config;
default:
return state;
diff --git a/kue.js b/kue.js
new file mode 100644
index 000000000..e2229d424
--- /dev/null
+++ b/kue.js
@@ -0,0 +1,11 @@
+const kue = require('kue');
+const redis = require('./redis');
+
+module.exports = {
+ queue: kue.createQueue({
+ redis: {
+ createClientFactory: () => redis.createClient()
+ }
+ }),
+ kue
+};
diff --git a/redis.js b/redis.js
index c37fcc64e..9f67c34bb 100644
--- a/redis.js
+++ b/redis.js
@@ -2,38 +2,42 @@ const redis = require('redis');
const debug = require('debug')('talk:redis');
const url = process.env.TALK_REDIS_URL || 'redis://localhost';
-const client = redis.createClient(url, {
- retry_strategy: function(options) {
- if (options.error && options.error.code === 'ECONNREFUSED') {
+module.exports = {
+ createClient() {
+ let client = redis.createClient(url, {
+ retry_strategy: function(options) {
+ if (options.error && options.error.code === 'ECONNREFUSED') {
- // End reconnecting on a specific error and flush all commands with a individual error
- return new Error('The server refused the connection');
- }
- if (options.total_retry_time > 1000 * 60 * 60) {
+ // End reconnecting on a specific error and flush all commands with a individual error
+ return new Error('The server refused the connection');
+ }
+ if (options.total_retry_time > 1000 * 60 * 60) {
- // End reconnecting after a specific timeout and flush all commands with a individual error
- return new Error('Retry time exhausted');
- }
+ // End reconnecting after a specific timeout and flush all commands with a individual error
+ return new Error('Retry time exhausted');
+ }
- if (options.times_connected > 10) {
+ if (options.times_connected > 10) {
- // End reconnecting with built in error
- return undefined;
- }
+ // End reconnecting with built in error
+ return undefined;
+ }
- // reconnect after
- return Math.max(options.attempt * 100, 3000);
+ // reconnect after
+ return Math.max(options.attempt * 100, 3000);
+ }
+ });
+
+ client.ping((err) => {
+ if (err) {
+ console.error('Can\'t ping the redis server!');
+
+ throw err;
+ }
+
+ debug('connection established');
+ });
+
+ return client;
}
-});
-
-client.ping((err) => {
- if (err) {
- console.error('Can\'t ping the redis server!');
-
- throw err;
- }
-
- debug('connection established');
-});
-
-module.exports = client;
+};
diff --git a/routes/api/index.js b/routes/api/index.js
index 8da3f791b..9b4f0432b 100644
--- a/routes/api/index.js
+++ b/routes/api/index.js
@@ -15,6 +15,6 @@ router.use('/stream', require('./stream'));
router.use('/user', require('./user'));
// Bind the kue handler to the /kue path.
-router.use('/kue', authorization.needed('admin'), require('kue').app);
+router.use('/kue', authorization.needed('admin'), require('../../kue').kue.app);
module.exports = router;
diff --git a/routes/api/settings/index.js b/routes/api/settings/index.js
index 6e8c64d4f..910524fb2 100644
--- a/routes/api/settings/index.js
+++ b/routes/api/settings/index.js
@@ -1,6 +1,5 @@
const express = require('express');
const Setting = require('../../../models/setting');
-const _ = require('lodash');
const router = express.Router();
@@ -8,8 +7,7 @@ router.get('/', (req, res, next) => {
Setting
.getSettings()
.then(settings => {
- const whitelist = ['moderation'];
- res.json(_.pick(settings, whitelist));
+ res.json(settings);
})
.catch(next);
});
diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js
index 0ef97aad2..7b793cdb6 100644
--- a/routes/api/stream/index.js
+++ b/routes/api/stream/index.js
@@ -14,7 +14,6 @@ router.get('/', (req, res, next) => {
// Get the asset_id for this url (or create it if it doesn't exist)
Promise.all([
-
// Find or create the asset by url.
Asset.findOrCreateByUrl(decodeURIComponent(req.query.asset_url))
@@ -30,29 +29,18 @@ router.get('/', (req, res, next) => {
// Get the moderation setting from the settings.
Setting.getModerationSetting()
])
- .then(([asset, {moderation}]) => {
+ .then(([asset, settings]) => {
+ // Get the sitewide moderation setting and return the appropriate comments
let comments;
-
- if (moderation === 'post') {
+ if (settings.moderation === 'pre') {
comments = Comment.findAcceptedByAssetId(asset.id);
} else {
-
- // Defaults to 'pre' moderation.
comments = Comment.findAcceptedAndNewByAssetId(asset.id);
}
- return Promise.all([
-
- // This is the promised component... Fetch the comments based on the
- // moderation settings.
- comments,
-
- // Send back the reference to the asset.
- asset
- ]);
+ return Promise.all([comments, asset, settings]);
})
- // Get all the users and actions for those comments.
- .then(([comments, asset]) => {
+ .then(([comments, asset, settings]) => {
// Get the user id's from the author id's as a unique array that gets
// sorted.
@@ -82,21 +70,23 @@ router.get('/', (req, res, next) => {
// It's comments...
comments,
- // All the users/authors of those comments...
+ // The users who wrote those comments
users,
- // And all actions about the asset, comments, and users.
- actions
+ // The actions on the above items
+ actions,
+
+ // And the relevant settings
+ settings
]);
})
- .then(([asset, comments, users, actions]) => {
-
- // Send back the payload containing all this data.
+ .then(([asset, comments, users, actions, settings]) => {
res.json({
assets: [asset],
comments,
users,
- actions
+ actions,
+ settings
});
})
.catch(error => {
diff --git a/services/scraper.js b/services/scraper.js
index 665d4fc66..922ef77bc 100644
--- a/services/scraper.js
+++ b/services/scraper.js
@@ -1,5 +1,4 @@
-const kue = require('kue');
-const queue = kue.createQueue();
+const kue = require('../kue');
const debug = require('debug')('talk:services:scraper');
const Asset = require('../models/asset');
const JOB_NAME = 'scraper';
@@ -19,7 +18,7 @@ const scraper = {
return new Promise((resolve, reject) => {
debug(`Creating job for Asset[${asset.id}]`);
- let job = queue
+ let job = kue.queue
.create(JOB_NAME, {
title: `Scrape for asset ${asset.id}`,
asset_id: asset.id
@@ -72,7 +71,7 @@ const scraper = {
debug(`Now processing ${JOB_NAME} jobs`);
// Process jobs with the processJob function.
- queue.process(JOB_NAME, (job, done) => {
+ kue.queue.process(JOB_NAME, (job, done) => {
debug(`Starting on Job[${job.id}] for Asset[${job.data.asset_id}]`);
@@ -123,7 +122,7 @@ const scraper = {
// Shutdown and give the queue 5 seconds to shutdown before we start
// killing jobs.
- queue.shutdown(5000, (err) => {
+ kue.queue.shutdown(5000, (err) => {
if (err) {
return reject(err);
}
diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js
index 340ec9549..45c41025f 100644
--- a/tests/client/coral-framework/store/itemActions.spec.js
+++ b/tests/client/coral-framework/store/itemActions.spec.js
@@ -127,6 +127,7 @@ describe('itemActions', () => {
'Accept': 'application/json',
'Content-Type':'application/json'
},
+ credentials: 'same-origin',
body: JSON.stringify(item.data)
}
);
diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js
index 7d05c9708..2e419a8e1 100644
--- a/tests/routes/api/stream/index.js
+++ b/tests/routes/api/stream/index.js
@@ -89,10 +89,11 @@ describe('api/stream: routes', () => {
.query({'asset_url': 'http://test.com'})
.then(res => {
expect(res).to.have.status(200);
- expect(res.body.assets.length).to.equal(1);
- expect(res.body.comments.length).to.equal(2);
- expect(res.body.users.length).to.equal(2);
- expect(res.body.actions.length).to.equal(1);
+ expect(res.body.assets[0]).to.have.property('url');
+ expect(res.body.comments[0]).to.have.property('body');
+ expect(res.body.users[0]).to.have.property('displayName');
+ expect(res.body.actions[0]).to.have.property('action_type');
+ expect(res.body.settings).to.have.property('moderation');
});
});
});