mirror of
https://github.com/wassname/talk.git
synced 2026-07-13 17:45:56 +08:00
Merge branch 'master' of github.com:coralproject/talk into community
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
# Talk [](https://circleci.com/gh/coralproject/talk)
|
||||
A commenting platform from The Coral Project. [https://coralproject.net](https://coralproject.net)
|
||||
|
||||
## Contributing to Talk
|
||||
|
||||
### Local Dependencies
|
||||
Node
|
||||
Mongo
|
||||
|
||||
### Getting Started
|
||||
`npm install`
|
||||
Run it once to install the dependencies.
|
||||
@@ -8,16 +14,22 @@ Run it once to install the dependencies.
|
||||
`npm start`
|
||||
Runs Talk.
|
||||
|
||||
### Running with Docker
|
||||
Make sure you have Docker running first and then run `docker-compose up -d`
|
||||
|
||||
### Testing
|
||||
`npm test`
|
||||
|
||||
### Lint
|
||||
`npm run lint`
|
||||
|
||||
### Helpful URLs
|
||||
Bare comment stream: http://localhost:5000/client/coral-embed-stream/
|
||||
Comment stream embedded on sample article: http://localhost:5000/client/coral-embed-stream/samplearticle.html
|
||||
Moderator view: http://localhost:5000/admin/
|
||||
|
||||
### Docs
|
||||
`swagger.yaml`
|
||||
|
||||
### Mantainers
|
||||
|
||||
### License
|
||||
**Apache-2.0**
|
||||
|
||||
@@ -8,6 +8,8 @@ const app = express();
|
||||
// Middleware declarations.
|
||||
app.use(morgan('dev'));
|
||||
app.use(bodyParser.json());
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// API Routes.
|
||||
app.use('/api/v1', require('./routes/api'));
|
||||
@@ -15,4 +17,8 @@ app.use('/api/v1', require('./routes/api'));
|
||||
// Static Routes.
|
||||
app.use('/client/', express.static(path.join(__dirname, 'dist')));
|
||||
|
||||
app.get('/admin*', (req, res) => {
|
||||
res.render('admin', {basePath: '/client/coral-admin'});
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
|
||||
@@ -3,5 +3,4 @@ public/bundle.js
|
||||
public/embed/comment-stream
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
config.json
|
||||
yarn.lock
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"basePath": "admin",
|
||||
"talkHost": "http://localhost:16180",
|
||||
"xeniaHost": "http://localhost:16180"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"base": "client/coral-admin"
|
||||
}
|
||||
@@ -27,8 +27,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.5.0",
|
||||
"buble": "0.13.0",
|
||||
"buble-loader": "0.3.0",
|
||||
"babel-core": "^6.18.2",
|
||||
"babel-loader": "^6.2.7",
|
||||
"copy-webpack-plugin": "3.0.1",
|
||||
"css-loader": "0.25.0",
|
||||
"json-loader": "0.5.4",
|
||||
@@ -37,7 +37,7 @@
|
||||
"precss": "1.4.0",
|
||||
"standard": "8.2.0",
|
||||
"style-loader": "0.13.1",
|
||||
"webpack": "2.1.0-beta.25",
|
||||
"webpack": "^1.13.3",
|
||||
"webpack-dev-server": "1.16.1"
|
||||
},
|
||||
"config": {
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="undefined/bundle.js" charset="utf-8"></script>
|
||||
<script src="client/coral-admin/bundle.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -120,7 +120,7 @@ export default class CommentList extends React.Component {
|
||||
{commentIds.map((commentId, index) => (
|
||||
<Comment comment={comments.get(commentId)}
|
||||
ref={el => { if (el && commentId === active) { this._active = el } }}
|
||||
key={index}
|
||||
key={index + 'comment'}
|
||||
index={index}
|
||||
onClickAction={this.onClickAction}
|
||||
actions={this.props.actions}
|
||||
|
||||
@@ -28,8 +28,8 @@ export default ({ open, onClose }) => (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<h3>{lang.t('modqueue.shortcuts')}</h3>
|
||||
<div className={styles.container}>
|
||||
{shortcuts.map(shortcut => (
|
||||
<table className={styles.table}>
|
||||
{shortcuts.map((shortcut, i) => (
|
||||
<table className={styles.table} key={i}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{lang.t(shortcut.title)}</th>
|
||||
@@ -37,7 +37,7 @@ export default ({ open, onClose }) => (
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.keys(shortcut.shortcuts).map(key => (
|
||||
<tr>
|
||||
<tr key={key + 'tr'}>
|
||||
<td className={styles.shortcut}><span className={styles.key}>{key}</span></td>
|
||||
<td>{lang.t(shortcut.shortcuts[key])}</td>
|
||||
</tr>
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
}
|
||||
|
||||
.configSettingEmbed {
|
||||
composes: configSetting;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
height: 90px;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
height: 170px;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
Icon
|
||||
} from 'react-mdl'
|
||||
import styles from './Configure.css'
|
||||
import I18n from 'coral-framework/i18n/i18n'
|
||||
import translations from '../translations'
|
||||
|
||||
class Configure extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -39,12 +41,28 @@ class Configure extends React.Component {
|
||||
</List>
|
||||
}
|
||||
|
||||
copyToClipBoard (event) {
|
||||
const copyTextarea = document.querySelector('.' + styles.embedInput)
|
||||
copyTextarea.select()
|
||||
|
||||
try {
|
||||
document.execCommand('copy')
|
||||
} catch (err) {
|
||||
console.error('Unable to copy')
|
||||
}
|
||||
}
|
||||
|
||||
getEmbed () {
|
||||
const embedText =
|
||||
`<div id='coralStreamEmbed'></div><script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/client/coral-embed-stream/', {title: 'comments'});</script>`
|
||||
|
||||
return <List>
|
||||
<ListItem className={styles.configSettingEmbed}>
|
||||
<p>Copy and paste code below into your CMS to embed your comment box in your articles</p>
|
||||
<input type='text' className={styles.embedInput} />
|
||||
<Button raised colored>Copy</Button>
|
||||
<textarea type='text' className={styles.embedInput}>
|
||||
{embedText}
|
||||
</textarea>
|
||||
<Button raised colored>{lang.t('embedlink.copy')}</Button>
|
||||
</ListItem>
|
||||
</List>
|
||||
}
|
||||
@@ -59,35 +77,37 @@ class Configure extends React.Component {
|
||||
: 'Embed Comment Stream'
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List>
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'comments')}
|
||||
icon='settings'>Comment Settings</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'embed')}
|
||||
icon='code'>Embed Comment Stream</ListItemContent>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Button raised colored>
|
||||
<Icon name='save' /> Save Changes
|
||||
</Button>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.leftColumn}>
|
||||
<List>
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'comments')}
|
||||
icon='settings'>Comment Settings</ListItemContent>
|
||||
</ListItem>
|
||||
<ListItem className={styles.settingOption}>
|
||||
<ListItemContent
|
||||
onClick={this.changeSection.bind(this, 'embed')}
|
||||
icon='code'>Embed Comment Stream</ListItemContent>
|
||||
</ListItem>
|
||||
</List>
|
||||
<Button raised colored>
|
||||
<Icon name='save' /> Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<h1>{pageTitle}</h1>
|
||||
{
|
||||
this.state.activeSection === 'comments'
|
||||
? this.getCommentSettings()
|
||||
: this.getEmbed()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.mainContent}>
|
||||
<h1>{pageTitle}</h1>
|
||||
{
|
||||
this.state.activeSection === 'comments'
|
||||
? this.getCommentSettings()
|
||||
: this.getEmbed()
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(x => x)(Configure)
|
||||
|
||||
const lang = new I18n(translations)
|
||||
|
||||
@@ -59,52 +59,55 @@ class ModerationQueue extends React.Component {
|
||||
render () {
|
||||
const { comments } = this.props
|
||||
const { activeTab, singleView, modalOpen } = this.state
|
||||
console.log('moderation queue', styles)
|
||||
|
||||
return (
|
||||
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
|
||||
<div className='mdl-tabs__tab-bar'>
|
||||
<a href='#pending' onClick={() => this.onTabClick('pending')}
|
||||
className={`mdl-tabs__tab is-active ${styles.tab}`}>{lang.t('modqueue.pending')}</a>
|
||||
<a href='#rejected' onClick={() => this.onTabClick('rejected')}
|
||||
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')}</a>
|
||||
<a href='#flagged' onClick={() => this.onTabClick('flagged')}
|
||||
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.flagged')}</a>
|
||||
<div>
|
||||
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
|
||||
<div className='mdl-tabs__tab-bar'>
|
||||
<a href='#pending' onClick={() => this.onTabClick('pending')}
|
||||
className={`mdl-tabs__tab is-active ${styles.tab}`}>{lang.t('modqueue.pending')}</a>
|
||||
<a href='#rejected' onClick={() => this.onTabClick('rejected')}
|
||||
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')}</a>
|
||||
<a href='#flagged' onClick={() => this.onTabClick('flagged')}
|
||||
className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.flagged')}</a>
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='pending'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'pending'}
|
||||
singleView={singleView}
|
||||
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']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
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']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
commentIds={comments.get('ids').filter(id => {
|
||||
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)}
|
||||
actions={['reject', 'approve']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<ModerationKeysModal open={modalOpen}
|
||||
onClose={() => this.setState({ modalOpen: false })} />
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel is-active ${styles.listContainer}`} id='pending'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'pending'}
|
||||
singleView={singleView}
|
||||
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']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
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']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='flagged'>
|
||||
<CommentList
|
||||
isActive={activeTab === 'rejected'}
|
||||
singleView={singleView}
|
||||
commentIds={comments.get('ids').filter(id => {
|
||||
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)}
|
||||
actions={['reject', 'approve']}
|
||||
loading={comments.loading} />
|
||||
</div>
|
||||
<ModerationKeysModal open={modalOpen}
|
||||
onClose={() => this.setState({ modalOpen: false })} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import App from 'components/App'
|
||||
import App from './components/App'
|
||||
|
||||
// Render the application into the DOM
|
||||
ReactDOM.render(<App />, document.querySelector('#root'))
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const webpack = require('webpack')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const precss = require('precss')
|
||||
const config = require('./config.json')
|
||||
|
||||
// doing a string replace here because I spent a day trying to do it the "webpack" way
|
||||
// ond nothing works. just trying to replace a string in an index.html file is
|
||||
// apparently something no one has ever done in the js community.
|
||||
let templateString = fs.readFileSync('./index.ejs').toString()
|
||||
templateString = templateString.replace('<%= basePath %>', config.basePath)
|
||||
fs.writeFileSync('./public/index.html', templateString)
|
||||
|
||||
console.log(templateString)
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
@@ -25,18 +12,14 @@ module.exports = {
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /.js$/, loaders: 'buble', include: path.join(__dirname, 'src') },
|
||||
{ test: /.json$/, loaders: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /\.css$/, loader: 'style-loader!css-loader?modules&importLoaders=1!postcss-loader' }
|
||||
{ test: /.js$/, loader: 'babel', include: path.join(__dirname, 'src'), exclude: /node_modules/ },
|
||||
{ test: /\.json$/, loaders: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /.css$/, loaders: ['style-loader', 'css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]', 'postcss-loader'] }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
options: {
|
||||
context: __dirname,
|
||||
postcss: [autoprefixer, precss]
|
||||
}
|
||||
}),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': JSON.stringify('development'),
|
||||
@@ -45,10 +28,9 @@ module.exports = {
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
root: [
|
||||
path.resolve('./src'),
|
||||
path.resolve('../'),
|
||||
'node_modules'
|
||||
path.resolve('../')
|
||||
]
|
||||
},
|
||||
devServer: {
|
||||
@@ -56,4 +38,4 @@ module.exports = {
|
||||
index: '/'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,26 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const devConfig = require('./webpack.config.dev')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const precss = require('precss')
|
||||
const Copy = require('copy-webpack-plugin')
|
||||
const webpack = require('webpack')
|
||||
const config = require('./config.json')
|
||||
|
||||
// doing a string replace here because I spent a day trying to do it the "webpack" way
|
||||
// ond nothing works. just trying to replace a string in an index.html file is
|
||||
// apparently something no one has ever done in the js community.
|
||||
let templateString = fs.readFileSync('./index.ejs').toString()
|
||||
templateString = templateString.replace('<%= basePath %>', config.basePath)
|
||||
fs.writeFileSync('./public/index.html', templateString)
|
||||
|
||||
module.exports = Object.assign({}, devConfig, {
|
||||
module: {
|
||||
context: __dirname,
|
||||
loaders: [
|
||||
{ test: /.js$/, loaders: 'buble', include: [path.join(__dirname, 'src'), path.join(__dirname, '../', 'coral-framework')] },
|
||||
{ test: /.json$/, loaders: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /\.css$/, loader: 'style-loader!css-loader?modules&importLoaders=1!postcss-loader' }
|
||||
{ test: /.js$/, loader: 'babel', include: [path.join(__dirname, 'src'), path.join(__dirname, '../', 'coral-framework')], exclude: /node_modules/ },
|
||||
{ test: /.json$/, loader: 'json', include: __dirname, exclude: /node_modules/ },
|
||||
{ test: /.css$/, loaders: ['style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'postcss-loader'] }
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy([{
|
||||
from: path.join(__dirname, '..', 'coral-embed-stream', 'dist'),
|
||||
to: './embed/comment-stream'
|
||||
}]),
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
options: {
|
||||
context: __dirname,
|
||||
postcss: [autoprefixer, precss],
|
||||
minimize: true,
|
||||
debug: false
|
||||
}
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin(),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'NODE_ENV': JSON.stringify('production'),
|
||||
'NODE_ENV': JSON.stringify('development'),
|
||||
'VERSION': JSON.stringify(require("./package.json").version)
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
})
|
||||
+27
-1
@@ -28,7 +28,7 @@ ActionSchema.statics.findById = function(id) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds users in an array of ids.
|
||||
* Finds actions in an array of ids.
|
||||
* @param {String} ids array of user identifiers (uuid)
|
||||
*/
|
||||
ActionSchema.statics.findByItemIdArray = function(item_ids) {
|
||||
@@ -37,6 +37,32 @@ ActionSchema.statics.findByItemIdArray = function(item_ids) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
ActionSchema.statics.findByType = function(action_type, item_type) {
|
||||
return Action.find({
|
||||
'action_type': action_type,
|
||||
'item_type': item_type
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds all comments ids for a specific action.
|
||||
* @param {String} action_type type of action
|
||||
* @param {String} item_type type of item the action is on
|
||||
*/
|
||||
ActionSchema.statics.findCommentsIdByActionType = function(action_type, item_type) {
|
||||
return Action.find({
|
||||
'action_type': action_type,
|
||||
'item_type': item_type
|
||||
},
|
||||
'item_id'
|
||||
);
|
||||
};
|
||||
|
||||
const Action = mongoose.model('Action', ActionSchema);
|
||||
|
||||
module.exports = Action;
|
||||
|
||||
@@ -31,6 +31,23 @@ const CommentSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// New Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Create a comment.
|
||||
* @param {String} body content of comment
|
||||
*/
|
||||
CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, status, username) {
|
||||
let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
|
||||
return comment.save();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Find Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Finds a comment by the id.
|
||||
* @param {String} id identifier of comment (uuid)
|
||||
@@ -47,6 +64,28 @@ CommentSchema.statics.findByAssetId = function(asset_id) {
|
||||
return Comment.find({asset_id});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments by an action that was performed on them.
|
||||
* @param {String} action_type the type of action that was performed on the comment
|
||||
*/
|
||||
CommentSchema.statics.findByActionType = function(action_type) {
|
||||
return Action.findCommentsIdByActionType(action_type, 'comment').then((actions) => {
|
||||
return Comment.find({'id': {'$in': actions.map(function(a){return a.item_id;})}});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments by their status.
|
||||
* @param {String} status the status to search for
|
||||
*/
|
||||
CommentSchema.statics.findByStatus = function(status) {
|
||||
return Comment.find({'status': status});
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Update Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
@@ -72,6 +111,19 @@ CommentSchema.statics.addAction = function(id, user_id, action_type) {
|
||||
return action.save();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Remove Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
CommentSchema.statics.removeById = function(id) {
|
||||
return Comment.remove({'id': id});
|
||||
};
|
||||
|
||||
const Comment = mongoose.model('Comment', CommentSchema);
|
||||
|
||||
module.exports = Comment;
|
||||
|
||||
+12
-4
@@ -5,12 +5,12 @@
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "./bin/www",
|
||||
"build": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.js",
|
||||
"build": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.js && cd client/coral-admin && npm install && npm run build",
|
||||
"lint": "eslint .",
|
||||
"pretest": "npm install",
|
||||
"test": "mocha tests --recursive",
|
||||
"test-watch": "mocha tests --recursive -w",
|
||||
"embed-start": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.dev.js && ./bin/www"
|
||||
"embed-start": "npm run build && ./bin/www"
|
||||
},
|
||||
"config": {
|
||||
"pre-git": {
|
||||
@@ -42,14 +42,17 @@
|
||||
},
|
||||
"homepage": "https://github.com/coralproject/talk#readme",
|
||||
"dependencies": {
|
||||
"autoprefixer": "^6.5.2",
|
||||
"body-parser": "^1.15.2",
|
||||
"debug": "^2.2.0",
|
||||
"ejs": "^2.5.2",
|
||||
"express": "^4.14.0",
|
||||
"mongoose": "^4.6.5",
|
||||
"uuid": "^2.0.3",
|
||||
"morgan": "^1.7.0"
|
||||
"morgan": "^1.7.0",
|
||||
"uuid": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "6.5.0",
|
||||
"babel-core": "6.14.0",
|
||||
"babel-jest": "^15.0.0",
|
||||
"babel-loader": "6.2.5",
|
||||
@@ -64,6 +67,7 @@
|
||||
"chai": "^3.5.0",
|
||||
"chai-http": "^3.0.0",
|
||||
"copy-webpack-plugin": "^3.0.1",
|
||||
"css-loader": "0.25.0",
|
||||
"eslint": "^3.9.1",
|
||||
"exports-loader": "^0.6.3",
|
||||
"immutable": "^3.8.1",
|
||||
@@ -71,6 +75,9 @@
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^3.1.2",
|
||||
"mocha-junit-reporter": "^1.12.1",
|
||||
"postcss-loader": "0.13.0",
|
||||
"postcss-modules": "0.5.2",
|
||||
"precss": "1.4.0",
|
||||
"pre-git": "^3.10.0",
|
||||
"pym.js": "^1.1.1",
|
||||
"react": "15.3.2",
|
||||
@@ -79,6 +86,7 @@
|
||||
"redux": "^3.6.0",
|
||||
"redux-thunk": "^2.1.0",
|
||||
"regenerator": "^0.8.46",
|
||||
"style-loader": "0.13.1",
|
||||
"supertest": "^2.0.1",
|
||||
"timeago.js": "^2.0.3",
|
||||
"webpack": "^1.13.2",
|
||||
|
||||
@@ -4,7 +4,7 @@ const Comment = require('../../../models/comment');
|
||||
const router = express.Router();
|
||||
|
||||
//==============================================================================
|
||||
// Routes
|
||||
// Get Routes
|
||||
//==============================================================================
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
@@ -23,16 +23,54 @@ router.get('/:comment_id', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
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': id});
|
||||
//==============================================================================
|
||||
// Moderation Queues Routes
|
||||
//==============================================================================
|
||||
|
||||
router.get('/action/:action_type', (req, res, next) => {
|
||||
Comment.findByActionType(req.params.action_type).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/status/rejected', (req, res, next) => {
|
||||
Comment.findByStatus('rejected').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/status/pending', (req, res, next) => {
|
||||
Comment.findByStatus('').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Post Routes
|
||||
//==============================================================================
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
const {body, author_id, asset_id, parent_id, status, username} = req.body;
|
||||
Comment.new(body, author_id, asset_id, parent_id, status, username).then((comment) => {
|
||||
res.status(200).send({'id': comment.id});
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
Comment.findById(req.params.comment_id).then((comment) => {
|
||||
comment.body = req.body.body;
|
||||
@@ -48,14 +86,6 @@ router.post('/:comment_id', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment.remove(req.params.comment_id).then(() => {
|
||||
res.status(201).send('OK. Deleted');
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id/status', (req, res, next) => {
|
||||
Comment.changeStatus(req.params.comment_id, req.body.status).then((comment) => {
|
||||
res.status(200).send(comment);
|
||||
@@ -72,4 +102,16 @@ router.post('/:comment_id/actions', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Delete Routes
|
||||
//==============================================================================
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment.removeById(req.params.comment_id).then(() => {
|
||||
res.status(201).send('OK. Removed');
|
||||
}).catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -66,6 +66,86 @@ describe('Get /comments', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get moderation queues rejected, pending, flags', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456'
|
||||
}, {
|
||||
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',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Comment.create(comments).then(() => {
|
||||
return User.create(users);
|
||||
}).then(() => {
|
||||
return Action.create(actions);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/rejected')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the pending comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/pending')
|
||||
.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 flagged comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/action/flag')
|
||||
.end(function(err, res){
|
||||
expect(res).to.have.status(200);
|
||||
expect(err).to.be.null;
|
||||
expect(res.body.length).to.equal(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post /comments', () => {
|
||||
const users = [{
|
||||
id: '123',
|
||||
@@ -128,10 +208,12 @@ describe('Get /:comment_id', () => {
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
item_id: 'abc',
|
||||
item_type: 'comment'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
item_id: 'hij',
|
||||
item_type: 'comment'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -144,13 +226,12 @@ describe('Get /:comment_id', () => {
|
||||
|
||||
it('should return the right comment for the comment_id', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments')
|
||||
.query({'comment_id': 'abc'})
|
||||
.get('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('body');
|
||||
expect(res.body[0].body).to.equal('comment 10');
|
||||
expect(res).to.have.property('body');
|
||||
expect(res.body).to.have.property('body', 'comment 10');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -205,14 +286,13 @@ describe('Put /:comment_id', () => {
|
||||
.end(function(err, res){
|
||||
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.');
|
||||
expect(res.body).to.have.property('body', 'Something body.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete /:comment_id', () => {
|
||||
describe('Remove /:comment_id', () => {
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
@@ -258,9 +338,10 @@ describe('Delete /:comment_id', () => {
|
||||
chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(201);
|
||||
Comment.findById({'id': 'abc'}).then((comment) => {
|
||||
expect(comment).to.be.null;
|
||||
Comment.findById('abc').then((comment) => {
|
||||
expect(comment).to.be.empty;
|
||||
});
|
||||
done();
|
||||
});
|
||||
@@ -320,8 +401,7 @@ describe('Post /:comment_id/status', () => {
|
||||
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');
|
||||
expect(res.body).to.have.property('status', 'accepted');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -380,14 +460,10 @@ describe('Post /:comment_id/actions', () => {
|
||||
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');
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,8 +24,7 @@ describe('GET /settings', () => {
|
||||
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');
|
||||
expect(res.body).to.have.property('moderation', 'pre');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
@@ -33,30 +32,24 @@ describe('GET /settings', () => {
|
||||
|
||||
// update the settings.
|
||||
describe('update settings', () => {
|
||||
it('should respond ok to a PUT', () => {
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
|
||||
.then(() => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/settings')
|
||||
.send({moderation: 'post'})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
before(() => {
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
});
|
||||
return Setting.getSettings();
|
||||
|
||||
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');
|
||||
}).then(settings => {
|
||||
// confirm updated settings in db
|
||||
expect(settings).to.have.property('moderation');
|
||||
expect(settings.moderation).to.equal('post');
|
||||
}).catch(err => {
|
||||
throw err;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,6 @@ describe('api/stream: routes', () => {
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
if (err) {return done(err);}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user