- );
- }
-}
diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js
index 0d4cf58d6..229c4e948 100644
--- a/client/coral-admin/src/containers/Configure.js
+++ b/client/coral-admin/src/containers/Configure.js
@@ -80,8 +80,7 @@ class Configure extends React.Component {
}
getEmbed () {
- const embedText =
- ``;
+ const embedText = ``;
return
diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js
index 50f54a631..921af251d 100644
--- a/client/coral-admin/src/containers/LayoutContainer.js
+++ b/client/coral-admin/src/containers/LayoutContainer.js
@@ -1,18 +1,19 @@
-import React, {Component}from 'react';
+import React, {Component} from 'react';
import {connect} from 'react-redux';
import {Layout} from '../components/ui/Layout';
class LayoutContainer extends Component {
render () {
- return ;
+ return ;
}
}
LayoutContainer.propTypes = {};
-const mapStateToProps = () => ({data: {}});
+const mapStateToProps = () => ({});
const mapDispatchToProps = (dispatch) => ({dispatch});
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
+
diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js
index 6cd9f46fc..9178bda57 100644
--- a/client/coral-admin/src/containers/ModerationQueue.js
+++ b/client/coral-admin/src/containers/ModerationQueue.js
@@ -73,7 +73,12 @@ class ModerationQueue extends React.Component {
!comments.get('byId').get(id).get('status'))}
+ commentIds={
+ comments.get('ids')
+ .filter(id => !comments.get('byId')
+ .get(id)
+ .get('status'))
+ }
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
@@ -83,7 +88,15 @@ class ModerationQueue extends React.Component {
comments.get('byId').get(id).get('status') === 'rejected')}
+ commentIds={
+ comments
+ .get('ids')
+ .filter(id =>
+ comments
+ .get('byId')
+ .get(id)
+ .get('status') === 'rejected')
+ }
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
diff --git a/client/coral-admin/src/helpers/response.js b/client/coral-admin/src/helpers/response.js
new file mode 100644
index 000000000..439ba5a8a
--- /dev/null
+++ b/client/coral-admin/src/helpers/response.js
@@ -0,0 +1,27 @@
+export const base = '/api/v1';
+
+export const getInit = (method, body) => {
+ const headers = new Headers({
+ 'Content-Type': 'application/json',
+ 'Accept': 'application/json'
+ });
+
+ const init = {method, headers};
+ if (method.toLowerCase() !== 'get') {
+ init.body = JSON.stringify(body);
+ }
+
+ return init;
+};
+
+export const handleResp = res => {
+ if (res.status === 401) {
+ throw new Error('Not Authorized to make this request');
+ } else if (res.status > 399) {
+ throw new Error('Error! Status ', res.status);
+ } else if (res.status === 204) {
+ return res.text();
+ } else {
+ return res.json();
+ }
+};
diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js
index aed9d5748..84d1b84ad 100644
--- a/client/coral-admin/src/index.js
+++ b/client/coral-admin/src/index.js
@@ -1,4 +1,3 @@
-
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
diff --git a/client/coral-admin/src/reducers/community.js b/client/coral-admin/src/reducers/community.js
new file mode 100644
index 000000000..8d5dfd2c3
--- /dev/null
+++ b/client/coral-admin/src/reducers/community.js
@@ -0,0 +1,47 @@
+import {Map} from 'immutable';
+
+import {
+ FETCH_COMMENTERS_REQUEST,
+ FETCH_COMMENTERS_FAILURE,
+ FETCH_COMMENTERS_SUCCESS,
+ SORT_UPDATE
+} from '../constants/community';
+
+const initialState = Map({
+ community: Map(),
+ isFetching: false,
+ error: '',
+ commenters: [],
+ field: 'created_at',
+ asc: false,
+ totalPages: 0,
+ page: 0
+});
+
+export default function community (state = initialState, action) {
+ switch (action.type) {
+ case FETCH_COMMENTERS_REQUEST :
+ return state
+ .set('isFetching', true);
+ case FETCH_COMMENTERS_FAILURE :
+ return state
+ .set('isFetching', false)
+ .set('error', action.error);
+ case FETCH_COMMENTERS_SUCCESS : {
+ const {commenters, type, ...rest} = action; // eslint-disable-line
+ return state
+ .merge({
+ isFetching: false,
+ error: '',
+ ...rest
+ })
+ .set('commenters', commenters); // Sets to normal array
+ }
+ case SORT_UPDATE :
+ return state
+ .set('field', action.sort.field)
+ .set('asc', !state.get('asc'));
+ default :
+ return state;
+ }
+}
diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js
index 63176ca3b..1b29ced69 100644
--- a/client/coral-admin/src/reducers/index.js
+++ b/client/coral-admin/src/reducers/index.js
@@ -1,10 +1,12 @@
-
import {combineReducers} from 'redux';
import comments from 'reducers/comments';
import settings from 'reducers/settings';
+import community from 'reducers/community';
// Combine all reducers into a main one
export default combineReducers({
settings,
- comments
+ comments,
+ community
});
+
diff --git a/client/coral-admin/src/translations.js b/client/coral-admin/src/translations.js
index 5302c1221..67a8142fd 100644
--- a/client/coral-admin/src/translations.js
+++ b/client/coral-admin/src/translations.js
@@ -1,5 +1,9 @@
export default {
en: {
+ 'community': {
+ username_and_email: 'Username and Email',
+ account_creation_date: 'Account Creation Date'
+ },
'modqueue': {
'pending': 'pending',
'rejected': 'rejected',
@@ -24,6 +28,10 @@ export default {
}
},
es: {
+ 'community': {
+ username_and_email: 'Usuario y E-mail',
+ account_creation_date: 'Fecha de creación de la cuenta'
+ },
'modqueue': {
'pending': 'pendiente',
'rejected': 'rechazado',
diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js
index 5b44de359..07f8e54af 100644
--- a/client/coral-framework/store/actions/items.js
+++ b/client/coral-framework/store/actions/items.js
@@ -241,7 +241,8 @@ export function postAction (item_id, action_type, user_id, item_type) {
return response.ok ? response.json()
: Promise.reject(`${response.status} ${response.statusText}`);
}
- ).then((json)=>{
+ )
+ .then((json)=>{
return json;
});
};
diff --git a/init.js b/init.js
new file mode 100644
index 000000000..9c3c2bda5
--- /dev/null
+++ b/init.js
@@ -0,0 +1,6 @@
+const Setting = require('./models/setting');
+
+const defaults = {id: '1', moderation: 'pre'};
+module.exports = Setting.init(defaults);
+
+// presumably this file will grow, which is why I've broken it out.
diff --git a/models/comment.js b/models/comment.js
index 3f99bcb6a..8eab6aff4 100644
--- a/models/comment.js
+++ b/models/comment.js
@@ -86,9 +86,13 @@ CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
* @param {String} action_type the type of action that was performed on the comment
*/
CommentSchema.statics.findByActionType = function(action_type) {
- return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
- return Comment.find({'id': {'$in': actions.map(function(a){return a.item_id;})}});
- });
+ return Action
+ .findCommentsIdByActionType(action_type, 'comment')
+ .then((actions) => {
+ return Comment.find({'id': {'$in': actions.map(function(a){
+ return a.item_id;})}
+ });
+ });
};
/**
@@ -97,9 +101,20 @@ CommentSchema.statics.findByActionType = function(action_type) {
* @param {String} status the status of the comment to search for
*/
CommentSchema.statics.findByStatusByActionType = function(status, action_type) {
- return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
- return Comment.find({'status': status, 'id': {'$in': actions.map(function(a){return a.item_id;})}});
- });
+ return Action
+ .findCommentsIdByActionType(action_type, 'comment')
+ .then((actions) => {
+
+ return Comment.find({
+ 'status': status,
+ 'id': {
+ '$in': actions.map(a => {
+ return a.item_id;
+ })
+ }
+ });
+
+ });
};
/**
diff --git a/models/setting.js b/models/setting.js
index 15c457307..09b13e099 100644
--- a/models/setting.js
+++ b/models/setting.js
@@ -12,7 +12,15 @@ const SettingSchema = new Schema({
});
/**
- * gets the entire settings record and sends it back
+ * this is run once when the app starts to ensure settings are populated
+ * @return {Promise} null initialize the global settings object
+ */
+SettingSchema.statics.init = function (defaults) {
+ return this.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
+};
+
+/**
+ * Gets the entire settings record and sends it back
* @return {Promise} settings the whole settings record
*/
SettingSchema.statics.getSettings = function () {
@@ -20,7 +28,7 @@ SettingSchema.statics.getSettings = function () {
};
/**
- * gets the moderation settings and sends it back
+ * Gets the moderation settings and sends it back
* @return {Promise} moderation the settings for how to moderate comments
*/
SettingSchema.statics.getModerationSetting = function () {
@@ -28,12 +36,13 @@ SettingSchema.statics.getModerationSetting = function () {
};
/**
- * this will update the settings object with whatever you pass in
+ * This will update the settings object with whatever you pass in
* @param {object} setting a hash of whatever settings you want to update
* @return {Promise} settings Promise that resolves to the entire (updated) settings object.
*/
SettingSchema.statics.updateSettings = function (setting) {
- // there should only ever be one record unless something has gone wrong.
+ // There should only ever be one record unless something has gone wrong.
+ // In the future we may have multiple records for custom settings for objects/users.
return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true});
};
diff --git a/models/user.js b/models/user.js
index 6800b59ef..c0a846f1a 100644
--- a/models/user.js
+++ b/models/user.js
@@ -25,6 +25,11 @@ const UserSchema = new mongoose.Schema({
}
}],
roles: [String]
+}, {
+ timestamps: {
+ createdAt: 'created_at',
+ updatedAt: 'updated_at'
+ }
});
// Add the indixies on the user profile data.
@@ -52,6 +57,22 @@ UserSchema.options.toJSON.transform = (doc, ret, options) => {
return ret;
};
+/**
+ * toObject overrides to remove the password field from the toObject
+ * output.
+ */
+UserSchema.options.toObject = {};
+UserSchema.options.toObject.hide = 'password';
+UserSchema.options.toObject.transform = (doc, ret, options) => {
+ if (options.hide) {
+ options.hide.split(' ').forEach((prop) => {
+ delete ret[prop];
+ });
+ }
+
+ return ret;
+};
+
/**
* Finds a user given their email address that we have for them in the system
* and ensures that the retuned user matches the password passed in as well.
@@ -100,19 +121,22 @@ UserSchema.statics.findLocalUser = function(email, password) {
UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
let srcUser, dstUser;
- return Promise.all([
- User.findOne({id: dstUserID}).exec(),
- User.findOne({id: srcUserID}).exec()
- ]).then((users) => {
- dstUser = users[0];
- srcUser = users[1];
+ return Promise
+ .all([
+ User.findOne({id: dstUserID}).exec(),
+ User.findOne({id: srcUserID}).exec()
+ ])
+ .then((users) => {
+ dstUser = users[0];
+ srcUser = users[1];
- srcUser.profiles.forEach((profile) => {
- dstUser.profiles.push(profile);
- });
+ srcUser.profiles.forEach((profile) => {
+ dstUser.profiles.push(profile);
+ });
- return srcUser.remove();
- }).then(() => dstUser.save());
+ return srcUser.remove();
+ })
+ .then(() => dstUser.save());
};
/**
@@ -122,33 +146,34 @@ UserSchema.statics.mergeUsers = function(dstUserID, srcUserID) {
* @param {Function} done [description]
*/
UserSchema.statics.findOrCreateExternalUser = function(profile) {
- return User.findOne({
- profiles: {
- $elemMatch: {
- id: profile.id,
- provider: profile.provider
- }
- }
- })
- .then((user) => {
- if (user) {
- return user;
- }
-
- // The user was not found, lets create them!
- user = new User({
- displayName: profile.displayName,
- roles: [],
- profiles: [
- {
+ return User
+ .findOne({
+ profiles: {
+ $elemMatch: {
id: profile.id,
provider: profile.provider
}
- ]
- });
+ }
+ })
+ .then((user) => {
+ if (user) {
+ return user;
+ }
- return user.save();
- });
+ // The user was not found, lets create them!
+ user = new User({
+ displayName: profile.displayName,
+ roles: [],
+ profiles: [
+ {
+ id: profile.id,
+ provider: profile.provider
+ }
+ ]
+ });
+
+ return user.save();
+ });
};
UserSchema.statics.changePassword = function(id, password) {
diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js
index a2e77b417..5126bf93c 100644
--- a/routes/api/comments/index.js
+++ b/routes/api/comments/index.js
@@ -12,17 +12,17 @@ const router = express.Router();
router.get('/', (req, res, next) => {
Comment.find({}).then((comments) => {
res.status(200).json(comments);
- }).catch(error => {
- next(error);
- });
+ })
+ .catch(next);
});
router.get('/:comment_id', (req, res, next) => {
- Comment.findById(req.params.comment_id).then((comment) => {
- res.status(200).json(comment);
- }).catch(error => {
- next(error);
- });
+ Comment
+ .findById(req.params.comment_id)
+ .then(comment => {
+ res.status(200).json(comment);
+ })
+ .catch(next);
});
//==============================================================================
@@ -31,20 +31,22 @@ router.get('/:comment_id', (req, res, next) => {
// Get all the comments that have that action_type over them.
router.get('/action/:action_type', (req, res, next) => {
- Comment.findByActionType(req.params.action_type).then((comments) => {
- res.status(200).json(comments);
- }).catch(error => {
- next(error);
- });
+ Comment
+ .findByActionType(req.params.action_type)
+ .then((comments) => {
+ res.status(200).json(comments);
+ })
+ .catch(next);
});
// Get all the comments that were rejected.
router.get('/status/rejected', (req, res, next) => {
- Comment.findByStatus('rejected').then((comments) => {
- res.status(200).json(comments);
- }).catch(error => {
- next(error);
- });
+ Comment
+ .findByStatus('rejected')
+ .then(comments => {
+ res.status(200).json(comments);
+ })
+ .catch(next);
});
// Returns back all the comments that are in the moderation queue. The moderation queue is pre or post moderated,
@@ -52,17 +54,21 @@ router.get('/status/rejected', (req, res, next) => {
// Pre-moderation: New comments are shown in the moderator queues immediately.
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
router.get('/status/pending', (req, res, next) => {
- Setting.getModerationSetting().then(function({moderation}){
- let moderationValue = req.query.moderation;
- if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
- moderationValue = moderation;
- }
- Comment.moderationQueue(moderationValue).then((comments) => {
- res.status(200).json(comments);
- });
- }).catch(error => {
- next(error);
- });
+
+ Setting
+ .getModerationSetting()
+ .then(({moderation}) => {
+ let moderationValue = req.query.moderation;
+ if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
+ moderationValue = moderation;
+ }
+ Comment
+ .moderationQueue(moderationValue)
+ .then((comments) => {
+ res.status(200).json(comments);
+ });
+ })
+ .catch(next);
});
//==============================================================================
@@ -70,27 +76,36 @@ router.get('/status/pending', (req, res, next) => {
//==============================================================================
router.post('/', (req, res, next) => {
+
const {body, author_id, asset_id, parent_id, status, username} = req.body;
- Comment.new(body, author_id, asset_id, parent_id, status, username).then((comment) => {
- res.status(200).send({'id': comment.id});
- }).catch(error => {
- next(error);
- });
+
+ Comment
+ .new(body, author_id, asset_id, parent_id, status, username)
+ .then((comment) => {
+ res.status(200).send({'id': comment.id});
+ })
+ .catch(error => {
+ next(error);
+ });
});
router.post('/:comment_id', (req, res, next) => {
- Comment.findById(req.params.comment_id).then((comment) => {
- comment.body = req.body.body;
- comment.author_id = req.body.author_id;
- comment.asset_id = req.body.asset_id;
- comment.parent_id = req.body.parent_id;
- comment.status = req.body.status;
- return comment.save();
- }).then((comment) => {
- res.status(200).send(comment);
- }).catch(error => {
- next(error);
- });
+ Comment
+ .findById(req.params.comment_id)
+ .then((comment) => {
+ comment.body = req.body.body;
+ comment.author_id = req.body.author_id;
+ comment.asset_id = req.body.asset_id;
+ comment.parent_id = req.body.parent_id;
+ comment.status = req.body.status;
+ return comment.save();
+ })
+ .then((comment) => {
+ res.status(200).send(comment);
+ })
+ .catch(error => {
+ next(error);
+ });
});
router.post('/:comment_id/status', (req, res, next) => {
@@ -103,11 +118,14 @@ router.post('/:comment_id/status', (req, res, next) => {
});
router.post('/:comment_id/actions', (req, res, next) => {
- Comment.addAction(req.params.comment_id, req.body.user_id, req.body.action_type).then((action) => {
- res.status(200).send(action);
- }).catch(error => {
- next(error);
- });
+ Comment
+ .addAction(req.params.comment_id, req.body.user_id, req.body.action_type)
+ .then((action) => {
+ res.status(200).send(action);
+ })
+ .catch(error => {
+ next(error);
+ });
});
//==============================================================================
@@ -115,11 +133,14 @@ router.post('/:comment_id/actions', (req, res, next) => {
//==============================================================================
router.delete('/:comment_id', (req, res, next) => {
- Comment.removeById(req.params.comment_id).then(() => {
- res.status(201).send('OK. Removed');
- }).catch(error => {
- next(error);
- });
+ Comment
+ .removeById(req.params.comment_id)
+ .then(() => {
+ res.status(201).send('OK. Removed');
+ })
+ .catch(error => {
+ next(error);
+ });
});
module.exports = router;
diff --git a/routes/api/index.js b/routes/api/index.js
index d161cf622..2fb489b88 100644
--- a/routes/api/index.js
+++ b/routes/api/index.js
@@ -6,5 +6,6 @@ router.use('/asset', require('./asset'));
router.use('/comments', require('./comments'));
router.use('/settings', require('./settings'));
router.use('/stream', require('./stream'));
+router.use('/user', require('./user'));
module.exports = router;
diff --git a/routes/api/settings/index.js b/routes/api/settings/index.js
index 53bf53f3c..585bf083a 100644
--- a/routes/api/settings/index.js
+++ b/routes/api/settings/index.js
@@ -3,11 +3,17 @@ const router = express.Router();
const Setting = require('../../../models/setting');
router.get('/', (req, res, next) => {
- Setting.getSettings().then(settings => res.json(settings)).catch(next);
+ Setting
+ .getSettings()
+ .then(settings => res.json(settings))
+ .catch(next);
});
router.put('/', (req, res, next) => {
- Setting.updateSettings(req.body).then(() => res.status(204).end()).catch(next);
+ Setting
+ .updateSettings(req.body)
+ .then(() => res.status(204).end())
+ .catch(next);
});
module.exports = router;
diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js
index 1e3e39d81..3f49d48fc 100644
--- a/routes/api/stream/index.js
+++ b/routes/api/stream/index.js
@@ -36,7 +36,8 @@ router.get('/', (req, res, next) => {
users,
actions
});
- }).catch(error => {
+ })
+ .catch(error => {
next(error);
});
});
diff --git a/routes/api/user/index.js b/routes/api/user/index.js
new file mode 100644
index 000000000..18872eab7
--- /dev/null
+++ b/routes/api/user/index.js
@@ -0,0 +1,63 @@
+const express = require('express');
+const router = express.Router();
+const User = require('../../../models/user');
+
+router.get('/', (req, res, next) => {
+ const {
+ value = '',
+ field = 'created_at',
+ page = 1,
+ asc = 'false',
+ limit = 50 // Total Per Page
+ } = req.query;
+
+ let q = {
+ $or: [
+ {
+ 'displayName': {
+ $regex: new RegExp(`^${value}`),
+ $options: 'i'
+ },
+ 'profiles': {
+ $elemMatch: {
+ id: {
+ $regex: new RegExp(`^${value}`),
+ $options: 'i'
+ },
+ provider: 'local'
+ }
+ }
+ }
+ ]
+ };
+
+ Promise.all([
+ User.find(q)
+ .sort({[field]: (asc === 'true') ? 1 : -1})
+ .skip((page - 1) * limit)
+ .limit(limit),
+ User.count()
+ ])
+ .then(([data, count]) => {
+ const users = data.map((user) => {
+ const {displayName, created_at} = user;
+ return {
+ displayName,
+ created_at,
+ profiles: user.toObject().profiles
+ };
+ });
+
+ res.json({
+ result: users,
+ limit: Number(limit),
+ count,
+ page: Number(page),
+ totalPages: Math.ceil(count / limit)
+ });
+
+ })
+ .catch(next);
+});
+
+module.exports = router;
diff --git a/routes/embed/index.js b/routes/embed/index.js
new file mode 100644
index 000000000..519de9d2f
--- /dev/null
+++ b/routes/embed/index.js
@@ -0,0 +1,14 @@
+const express = require('express');
+const router = express.Router();
+
+router.use('/:embed', (req, res, next) => {
+ switch (req.params.embed) {
+ case 'stream':
+ return res.render('embed/stream', {});
+ default:
+ // will return a 404.
+ return next();
+ }
+});
+
+module.exports = router;
diff --git a/routes/index.js b/routes/index.js
new file mode 100644
index 000000000..cde5b07c7
--- /dev/null
+++ b/routes/index.js
@@ -0,0 +1,12 @@
+const express = require('express');
+const router = express.Router();
+
+router.use('/api/v1', require('./api'));
+router.use('/admin', require('./admin'));
+router.use('/embed', require('./embed'));
+
+router.get('/', (req, res) => {
+ return res.render('home', {});
+});
+
+module.exports = router;
diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js
index 5bcf9de28..41c37828e 100644
--- a/tests/routes/api/settings/index.js
+++ b/tests/routes/api/settings/index.js
@@ -43,11 +43,13 @@ describe('update settings', () => {
return Setting.getSettings();
- }).then(settings => {
+ })
+ .then(settings => {
// confirm updated settings in db
expect(settings).to.have.property('moderation');
expect(settings.moderation).to.equal('post');
- }).catch(err => {
+ })
+ .catch(err => {
throw err;
});
});
diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs
new file mode 100644
index 000000000..dd7142c3b
--- /dev/null
+++ b/views/embed/stream.ejs
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/views/home.ejs b/views/home.ejs
new file mode 100644
index 000000000..5654e1745
--- /dev/null
+++ b/views/home.ejs
@@ -0,0 +1,16 @@
+
+
+
+
+
+
Corem ipsal
+
Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.