Merge branch 'master' of https://github.com/coralproject/talk into post-comment

This commit is contained in:
David Jay
2016-11-07 16:29:06 -05:00
38 changed files with 681 additions and 288 deletions
+14 -1
View File
@@ -45,6 +45,19 @@
"space-infix-ops": ["error"],
"no-const-assign": [2],
"no-duplicate-imports": [2],
"prefer-template": [1]
"prefer-template": [1],
"comma-spacing": [
"error",
{
"after": true
}
],
"no-var": [2],
"no-lonely-if": [2],
"curly": [2],
"no-multiple-empty-lines": [
"error",
{"max": 1}
]
}
}
+2
View File
@@ -1,6 +1,8 @@
node_modules
npm-debug.log
dist
!dist/coral-admin
dist/coral-admin/bundle.js
.DS_Store
*.iml
.env
+1 -3
View File
@@ -1,5 +1,3 @@
{
"basePath": "http://localhost:3142",
"talkHost": "http://localhost:16180",
"xeniaHost": "http://localhost:16180"
"base": "client/coral-admin"
}
+1 -3
View File
@@ -11,7 +11,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"coral-framework": "0.0.1",
"hammerjs": "2.0.8",
"immutable": "3.8.1",
"keymaster": "1.6.2",
@@ -23,8 +22,7 @@
"react-router": "^3.0.0",
"redux": "3.6.0",
"redux-thunk": "2.1.0",
"timeago.js": "2.0.2",
"xenia-driver": "0.0.4"
"timeago.js": "2.0.2"
},
"devDependencies": {
"autoprefixer": "6.5.0",
+1 -1
View File
@@ -17,6 +17,6 @@
</head>
<body>
<div id="root"></div>
<script src="http://localhost:3142/bundle.js" charset="utf-8"></script>
<script src="undefined/bundle.js" charset="utf-8"></script>
</body>
</html>
+6 -5
View File
@@ -1,7 +1,7 @@
import React from 'react'
import { Provider } from 'react-redux'
import { Layout, Content } from 'react-mdl'
import { Layout } from 'react-mdl'
import 'material-design-lite'
import { Router, Route, browserHistory } from 'react-router'
import ModerationQueue from 'containers/ModerationQueue'
@@ -10,6 +10,7 @@ import store from 'services/store'
import CommentStream from 'containers/CommentStream'
import EmbedLink from 'components/EmbedLink'
import Configure from 'containers/Configure'
import config from 'services/config'
export default class App extends React.Component {
render (props) {
@@ -19,10 +20,10 @@ export default class App extends React.Component {
<Header>
<div>
<Router history={browserHistory}>
<Route path='/' component={ModerationQueue} />
<Route path='embed' component={CommentStream} />
<Route path='embedlink' compoent={EmbedLink} />
<Route path='configure' component={Configure} />
<Route path={config.base} component={ModerationQueue} />
<Route path={`${config.base}/embed`} component={CommentStream} />
<Route path={`${config.base}/embedlink`} component={EmbedLink} />
<Route path={`${config.base}/configure`} component={Configure} />
</Router>
</div>
</Header>
+9 -8
View File
@@ -12,14 +12,14 @@ export default props => (
<div className={styles.itemHeader}>
<div className={styles.author}>
<i className={`material-icons ${styles.avatar}`}>person</i>
<span>{props.comment.get('data').get('name') || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(props.comment.get('data').get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{props.comment.get('data').get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
<span>{props.comment.get('name') || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{props.comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
<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('item_id'))}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('_id'))}
fab colored>
<Icon name={props.actionsMap[action].icon} />
</Button>
@@ -27,16 +27,17 @@ export default props => (
</div>
</div>
<div className={styles.itemBody}>
<span className={styles.body}>{props.comment.get('data').get('body')}</span>
<span className={styles.body}>{props.comment.get('body')}</span>
</div>
</li>
)
// Check if an action can be performed over a comment
const canShowAction = (action, comment) => {
const status = comment.get('data').get('status')
const flagged = comment.get('data').get('flagged')
if (action === 'flag' && (status !== 'Untouched' || flagged === true)) {
const status = comment.get('status')
const flagged = comment.get('flagged')
if (action === 'flag' && (status || flagged === true)) {
return false
}
return true
@@ -112,8 +112,9 @@ export default class CommentList extends React.Component {
}
render () {
const {singleView, actions, commentIds, comments, hideActive} = this.props
const {singleView, commentIds, comments, hideActive} = this.props
const {active} = this.state
return (
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`}>
{commentIds.map((commentId, index) => (
@@ -122,7 +123,7 @@ export default class CommentList extends React.Component {
key={index}
index={index}
onClickAction={this.onClickAction}
actions={actions}
actions={this.props.actions}
actionsMap={actions}
isActive={commentId === active}
hideActive={hideActive} />
+6 -4
View File
@@ -1,20 +1,22 @@
import React from 'react'
import { Layout, Navigation, Drawer, Header } from 'react-mdl'
import { Link } from 'react-router'
import styles from './Header.css'
import config from 'services/config'
// App header. If we add a navbar it should be here
export default (props) => (
<Layout fixedDrawer>
<Header title='Talk'>
<Navigation>
<a className={styles.navLink} href='/'>Moderate</a>
<a className={styles.navLink} href='/configure'>Configure</a>
<Link className={styles.navLink} href={`/${config.base}/`}>Moderate</Link>
<Link className={styles.navLink} href={`/${config.base}/configure`}>Configure</Link>
</Navigation>
</Header>
<Drawer>
<Navigation>
<a className={styles.navLink} href='/'>Moderate</a>
<a className={styles.navLink} href='/configure'>Configure</a>
<Link className={styles.navLink} href={`/${config.base}/`}>Moderate</Link>
<Link className={styles.navLink} href={`/${config.base}/configure`}>Configure</Link>
</Navigation>
</Drawer>
{props.children}
@@ -74,7 +74,7 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'pending'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => comments.get('byId').get(id).get('data').get('status') === 'Untouched')}
commentIds={comments.get('ids').filter(id => !comments.get('byId').get(id).get('status'))}
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
@@ -84,7 +84,7 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => comments.get('byId').get(id).get('data').get('status') === 'Rejected')}
commentIds={comments.get('ids').filter(id => comments.get('byId').get(id).get('status') === 'rejected')}
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
@@ -95,8 +95,8 @@ class ModerationQueue extends React.Component {
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => {
const data = comments.get('byId').get(id).get('data')
return data.get('status') === 'Untouched' && data.get('flagged') === true
const data = comments.get('byId').get(id)
return !data.get('status') && data.get('flagged') === true
})}
comments={comments.get('byId')}
onClickAction={(action, id) => this.onCommentAction(action, id)}
+1 -1
View File
@@ -46,7 +46,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.item_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()))
}
+13 -46
View File
@@ -7,9 +7,6 @@
* for the coral but also for wordpress comments, disqus and many more.
*/
import { talkHost, xeniaHost } from 'services/config'
import XeniaDriver from 'xenia-driver'
// Intercept redux actions and act over the ones we are interested
export default store => next => action => {
switch (action.type) {
@@ -30,64 +27,34 @@ export default store => next => action => {
next(action)
}
// Setup xenia driver
const xenia = XeniaDriver(`${xeniaHost}/v1`, {username: 'user', password: 'pass'})
// Get comments to fill each of the three lists on the mod queue
const fetchModerationQueueComments = store => xenia()
.collection('items')
.match({type: 'comment', 'data.status': 'Untouched', 'data.createdAt': { $exists: true }})
.sort(['data.createdAt', 1])
.skip(0).limit(50)
.addQuery().collection('items')
.match({type: 'comment', 'data.status': 'Rejected', 'data.createdAt': { $exists: true }})
.sort(['data.createdAt', 1])
.skip(0).limit(50)
.addQuery().collection('items')
.match({type: 'comment', 'data.status': 'Untouched', 'data.flagged': true, 'data.createdAt': { $exists: true }})
.sort(['data.createdAt', 1])
.skip(0).limit(50)
.exec()
const fetchModerationQueueComments = store =>
fetch(`/api/v1/queue`)
.then(res => res.json())
.then(res => store.dispatch({ type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
comments: res.results.map(res => res.Docs).reduce((p, c) => p.concat(c), []) }))
comments: res }))
.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(`${talkHost}/v1/item`, {
method: 'PUT',
mode: 'cors',
body: JSON.stringify(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 }))
// Create a new comment
const createComment = (store, name, comment) =>
fetch(`${talkHost}/v1/item`, {
fetch(`/api/v1/comments`, {
method: 'POST',
mode: 'cors',
body: JSON.stringify({
type: 'comment',
version: 1,
data: {
status: 'Untouched',
body: comment,
name: name,
createdAt: Date.now()
}
status: 'Untouched',
body: comment,
name: name,
createdAt: Date.now()
})
}).then(res => res.json())
.then(res => store.dispatch({ type: 'COMMENT_CREATE_SUCCESS', comment: res }))
.catch(error => store.dispatch({ type: 'COMMENT_CREATE_FAILED', error }))
// Get a comment stream. Now we don't have the concept of assets, this should
// be adapted to retrieve the current asset when the backend supports it
const fetchCommentStream = store => xenia()
.collection('items')
.match({type: 'comment', 'data.status': { $ne: 'Rejected' }, 'data.createdAt': { $exists: true }})
.sort(['data.createdAt', 1])
.skip(0).limit(100)
.exec()
.then(res => store.dispatch({ type: 'COMMENT_STREAM_FETCH_SUCCESS', comments: res.results[0].Docs }))
.catch(error => store.dispatch({ type: 'COMMENT_STREAM_FETCH_FAILED', error }))
+2 -1
View File
@@ -20,7 +20,7 @@ module.exports = {
'bundle': path.join(__dirname, 'src', 'index')
},
output: {
path: path.join(__dirname, 'public'),
path: path.join(__dirname, '..', '..', 'dist', 'coral-admin'),
filename: '[name].js'
},
module: {
@@ -41,6 +41,7 @@ module.exports = {
resolve: {
modules: [
path.resolve('./src'),
path.resolve('../'),
'node_modules'
]
},
+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<title>Talk - Coral Admin</title>
<base href="/client/coral-admin/" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
<style media="screen">
body, #root {
width: 100%;
height: 100%;
margin: 0;
background: #fff;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="bundle.js" charset="utf-8"></script>
</body>
</html>
+1 -1
View File
@@ -12,7 +12,7 @@ const ActionSchema = new Schema({
item_type: String,
item_id: String,
user_id: String
},{
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
+106
View File
@@ -0,0 +1,106 @@
const mongoose = require('../mongoose');
const uuid = require('uuid');
const Schema = mongoose.Schema;
const AssetSchema = new Schema({
id: {
type: String,
default: uuid.v4,
unique: true,
index: true
},
url: {
type: String,
unique: true,
index: true
},
type: {
type: String,
default: 'article'
},
headline: String,
summary: String,
section: String,
subsection: String,
authors: [String],
publication_date: Date
}, {
versionKey: false,
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
/**
* Search for assets. Currently only returns all.
*/
AssetSchema.statics.search = function(query) {
return Asset.find(query).exec();
};
/**
* Finds an asset by its id.
* @param {String} id identifier of the asset (uuid).
*/
AssetSchema.statics.findById = function(id) {
return Asset.findOne({id}).exec();
};
/**
* Finds a asset by its url.
* @param {String} url identifier of the asset (uuid).
*/
AssetSchema.statics.findByUrl = function(url) {
return Asset.findOne({'url': url}).exec();
};
/**
* Upserts an asset.
*/
AssetSchema.statics.upsert = function(data) {
// If an id is not sent, create one.
if (typeof data.id === 'undefined') {
data.id = uuid.v4();
}
// Perform the upsert against the id field.
let updatePromise = Asset.update({id: data.id}, data, {upsert: true}).exec()
.then(() => {
// Pull the freshly minted asset out and return.
return Asset.findById(data.id);
})
.catch((err) => {
console.error('Error upserting asset.', err);
//return new Promise(); // ??? what do we return on error?
});
return updatePromise;
};
/**
* Remove assets from the db.
* @param {String} query bson query to identify assets to be removed.
*/
AssetSchema.statics.removeAll = function(query) {
return Asset.remove(query).exec();
};
const Asset = mongoose.model('Asset', AssetSchema);
module.exports = Asset;
+3 -11
View File
@@ -24,7 +24,7 @@ const CommentSchema = new Schema({
default: ''
},
parent_id: String
},{
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
@@ -53,15 +53,7 @@ CommentSchema.statics.findByAssetId = function(asset_id) {
* @param {String} status the new status of the comment
*/
CommentSchema.statics.changeStatus = function(id, status) {
return Comment.update({'id': id}, {$set: {'status': status}}, {upsert: false}).then(() => {
Comment.findById(id).then((comment) => {
return comment;
}).catch((err) => {
console.log('Error updating status for the comment.', err);
});
}).catch((err) => {
console.log('Error updating status for the comment.', err);
});
return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}}, {upsert: false, new: true});
};
/**
@@ -71,7 +63,7 @@ CommentSchema.statics.changeStatus = function(id, status) {
*/
CommentSchema.statics.addAction = function(id, user_id, action_type) {
// check that the comment exist
var action = new Action({
let action = new Action({
action_type: action_type,
item_type: 'comment',
item_id: id,
+1 -1
View File
@@ -11,7 +11,7 @@ const UserProfileSchema = new Schema({
},
display_name: String,
auth_user_id: String
},{
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
+1 -1
View File
@@ -11,7 +11,7 @@ if (enabled('talk:db')) {
try {
mongoose.connect(url, (err) => {
if (err) throw err;
if (err) {throw err;}
console.log('Connected to MongoDB!');
});
} catch (err) {
+1 -1
View File
@@ -62,7 +62,7 @@
"babel-preset-es2015-minimal": "^2.1.0",
"babel-preset-stage-0": "^6.16.0",
"chai": "^3.5.0",
"chai-http": "^1.0.0",
"chai-http": "^3.0.0",
"copy-webpack-plugin": "^3.0.1",
"eslint": "^3.9.1",
"exports-loader": "^0.6.3",
+44
View File
@@ -0,0 +1,44 @@
const express = require('express');
const router = express.Router();
const Asset = require('../../../models/asset');
// Search assets.
router.get('/', (req, res, next) => {
let query = {};
if (typeof req.query.url !== 'undefined') {
query.url = req.query.url;
}
Asset.search(query)
.then((asset) => {
res.json(asset);
})
.catch(next);
});
// Get an asset by id
router.get('/:id', (req, res, next) => {
Asset.findById(req.params.id)
.then((asset) => {
res.json(asset);
})
.catch(next);
});
// Upsert an asset and return the affected document.
router.put('/', (req, res, next) => {
Asset.upsert(req.body)
.then((asset) => {
res.json(asset);
})
.catch(next);
});
module.exports = router;
+8 -18
View File
@@ -7,7 +7,6 @@ const router = express.Router();
// Routes
//==============================================================================
router.get('/', (req, res, next) => {
Comment.find({}).then((comments) => {
res.status(200).json(comments);
@@ -25,16 +24,10 @@ router.get('/:comment_id', (req, res, next) => {
});
router.post('/', (req, res, next) => {
let comment = new Comment({
body: req.body.body,
author_id: req.body.author_id,
asset_id: req.body.asset_id,
parent_id: req.body.parent_id,
status: req.body.status,
username: req.body.username
});
const {body, author_id, asset_id, parent_id, status, username} = req.body;
let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
comment.save().then(({id}) => {
res.status(200).send(id);
res.status(200).send({'id': id});
}).catch(error => {
next(error);
});
@@ -47,20 +40,17 @@ router.post('/:comment_id', (req, res, next) => {
comment.asset_id = req.body.asset_id;
comment.parent_id = req.body.parent_id;
comment.status = req.body.status;
comment.save().then((comment) => {
res.status(200).send(comment);
});
return comment.save();
}).then((comment) => {
res.status(200).send(comment);
}).catch(error => {
next(error);
});
});
router.delete('/:comment_id', (req, res, next) => {
Comment.findById(req.params.comment_id).then((comment) => {
comment.remove().then(() => {
res.status(201).send('OK. Deleted');
});
Comment.remove(req.params.comment_id).then(() => {
res.status(201).send('OK. Deleted');
}).catch(error => {
next(error);
});
+3 -2
View File
@@ -2,9 +2,10 @@ const express = require('express');
const router = express.Router();
router.use('/asset', require('./asset'));
router.use('/comments', require('./comments'));
router.use('/stream', require('./stream'));
router.use('/settings', require('./settings'));
router.use('/queue', require('./queue'));
router.use('/settings', require('./settings'));
router.use('/stream', require('./stream'));
module.exports = router;
+2 -1
View File
@@ -3,13 +3,14 @@ const express = require('express');
const router = express.Router();
const defaultComment = {
_id: '23423423432aa',
body: 'This is a comment!',
name: 'John Doe',
createdAt: Date.now()
};
router.get('/', (req, res) => {
const status = req.query.type || 'pending';
const status = req.query.type;
res.json([Object.assign({}, defaultComment, {status})]);
});
+1 -1
View File
@@ -17,7 +17,7 @@ router.get('/', (req, res, next) => {
Action.findByItemIdArray(comments.map((comment) => comment.id))
]);
}).then(([comments, users, actions]) => {
res.json([...comments,...users,...actions]);
res.json([...comments, ...users, ...actions]);
}).catch(error => {
next(error);
});
+62 -8
View File
@@ -3,10 +3,10 @@ info:
title: Talk API
description: A commenting platform from The Coral Project. https://coralproject.net
version: "0.0.1"
host: api.talk-coralproject.net
host: talk-stg.coralproject.net
schemes:
- https
basePath: /v1
basePath: /api/v1
produces:
- application/json
paths:
@@ -161,7 +161,35 @@ paths:
description: Settings
responses:
204:
description: Updated Setting
description: OK
/asset:
get:
tags:
- Asset
description: Get an asset by id.
responses:
200:
description: OK
put:
tags:
- Asset
description: Upsert an asset.
responses:
204:
description: OK
/asset?url={url}:
get:
tags:
- Asset
parameters:
- name: url
in: query
description: The url of the asset.
required: true
description: Get an asset by its url.
responses:
200:
description: OK
definitions:
Item:
@@ -214,11 +242,6 @@ definitions:
type: string
user_id:
type: string
Setting:
type: object
properties:
id:
type: string
moderation:
type: string
enum:
@@ -232,3 +255,34 @@ definitions:
type: string
format: date-time
description: Updated Date-Time
Asset:
type: object
properties:
id:
type: string
description: The uuid.v4 id of the asset.
url:
type: string
description: The url where the asset is found.
type:
type: string
description: What kind of asset it is.
default: article
headline:
type: string
description: The headline of the asset, inferred on initial load.
summary:
type: string
description: A summary of the asset, inferred on initial load.
section:
type: string
description: The section the asset is in.
subsection:
type: string
description: The subsection that the asset is in.
authors:
type: string
description: An array of the authors for this asset.
publication_date:
type: date
desctipion: When this asset was published.
+3 -2
View File
@@ -1,9 +1,10 @@
{
"env": {
"es6": true,
"node": true
"node": true,
"mocha": true
},
"extends": "eslint:recommended",
"extends": "../.eslintrc.json",
"rules": {
"no-undef": [0]
}
-5
View File
@@ -1,10 +1,5 @@
/* eslint-env node, mocha */
'use strict';
// require('./utils/mongoose')
const expect = require('chai').expect;
describe('Comment', () => {
describe('#add', () => {
it('should add a comment', () => {
+5 -6
View File
@@ -1,19 +1,18 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const Action = require('../../models/action');
const expect = require('chai').expect;
describe('Action: models', () => {
var mockActions;
let mockActions;
beforeEach(() => {
return Action.create([{
action_type: 'flag',
item_id: '123'
},{
}, {
action_type: 'like',
item_id: '789'
},{
}, {
action_type: 'flag',
item_id: '456'
}]).then((actions) => {
@@ -32,7 +31,7 @@ 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) => {
return Action.findByItemIdArray(['123', '456']).then((result) => {
expect(result).to.have.length(2);
});
});
+95
View File
@@ -0,0 +1,95 @@
require('../utils/mongoose');
const chai = require('chai');
const expect = chai.expect;
const server = require('../../app');
// Setup chai.
chai.should();
chai.use(require('chai-http'));
let fixture = {
'url': 'http://hhgg.com/total-perspective-vortex',
'type': 'article',
'headline': 'The Total Perspective Vortex',
'summary': 'You are an insignificant dot on an insignificant dot.',
'section': 'Everything',
'authors': ['Ford Prefect']
};
describe('Asset: models', () => {
describe('/GET Asset', () => {
describe('#get', () => {
it('It should get an empty array when there are no assets.', (done) => {
chai.request(server)
.get('/api/v1/asset')
.end((err, res) => {
if (err) {
throw new Error(err);
}
res.should.have.status(200);
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
});
// This test checks PUT and read
describe('/PUT Asset', () => {
describe('#put', () => {
it('It should save an asset and load it again.', (done) => {
chai.request(server)
.put('/api/v1/asset')
.send(fixture)
.end((err, res) => {
if (err) {
throw new Error(err);
}
res.should.have.status(200);
res.body.should.be.a('object');
// Id should be generated by the model if absent.
res.body.should.have.property('id');
// Save the asset id to compare with GET result.
let assetId = res.body.id;
// Load the asset to make sure it's really there.
chai.request(server)
.get(`/api/v1/asset?url=${encodeURIComponent(fixture.url)}`)
.end((err, res) => {
if (err) {
throw new Error(err);
}
res.should.have.status(200);
res.body.should.be.an('array');
let asset = res.body[0];
expect(asset).to.have.property('id');
// Ensure the asset has the same id as above.
// This tests the single url per Id concept.
expect(assetId).to.equal(asset.id);
done();
});
});
});
});
}); // End describe /PUT Asset
});
+4 -6
View File
@@ -1,20 +1,18 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const Comment = require('../../models/comment');
const expect = require('chai').expect;
describe('Comment: models', () => {
var mockComments;
let mockComments;
beforeEach(() => {
return Comment.create([{
body: 'comment 10',
asset_id: '123'
},{
}, {
body: 'comment 20',
asset_id: '123'
},{
}, {
body: 'comment 30',
asset_id: '456'
}]).then((comments) => {
@@ -35,7 +33,7 @@ describe('Comment: models', () => {
it('should find an array of comments by asset id', () => {
return Comment.findByAssetId('123').then((result) => {
expect(result).to.have.length(2);
result.sort((a,b) => {
result.sort((a, b) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
+32
View File
@@ -0,0 +1,32 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const Setting = require('../../models/setting');
const expect = require('chai').expect;
describe('Setting: model', () => {
beforeEach(() => {
const defaults = {id: 1, moderation: 'pre'};
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
describe('#getSettings()', () => {
it('should have a moderation field defined', () => {
return Setting.getSettings().then(settings => {
expect(settings).to.have.property('moderation').and.to.equal('pre');
});
});
});
describe('#updateSettings()', () => {
it('should update the settings with a passed object', () => {
const mockSettings = {moderation: 'post'};
return Setting.updateSettings(mockSettings).then(updatedSettings => {
expect(updatedSettings).to.have.property('moderation').and.to.equal('post');
});
});
});
});
+5 -8
View File
@@ -1,17 +1,16 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const User = require('../../models/user');
const expect = require('chai').expect;
describe('User: models', () => {
var mockUsers;
let mockUsers;
beforeEach(() => {
return User.create([{
display_name: 'Stampi',
},{
}, {
display_name: 'Sockmonster',
},{
}, {
display_name: 'Marvel',
}]).then((users) => {
mockUsers = users;
@@ -29,12 +28,10 @@ describe('User: models', () => {
describe('#findByIdArray()', () => {
it('should find an array of users from an array of ids', () => {
const ids = mockUsers.map((user) => user.id)
const ids = mockUsers.map((user) => user.id);
return User.findByIdArray(ids).then((result) => {
expect(result).to.have.length(3);
});
});
});
// });
});
+133 -116
View File
@@ -4,56 +4,56 @@ require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
var expect = chai.expect;
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const Comment = require('../../../../models/comment');
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
describe('Get /:comment_id', () => {
describe('Get /comments', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('should return all the comments', function(done){
chai.request(app)
@@ -61,46 +61,45 @@ describe('Get /:comment_id', () => {
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
//expect(res).to.have.a.length(3); // it fails
if (err) return done(err);
done();
});
})
})
});
});
describe('Post /comments', () => {
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return User.create(users).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('it should create a comment', function(done) {
chai.request(app)
.post('/api/v1/comments')
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
.end(function(err, res){
expect(res).to.have.status(200)
done()
})
})
})
expect(res).to.have.status(200);
expect(res.body).to.have.property('id');
done();
});
});
});
describe('Get /:comment_id', () => {
const comments = [{
@@ -108,40 +107,40 @@ describe('Get /:comment_id', () => {
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('should return the right comment for the comment_id', function(done){
chai.request(app)
@@ -150,11 +149,12 @@ describe('Get /:comment_id', () => {
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
if (err) return done(err);
expect(res.body[0]).to.have.property('body');
expect(res.body[0].body).to.equal('comment 10');
done();
});
})
})
});
});
describe('Put /:comment_id', () => {
@@ -163,51 +163,54 @@ describe('Put /:comment_id', () => {
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('it should update comment', function(done) {
chai.request(app)
.post('/api/v1/comments/abc')
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
.end(function(err, res){
expect(res).to.have.status(200)
done()
})
})
})
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body).to.have.property('body');
expect(res.body.body).to.equal('Something body.');
done();
});
});
});
describe('Delete /:comment_id', () => {
@@ -216,51 +219,53 @@ describe('Delete /:comment_id', () => {
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('it should remove comment', function(done) {
chai.request(app)
.delete('/api/v1/comments/abc')
.end(function(err, res){
expect(res).to.have.status(201)
done()
})
})
})
expect(res).to.have.status(201);
Comment.findById({'id': 'abc'}).then((comment) => {
expect(comment).to.be.null;
});
done();
});
});
});
describe('Post /:comment_id/status', () => {
@@ -270,55 +275,57 @@ describe('Post /:comment_id/status', () => {
asset_id: 'asset',
author_id: '123',
status: ''
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'rejected'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: 'accepted'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('it should update status', function(done) {
chai.request(app)
.post('/api/v1/comments/abc/status')
.send({'status': 'accepted'})
.end(function(res){
expect(res).to.have.status(200)
done()
})
})
})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res).to.have.body;
expect(res.body).to.have.property('status');
expect(res.body.status).to.equal('accepted');
done();
});
});
});
describe('Post /:comment_id/actions', () => {
@@ -328,50 +335,60 @@ describe('Post /:comment_id/actions', () => {
asset_id: 'asset',
author_id: '123',
status: ''
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'rejected'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: 'accepted'
}]
}];
const users = [{
id: '123',
display_name: 'Ana',
},{
}, {
id: '456',
display_name: 'Maria',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('it should update status', function(done) {
it('it should update actions', function(done) {
chai.request(app)
.post('/api/v1/comments/abc/actions')
.send({'user_id': '456', 'action_type': 'flag'})
.end(function(res){
expect(res).to.have.status(200)
done()
})
})
})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res).to.have.body;
expect(res.body).to.have.property('item_type');
expect(res.body.item_type).to.equal('comment');
expect(res.body).to.have.property('action_type');
expect(res.body.action_type).to.equal('flag');
expect(res.body).to.have.property('item_id');
expect(res.body.item_id).to.equal('abc');
expect(res.body).to.have.property('user_id');
expect(res.body.user_id).to.equal('456');
done();
});
});
});
+62
View File
@@ -0,0 +1,62 @@
process.env.NODE_ENV = 'test';
require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
const expect = chai.expect;
const Setting = require('../../../../models/setting');
const defaults = {id: '1', moderation: 'pre'};
describe('GET /settings', () => {
beforeEach(() => {
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
it('should return a settings object', done => {
chai.request(app)
.get('/api/v1/settings')
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.have.property('moderation');
expect(res.body.moderation).to.equal('pre');
done(err);
});
});
});
// update the settings.
describe('update settings', () => {
before(() => {
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
it('should respond to a PUT with new settings', () => {
chai.request(app)
.put('/api/v1/settings')
.send({moderation: 'post'}, (err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(204);
done(err);
});
});
it('should have updates settings', () => {
chai.request(app)
.get('/api/v1/settings')
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res).to.be.json;
expect(res.body).to.have.property('moderation');
expect(res.body.moderation).to.equal('post');
});
});
});
+19 -17
View File
@@ -2,9 +2,11 @@ require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
var expect = chai.expect;
const expect = chai.expect;
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
@@ -16,40 +18,40 @@ describe('api/stream: routes', () => {
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
}];
const users = [{
id: '123',
display_name: 'John',
},{
}, {
id: '456',
display_name: 'Paul',
}]
}];
const actions = [{
action_type: 'flag',
item_id: 'abc'
},{
}, {
action_type: 'like',
item_id: 'hij'
}]
}];
beforeEach(() => {
return Comment.create(comments).then(() => {
return User.create(users)
return User.create(users);
}).then(() => {
return Action.create(actions)
})
})
return Action.create(actions);
});
});
it('should return a stream with comments, users and actions', function(done){
chai.request(app)
@@ -58,8 +60,8 @@ describe('api/stream: routes', () => {
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
if (err) return done(err);
if (err) {return done(err);}
done();
});
})
})
});
});
+4 -4
View File
@@ -1,4 +1,4 @@
var mongoose = require('mongoose');
const mongoose = require('../../mongoose');
// Ensure the NODE_ENV is set to 'test',
// this is helpful when you would like to change behavior when testing.
@@ -6,18 +6,18 @@ process.env.NODE_ENV = 'test';
beforeEach(function (done) {
function clearDB() {
for (var i in mongoose.connection.collections) {
for (let i in mongoose.connection.collections) {
mongoose.connection.collections[i].remove(function() {});
}
return done();
}
if (mongoose.connection.readyState === 0) {
mongoose.connect('coral-talk-test', function (err) {
mongoose.on('open', function() {
if (err) {
throw err;
}
return clearDB();
});
} else {