diff --git a/README.md b/README.md
index d295ef326..f4d8b31de 100644
--- a/README.md
+++ b/README.md
@@ -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**
diff --git a/app.js b/app.js
index 838819e35..d4041c93a 100644
--- a/app.js
+++ b/app.js
@@ -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;
diff --git a/client/coral-admin/.gitignore b/client/coral-admin/.gitignore
index c9c7cb580..707c890e5 100644
--- a/client/coral-admin/.gitignore
+++ b/client/coral-admin/.gitignore
@@ -3,5 +3,4 @@ public/bundle.js
public/embed/comment-stream
.DS_Store
npm-debug.log
-config.json
yarn.lock
diff --git a/client/coral-admin/config.json b/client/coral-admin/config.json
new file mode 100644
index 000000000..c8f318cfc
--- /dev/null
+++ b/client/coral-admin/config.json
@@ -0,0 +1,5 @@
+{
+ "basePath": "admin",
+ "talkHost": "http://localhost:16180",
+ "xeniaHost": "http://localhost:16180"
+}
diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js
index 4b7c01e84..81eea850f 100644
--- a/client/coral-admin/src/components/App.js
+++ b/client/coral-admin/src/components/App.js
@@ -8,17 +8,16 @@ 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) {
return (
-
-
-
-
+
+
+
+
)
diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js
index 0e52686d8..ea268108e 100644
--- a/client/coral-admin/src/components/Header.js
+++ b/client/coral-admin/src/components/Header.js
@@ -9,14 +9,14 @@ export default (props) => (
- Moderate
- Configure
+ Moderate
+ Configure
- Moderate
- Configure
+ Moderate
+ Configure
{props.children}
diff --git a/client/coral-admin/webpack.config.dev.js b/client/coral-admin/webpack.config.dev.js
index bd5913b5a..e4d771e72 100644
--- a/client/coral-admin/webpack.config.dev.js
+++ b/client/coral-admin/webpack.config.dev.js
@@ -1,16 +1,6 @@
-
const path = require('path')
-const fs = require('fs')
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(path.join(__dirname, 'index.ejs')).toString()
-templateString = templateString.replace('<%= basePath %>', config.basePath)
-fs.writeFileSync(path.join(__dirname, 'public/index.html'), templateString)
module.exports = {
entry: {
@@ -24,7 +14,7 @@ module.exports = {
loaders: [
{ 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?importLoaders=1', 'postcss-loader'] }
+ { test: /.css$/, loaders: ['style-loader', 'css-loader?modules&localIdentName=[name]__[local]___[hash:base64:5]', 'postcss-loader'] }
]
},
plugins: [ autoprefixer, precss ],
diff --git a/client/coral-admin/webpack.config.js b/client/coral-admin/webpack.config.js
index 8c224bcdb..0cceded4b 100644
--- a/client/coral-admin/webpack.config.js
+++ b/client/coral-admin/webpack.config.js
@@ -1,17 +1,8 @@
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 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(path.join(__dirname, 'index.ejs')).toString()
-templateString = templateString.replace('<%= basePath %>', config.basePath)
-fs.writeFileSync(path.join(__dirname, 'public/index.html'), templateString)
module.exports = Object.assign({}, devConfig, {
module: {
@@ -19,7 +10,7 @@ module.exports = Object.assign({}, devConfig, {
loaders: [
{ 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?importLoaders=1', 'postcss-loader'] }
+ { test: /.css$/, loaders: ['style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]', 'postcss-loader'] }
]
},
plugins: [
diff --git a/models/action.js b/models/action.js
index 27c63747a..1d75c483c 100644
--- a/models/action.js
+++ b/models/action.js
@@ -28,7 +28,7 @@ ActionSchema.statics.findById = function(id) {
};
/**
- * Finds users in an array of ids.
+ * Finds actions in an array of ids.
* @param {String} ids array of user identifiers (uuid)
*/
ActionSchema.statics.findByItemIdArray = function(item_ids) {
@@ -37,6 +37,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;
diff --git a/models/comment.js b/models/comment.js
index 6ddc3b0ee..3aad5d978 100644
--- a/models/comment.js
+++ b/models/comment.js
@@ -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;
diff --git a/package.json b/package.json
index 2ee7de0ab..dd22a5f8c 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"main": "app.js",
"scripts": {
"start": "./bin/www",
- "build": "./node_modules/webpack/bin/webpack.js --config ./client/coral-embed-stream/webpack.config.js && cd client/coral-admin && npm run build",
+ "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",
@@ -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",
diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js
index bc35a78f9..b3e2bdee7 100644
--- a/routes/api/comments/index.js
+++ b/routes/api/comments/index.js
@@ -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;
diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js
index 4fbe52e94..378860bcb 100644
--- a/tests/routes/api/comments/index.js
+++ b/tests/routes/api/comments/index.js
@@ -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();
});
});
diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js
index f6d07c03e..5bcf9de28 100644
--- a/tests/routes/api/settings/index.js
+++ b/tests/routes/api/settings/index.js
@@ -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,7 +32,6 @@ 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(() => {
diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js
index 9744abb0c..b1348e88a 100644
--- a/tests/routes/api/stream/index.js
+++ b/tests/routes/api/stream/index.js
@@ -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();
});
});
diff --git a/client/coral-admin/index.ejs b/views/admin.ejs
similarity index 100%
rename from client/coral-admin/index.ejs
rename to views/admin.ejs