From eb00e63a4180e683f73686546ed0027a1f382d6b Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 8 Nov 2016 15:21:52 -0500 Subject: [PATCH 01/16] Adding action summaries function to actions model. --- models/action.js | 32 +++++++++++++++++++++++++++++++- tests/models/action.js | 24 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/models/action.js b/models/action.js index 27c63747a..043699988 100644 --- a/models/action.js +++ b/models/action.js @@ -28,7 +28,7 @@ ActionSchema.statics.findById = function(id) { }; /** - * Finds users in an array of ids. + * Finds actions in an array of ids. * @param {String} ids array of user identifiers (uuid) */ ActionSchema.statics.findByItemIdArray = function(item_ids) { @@ -37,6 +37,36 @@ ActionSchema.statics.findByItemIdArray = function(item_ids) { }); }; +/** + * Returns summaries of actions for an array of ids + * @param {String} ids array of user identifiers (uuid) +*/ +ActionSchema.statics.getActionSummaries = function(item_ids) { + return ActionSchema.statics.findByItemIdArray(item_ids).then((rawActions) => { + // Create an object with a count of each action type for each item + const actionSummaries = rawActions.reduce((actionObj, action) => { + if (!actionObj[action.item_id]) { + actionObj[action.item_id] = { + type: action.action_type, + count: 1, + current_user: false //Corrent this later when we have authentication + }; + } else { + actionObj[action.item_id].count ++; + } + return actionObj; + }, {}); + + // Return an array extracted from the actionSummaries object + return Object.keys(actionSummaries).reduce((actions, key) => { + let actionSummary = actionSummaries[key]; + actionSummary.item_id = key; + actions.push(actionSummary); + return actions; + }, []); + }); +}; + const Action = mongoose.model('Action', ActionSchema); module.exports = Action; diff --git a/tests/models/action.js b/tests/models/action.js index 34a9f2dca..95d8dc29b 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -15,6 +15,9 @@ describe('Action: models', () => { }, { action_type: 'flag', item_id: '456' + }, { + action_type: 'flag', + item_id: '123' }]).then((actions) => { mockActions = actions; }); @@ -32,7 +35,28 @@ describe('Action: models', () => { describe('#findByItemIdArray()', () => { it('should find an array of actions from an array of item_ids', () => { return Action.findByItemIdArray(['123', '456']).then((result) => { + expect(result).to.have.length(3); + }); + }); + }); + + describe('#getActionSummaries()', () => { + it('should return properly formatted summaries from an array of item_ids', () => { + return Action.getActionSummaries(['123', '789']).then((result) => { expect(result).to.have.length(2); + const sorted = result.sort((a, b) => a.count - b.count); + expect(sorted[0]).to.deep.equal({ + type: 'like', + count: 1, + item_id: '789', + current_user: false + }); + expect(sorted[1]).to.deep.equal({ + type: 'flag', + count: 2, + item_id: '123', + current_user: false + }); }); }); }); From d3b8184ff43f376197deb8c9fc49b3f64ca8d93d Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 8 Nov 2016 15:22:38 -0500 Subject: [PATCH 02/16] Removing randomness from comments api tests. --- tests/routes/api/comments/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 4fbe52e94..b31fb522a 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -147,10 +147,11 @@ describe('Get /:comment_id', () => { .get('/api/v1/comments') .query({'comment_id': 'abc'}) .end(function(err, res){ + const sorted = res.body.sort((a, b) => a.body - b.body); expect(err).to.be.null; expect(res).to.have.status(200); - expect(res.body[0]).to.have.property('body'); - expect(res.body[0].body).to.equal('comment 10'); + expect(sorted[0]).to.have.property('body') + .and.to.equal('comment 10'); done(); }); }); From fcd65c18fd323a561a3a1044b1dfeeef12f24f9f Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 8 Nov 2016 15:23:04 -0500 Subject: [PATCH 03/16] Switching stream to use action summaries function. --- routes/api/stream/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 67216a252..8a51fc75d 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -14,7 +14,7 @@ router.get('/', (req, res, next) => { return Promise.all([ comments, User.findByIdArray(comments.map((comment) => comment.author_id)), - Action.findByItemIdArray(comments.map((comment) => comment.id)) + Action.getActionSummaries(comments.map((comment) => comment.id)) ]); }).then(([comments, users, actions]) => { res.json([...comments, ...users, ...actions]); From e8584b499b2f7281509bdf0d21b4daa12e4dd198 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 8 Nov 2016 15:32:52 -0500 Subject: [PATCH 04/16] Returning actions, items, and comments as seperate arrays. --- models/action.js | 3 ++- routes/api/stream/index.js | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/models/action.js b/models/action.js index 043699988..cc310c5a8 100644 --- a/models/action.js +++ b/models/action.js @@ -47,9 +47,10 @@ ActionSchema.statics.getActionSummaries = function(item_ids) { const actionSummaries = rawActions.reduce((actionObj, action) => { if (!actionObj[action.item_id]) { actionObj[action.item_id] = { + id: action.id, type: action.action_type, count: 1, - current_user: false //Corrent this later when we have authentication + current_user: false //Update this later when we have authentication }; } else { actionObj[action.item_id].count ++; diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index 8a51fc75d..8908b27eb 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -17,7 +17,11 @@ router.get('/', (req, res, next) => { Action.getActionSummaries(comments.map((comment) => comment.id)) ]); }).then(([comments, users, actions]) => { - res.json([...comments, ...users, ...actions]); + res.json({ + comments, + users, + actions + }); }).catch(error => { next(error); }); From d7ead01180bf70887734a36dda621d0718dbd356 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 8 Nov 2016 17:53:38 -0500 Subject: [PATCH 05/16] Expecting stream endpoint to return items broken out by type. --- client/coral-framework/store/actions/items.js | 13 ++++++++++--- tests/models/action.js | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 8b30e2548..956b70350 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -89,12 +89,19 @@ export function getStream (assetId) { ) .then((json) => { + /* Add items to the store */ + const itemTypes = Object.keys(json); + for (let i=0; i < itemTypes.length; i++ ) { + for (var j=0; j < json[itemTypes[i]].length; j++ ) { + dispatch(addItem(json[itemTypes[i]][j])); + } + } + /* Sort comments by date*/ let rootComments = [] let childComments = {} - json.sort((a,b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) - json.reduce((prev, item) => { - dispatch(addItem(item)) + json.comments.sort((a,b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) + json.comments.reduce((prev, item) => { /* Check for root and child comments. */ if ( diff --git a/tests/models/action.js b/tests/models/action.js index 95d8dc29b..e3ead7047 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -45,6 +45,8 @@ describe('Action: models', () => { return Action.getActionSummaries(['123', '789']).then((result) => { expect(result).to.have.length(2); const sorted = result.sort((a, b) => a.count - b.count); + delete sorted[0].id; + delete sorted[1].id; expect(sorted[0]).to.deep.equal({ type: 'like', count: 1, From 6616cf0c51df21ffc9e99f97ee7d67f9522e40c6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 9 Nov 2016 13:42:03 -0500 Subject: [PATCH 06/16] Uncommenting flags and hydrating actions. --- .../coral-embed-stream/src/CommentStream.js | 28 ++++++++----------- client/coral-framework/store/actions/items.js | 12 ++++++-- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index cda7ffd57..65e02e2ee 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -123,14 +123,12 @@ class CommentStream extends Component {
- { - // - } + @@ -153,14 +151,12 @@ class CommentStream extends Component {
- { - // - } + diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index 956b70350..c292426d1 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -121,9 +121,15 @@ export function getStream (assetId) { comments: rootComments })) - const keys = Object.keys(childComments) - for (var i=0; i < keys.length; i++ ) { - dispatch(updateItem(keys[i], 'children', childComments[keys[i]].reverse())) + const childKeys = Object.keys(childComments) + for (var i=0; i < childKeys.length; i++ ) { + dispatch(updateItem(childKeys[i], 'children', childComments[childKeys[i]].reverse())) + } + + /* Hydrate actions on comments */ + const actions = Object.keys(json.actions) + for (var i=0; i < actions.length; i++ ) { + dispatch(updateItem(actions[i].item_id, actions[i].type, actions[i].id)) } return (json) From 13aabb99744f0eaf33a6bc145864a800307f2e62 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 9 Nov 2016 13:54:44 -0500 Subject: [PATCH 07/16] Re-adding notification length to config. --- client/coral-embed-stream/src/CommentStream.js | 12 ++++++------ client/coral-framework/store/actions/config.js | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 65e02e2ee..0804530e8 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -151,12 +151,12 @@ class CommentStream extends Component {
- + diff --git a/client/coral-framework/store/actions/config.js b/client/coral-framework/store/actions/config.js index 243cb9049..c5368a04b 100644 --- a/client/coral-framework/store/actions/config.js +++ b/client/coral-framework/store/actions/config.js @@ -21,7 +21,9 @@ export const fetchConfig = () => async (dispatch) => { //TODO: Replace with fetching config from backend // const response = await fetch(`./talk.config.json`) // const json = await response.json() - dispatch({ type: FETCH_CONFIG_SUCCESS, config: fromJS({}) }) + dispatch({ type: FETCH_CONFIG_SUCCESS, config: fromJS({ + notifLength: 4500 + }) }) } catch (error) { dispatch({ type: FETCH_CONFIG_FAILED }) } From e0e80aa5cdf865820903cd6de4d60fd4832fc947 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 9 Nov 2016 14:10:01 -0500 Subject: [PATCH 08/16] Moving coral-framework tests over and updating mocha to transpile them. --- package.json | 6 ++++-- .../client/coral-framework}/store/authReducer.js | 4 ++-- .../client/coral-framework}/store/itemActions.spec.js | 2 +- .../client/coral-framework}/store/itemReducer.spec.js | 4 ++-- .../coral-framework}/store/notificationReducer.spec.js | 4 ++-- 5 files changed, 11 insertions(+), 9 deletions(-) rename {client/coral-framework/__tests__ => tests/client/coral-framework}/store/authReducer.js (82%) rename {client/coral-framework/__tests__ => tests/client/coral-framework}/store/itemActions.spec.js (98%) rename {client/coral-framework/__tests__ => tests/client/coral-framework}/store/itemReducer.spec.js (95%) rename {client/coral-framework/__tests__ => tests/client/coral-framework}/store/notificationReducer.spec.js (83%) diff --git a/package.json b/package.json index 979475b5b..94fb7d475 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "build-watch": "webpack --config webpack.config.dev.js --watch", "lint": "eslint .", "pretest": "npm install", - "test": "mocha tests --recursive", - "test-watch": "mocha tests --recursive -w", + "test": "mocha --compilers js:babel-core/register --recursive tests", + "test-watch": "mocha --compilers js:babel-core/register --recursive -w tests", "embed-start": "npm run build && ./bin/www" }, "config": { @@ -69,6 +69,7 @@ "css-loader": "^0.25.0", "eslint": "^3.9.1", "exports-loader": "^0.6.3", + "fetch-mock": "^5.5.0", "hammerjs": "^2.0.8", "immutable": "^3.8.1", "imports-loader": "^0.6.5", @@ -89,6 +90,7 @@ "react-redux": "^4.4.5", "react-router": "^3.0.0", "redux": "^3.6.0", + "redux-mock-store": "^1.2.1", "redux-thunk": "^2.1.0", "regenerator": "^0.8.46", "style-loader": "^0.13.1", diff --git a/client/coral-framework/__tests__/store/authReducer.js b/tests/client/coral-framework/store/authReducer.js similarity index 82% rename from client/coral-framework/__tests__/store/authReducer.js rename to tests/client/coral-framework/store/authReducer.js index dd9f58501..3b2148f7e 100644 --- a/client/coral-framework/__tests__/store/authReducer.js +++ b/tests/client/coral-framework/store/authReducer.js @@ -1,7 +1,7 @@ import { Map } from 'immutable' import {expect} from 'chai' -import authReducer from '../../store/reducers/auth' -import * as actions from '../../store/actions/auth' +import authReducer from '../../../../client/coral-framework/store/reducers/auth' +import * as actions from '../../../../client/coral-framework/store/actions/auth' describe ('authReducer', () => { describe('SET_LOGGED_IN_USER', () => { diff --git a/client/coral-framework/__tests__/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js similarity index 98% rename from client/coral-framework/__tests__/store/itemActions.spec.js rename to tests/client/coral-framework/store/itemActions.spec.js index 948aee32d..124174f36 100644 --- a/client/coral-framework/__tests__/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -2,7 +2,7 @@ import 'react' import 'redux' import {expect} from 'chai' import fetchMock from 'fetch-mock' -import * as actions from '../../store/actions/items' +import * as actions from '../../../../client/coral-framework/store/actions/items' import {Map} from 'immutable' import configureStore from 'redux-mock-store' diff --git a/client/coral-framework/__tests__/store/itemReducer.spec.js b/tests/client/coral-framework/store/itemReducer.spec.js similarity index 95% rename from client/coral-framework/__tests__/store/itemReducer.spec.js rename to tests/client/coral-framework/store/itemReducer.spec.js index 8a664ac8c..bd7b74b6c 100644 --- a/client/coral-framework/__tests__/store/itemReducer.spec.js +++ b/tests/client/coral-framework/store/itemReducer.spec.js @@ -1,7 +1,7 @@ import { Map, fromJS } from 'immutable' import {expect} from 'chai' -import itemsReducer from '../../store/reducers/items' -import * as actions from '../../store/actions/items' +import itemsReducer from '../../../../client/coral-framework/store/reducers/items' +import * as actions from '../../../../client/coral-framework/store/actions/items' describe ('itemsReducer', () => { describe('ADD_ITEM', () => { diff --git a/client/coral-framework/__tests__/store/notificationReducer.spec.js b/tests/client/coral-framework/store/notificationReducer.spec.js similarity index 83% rename from client/coral-framework/__tests__/store/notificationReducer.spec.js rename to tests/client/coral-framework/store/notificationReducer.spec.js index 4f37034a6..ee30b43bd 100644 --- a/client/coral-framework/__tests__/store/notificationReducer.spec.js +++ b/tests/client/coral-framework/store/notificationReducer.spec.js @@ -1,7 +1,7 @@ import { Map } from 'immutable' import {expect} from 'chai' -import notificationReducer from '../../store/reducers/notification' -import * as actions from '../../store/actions/notification' +import notificationReducer from '../../../../client/coral-framework/store/reducers/notification' +import * as actions from '../../../../client/coral-framework/store/actions/notification' describe ('notificationsReducer', () => { describe('ADD_NOTIFICATION', () => { From 0646645686a65959231cfcabbab7f6e8ab715a1c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 9 Nov 2016 12:51:32 -0700 Subject: [PATCH 09/16] Added new cli interface + users model --- bin/cli | 29 ++++ bin/cli-settings | 42 +++++ bin/cli-users | 408 +++++++++++++++++++++++++++++++++++++++++++++++ bin/init.js | 12 -- bin/www | 28 ++-- models/user.js | 277 +++++++++++++++++++++++++++++--- mongoose.js | 10 +- package.json | 5 +- 8 files changed, 758 insertions(+), 53 deletions(-) create mode 100755 bin/cli create mode 100755 bin/cli-settings create mode 100755 bin/cli-users delete mode 100644 bin/init.js diff --git a/bin/cli b/bin/cli new file mode 100755 index 000000000..bec5ea84f --- /dev/null +++ b/bin/cli @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +/** + * Setup the debug paramater. + */ + +process.env.DEBUG = process.env.TALK_DEBUG; + +/** + * Module dependencies. + */ + +const program = require('commander'); +const pkg = require('../package.json'); + +//============================================================================== +// Setting up the program command line arguments. +//============================================================================== + +program + .version(pkg.version) + .command('settings', 'work with the application settings') + .command('users', 'work with the application auth') + .parse(process.argv); + +// If there is no command listed, output help. +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/bin/cli-settings b/bin/cli-settings new file mode 100755 index 000000000..0920473da --- /dev/null +++ b/bin/cli-settings @@ -0,0 +1,42 @@ +#!/usr/bin/env node + +/** + * Setup the debug paramater. + */ + +process.env.DEBUG = process.env.TALK_DEBUG; + +/** + * Module dependencies. + */ + +const program = require('commander'); + +//============================================================================== +// Setting up the program command line arguments. +//============================================================================== + +program + .command('init') + .description('initilizes the talk settings') + .action(() => { + const mongoose = require('../mongoose'); + const Setting = require('../models/setting'); + const defaults = {id: '1', moderation: 'pre'}; + + Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}) + .then(() => { + console.log('Created settings object.'); + mongoose.disconnect(); + }).catch((err) => { + console.error(`failed to create the settings object ${JSON.stringify(err)}`); + throw new Error(err); // just to be safe + }); + }); + +program.parse(process.argv); + +// If there is no command listed, output help. +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/bin/cli-users b/bin/cli-users new file mode 100755 index 000000000..74aa902eb --- /dev/null +++ b/bin/cli-users @@ -0,0 +1,408 @@ +#!/usr/bin/env node + +/** + * Setup the debug paramater. + */ + +process.env.DEBUG = process.env.TALK_DEBUG; + +/** + * Module dependencies. + */ + +const program = require('commander'); +const pkg = require('../package.json'); +const prompt = require('prompt'); + +/** + * Prompts for input and registers a user based on those. + */ +function createUser(options) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + return new Promise((resolve, reject) => { + + if (options.flag_mode) { + return resolve({ + email: options.email, + password: options.password, + displayName: options.name, + }); + } + + prompt.start(); + + prompt.get([ + { + name: 'email', + description: 'Email', + format: 'email', + required: true + }, + { + name: 'password', + description: 'Password', + hidden: true, + required: true + }, + { + name: 'confirmPassword', + description: 'Confirm Password', + hidden: true, + required: true + }, + { + name: 'displayName', + description: 'Display Name', + required: true + } + ], (err, result) => { + if (err) { + return reject(err); + } + + if (result.password !== result.confirmPassword) { + return reject(new Error('Passwords do not match')); + } + + resolve(result); + }); + }) + .then((result) => { + return User.createLocalUser(result.email.trim(), result.password.trim(), result.displayName.trim()); + }).then((user) => { + console.log(`Created user ${user.id}.`); + mongoose.disconnect(); + }).catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Deletes a user. + */ +function deleteUser(userID) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .findOneAndRemove({ + id: userID + }) + .then(() => { + console.log('Deleted user'); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Changes the password for a user. + */ +function passwd(userID) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + prompt.start(); + + prompt.get([ + { + name: 'password', + description: 'Password', + hidden: true, + required: true + }, + { + name: 'confirmPassword', + description: 'Confirm Password', + hidden: true, + required: true + } + ], (err, result) => { + if (err) { + console.error(err); + mongoose.disconnect(); + return; + } + + if (result.password !== result.confirmPassword) { + console.error(new Error('Password mismatch')); + mongoose.disconnect(); + return; + } + + User + .changePassword(userID, result.password) + .then(() => { + console.log('Password changed.'); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); + }); +} + +/** + * Updates the user from the options array. + */ +function updateUser(userID, options) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + const updates = []; + + if (options.email && typeof options.email === 'string' && options.email.length > 0) { + let q = User.update({ + 'id': userID, + 'profiles.provider': 'local' + }, { + $set: { + 'profiles.$.id': options.email + } + }); + + updates.push(q); + } + + if (options.name && typeof options.name === 'string' && options.name.length > 0) { + let q = User.update({ + 'id': userID + }, { + $set: { + displayName: options.name + } + }); + + updates.push(q); + } + + Promise + .all(updates.map((q) => q.exec())) + .then(() => { + console.log(`User ${userID} updated.`); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Lists all the users registered in the database. + */ +function listUsers() { + const Table = require('cli-table'); + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .find() + .then((users) => { + let table = new Table({ + head: [ + 'ID', + 'Display Name', + 'Profiles', + 'Roles', + 'State' + ] + }); + + users.forEach((user) => { + table.push([ + user.id, + user.displayName, + user.profiles.map((p) => p.provider).join(', '), + user.roles.join(', '), + user.disabled ? 'Disabled' : 'Enabled' + ]); + }); + + console.log(table.toString()); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Merges two users using the specified ID's. + * @param {String} dstUserID id of the user to which is the target of the merge + * @param {String} srcUserID id of the user to which is the source of the merge + */ +function mergeUsers(dstUserID, srcUserID) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .mergeUsers(dstUserID, srcUserID) + .then(() => { + console.log(`User ${srcUserID} was merged into user ${dstUserID}.`); + mongoose.disconnect(); + }).catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Adds a role to a user + * @param {String} userUD id of the user to add the role to + * @param {String} role the role to add + */ +function addRole(userID, role) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .addRoleToUser(userID, role) + .then(() => { + console.log(`Added the ${role} role to User ${userID}.`); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Removes a role from a user + * @param {String} userUD id of the user to remove the role from + * @param {String} role the role to remove + */ +function removeRole(userID, role) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .removeRoleFromUser(userID, role) + .then(() => { + console.log(`Removed the ${role} role from User ${userID}.`); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Disable a given user. + * @param {String} userID the ID of a user to disable + */ +function disableUser(userID) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .disableUser(userID) + .then(() => { + console.log(`User ${userID} was disabled.`); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +/** + * Enabled a given user. + * @param {String} userID the ID of a user to enable + */ +function enableUser(userID) { + const User = require('../models/user'); + const mongoose = require('../mongoose'); + + User + .enableUser(userID) + .then(() => { + console.log(`User ${userID} was enabled.`); + mongoose.disconnect(); + }) + .catch((err) => { + console.error(err); + mongoose.disconnect(); + }); +} + +//============================================================================== +// Setting up the program command line arguments. +//============================================================================== + +program + .version(pkg.version); + +program + .command('create') + .option('--email [email]', 'Email to use') + .option('--password [password]', 'Password to use') + .option('--name [name]', 'Name to use') + .option('-f, --flag_mode', 'Source from flags instead of prompting') + .description('create a new user') + .action(createUser); + +program + .command('delete ') + .description('delete a user') + .action(deleteUser); + +program + .command('passwd ') + .description('change a password for a user') + .action(passwd); + +program + .command('update ') + .option('--email [email]', 'Email to use') + .option('--name [name]', 'Name to use') + .description('update a user') + .action(updateUser); + +program + .command('list') + .description('list all the users in the database') + .action(listUsers); + +program + .command('merge ') + .description('merge srcUser into the dstUser') + .action(mergeUsers); + +program + .command('addrole ') + .description('adds a role to a given user') + .action(addRole); + +program + .command('removerole ') + .description('removes a role from a given user') + .action(removeRole); + +program + .command('disable ') + .description('disable a given user from logging in') + .action(disableUser); + +program + .command('enable ') + .description('enable a given user from logging in') + .action(enableUser); + +program.parse(process.argv); + +// If there is no command listed, output help. +if (!process.argv.slice(2).length) { + program.outputHelp(); +} diff --git a/bin/init.js b/bin/init.js deleted file mode 100644 index d8be5a6fa..000000000 --- a/bin/init.js +++ /dev/null @@ -1,12 +0,0 @@ -const mongoose = require('../mongoose'); -const Setting = require('../models/setting'); -const defaults = {id: '1', moderation: 'pre'}; - -Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}) - .then(() => { - console.log('Created settings object.'); - mongoose.disconnect(); - }).catch((err) => { - console.error(`failed to create the settings object ${JSON.stringify(err)}`); - throw new Error(err); // just to be safe - }); diff --git a/bin/www b/bin/www index 94408f5e8..af91c2c40 100755 --- a/bin/www +++ b/bin/www @@ -40,7 +40,7 @@ server.on('listening', onListening); */ function normalizePort(val) { - var port = parseInt(val, 10); + let port = parseInt(val, 10); if (isNaN(port)) { // named pipe @@ -64,23 +64,21 @@ function onError(error) { throw error; } - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; + let bind = typeof port === 'string' + ? `Pipe ${ port}` + : `Port ${ port}`; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); + console.error(`${bind} requires elevated privileges`); break; case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); + console.error(`${bind} is already in use`); break; - default: - throw error; } + + throw error; } /** @@ -88,9 +86,9 @@ function onError(error) { */ function onListening() { - var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - debug('Listening on ' + bind); + let addr = server.address(); + let bind = typeof addr === 'string' + ? `pipe ${ addr}` + : `port ${ addr.port}`; + debug(`Listening on ${ bind}`); } diff --git a/models/user.js b/models/user.js index 4592ec847..fe06b9ab2 100644 --- a/models/user.js +++ b/models/user.js @@ -1,45 +1,278 @@ - const mongoose = require('../mongoose'); const uuid = require('uuid'); -const Schema = mongoose.Schema; +const bcrypt = require('bcrypt'); -const UserProfileSchema = new Schema({ +const SALT_ROUNDS = 10; + +const UserSchema = new mongoose.Schema({ id: { type: String, default: uuid.v4, unique: true }, - display_name: String, - auth_user_id: String + displayName: String, + disabled: Boolean, + password: String, + profiles: [{ + id: String, + provider: String + }], + roles: [String] +}); + +// Add the indixies on the user profile data. +UserSchema.index({ + 'profiles.id': 1, + 'profiles.provider': 1 }, { - timestamps: { - createdAt: 'created_at', - updatedAt: 'updated_at' - } + unique: true, + background: false }); /** - * Finds a user by the id. - * @param {String} id identifier of the user (uuid) -*/ -UserProfileSchema.statics.findById = function(id) { - return UserProfile.findOne({id}); + * toJSON overrides to remove the password field from the json + * output. + */ +UserSchema.options.toJSON = {}; +UserSchema.options.toJSON.hide = 'password profiles roles disabled'; +UserSchema.options.toJSON.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. + * @param {string} email - email to look up the user by + * @param {string} password - password to match against the found user + * @param {Function} done [description] + */ +UserSchema.statics.findLocalUser = function(email, password) { + return User.findOne({ + profiles: { + $elemMatch: { + id: email, + provider: 'local' + } + } + }) + .then((user) => { + if (!user) { + return false; + } + + return new Promise((resolve, reject) => { + bcrypt.compare(password, user.password, (err, res) => { + if (err) { + return reject(err); + } + + if (!res) { + return resolve(false); + } + + return resolve(user); + }); + }); + }); +}; + +/** + * Merges two users together by taking all the profiles on a given user and + * pushing them into the source user followed by deleting the destination user's + * user account. This will not merge the roles associated with the source user. + * @param {String} dstUserID id of the user to which is the target of the merge + * @param {String} srcUserID id of the user to which is the source of the merge + * @return {Promise} resolves when the users are merged + */ +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]; + + srcUser.profiles.forEach((profile) => { + dstUser.profiles.push(profile); + }); + + return srcUser.remove(); + }).then(() => dstUser.save()); +}; + +/** + * Finds a user given a social profile and if the user does not exist, creates + * them. + * @param {Object} profile - User social/external profile + * @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: [ + { + id: profile.id, + provider: profile.provider + } + ] + }); + + return user.save(); + }); +}; + +UserSchema.statics.changePassword = function(id, password) { + return new Promise((resolve, reject) => { + bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => { + if (err) { + return reject(err); + } + + resolve(hashedPassword); + }); + }) + .then((hashedPassword) => { + return User.update({id}, { + $set: { + password: hashedPassword + } + }); + }); +}; + +/** + * Creates the local user with a given email, password, and name. + * @param {String} email email of the new user + * @param {String} password plaintext password of the new user + * @param {String} displayName name of the display user + * @param {Function} done callback + */ +UserSchema.statics.createLocalUser = function(email, password, displayName) { + return new Promise((resolve, reject) => { + bcrypt.hash(password, SALT_ROUNDS, (err, hashedPassword) => { + if (err) { + return reject(err); + } + + let user = new User({ + displayName: displayName, + password: hashedPassword, + roles: [], + profiles: [ + { + id: email, + provider: 'local' + } + ] + }); + + user.save((err) => { + if (err) { + return reject(err); + } + + return resolve(user); + }); + }); + }); +}; + +/** + * Disables a given user account. + * @param {String} id id of a user + * @param {Function} done callback after the operation is complete + */ +UserSchema.statics.disableUser = function(id) { + return User.update({ + id: id + }, { + $set: { + disabled: true + } + }); +}; + +/** + * Enables a given user account. + * @param {String} id id of a user + * @param {Function} done callback after the operation is complete + */ +UserSchema.statics.enableUser = function(id) { + return User.update({ + id: id + }, { + $set: { + disabled: false + } + }); +}; + +/** + * Adds a role to a user. + * @param {String} id id of a user + * @param {String} role role to add + * @param {Function} done callback after the operation is complete + */ +UserSchema.statics.addRoleToUser = function(id, role) { + return User.update({ + id: id + }, { + $addToSet: { + roles: role + } + }); +}; + +/** + * Removes a role from a user. + * @param {String} id id of a user + * @param {String} role role to remove + * @param {Function} done callback after the operation is complete + */ +UserSchema.statics.removeRoleFromUser = function(id, role) { + return User.update({ + id: id + }, { + $pull: { + roles: role + } + }); }; /** * Finds users in an array of idd. * @param {String} idd array of user identifiers (uuid) */ -UserProfileSchema.statics.findByIdArray = function(ids) { - return UserProfile.find({ +UserSchema.statics.findByIdArray = function(ids) { + return User.find({ 'id': {$in: ids} }); }; -// TO DO: methods -// modifications to user as statics -// find by auth user id +const User = mongoose.model('User', UserSchema); -const UserProfile = mongoose.model('UserProfile', UserProfileSchema); - -module.exports = UserProfile; +module.exports = User; +module.exports.Schema = UserSchema; diff --git a/mongoose.js b/mongoose.js index 8d9bb3bfb..9062816e6 100644 --- a/mongoose.js +++ b/mongoose.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose'); +const debug = require('debug')('talk:db'); const enabled = require('debug').enabled; const url = process.env.TALK_MONGO_URL || 'mongodb://localhost'; @@ -11,11 +12,14 @@ if (enabled('talk:db')) { try { mongoose.connect(url, (err) => { - if (err) {throw err;} - console.log('Connected to MongoDB!'); + if (err) { + throw err; + } + + debug('Connected to MongoDB!'); }); } catch (err) { - console.log('Cannot stablish a connection with MongoDB'); + console.error('Cannot stablish a connection with MongoDB', err); } module.exports = mongoose; diff --git a/package.json b/package.json index 979475b5b..b2e7b938b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "./bin/www", "build": "webpack --config webpack.config.js --bail", "build-watch": "webpack --config webpack.config.dev.js --watch", - "lint": "eslint .", + "lint": "eslint bin/* .", "pretest": "npm install", "test": "mocha tests --recursive", "test-watch": "mocha tests --recursive -w", @@ -43,12 +43,15 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { + "bcrypt": "^0.8.7", "body-parser": "^1.15.2", + "commander": "^2.9.0", "debug": "^2.2.0", "ejs": "^2.5.2", "express": "^4.14.0", "mongoose": "^4.6.5", "morgan": "^1.7.0", + "prompt": "^1.0.0", "uuid": "^2.0.3" }, "devDependencies": { From 6ab561b91be8183a5d68461d5ff4bf27fbd89d2a Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 9 Nov 2016 16:39:54 -0500 Subject: [PATCH 10/16] Updating and passing coral-framework tests --- client/coral-framework/store/actions/items.js | 39 +++--- .../coral-framework/store/reducers/items.js | 21 ++- .../coral-framework/store/itemActions.spec.js | 128 +++++++++++------- .../coral-framework/store/itemReducer.spec.js | 115 +++++----------- 4 files changed, 137 insertions(+), 166 deletions(-) diff --git a/client/coral-framework/store/actions/items.js b/client/coral-framework/store/actions/items.js index c292426d1..e51e1573e 100644 --- a/client/coral-framework/store/actions/items.js +++ b/client/coral-framework/store/actions/items.js @@ -24,13 +24,14 @@ export const APPEND_ITEM_ARRAY = 'APPEND_ITEM_ARRAY' * */ -export const addItem = (item) => { +export const addItem = (item, item_type) => { if (!item.id) { console.warn('addItem called without an item id.') } return { type: ADD_ITEM, - item: item, + item, + item_type, id: item.id } } @@ -47,22 +48,24 @@ export const addItem = (item) => { */ -export const updateItem = (id, property, value) => { +export const updateItem = (id, property, value, item_type) => { return { type: UPDATE_ITEM, id, property, - value + value, + item_type } } -export const appendItemArray = (id, property, value, addToFront) => { +export const appendItemArray = (id, property, value, add_to_front, item_type) => { return { type: APPEND_ITEM_ARRAY, id, property, value, - addToFront + add_to_front, + item_type } } @@ -93,7 +96,7 @@ export function getStream (assetId) { const itemTypes = Object.keys(json); for (let i=0; i < itemTypes.length; i++ ) { for (var j=0; j < json[itemTypes[i]].length; j++ ) { - dispatch(addItem(json[itemTypes[i]][j])); + dispatch(addItem(json[itemTypes[i]][j], itemTypes[i])); } } @@ -118,18 +121,18 @@ export function getStream (assetId) { dispatch(addItem({ id: assetId, - comments: rootComments - })) + comments: rootComments, + }, 'assets')) const childKeys = Object.keys(childComments) for (var i=0; i < childKeys.length; i++ ) { - dispatch(updateItem(childKeys[i], 'children', childComments[childKeys[i]].reverse())) + dispatch(updateItem(childKeys[i], 'children', childComments[childKeys[i]].reverse(), 'comments')) } /* Hydrate actions on comments */ const actions = Object.keys(json.actions) for (var i=0; i < actions.length; i++ ) { - dispatch(updateItem(actions[i].item_id, actions[i].type, actions[i].id)) + dispatch(updateItem(actions[i].item_id, actions[i].type, actions[i].id, 'actions')) } return (json) @@ -195,7 +198,6 @@ export function postItem (item, type, id) { 'Content-Type':'application/json' } } - console.log('postItem', options); return fetch('/api/v1/' + type, options) .then( response => { @@ -204,7 +206,7 @@ export function postItem (item, type, id) { } ) .then((json) => { - dispatch(addItem({...item, id:json.id})) + dispatch(addItem({...item, id:json.id}, type)) return json.id }) } @@ -227,7 +229,7 @@ export function postItem (item, type, id) { * */ -export function postAction (id, type, user_id) { +export function postAction (item_id, type, user_id) { return (dispatch) => { const action = { type, @@ -238,13 +240,14 @@ export function postAction (id, type, user_id) { body: JSON.stringify(action) } - dispatch(appendItemArray(id, type, user_id)) - return fetch('/api/v1/comments/' + id + '/actions', options) + return fetch('/api/v1/comments/' + item_id + '/actions', options) .then( response => { - return response.ok ? response.text() + return response.ok ? response.json() : Promise.reject(response.status + ' ' + response.statusText) } - ) + ).then((json)=>{ + return json.id + }) } } diff --git a/client/coral-framework/store/reducers/items.js b/client/coral-framework/store/reducers/items.js index d3eb4d3af..d1fcd6b43 100644 --- a/client/coral-framework/store/reducers/items.js +++ b/client/coral-framework/store/reducers/items.js @@ -3,26 +3,23 @@ import { Map, fromJS } from 'immutable' import * as actions from '../actions/items' -const initialState = fromJS({}) +const initialState = Map({}); export default (state = initialState, action) => { switch (action.type) { case actions.ADD_ITEM: - return state.set(action.id, fromJS(action.item)) + return state.setIn([action.item_type, action.id], fromJS(action.item)); case actions.UPDATE_ITEM: - return state.updateIn([action.id, action.property], () => - fromJS(action.value) - ) + return state.setIn([action.item_type, action.id, action.property], fromJS(action.value)); case actions.APPEND_ITEM_ARRAY: - return state.updateIn([action.id, action.property], (prop) => { - if (action.addToFront) { - return prop ? prop.unshift(action.value) : fromJS([action.value]) + return state.updateIn([action.item_type, action.id, action.property], (prop) => { + console.log(prop); + if (action.add_to_front) { + return prop ? prop.unshift(fromJS(action.value)) : fromJS([action.value]); } else { - return prop ? prop.push(action.value) : fromJS([action.value]) + return prop ? prop.push(fromJS(action.value)) : fromJS([action.value]); } - - } - ) + }); default: return state } diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index 124174f36..360f6b79a 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -11,74 +11,88 @@ const mockStore = configureStore() describe('itemActions', () => { let store - const host = 'http://test.host' beforeEach(() => { store = mockStore(new Map({})) fetchMock.restore() }) - describe('getItemsQuery', () => { - const query = 'all' - const rootId = '1234' - const view = 'testView' - const response = {results: [ - {Docs: [ - {type: 'comment', data: {content: 'stuff'}, item_id: '123'}, - {type: 'comment', data: {content: 'morestuff'}, item_id: '456'} - ]} - ]} + describe('getStream', () => { + const rootId = '1234'; + const response = { + comments: [ + { body: 'stuff', id: '123'}, + { body: 'morestuff', id: '456'} + ], + actions: [ + { + type: 'like', + id: '123', + count: 1, + current_user: false + }, + { + type: 'flag', + id: '456', + count: 5, + current_user: true + } + ] + }; - it('should get an item from a query and send the appropriate dispatches', () => { - fetchMock.get('*', JSON.stringify(response)) - return actions.getItemsQuery(query, rootId, view, host)(store.dispatch) + it('should get an stream from an asset_id and send the appropriate dispatches', () => { + fetchMock.get('*', JSON.stringify(response)); + return actions.getStream(rootId)(store.dispatch) .then((res) => { - expect(fetchMock.calls().matched[0][0]).to.equal('http://test.host/v1/exec/all/view/testView/1234') - expect(res).to.deep.equal(response.results[0].Docs) + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/stream?asset_id=1234') + expect(res).to.deep.equal(response); expect(store.getActions()[0]).to.deep.equal({ type: actions.ADD_ITEM, - item: response.results[0].Docs[0], - item_id: '123' - }) + item: response.comments[0], + item_type: 'comments', + id: '123' + }); expect(store.getActions()[1]).to.deep.equal({ type: actions.ADD_ITEM, - item: response.results[0].Docs[1], - item_id: '456' - }) - }) + item: response.comments[1], + item_type: 'comments', + id: '456' + }); + }); }) it('should handle an error', () => { fetchMock.get('*', 404) - return actions.getItemsQuery(query, rootId, view, host)(store.dispatch) + return actions.getStream(rootId)(store.dispatch) .catch((err) => { - expect(err).to.be.truthy - }) - }) - }) + expect(err).to.be.truthy; + }); + }); + }); - describe('getItemsArray', () => { - const response = {items: [{type: 'comment', item_id: '123'}, {type: 'comment', item_id: '456'}]} + //Disabling tests for this function until is is used again. + xdescribe('getItemsArray', () => { + const response = {items: [{type: 'comment', id: '123'}, {type: 'comment', id: '456'}]} const ids = [1, 2] it('should get an item from an array of ids and send the appropriate dispatches', () => { fetchMock.get('*', JSON.stringify(response)) - return actions.getItemsArray(ids, host)(store.dispatch) + return actions.getItemsArray(ids)(store.dispatch) .then((res) => { expect(res).to.deep.equal(response.items) expect(store.getActions()[0]).to.deep.equal({ type: actions.ADD_ITEM, item: { type: 'comment', - item_id: '123' + id: '123' }, - item_id: '123' + id: '123' }) expect(store.getActions()[1]).to.deep.equal({ type: actions.ADD_ITEM, item: { - type: 'comment', item_id: '456' + type: 'comment', id: '456' }, - item_id: '456' + id: '456' }) }) }) @@ -93,37 +107,38 @@ describe('itemActions', () => { describe('postItem', () => { const item = { - type: 'comment', - data:{content: 'stuff'} + type: 'comments', + data: {body: 'stuff'} } it ('should post an item, return an id, then dispatch that item to the store', () => { - fetchMock.post('*', {item_id: '123', type: 'comment', data: {content: 'stuff'}}) - return actions.postItem(item.data, item.type, undefined, host)(store.dispatch) + fetchMock.post('*', {id: '123'}) + return actions.postItem(item.data, item.type, undefined)(store.dispatch) .then((id) => { expect(fetchMock.calls().matched[0][1]).to.deep.equal( { method: 'POST', - body: JSON.stringify({...item, version: 1}) + headers: { + 'Content-Type':'application/json' + }, + body: JSON.stringify(item.data) } ) expect(id).to.equal('123') expect(store.getActions()[0]).to.deep.equal({ type: actions.ADD_ITEM, item: { - type: 'comment', - data: { - content: 'stuff' - }, - item_id: '123' + body: 'stuff', + id: '123' }, - item_id: '123' + item_type: 'comments', + id: '123' }) }) }) it('should handle an error', () => { fetchMock.post('*', 404) - return actions.postItem(item, host)(store.dispatch) + return actions.postItem(item)(store.dispatch) .catch((err) => { expect(err).to.be.truthy }) @@ -132,17 +147,26 @@ describe('itemActions', () => { describe('postAction', () => { it ('should post an action', () => { - fetchMock.post('*', 200) - return actions.postAction('abc', 'flag', '123', host)(store.dispatch) + fetchMock.post('*', {id: '456'}) + return actions.postAction('abc', 'flag', '123')(store.dispatch) .then(response => { - expect(fetchMock.calls().matched[0][0]).to.equal('http://test.host/v1/action/flag/user/123/on/item/abc') - expect(response).to.equal('') + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions') + expect(response).to.equal('456') + // expect(store.getActions()[0]).to.deep.equal({ + // type: actions.ADD_ITEM, + // item: { + // type: 'flag', + // item_id: 'abc', + // id: '123' + // }, + // id: '123' + // }) }) }) it('should handle an error', () => { fetchMock.post('*', 404) - return actions.postItem('abc', 'flag', '123', host)(store.dispatch) + return actions.postAction('abc', 'flag', '123')(store.dispatch) .catch((err) => { expect(err).to.be.truthy }) diff --git a/tests/client/coral-framework/store/itemReducer.spec.js b/tests/client/coral-framework/store/itemReducer.spec.js index bd7b74b6c..97482137c 100644 --- a/tests/client/coral-framework/store/itemReducer.spec.js +++ b/tests/client/coral-framework/store/itemReducer.spec.js @@ -9,22 +9,17 @@ describe ('itemsReducer', () => { const action = { type: 'ADD_ITEM', item: { - type: 'comment', - data: { - content: 'stuff' - }, - item_id: '123' + body: 'stuff', + id: '123' }, - item_id: '123' + item_type: 'comments', + id: '123' } const store = new Map({}) const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - type: 'comment', - data: { - content: 'stuff' - }, - item_id: '123' + expect(result.getIn(['comments','123']).toJS()).to.deep.equal({ + body: 'stuff', + id: '123' }) }) }) @@ -35,22 +30,21 @@ describe ('itemsReducer', () => { type: 'UPDATE_ITEM', property: 'stuff', value: 'things', - item_id: '123' + item_type: 'comments', + id: '123' } const store = fromJS({ - '123': { - item_id: '123', - data: { + 'comments': { + '123': { + id: '123', stuff: 'morestuff' } } - }) + }); const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - item_id: '123', - data: { - stuff: 'things' - } + expect(result.getIn(['comments','123']).toJS()).to.deep.equal({ + id: '123', + stuff: 'things' }) }) }) @@ -64,12 +58,13 @@ describe ('itemsReducer', () => { type: 'APPEND_ITEM_ARRAY', property: 'stuff', value: 'things', - item_id: '123' + id: '123', + item_type: 'comments' } store = fromJS({ - '123': { - item_id: '123', - data: { + 'comments': { + '123': { + id: '123', stuff: ['morestuff'] } } @@ -77,73 +72,25 @@ describe ('itemsReducer', () => { }) it ('should append to an existing array', () => { const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - item_id: '123', - data: { - stuff: ['morestuff', 'things'] - } + expect(result.getIn(['comments','123']).toJS()).to.deep.equal({ + id: '123', + stuff: ['morestuff', 'things'] }) }) it ('should create a new array', () => { store = fromJS({ - '123': { - item_id: '123', - data: {} - } - }) - const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - item_id: '123', - data: { - stuff: ['things'] - } - }) - }) - }) - - describe('APPEND_ITEM_RELATED', () => { - let action - let store - - beforeEach (() => { - action = { - type: 'APPEND_ITEM_RELATED', - property: 'stuff', - value: 'things', - item_id: '123' - } - store = fromJS({ - '123': { - item_id: '123', - related: { - stuff: ['morestuff'] + 'comments': { + '123': { + id: '123' } } }) - }) - it ('should append to an existing array', () => { const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - item_id: '123', - related: { - stuff: ['morestuff', 'things'] - } - }) - }) - it ('should create a new array', () => { - store = fromJS({ - '123': { - item_id: '123', - related: {} - } - }) - const result = itemsReducer(store, action) - expect(result.get('123').toJS()).to.deep.equal({ - item_id: '123', - related: { - stuff: ['things'] - } + expect(result.getIn(['comments','123']).toJS()).to.deep.equal({ + id: '123', + stuff: ['things'] }) }) }) + }) From b7ee23d000f0f03352825897a62fddae45624002 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 9 Nov 2016 17:01:50 -0500 Subject: [PATCH 11/16] Updating commentstream to reflect new store structure. --- client/coral-embed-stream/src/CommentStream.js | 18 +++++++++--------- client/coral-plugin-commentbox/CommentBox.js | 9 ++++++--- client/coral-plugin-replies/ReplyButton.js | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 0804530e8..3a1acbbc6 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -30,11 +30,11 @@ const {setLoggedInUser} = authActions }, (dispatch) => { return { - addItem: (item) => { - return dispatch(addItem(item)) + addItem: (item, itemType) => { + return dispatch(addItem(item, itemType)) }, - updateItem: (id, property, value) => { - return dispatch(updateItem(id, property, value)) + updateItem: (id, property, value, itemType) => { + return dispatch(updateItem(id, property, value, itemType)) }, postItem: (data, type, id) => { return dispatch(postItem(data, type, id)) @@ -54,8 +54,8 @@ const {setLoggedInUser} = authActions postAction: (item, action, user) => { return dispatch(postAction(item, action, user)) }, - appendItemArray: (item, property, value, addToFront) => { - return dispatch(appendItemArray(item, property, value, addToFront)) + appendItemArray: (item, property, value, addToFront, itemType) => { + return dispatch(appendItemArray(item, property, value, addToFront, itemType)) } } } @@ -97,7 +97,7 @@ class CommentStream extends Component { const rootItemId = 'assetTest' - const rootItem = this.props.items[rootItemId] + const rootItem = this.props.items.assets && this.props.items.assets[rootItemId] return
{ rootItem ? @@ -116,7 +116,7 @@ class CommentStream extends Component {
{ rootItem.comments.map((commentId) => { - const comment = this.props.items[commentId] + const comment = this.props.items.comments[commentId] return

@@ -144,7 +144,7 @@ class CommentStream extends Component { { comment.children && comment.children.map((replyId) => { - let reply = this.props.items[replyId] + let reply = this.props.items.comments[replyId] return

diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 0f1578859..f6f8a7276 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -25,17 +25,20 @@ class CommentBox extends Component { asset_id: id, username: this.state.username } - let related + let related; + let parent_type; if (parent_id) { comment.parent_id = parent_id related = 'children' + parent_type = 'comments' } else { related = 'comments' + parent_type = 'assets' } - updateItem(parent_id, 'showReply', false) + updateItem(parent_id, 'showReply', false, 'comments') postItem(comment, 'comments') .then((comment_id) => { - appendItemArray(parent_id || id, related, comment_id, parent_id ? false : true) + appendItemArray(parent_id || id, related, comment_id, parent_id ? false : true, parent_type) addNotification('success', 'Your comment has been posted.') }).catch((err) => console.error(err)) this.setState({body: ''}) diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index 157d24aaf..d0c10355b 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -5,7 +5,7 @@ const name = 'coral-plugin-replies' const ReplyButton = (props) =>