mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
Merge branch 'master' into remove-queue-folder
This commit is contained in:
@@ -6,7 +6,12 @@ const path = require('path');
|
||||
const app = express();
|
||||
|
||||
// Middleware declarations.
|
||||
app.use(morgan('dev'));
|
||||
|
||||
// Add the logging middleware only if we aren't testing.
|
||||
if (app.get('env') !== 'test') {
|
||||
app.use(morgan('dev'));
|
||||
}
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
Executable
+42
@@ -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();
|
||||
}
|
||||
Executable
+408
@@ -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 <userID>')
|
||||
.description('delete a user')
|
||||
.action(deleteUser);
|
||||
|
||||
program
|
||||
.command('passwd <userID>')
|
||||
.description('change a password for a user')
|
||||
.action(passwd);
|
||||
|
||||
program
|
||||
.command('update <userID>')
|
||||
.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 <dstUserID> <srcUserID>')
|
||||
.description('merge srcUser into the dstUser')
|
||||
.action(mergeUsers);
|
||||
|
||||
program
|
||||
.command('addrole <userID> <role>')
|
||||
.description('adds a role to a given user')
|
||||
.action(addRole);
|
||||
|
||||
program
|
||||
.command('removerole <userID> <role>')
|
||||
.description('removes a role from a given user')
|
||||
.action(removeRole);
|
||||
|
||||
program
|
||||
.command('disable <userID>')
|
||||
.description('disable a given user from logging in')
|
||||
.action(disableUser);
|
||||
|
||||
program
|
||||
.command('enable <userID>')
|
||||
.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();
|
||||
}
|
||||
-12
@@ -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
|
||||
});
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -45,15 +45,16 @@ Promise.all([fetch('/api/v1/comments/status/pending'), fetch('/api/v1/comments/s
|
||||
.catch(error => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_FAILED', error}));
|
||||
|
||||
// Update a comment. Now to update a comment we need to send back the whole object
|
||||
|
||||
const updateComment = (store, comment) => {
|
||||
fetch(`/api/v1/comments/${comment.get('id')}/status`, {
|
||||
method: 'POST',
|
||||
headers: jsonHeader,
|
||||
body: JSON.stringify({status: comment.get('status')})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
|
||||
.then(res => res.json())
|
||||
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
|
||||
};
|
||||
|
||||
// Create a new comment
|
||||
|
||||
@@ -13,52 +13,52 @@ import Count from '../../coral-plugin-comment-count/CommentCount';
|
||||
import AuthorName from '../../coral-plugin-author-name/AuthorName';
|
||||
import {ReplyBox, ReplyButton} from '../../coral-plugin-replies';
|
||||
import Pym from 'pym.js';
|
||||
import FlagButton from '../../coral-plugin-flags/FlagButton';
|
||||
|
||||
const {addItem, updateItem, postItem, getStream, postAction, appendItemArray} = itemActions;
|
||||
const {addNotification, clearNotification} = notificationActions;
|
||||
const {setLoggedInUser} = authActions;
|
||||
|
||||
@connect(
|
||||
(state) => {
|
||||
return {
|
||||
config: state.config.toJS(),
|
||||
items: state.items.toJS(),
|
||||
notification: state.notification.toJS(),
|
||||
auth: state.auth.toJS()
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
return {
|
||||
addItem: (item) => {
|
||||
return dispatch(addItem(item));
|
||||
},
|
||||
updateItem: (id, property, value) => {
|
||||
return dispatch(updateItem(id, property, value));
|
||||
},
|
||||
postItem: (data, type, id) => {
|
||||
return dispatch(postItem(data, type, id));
|
||||
},
|
||||
getStream: (rootId) => {
|
||||
return dispatch(getStream(rootId));
|
||||
},
|
||||
addNotification: (type, text) => {
|
||||
return dispatch(addNotification(type, text));
|
||||
},
|
||||
clearNotification: () => {
|
||||
return dispatch(clearNotification());
|
||||
},
|
||||
setLoggedInUser: (user_id) => {
|
||||
return dispatch(setLoggedInUser(user_id));
|
||||
},
|
||||
postAction: (item, action, user) => {
|
||||
return dispatch(postAction(item, action, user));
|
||||
},
|
||||
appendItemArray: (item, property, value, addToFront) => {
|
||||
return dispatch(appendItemArray(item, property, value, addToFront));
|
||||
}
|
||||
};
|
||||
}
|
||||
)
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
config: state.config.toJS(),
|
||||
items: state.items.toJS(),
|
||||
notification: state.notification.toJS(),
|
||||
auth: state.auth.toJS()
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
addItem: (item, itemType) => {
|
||||
return dispatch(addItem(item, itemType));
|
||||
},
|
||||
updateItem: (id, property, value, itemType) => {
|
||||
return dispatch(updateItem(id, property, value, itemType));
|
||||
},
|
||||
postItem: (data, type, id) => {
|
||||
return dispatch(postItem(data, type, id));
|
||||
},
|
||||
getStream: (rootId) => {
|
||||
return dispatch(getStream(rootId));
|
||||
},
|
||||
addNotification: (type, text) => {
|
||||
return dispatch(addNotification(type, text));
|
||||
},
|
||||
clearNotification: () => {
|
||||
return dispatch(clearNotification());
|
||||
},
|
||||
setLoggedInUser: (user_id) => {
|
||||
return dispatch(setLoggedInUser(user_id));
|
||||
},
|
||||
postAction: (item, action, user, itemType) => {
|
||||
return dispatch(postAction(item, action, user, itemType));
|
||||
},
|
||||
appendItemArray: (item, property, value, addToFront, itemType) => {
|
||||
return dispatch(appendItemArray(item, property, value, addToFront, itemType));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class CommentStream extends Component {
|
||||
|
||||
@@ -90,93 +90,92 @@ class CommentStream extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Replace teststream id with id from params
|
||||
// TODO: Replace teststream id with id from params
|
||||
|
||||
const rootItemId = 'assetTest';
|
||||
const rootItem = this.props.items[rootItemId];
|
||||
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
|
||||
return <div>
|
||||
{
|
||||
rootItem ?
|
||||
<div>
|
||||
<div id="commentBox">
|
||||
<Count
|
||||
id={rootItemId}
|
||||
items={this.props.items}/>
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
reply={false}/>
|
||||
</div>
|
||||
{
|
||||
rootItem.comments.map((commentId) => {
|
||||
const comment = this.props.items[commentId];
|
||||
return <div className="comment" key={commentId}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName name={comment.username}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActions">
|
||||
{
|
||||
// <Flag
|
||||
// addNotification={this.props.addNotification}
|
||||
// id={commentId}
|
||||
// flag={comment.flag}
|
||||
// postAction={this.props.postAction}
|
||||
// currentUser={this.props.auth.user}/>
|
||||
}
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
parent_id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
comment.children &&
|
||||
comment.children.map((replyId) => {
|
||||
let reply = this.props.items[replyId];
|
||||
return <div className="reply" key={replyId}>
|
||||
{
|
||||
rootItem
|
||||
? <div>
|
||||
<div id="commentBox">
|
||||
<Count
|
||||
id={rootItemId}
|
||||
items={this.props.items}/>
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
reply={false}/>
|
||||
</div>
|
||||
{
|
||||
rootItem.comments.map((commentId) => {
|
||||
const comment = this.props.items.comments[commentId];
|
||||
return <div className="comment" key={commentId}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName name={comment.username}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActions">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
flag={this.props.items.actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
parent_id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
comment.children &&
|
||||
comment.children.map((replyId) => {
|
||||
let reply = this.props.items.comments[replyId];
|
||||
return <div className="reply" key={replyId}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName name={reply.username}/>
|
||||
<PubDate created_at={reply.created_at}/>
|
||||
<Content body={reply.body}/>
|
||||
<div className="replyActions">
|
||||
{
|
||||
// <Flag
|
||||
// addNotificiation={this.props.addNotification}
|
||||
// id={replyId}
|
||||
// flag={reply.flag}
|
||||
// postAction={this.props.postAction}
|
||||
// currentUser={this.props.auth.user}/>
|
||||
}
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
parent_id={reply.parent_id}/>
|
||||
</div>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<Notification
|
||||
notifLength={this.props.config.notifLength}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}/>
|
||||
</div>
|
||||
: 'Loading'
|
||||
}
|
||||
</div>;
|
||||
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<Notification
|
||||
notifLength={this.props.config.notifLength}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}/>
|
||||
</div>
|
||||
: 'Loading'
|
||||
}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default CommentStream;
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentStream);
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import {Map, fromJS} from 'immutable';
|
||||
import {expect} from 'chai';
|
||||
import itemsReducer from '../../store/reducers/items';
|
||||
|
||||
describe ('itemsReducer', () => {
|
||||
describe('ADD_ITEM', () => {
|
||||
it('should add an item', () => {
|
||||
const action = {
|
||||
type: 'ADD_ITEM',
|
||||
item: {
|
||||
type: 'comment',
|
||||
data: {
|
||||
content: 'stuff'
|
||||
},
|
||||
item_id: '123'
|
||||
},
|
||||
item_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'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe ('UPDATE_ITEM', () => {
|
||||
it ('should update an item', () => {
|
||||
const action = {
|
||||
type: 'UPDATE_ITEM',
|
||||
property: 'stuff',
|
||||
value: 'things',
|
||||
item_id: '123'
|
||||
};
|
||||
const store = fromJS({
|
||||
'123': {
|
||||
item_id: '123',
|
||||
data: {
|
||||
stuff: 'morestuff'
|
||||
}
|
||||
}
|
||||
});
|
||||
const result = itemsReducer(store, action);
|
||||
expect(result.get('123').toJS()).to.deep.equal({
|
||||
item_id: '123',
|
||||
data: {
|
||||
stuff: 'things'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('APPEND_ITEM_ARRAY', () => {
|
||||
let action;
|
||||
let store;
|
||||
|
||||
beforeEach (() => {
|
||||
action = {
|
||||
type: 'APPEND_ITEM_ARRAY',
|
||||
property: 'stuff',
|
||||
value: 'things',
|
||||
item_id: '123'
|
||||
};
|
||||
store = fromJS({
|
||||
'123': {
|
||||
item_id: '123',
|
||||
data: {
|
||||
stuff: ['morestuff']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
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']
|
||||
}
|
||||
});
|
||||
});
|
||||
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']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
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']
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -21,13 +21,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
|
||||
};
|
||||
};
|
||||
@@ -42,23 +43,24 @@ export const addItem = (item) => {
|
||||
* value - the value that the property should be set to
|
||||
*
|
||||
*/
|
||||
|
||||
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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -80,39 +82,49 @@ export function getStream (assetId) {
|
||||
return fetch(`/api/v1/stream?asset_id=${assetId}`)
|
||||
.then(
|
||||
response => {
|
||||
return response.ok ? response.json() : Promise.reject(`${response.status } ${ response.statusText}`);
|
||||
return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
)
|
||||
.then((json) => {
|
||||
|
||||
/* 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.forEach(item => {
|
||||
dispatch(addItem(item));
|
||||
/* Add items to the store */
|
||||
const itemTypes = Object.keys(json);
|
||||
for (let i = 0; i < itemTypes.length; i++ ) {
|
||||
for (let j = 0; j < json[itemTypes[i]].length; j++ ) {
|
||||
dispatch(addItem(json[itemTypes[i]][j], itemTypes[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/* Sort comments by date*/
|
||||
json.comments.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
||||
const rels = json.comments.reduce((h, item) => {
|
||||
/* Check for root and child comments. */
|
||||
if (
|
||||
item.asset_id === assetId &&
|
||||
!item.parent_id) {
|
||||
rootComments.push(item.id);
|
||||
h.rootComments.push(item.id);
|
||||
} else if (
|
||||
item.asset_id === assetId
|
||||
) {
|
||||
let children = childComments[item.parent_id] || [];
|
||||
childComments[item.parent_id] = children.concat(item.id);
|
||||
let children = h.childComments[item.parent_id] || [];
|
||||
h.childComments[item.parent_id] = children.concat(item.id);
|
||||
}
|
||||
}, {});
|
||||
return h;
|
||||
}, {rootComments: [], childComments: {}});
|
||||
|
||||
dispatch(addItem({
|
||||
id: assetId,
|
||||
comments: rootComments
|
||||
}));
|
||||
comments: rels.rootComments,
|
||||
}, 'assets'));
|
||||
|
||||
const keys = Object.keys(childComments);
|
||||
for (let i = 0; i < keys.length; i++ ) {
|
||||
dispatch(updateItem(keys[i], 'children', childComments[keys[i]].reverse()));
|
||||
const childKeys = Object.keys(rels.childComments);
|
||||
for (let i = 0; i < childKeys.length; i++ ) {
|
||||
dispatch(updateItem(childKeys[i], 'children', rels.childComments[childKeys[i]].reverse(), 'comments'));
|
||||
}
|
||||
|
||||
/* Hydrate actions on comments */
|
||||
for (let i = 0; i < json.actions.length; i++ ) {
|
||||
dispatch(updateItem(json.actions[i].item_id, json.actions[i].action_type, json.actions[i].id, 'comments'));
|
||||
}
|
||||
|
||||
return (json);
|
||||
@@ -178,16 +190,15 @@ export function postItem (item, type, id) {
|
||||
'Content-Type':'application/json'
|
||||
}
|
||||
};
|
||||
console.log('postItem', options);
|
||||
return fetch(`/api/v1/${ type}`, options)
|
||||
return fetch(`/api/v1/${type}`, options)
|
||||
.then(
|
||||
response => {
|
||||
return response.ok ? response.json()
|
||||
: Promise.reject(`${response.status } ${ response.statusText}`);
|
||||
: Promise.reject(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
)
|
||||
.then((json) => {
|
||||
dispatch(addItem({...item, id:json.id}));
|
||||
dispatch(addItem({...item, id:json.id}, type));
|
||||
return json.id;
|
||||
});
|
||||
};
|
||||
@@ -210,24 +221,28 @@ export function postItem (item, type, id) {
|
||||
*
|
||||
*/
|
||||
|
||||
export function postAction (id, type, user_id) {
|
||||
return (dispatch) => {
|
||||
export function postAction (item_id, action_type, user_id, item_type) {
|
||||
return () => {
|
||||
const action = {
|
||||
type,
|
||||
action_type,
|
||||
user_id
|
||||
};
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type':'application/json'
|
||||
},
|
||||
body: JSON.stringify(action)
|
||||
};
|
||||
|
||||
dispatch(appendItemArray(id, type, user_id));
|
||||
return fetch(`/api/v1/comments/${ id }/actions`, options)
|
||||
return fetch(`/api/v1/${item_type}/${item_id}/actions`, options)
|
||||
.then(
|
||||
response => {
|
||||
return response.ok ? response.text()
|
||||
: Promise.reject(`${response.status } ${ response.statusText}`);
|
||||
return response.ok ? response.json()
|
||||
: Promise.reject(`${response.status} ${response.statusText}`);
|
||||
}
|
||||
);
|
||||
).then((json)=>{
|
||||
return json;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,26 +3,27 @@
|
||||
import {fromJS} from 'immutable';
|
||||
import * as actions from '../actions/items';
|
||||
|
||||
const initialState = fromJS({});
|
||||
const initialState = fromJS({
|
||||
comments: {},
|
||||
users: {},
|
||||
actions: {}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -26,16 +26,19 @@ class CommentBox extends Component {
|
||||
username: this.state.username
|
||||
};
|
||||
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));
|
||||
appendItemArray(parent_id || id, related, comment_id, !parent_id, parent_type);
|
||||
addNotification('success', 'Your comment has been posted.');
|
||||
}).catch((err) => console.error(err));
|
||||
this.setState({body: ''});
|
||||
@@ -45,9 +48,9 @@ class CommentBox extends Component {
|
||||
const {styles, reply} = this.props;
|
||||
// How to handle language in plugins? Should we have a dependency on our central translation file?
|
||||
return <div>
|
||||
<div className={`${name }-container`}>
|
||||
<div className={`${name}-container`}>
|
||||
<input type='text'
|
||||
className={`${name }-username`}
|
||||
className={`${name}-username`}
|
||||
style={styles && styles.textarea}
|
||||
value={this.state.username}
|
||||
id={reply ? 'replyUser' : 'commentUser'}
|
||||
@@ -55,7 +58,7 @@ class CommentBox extends Component {
|
||||
onChange={(e) => this.setState({username: e.target.value})}/>
|
||||
</div>
|
||||
<div
|
||||
className={`${name }-container`}>
|
||||
className={`${name}-container`}>
|
||||
<label
|
||||
htmlFor={ reply ? 'replyText' : 'commentText'}
|
||||
className="screen-reader-text"
|
||||
@@ -63,7 +66,7 @@ class CommentBox extends Component {
|
||||
{reply ? lang.t('reply') : lang.t('comment')}
|
||||
</label>
|
||||
<textarea
|
||||
className={`${name }-textarea`}
|
||||
className={`${name}-textarea`}
|
||||
style={styles && styles.textarea}
|
||||
value={this.state.body}
|
||||
placeholder='Comment'
|
||||
@@ -71,9 +74,9 @@ class CommentBox extends Component {
|
||||
onChange={(e) => this.setState({body: e.target.value})}
|
||||
rows={3}/>
|
||||
</div>
|
||||
<div className={`${name }-button-container`}>
|
||||
<div className={`${name}-button-container`}>
|
||||
<button
|
||||
className={`${name }-button`}
|
||||
className={`${name}-button`}
|
||||
style={styles && styles.button}
|
||||
onClick={this.postComment}>
|
||||
{lang.t('post')}
|
||||
|
||||
@@ -4,7 +4,7 @@ const name = 'coral-plugin-replies';
|
||||
const Content = ({body, styles}) => {
|
||||
const textbreaks = body.split('\n');
|
||||
return <div
|
||||
className={`${name }-text`}
|
||||
className={`${name}-text`}
|
||||
style={styles && styles.text}>
|
||||
{
|
||||
textbreaks.map((line, i) => <span key={i} className={`${name}-line`}>
|
||||
|
||||
@@ -2,13 +2,17 @@ import React from 'react';
|
||||
|
||||
const name = 'coral-plugin-flags';
|
||||
|
||||
const FlagButton = ({flag, item_id, postAction, currentUser, addNotification}) => {
|
||||
const flagged = flag && flag.includes(currentUser);
|
||||
const FlagButton = ({flag, id, postAction, addItem, updateItem, addNotification}) => {
|
||||
const flagged = flag && flag.current_user;
|
||||
const onFlagClick = () => {
|
||||
postAction(item_id, 'flag', currentUser);
|
||||
postAction(id, 'flag', '123', 'comments')
|
||||
.then((action) => {
|
||||
addItem({...action, current_user:true}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, action.id, 'comments');
|
||||
});
|
||||
addNotification('success', 'Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.');
|
||||
|
||||
};
|
||||
|
||||
return <div className={`${name }-container`}>
|
||||
<button onClick={onFlagClick} className={`${name }-button`}>
|
||||
<i className={`${name }-icon material-icons`}
|
||||
|
||||
@@ -4,9 +4,9 @@ import {I18n} from '../coral-framework';
|
||||
const name = 'coral-plugin-replies';
|
||||
|
||||
const ReplyButton = (props) => <button
|
||||
className={`${name }-reply-button`}
|
||||
onClick={() => props.updateItem(props.id || props.parent_id, 'showReply', true)}>
|
||||
<i className={`${name }-icon material-icons`}
|
||||
className={`${name}-reply-button`}
|
||||
onClick={() => props.updateItem(props.id || props.parent_id, 'showReply', true, 'comments')}>
|
||||
<i className={`${name}-icon material-icons`}
|
||||
aria-hidden={true}>reply</i>
|
||||
{lang.t('reply')}
|
||||
</button>;
|
||||
|
||||
@@ -38,6 +38,38 @@ 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] = {
|
||||
id: action.id,
|
||||
item_type: action.item_type,
|
||||
action_type: action.action_type,
|
||||
count: 1,
|
||||
current_user: false //Update 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;
|
||||
}, []);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Finds all comments for a specific action.
|
||||
* @param {String} action_type type of action
|
||||
* @param {String} item_type type of item the action is on
|
||||
|
||||
+295
-23
@@ -1,45 +1,317 @@
|
||||
|
||||
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
|
||||
unique: true,
|
||||
required: true
|
||||
},
|
||||
display_name: String,
|
||||
auth_user_id: String
|
||||
displayName: String,
|
||||
disabled: Boolean,
|
||||
password: String,
|
||||
profiles: [{
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}],
|
||||
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)
|
||||
* 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 local users.
|
||||
* @param {Array} users Users to create
|
||||
* @return {Promise} Resolves with the users that were created
|
||||
*/
|
||||
UserSchema.statics.createLocalUsers = function(users) {
|
||||
return Promise.all(users.map((user) => {
|
||||
return User
|
||||
.createLocalUser(user.email, user.password, user.displayName);
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (!email) {
|
||||
return Promise.reject('email is required');
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
return Promise.reject('password is required');
|
||||
}
|
||||
|
||||
if (!displayName) {
|
||||
return Promise.reject('displayName is required');
|
||||
}
|
||||
|
||||
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 a user with the id.
|
||||
* @param {String} id user id (uuid)
|
||||
*/
|
||||
UserProfileSchema.statics.findById = function(id) {
|
||||
return UserProfile.findOne({id});
|
||||
UserSchema.statics.findById = function(id) {
|
||||
return User.findOne({id});
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds users in an array of idd.
|
||||
* @param {String} idd array of user identifiers (uuid)
|
||||
* @param {Array} ids 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;
|
||||
|
||||
+7
-3
@@ -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;
|
||||
|
||||
+19
-7
@@ -7,17 +7,19 @@
|
||||
"start": "./bin/www",
|
||||
"build": "webpack --config webpack.config.js --bail",
|
||||
"build-watch": "webpack --config webpack.config.dev.js --watch",
|
||||
"lint": "eslint .",
|
||||
"lint": "eslint bin/* .",
|
||||
"lint-fix": "eslint . --fix",
|
||||
"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": {
|
||||
"pre-git": {
|
||||
"commit-msg": [],
|
||||
"pre-commit": [
|
||||
"npm run lint"
|
||||
"npm run lint",
|
||||
"npm test"
|
||||
],
|
||||
"pre-push": [
|
||||
"npm test"
|
||||
@@ -43,20 +45,22 @@
|
||||
},
|
||||
"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",
|
||||
"eslint-config-postcss": "^2.0.2",
|
||||
"express": "^4.14.0",
|
||||
"mongoose": "^4.6.5",
|
||||
"morgan": "^1.7.0",
|
||||
"prompt": "^1.0.0",
|
||||
"uuid": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.5.2",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-eslint": "^7.1.0",
|
||||
"babel-jest": "^15.0.0",
|
||||
"autoprefixer": "^6.5.0",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-loader": "^6.2.7",
|
||||
"babel-plugin-transform-async-to-generator": "^6.16.0",
|
||||
"babel-plugin-transform-class-properties": "^6.18.0",
|
||||
@@ -73,8 +77,15 @@
|
||||
"copy-webpack-plugin": "^4.0.0",
|
||||
"css-loader": "^0.25.0",
|
||||
"eslint": "^3.9.1",
|
||||
"eslint-config-postcss": "^2.0.2",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-flowtype": "^2.25.0",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^6.6.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"exports-loader": "^0.6.3",
|
||||
"fetch-mock": "^5.5.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
"immutable": "^3.8.1",
|
||||
"imports-loader": "^0.6.5",
|
||||
@@ -95,6 +106,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",
|
||||
|
||||
@@ -28,10 +28,14 @@ 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.status(200).json([...comments, ...users, ...actions]);
|
||||
res.json({
|
||||
comments,
|
||||
users,
|
||||
actions
|
||||
});
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"mocha": true
|
||||
},
|
||||
"extends": "../.eslintrc.json",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"parser": "babel-eslint",
|
||||
"plugins": [
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": "error",
|
||||
"react/jsx-uses-vars": "error"
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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', () => {
|
||||
+59
-44
@@ -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';
|
||||
@@ -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';
|
||||
describe('getStream', () => {
|
||||
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'}
|
||||
]}
|
||||
]};
|
||||
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', () => {
|
||||
it('should get an stream from an asset_id and send the appropriate dispatches', () => {
|
||||
fetchMock.get('*', JSON.stringify(response));
|
||||
return actions.getItemsQuery(query, rootId, view, host)(store.dispatch)
|
||||
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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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,17 @@ 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', 'comments')(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.deep.equal({id:'456'});
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import {Map, fromJS} from 'immutable';
|
||||
import {expect} from 'chai';
|
||||
import itemsReducer from '../../../../client/coral-framework/store/reducers/items';
|
||||
|
||||
describe ('itemsReducer', () => {
|
||||
describe('ADD_ITEM', () => {
|
||||
it('should add an item', () => {
|
||||
const action = {
|
||||
type: 'ADD_ITEM',
|
||||
item: {
|
||||
body: 'stuff',
|
||||
id: '123'
|
||||
},
|
||||
item_type: 'comments',
|
||||
id: '123'
|
||||
};
|
||||
const store = new Map({});
|
||||
const result = itemsReducer(store, action);
|
||||
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
|
||||
body: 'stuff',
|
||||
id: '123'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe ('UPDATE_ITEM', () => {
|
||||
it ('should update an item', () => {
|
||||
const action = {
|
||||
type: 'UPDATE_ITEM',
|
||||
property: 'stuff',
|
||||
value: 'things',
|
||||
item_type: 'comments',
|
||||
id: '123'
|
||||
};
|
||||
const store = fromJS({
|
||||
'comments': {
|
||||
'123': {
|
||||
id: '123',
|
||||
stuff: 'morestuff'
|
||||
}
|
||||
}
|
||||
});
|
||||
const result = itemsReducer(store, action);
|
||||
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
|
||||
id: '123',
|
||||
stuff: 'things'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('APPEND_ITEM_ARRAY', () => {
|
||||
let action;
|
||||
let store;
|
||||
|
||||
beforeEach (() => {
|
||||
action = {
|
||||
type: 'APPEND_ITEM_ARRAY',
|
||||
property: 'stuff',
|
||||
value: 'things',
|
||||
id: '123',
|
||||
item_type: 'comments'
|
||||
};
|
||||
store = fromJS({
|
||||
'comments': {
|
||||
'123': {
|
||||
id: '123',
|
||||
stuff: ['morestuff']
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
it ('should append to an existing array', () => {
|
||||
const result = itemsReducer(store, action);
|
||||
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
|
||||
id: '123',
|
||||
stuff: ['morestuff', 'things']
|
||||
});
|
||||
});
|
||||
it ('should create a new array', () => {
|
||||
store = fromJS({
|
||||
'comments': {
|
||||
'123': {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
});
|
||||
const result = itemsReducer(store, action);
|
||||
expect(result.getIn(['comments', '123']).toJS()).to.deep.equal({
|
||||
id: '123',
|
||||
stuff: ['things']
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
+2
-2
@@ -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', () => {
|
||||
+35
-3
@@ -8,13 +8,20 @@ describe('Action: models', () => {
|
||||
beforeEach(() => {
|
||||
return Action.create([{
|
||||
action_type: 'flag',
|
||||
item_id: '123'
|
||||
item_id: '123',
|
||||
item_type: 'comments'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: '789'
|
||||
item_id: '789',
|
||||
item_type: 'comments'
|
||||
}, {
|
||||
action_type: 'flag',
|
||||
item_id: '456'
|
||||
item_id: '456',
|
||||
item_type: 'comments'
|
||||
}, {
|
||||
action_type: 'flag',
|
||||
item_id: '123',
|
||||
item_type: 'comments'
|
||||
}]).then((actions) => {
|
||||
mockActions = actions;
|
||||
});
|
||||
@@ -32,7 +39,32 @@ 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);
|
||||
delete sorted[0].id;
|
||||
delete sorted[1].id;
|
||||
expect(sorted[0]).to.deep.equal({
|
||||
action_type: 'like',
|
||||
count: 1,
|
||||
item_id: '789',
|
||||
item_type: 'comments',
|
||||
current_user: false
|
||||
});
|
||||
expect(sorted[1]).to.deep.equal({
|
||||
action_type: 'flag',
|
||||
count: 2,
|
||||
item_id: '123',
|
||||
item_type: 'comments',
|
||||
current_user: false
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+12
-11
@@ -41,11 +41,13 @@ describe('Comment: models', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
email: 'stampi@gmail.com',
|
||||
displayName: 'Stampi',
|
||||
password: '1Coral!'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
email: 'sockmonster@gmail.com',
|
||||
displayName: 'Sockmonster',
|
||||
password: '2Coral!'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -61,13 +63,12 @@ describe('Comment: models', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Setting.create(settings).then(() => {
|
||||
return Comment.create(comments);
|
||||
}).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Setting.create(settings),
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
describe('#findById()', () => {
|
||||
|
||||
+37
-8
@@ -6,12 +6,18 @@ const expect = require('chai').expect;
|
||||
describe('User: models', () => {
|
||||
let mockUsers;
|
||||
beforeEach(() => {
|
||||
return User.create([{
|
||||
display_name: 'Stampi',
|
||||
return User.createLocalUsers([{
|
||||
email: 'stampi@gmail.com',
|
||||
displayName: 'Stampi',
|
||||
password: '1Coral!'
|
||||
}, {
|
||||
display_name: 'Sockmonster',
|
||||
email: 'sockmonster@gmail.com',
|
||||
displayName: 'Sockmonster',
|
||||
password: '2Coral!'
|
||||
}, {
|
||||
display_name: 'Marvel',
|
||||
email: 'marvel@gmail.com',
|
||||
displayName: 'Marvel',
|
||||
password: '3Coral!'
|
||||
}]).then((users) => {
|
||||
mockUsers = users;
|
||||
});
|
||||
@@ -19,10 +25,12 @@ describe('User: models', () => {
|
||||
|
||||
describe('#findById()', () => {
|
||||
it('should find a user by id', () => {
|
||||
return User.findById(mockUsers[0].id).then((result) => {
|
||||
expect(result).to.have.property('display_name')
|
||||
.and.to.equal('Stampi');
|
||||
});
|
||||
return User
|
||||
.findById(mockUsers[0].id)
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('displayName')
|
||||
.and.to.equal('Stampi');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,4 +42,25 @@ describe('User: models', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findLocalUser', () => {
|
||||
|
||||
it('should find a user when we give the right credentials', () => {
|
||||
return User
|
||||
.findLocalUser(mockUsers[0].profiles[0].id, '1Coral!')
|
||||
.then((user) => {
|
||||
expect(user).to.have.property('displayName')
|
||||
.and.to.equal(mockUsers[0].displayName);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not find the user when we give the wrong credentials', () => {
|
||||
return User
|
||||
.findLocalUser(mockUsers[0].profiles[0].id, '1Coral!<nope>')
|
||||
.then((user) => {
|
||||
expect(user).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,11 +39,13 @@ describe('Get /comments', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -55,11 +57,11 @@ describe('Get /comments', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the comments', function(done){
|
||||
@@ -93,11 +95,13 @@ describe('Get moderation queues rejected, pending, flags', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -111,11 +115,11 @@ describe('Get moderation queues rejected, pending, flags', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', function(done){
|
||||
@@ -179,11 +183,13 @@ describe('Get moderation queues rejected, pending, flags', () => {
|
||||
|
||||
describe('Post /comments', () => {
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -195,9 +201,10 @@ describe('Post /comments', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return User.create(users).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should create a comment', function(done) {
|
||||
@@ -230,11 +237,13 @@ describe('Get /:comment_id', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -248,11 +257,11 @@ describe('Get /:comment_id', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the right comment for the comment_id', function(done){
|
||||
@@ -287,11 +296,13 @@ describe('Put /:comment_id', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -303,11 +314,11 @@ describe('Put /:comment_id', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should update comment', function(done) {
|
||||
@@ -342,11 +353,13 @@ describe('Remove /:comment_id', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -358,27 +371,32 @@ describe('Remove /:comment_id', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should remove comment', function(done) {
|
||||
chai.request(app)
|
||||
it('it should remove comment', () => {
|
||||
return chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
Comment.findById('abc').then((comment) => {
|
||||
expect(comment).to.be.empty;
|
||||
});
|
||||
done();
|
||||
|
||||
return Comment.findById('abc');
|
||||
})
|
||||
.then((comment) => {
|
||||
expect(comment).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('Reason: ');
|
||||
console.error(reason);
|
||||
});
|
||||
|
||||
describe('Post /:comment_id/status', () => {
|
||||
|
||||
const comments = [{
|
||||
@@ -401,11 +419,13 @@ describe('Post /:comment_id/status', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -417,23 +437,21 @@ describe('Post /:comment_id/status', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should update status', function(done) {
|
||||
chai.request(app)
|
||||
it('it should update status', function() {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/status')
|
||||
.send({'status': 'accepted'})
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('status', 'accepted');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -460,11 +478,13 @@ describe('Post /:comment_id/actions', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'Ana',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Maria',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -476,26 +496,24 @@ describe('Post /:comment_id/actions', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should update actions', function(done) {
|
||||
chai.request(app)
|
||||
it('it should update actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/actions')
|
||||
.send({'user_id': '456', 'action_type': 'flag'})
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('item_type', 'comment');
|
||||
expect(res.body).to.have.property('action_type', 'flag');
|
||||
expect(res.body).to.have.property('item_id', 'abc');
|
||||
expect(res.body).to.have.property('user_id', '456');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,14 +22,14 @@ describe('api/stream: routes', () => {
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456',
|
||||
author_id: '',
|
||||
parent_id: '',
|
||||
status: ''
|
||||
}, {
|
||||
@@ -47,11 +47,13 @@ describe('api/stream: routes', () => {
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
id: '123',
|
||||
display_name: 'John',
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
id: '456',
|
||||
display_name: 'Paul',
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
@@ -63,24 +65,33 @@ describe('api/stream: routes', () => {
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Setting.create(settings).then(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
|
||||
return User
|
||||
.createLocalUsers(users)
|
||||
.then(users => {
|
||||
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should return a stream with comments, users and actions', function(done){
|
||||
chai.request(app)
|
||||
it('should return a stream with comments, users and actions', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
.query({'asset_id': 'asset'})
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.then(res => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body.length).to.equal(3);
|
||||
done();
|
||||
expect(res.body.comments.length).to.equal(1);
|
||||
expect(res.body.users.length).to.equal(1);
|
||||
expect(res.body.actions.length).to.equal(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user