Merging and updating tests

This commit is contained in:
David Erwin
2016-11-10 12:16:58 -05:00
22 changed files with 4746 additions and 246 deletions
+6 -1
View File
@@ -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');
Executable
+29
View File
@@ -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();
}
+42
View File
@@ -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
View File
@@ -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
View File
@@ -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
});
+13 -15
View File
@@ -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}`);
}
+1 -1
View File
@@ -19,7 +19,7 @@ export default props => (
<div className={styles.actions}>
{props.actions.map(action => canShowAction(action, props.comment) ? (
<Button className={styles.actionButton}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('_id'))}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
fab colored>
<Icon name={props.actionsMap[action].icon} />
</Button>
@@ -7,8 +7,8 @@ import Comment from 'components/Comment';
// Each action has different meaning and configuration
const actions = {
'reject': {status: 'Rejected', icon: 'close', key: 'r'},
'approve': {status: 'Approved', icon: 'done', key: 't'},
'reject': {status: 'rejected', icon: 'close', key: 'r'},
'approve': {status: 'accepted', icon: 'done', key: 't'},
'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'}
};
+3 -4
View File
@@ -31,9 +31,8 @@ export default (state = initialState, action) => {
// Update a comment status
const updateStatus = (state, action) => {
const byId = state.get('byId');
const data = byId.get(action.id).get('data').set('status', action.status);
const comment = byId.get(action.id).set('data', data);
return state.set('byId', byId.set(action.id, comment));
const data = byId.get(action.id).set('status', action.status.toLowerCase());
return state.set('byId', byId.set(action.id, data));
};
// Flag a comment
@@ -46,7 +45,7 @@ const flag = (state, action) => {
// Replace the comment list with a new one
const replaceComments = (action, state) => {
const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr._id] = curr; return prev; }, {}));
const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {}));
return state.set('byId', comments).set('loading', false)
.set('ids', List(comments.keys()));
};
@@ -7,8 +7,12 @@
* for the coral but also for wordpress comments, disqus and many more.
*/
// Default headers for json payloads.
const jsonHeader = new Headers({'Content-Type': 'application/json'});
// Intercept redux actions and act over the ones we are interested
export default store => next => action => {
switch (action.type) {
case 'COMMENTS_MODERATION_QUEUE_FETCH':
fetchModerationQueueComments(store);
@@ -41,14 +45,17 @@ 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._id }/status`, {
method: 'POST',
body: JSON.stringify({status: comment.status})
})
.then(res => res.json())
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
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}));
};
// Create a new comment
const createComment = (store, name, comment) =>
File diff suppressed because it is too large Load Diff
+33 -23
View File
@@ -1,7 +1,6 @@
const mongoose = require('../mongoose');
const uuid = require('uuid');
const Action = require('./action');
const Setting = require('./setting');
const Schema = mongoose.Schema;
@@ -58,13 +57,30 @@ CommentSchema.statics.findById = function(id) {
};
/**
* Finds a comment by the asset_id.
* Finds ALL the comments by the asset_id.
* @param {String} asset_id identifier of the asset which owns this comment (uuid)
*/
CommentSchema.statics.findByAssetId = function(asset_id) {
return Comment.find({asset_id});
};
/**
* Finds the accepted comments by the asset_id.
* get the comments that are accepted.
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
*/
CommentSchema.statics.findAcceptedByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status:'accepted'});
};
/**
* Finds the new and accepted comments by the asset_id.
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
*/
CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}});
};
/**
* Find comments by an action that was performed on them.
* @param {String} action_type the type of action that was performed on the comment
@@ -98,27 +114,21 @@ CommentSchema.statics.findByStatus = function(status) {
* Find comments that need to be moderated (aka moderation queue).
* @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings.
*/
CommentSchema.statics.moderationQueue = function(moderationValue) {
return Setting.getModerationSetting().then(function({moderation}){
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
moderationValue = moderation;
}
switch(moderationValue){
// Pre-moderation: New comments are shown in the moderator queues immediately.
case 'pre':
return Comment.findByStatus('').then((comments) => {
return comments;
});
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
case 'post':
return Comment.findByStatusByActionType('', 'flag').then((comments) => {
return comments;
});
default:
throw new Error('Moderation setting not found.');
}
});
CommentSchema.statics.moderationQueue = function(moderation) {
switch(moderation){
// Pre-moderation: New comments are shown in the moderator queues immediately.
case 'pre':
return Comment.findByStatus('').then((comments) => {
return comments;
});
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
case 'post':
return Comment.findByStatusByActionType('', 'flag').then((comments) => {
return comments;
});
default:
throw new Error('Moderation setting not found.');
}
};
//==============================================================================
+295 -23
View File
@@ -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
View File
@@ -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;
+6 -2
View File
@@ -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/* .",
"lint-fix": "eslint . --fix",
"pretest": "npm install",
"test": "mocha --compilers js:babel-core/register --recursive tests",
@@ -18,7 +18,8 @@
"pre-git": {
"commit-msg": [],
"pre-commit": [
"npm run lint"
"npm run lint",
"npm test"
],
"pre-push": [
"npm test"
@@ -44,12 +45,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": {
+16 -14
View File
@@ -1,6 +1,8 @@
const express = require('express');
const Comment = require('../../../models/comment');
const Setting = require('../../../models/setting');
const router = express.Router();
//==============================================================================
@@ -50,8 +52,14 @@ router.get('/status/rejected', (req, res, next) => {
// Pre-moderation: New comments are shown in the moderator queues immediately.
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
router.get('/status/pending', (req, res, next) => {
Comment.moderationQueue(req.query.moderation).then((comments) => {
res.status(200).json(comments);
Setting.getModerationSetting().then(function({moderation}){
let moderationValue = req.query.moderation;
if (typeof moderationValue === 'undefined' || moderationValue === undefined) {
moderationValue = moderation;
}
Comment.moderationQueue(moderationValue).then((comments) => {
res.status(200).json(comments);
});
}).catch(error => {
next(error);
});
@@ -68,13 +76,6 @@ router.post('/', (req, res, next) => {
}).catch(error => {
next(error);
});
// let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
// comment.save().then(({id}) => {
// res.status(200).send({'id': id});
// }).catch(error => {
// next(error);
// });
});
router.post('/:comment_id', (req, res, next) => {
@@ -93,11 +94,12 @@ router.post('/:comment_id', (req, res, next) => {
});
router.post('/:comment_id/status', (req, res, next) => {
Comment.changeStatus(req.params.comment_id, req.body.status).then((comment) => {
res.status(200).send(comment);
}).catch(error => {
next(error);
});
Comment
.changeStatus(req.params.comment_id, req.body.status)
.then(comment => res.status(200).send(comment))
.catch(error => next(error));
});
router.post('/:comment_id/actions', (req, res, next) => {
+16 -2
View File
@@ -4,12 +4,26 @@ const Comment = require('../../../models/comment');
const User = require('../../../models/user');
const Action = require('../../../models/action');
const Setting = require('../../../models/setting');
const router = express.Router();
// Find all the comments by a specific asset_id.
// . if pre: get the comments that are accepted.
// . if post: get the comments that are new and accepted.
router.get('/', (req, res, next) => {
const commentsPromise = Setting.getModerationSetting().then(({moderation}) => {
switch(moderation){
case 'pre':
return Comment.findAcceptedByAssetId(req.query.asset_id);
case 'post':
return Comment.findAcceptedAndNewByAssetId(req.query.asset_id);
default:
throw new Error('Moderation setting not found.');
}
});
const commentsPromise = Comment.findByAssetId(req.query.asset_id);
// Get all the users and actions for those comments.
commentsPromise.then(comments => {
return Promise.all([
comments,
+63 -21
View File
@@ -27,40 +27,48 @@ describe('Comment: models', () => {
}, {
body: 'comment 30',
asset_id: '456',
status: 'rejected',
status: '',
parent_id: '',
author_id: '456',
id: '3'
}, {
body: 'comment 40',
asset_id: '123',
status: 'rejected',
parent_id: '',
author_id: '456',
id: '4'
}];
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 = [{
action_type: 'flag',
item_id: comments[0].id,
item_id: '3',
item_type: 'comment',
user_id: '123'
}, {
action_type: 'like',
item_id: comments[1].id,
item_id: '1',
item_type: 'comment',
user_id: '456'
}];
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()', () => {
@@ -73,23 +81,57 @@ describe('Comment: models', () => {
});
describe('#findByAssetId()', () => {
it('should find an array of comments by asset id', () => {
it('should find an array of all comments by asset id', () => {
return Comment.findByAssetId('123').then((result) => {
expect(result).to.have.length(2);
expect(result).to.have.length(3);
result.sort((a, b) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
expect(result[0]).to.have.property('body', 'comment 10');
expect(result[1]).to.have.property('body', 'comment 20');
expect(result[2]).to.have.property('body', 'comment 40');
});
});
it('should find an array of accepted comments by asset id', () => {
return Comment.findAcceptedByAssetId('123').then((result) => {
expect(result).to.have.length(1);
result.sort((a, b) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
expect(result[0]).to.have.property('body', 'comment 20');
});
});
it('should find an array of new and accepted comments by asset id', () => {
return Comment.findAcceptedAndNewByAssetId('123').then((result) => {
expect(result).to.have.length(2);
result.sort((a, b) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
expect(result[0]).to.have.property('body', 'comment 10');
});
});
});
describe('#moderationQueue()', () => {
it('should find an array of new comments to moderate when pre-moderation');
it('should find an array of new comments to moderate when post-moderation');
it('should find an array of new comments to moderate when pre-moderation in settings');
it('should find an array of new comments to moderate when post-moderation in settings');
it('should fail when the moderation is not pre or post');
it('should find an array of new comments to moderate when pre-moderation', () => {
return Comment.moderationQueue('pre').then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.lengthOf(2);
});
});
it('should find an array of new comments to moderate when post-moderation', () => {
return Comment.moderationQueue('post').then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.lengthOf(1);
expect(result[0]).to.have.property('body', 'comment 30');
});
});
// it('should fail when the moderation is not pre or post', () => {
// return Comment.moderationQueue('any').catch(function(error) {
// expect(error).to.not.be.null;
// });
// });
});
});
+7
View File
@@ -29,4 +29,11 @@ describe('Setting: model', () => {
});
});
describe('#getModerationSetting', () => {
it('should return the moderation settings', () => {
return Setting.getModerationSetting().then(({moderation}) => {
expect(moderation).not.to.be.null;
});
});
});
});
+37 -8
View File
@@ -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);
});
});
});
});
+131 -89
View File
@@ -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){
@@ -140,6 +144,30 @@ describe('Get moderation queues rejected, pending, flags', () => {
});
});
it('should return all the pending comments as pre moderated', function(done){
chai.request(app)
.get('/api/v1/comments/status/pending')
.query({'moderation': 'pre'})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body[0]).to.have.property('id', 'def');
done();
});
});
it('should return all the pending comments as post moderated', function(done){
chai.request(app)
.get('/api/v1/comments/status/pending')
.query({'moderation': 'post'})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body).to.have.lengthOf(0);
done();
});
});
it('should return all the flagged comments', function(done){
chai.request(app)
.get('/api/v1/comments/action/flag')
@@ -155,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 = [{
@@ -171,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) {
@@ -206,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 = [{
@@ -224,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){
@@ -263,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 = [{
@@ -279,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) {
@@ -318,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 = [{
@@ -334,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 = [{
@@ -377,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 = [{
@@ -393,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();
});
});
});
@@ -436,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 = [{
@@ -452,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();
});
});
});
+49 -18
View File
@@ -12,29 +12,48 @@ const Action = require('../../../../models/action');
const User = require('../../../../models/user');
const Comment = require('../../../../models/comment');
const Setting = require('../../../../models/setting');
describe('api/stream: routes', () => {
const settings = {id: '1', moderation: 'pre'};
const comments = [{
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: ''
}, {
id: 'uio',
body: 'comment 30',
asset_id: 'asset',
author_id: '456',
parent_id: '',
status: ''
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
body: 'comment 40',
asset_id: '456',
status: 'rejected'
}];
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 = [{
@@ -46,21 +65,33 @@ describe('api/stream: routes', () => {
}];
beforeEach(() => {
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);
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);
});
});
});