From c80282dd918b28ec197273bdb95ce473993b8a4c Mon Sep 17 00:00:00 2001 From: David Erwin Date: Fri, 4 Nov 2016 22:24:06 -0400 Subject: [PATCH 01/21] Implement asset upsert and find --- app.js | 4 +++ models/asset.js | 74 +++++++++++++++++++++++++++++++++++++++ mongoose.js | 2 +- package.json | 1 + routes/api/asset/index.js | 30 ++++++++++++++++ routes/api/index.js | 5 +-- 6 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 models/asset.js create mode 100644 routes/api/asset/index.js diff --git a/app.js b/app.js index 7ce7c91ab..060836f19 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,10 @@ const express = require('express'); const app = express(); +const bodyParser = require('body-parser'); + +app.use(bodyParser.json()); // for parsing application/json +app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded app.use('/api/v1', require('./routes/api')); app.use('/client/', express.static('./dist')); diff --git a/models/asset.js b/models/asset.js new file mode 100644 index 000000000..9553ce2cd --- /dev/null +++ b/models/asset.js @@ -0,0 +1,74 @@ +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 +},{ + _id: false, + timestamps: { + createdAt: 'created_at', + updatedAt: 'updated_at' + } +}); + + +/** + * Finds an asset by its id. + * @param {String} id identifier of the asset (uuid) +*/ +AssetSchema.statics.findById = function(id) { + + return Asset.findOne({id}); + +}; + +/** + * Finds a asset by its url. + * @param {String} url identifier of the asset (uuid) +*/ +AssetSchema.statics.findByUrl = function(url) { + + return Asset.findOne({url: url}); + +}; + + +/** + * 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(); + } + + return Asset.update({id: data.id}, data, {upsert: true}); + +}; + +const Asset = mongoose.model('Asset', AssetSchema); + +module.exports = Asset; diff --git a/mongoose.js b/mongoose.js index 0aff4c22c..953eb0d11 100644 --- a/mongoose.js +++ b/mongoose.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const enabled = require('debug').enabled; -const url = process.env.TALK_MONGO_URL || 'mongodb://localhost'; +const url = process.env.TALK_MONGO_URL || 'mongodb://localhost/coral-talk'; // Use native promises mongoose.Promise = global.Promise; diff --git a/package.json b/package.json index 096863202..d392d0b09 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { + "body-parser": "^1.15.2", "debug": "^2.2.0", "express": "^4.14.0", "mongoose": "^4.6.5" diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js new file mode 100644 index 000000000..77fc55341 --- /dev/null +++ b/routes/api/asset/index.js @@ -0,0 +1,30 @@ +const express = require('express'); +const router = express.Router(); +const Asset = require('../../../models/asset'); + +// Get an asset by url +router.get('/url/:url', (req, res) => { + + Asset.findByUrl(req.params.url) + .then((asset) => { + res.json(asset); + }); + +}); + +// Upsert an asset +router.put('/', (req, res) => { + + Asset.upsert(req.body) + .then((asset) => { + res.json(asset); + }) + .catch((err) => { + console.error(err); + res.json(err); + }); + +}); + + +module.exports = router; \ No newline at end of file diff --git a/routes/api/index.js b/routes/api/index.js index c7e5601ed..acd007dae 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -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; From cb08f139ca29a91fd782cb1bf8e7d82f0e9069f2 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 08:50:34 -0400 Subject: [PATCH 02/21] Swagger and linting --- app.js | 2 +- bin/init.js | 2 +- routes/api/asset/index.js | 18 +++++++--- swagger.yaml | 69 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/app.js b/app.js index 060836f19..935990e9b 100644 --- a/app.js +++ b/app.js @@ -7,7 +7,7 @@ const app = express(); const bodyParser = require('body-parser'); app.use(bodyParser.json()); // for parsing application/json -app.use(bodyParser.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded +app.use(bodyParser.urlencoded({extended: true})); // for parsing application/x-www-form-urlencoded app.use('/api/v1', require('./routes/api')); app.use('/client/', express.static('./dist')); diff --git a/bin/init.js b/bin/init.js index d114d4819..6ee35bfe4 100644 --- a/bin/init.js +++ b/bin/init.js @@ -3,5 +3,5 @@ const defaults = {id: '1', moderation: 'pre'}; Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}).then(() => { console.log('created settings object.'); - process.exit(); +// process.exit(); // Removed to satisfy hooks }).catch(console.error); diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 77fc55341..a65f9687a 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -2,6 +2,16 @@ const express = require('express'); const router = express.Router(); const Asset = require('../../../models/asset'); +// Get an asset by id +router.get('/:id', (req, res) => { + + Asset.findById(req.params.id) + .then((asset) => { + res.json(asset); + }); + +}); + // Get an asset by url router.get('/url/:url', (req, res) => { @@ -17,14 +27,14 @@ router.put('/', (req, res) => { Asset.upsert(req.body) .then((asset) => { - res.json(asset); + res.json(asset); }) .catch((err) => { - console.error(err); - res.json(err); + console.error(err); + res.json(err); }); }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/swagger.yaml b/swagger.yaml index a7fdc973d..d9624c960 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -3,7 +3,7 @@ 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/api/v1 schemes: - https basePath: /v1 @@ -158,6 +158,33 @@ paths: responses: 204: description: OK + /asset: + get: + tags: + - Asset + description: Get an asset by id. + responses: + 200: + description: OK + 404: + description: Not Found + put: + tags: + - Asset + description: Upsert an asset. + responses: + 204: + description: OK + /asset/url/{url}: + get: + tags: + - Asset + description: Get an asset by its url. + responses: + 200: + description: OK + 404: + description: Not Found @@ -212,3 +239,43 @@ definitions: type: string user_id: type: string + 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. + + + + + + + + + From 17ccc830d94e2ad808af9171e634cfd326ccc03e Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 08:54:59 -0400 Subject: [PATCH 03/21] Update swagger --- swagger.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/swagger.yaml b/swagger.yaml index d9624c960..36af7a4c5 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -179,6 +179,11 @@ paths: 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: From a6b2215a4be3d6c9f6ec88b4ba1c37d68026d2ae Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 11:14:21 -0400 Subject: [PATCH 04/21] Implement asset tests --- models/asset.js | 44 ++++++++++++++-- package.json | 2 +- routes/api/asset/index.js | 14 +++++- tests/asset.js | 103 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 tests/asset.js diff --git a/models/asset.js b/models/asset.js index 9553ce2cd..de7cf8e65 100644 --- a/models/asset.js +++ b/models/asset.js @@ -27,6 +27,7 @@ const AssetSchema = new Schema({ publication_date: Date },{ _id: false, + versionKey: false, timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' @@ -34,9 +35,18 @@ const AssetSchema = new Schema({ }); +/** + * Search for assets. Currently only returns all. +*/ +AssetSchema.statics.search = function() { + + return Asset.find({}); + +}; + /** * Finds an asset by its id. - * @param {String} id identifier of the asset (uuid) + * @param {String} id identifier of the asset (uuid). */ AssetSchema.statics.findById = function(id) { @@ -46,11 +56,11 @@ AssetSchema.statics.findById = function(id) { /** * Finds a asset by its url. - * @param {String} url identifier of the asset (uuid) + * @param {String} url identifier of the asset (uuid). */ AssetSchema.statics.findByUrl = function(url) { - return Asset.findOne({url: url}); + return Asset.findOne({'url': url}); }; @@ -65,10 +75,36 @@ AssetSchema.statics.upsert = function(data) { data.id = uuid.v4(); } - return Asset.update({id: data.id}, data, {upsert: true}); + // Perform the upsert against the id field. + let updatePromise = Asset.update({id: data.id}, data, {upsert: true}) + .then((updateRes) => { + + // 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); + +}; + + const Asset = mongoose.model('Asset', AssetSchema); module.exports = Asset; diff --git a/package.json b/package.json index d392d0b09..e581146ee 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "start": "./bin/www", "build": "webpack --config ./client/coral-embed-stream/webpack.config.js", "lint": "eslint .", - "pretest": "npm install", "test": "mocha tests", "embed-start": "node client/coral-embed-stream/dev-server.js" }, @@ -42,6 +41,7 @@ "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { "body-parser": "^1.15.2", + "chai-http": "^3.0.0", "debug": "^2.2.0", "express": "^4.14.0", "mongoose": "^4.6.5" diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index a65f9687a..126e9ba06 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -2,6 +2,16 @@ const express = require('express'); const router = express.Router(); const Asset = require('../../../models/asset'); +// Get many assets +router.get('/', (req, res) => { + + Asset.search(req.params.id) + .then((asset) => { + res.json(asset); + }); + +}); + // Get an asset by id router.get('/:id', (req, res) => { @@ -22,12 +32,14 @@ router.get('/url/:url', (req, res) => { }); -// Upsert an asset +// Upsert an asset and return the affected document. router.put('/', (req, res) => { Asset.upsert(req.body) .then((asset) => { + res.json(asset); + }) .catch((err) => { console.error(err); diff --git a/tests/asset.js b/tests/asset.js new file mode 100644 index 000000000..14ab23125 --- /dev/null +++ b/tests/asset.js @@ -0,0 +1,103 @@ +/* eslint-env node, mocha */ +const Asset = require('../models/asset'); + +const expect = require('chai').expect; +let chai = require('chai'); +let chaiHttp = require('chai-http'); +let server = require('../app'); +let should = chai.should(); + +chai.use(chaiHttp); + +var fixture = { + "url": "simple", + "type": "article", + "headline": "The Total Perspective Vortex", + "summary": "You are an insignificant dot on an insignificant dot.", + "section": "Everything", + "authors": ["Ford Prefect"] +}; + + +describe('Asset', () => { + + beforeEach((done) => { + + // TODO: implement asset remove + Asset.removeAll({}) + .then((asset) => { + done(); + }); + + }); + + describe('/GET Asset', () => { + describe.only('#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.only('#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/' + fixture.url) + .end((err, res) => { + + if (err) { + throw new Error(err); + } + + res.should.have.status(200); + res.body.should.be.a('object'); + res.body.should.have.property('id'); + + // ensure the asset has the same id as above + expect(assetId).to.equal(res.body.id); + + done(); + + }); + }); + }); + }); + }); // End describe /PUT Asset + +}); From 712841679d52d0cb29caf830141690aa39b99ed8 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 11:18:18 -0400 Subject: [PATCH 05/21] Linting and clarifying tests --- models/asset.js | 2 +- tests/asset.js | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/models/asset.js b/models/asset.js index de7cf8e65..eb55094ac 100644 --- a/models/asset.js +++ b/models/asset.js @@ -77,7 +77,7 @@ AssetSchema.statics.upsert = function(data) { // Perform the upsert against the id field. let updatePromise = Asset.update({id: data.id}, data, {upsert: true}) - .then((updateRes) => { + .then(() => { // Pull the freshly minted asset out and return. return Asset.findById(data.id); diff --git a/tests/asset.js b/tests/asset.js index 14ab23125..187a9f1e7 100644 --- a/tests/asset.js +++ b/tests/asset.js @@ -5,17 +5,16 @@ const expect = require('chai').expect; let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../app'); -let should = chai.should(); chai.use(chaiHttp); var fixture = { - "url": "simple", - "type": "article", - "headline": "The Total Perspective Vortex", - "summary": "You are an insignificant dot on an insignificant dot.", - "section": "Everything", - "authors": ["Ford Prefect"] + 'url': 'simple', + 'type': 'article', + 'headline': 'The Total Perspective Vortex', + 'summary': 'You are an insignificant dot on an insignificant dot.', + 'section': 'Everything', + 'authors': ['Ford Prefect'] }; @@ -24,8 +23,8 @@ describe('Asset', () => { beforeEach((done) => { // TODO: implement asset remove - Asset.removeAll({}) - .then((asset) => { + return Asset.removeAll({}) + .then(() => { done(); }); @@ -94,8 +93,8 @@ describe('Asset', () => { done(); - }); - }); + }); + }); }); }); }); // End describe /PUT Asset From 2c217c240a52e4d2ed64ec7c03f16095967ebf8f Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 11:26:01 -0400 Subject: [PATCH 06/21] Rectify linting and testing disagreements --- tests/asset.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/asset.js b/tests/asset.js index 187a9f1e7..7ba98679d 100644 --- a/tests/asset.js +++ b/tests/asset.js @@ -5,6 +5,8 @@ const expect = require('chai').expect; let chai = require('chai'); let chaiHttp = require('chai-http'); let server = require('../app'); +let should = chai.should(); +should; // nullop to satisfy linting chai.use(chaiHttp); @@ -23,11 +25,13 @@ describe('Asset', () => { beforeEach((done) => { // TODO: implement asset remove - return Asset.removeAll({}) + Asset.removeAll({}) .then(() => { done(); }); + Asset; // nullop to satisfy linting. + }); describe('/GET Asset', () => { @@ -88,7 +92,8 @@ describe('Asset', () => { res.body.should.be.a('object'); res.body.should.have.property('id'); - // ensure the asset has the same id as above + // Ensure the asset has the same id as above. + // This tests the single url per Id concept. expect(assetId).to.equal(res.body.id); done(); From c1359a9af9458c380253c29f223341223eddb6ef Mon Sep 17 00:00:00 2001 From: David Erwin Date: Sat, 5 Nov 2016 14:44:42 -0400 Subject: [PATCH 07/21] Move url route into general asset search --- models/asset.js | 4 ++-- routes/api/asset/index.js | 20 ++++++++------------ tests/asset.js | 13 ++++++++----- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/models/asset.js b/models/asset.js index eb55094ac..afae0c677 100644 --- a/models/asset.js +++ b/models/asset.js @@ -38,9 +38,9 @@ const AssetSchema = new Schema({ /** * Search for assets. Currently only returns all. */ -AssetSchema.statics.search = function() { +AssetSchema.statics.search = function(query) { - return Asset.find({}); + return Asset.find(query); }; diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 126e9ba06..0b3959204 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -2,10 +2,16 @@ const express = require('express'); const router = express.Router(); const Asset = require('../../../models/asset'); -// Get many assets +// Search assets. router.get('/', (req, res) => { - Asset.search(req.params.id) + let query = {}; + + if (typeof req.query.url !== 'undefined') { + query.url = req.query.url; + } + + Asset.search(query) .then((asset) => { res.json(asset); }); @@ -22,16 +28,6 @@ router.get('/:id', (req, res) => { }); -// Get an asset by url -router.get('/url/:url', (req, res) => { - - Asset.findByUrl(req.params.url) - .then((asset) => { - res.json(asset); - }); - -}); - // Upsert an asset and return the affected document. router.put('/', (req, res) => { diff --git a/tests/asset.js b/tests/asset.js index 7ba98679d..a2019966f 100644 --- a/tests/asset.js +++ b/tests/asset.js @@ -11,7 +11,7 @@ should; // nullop to satisfy linting chai.use(chaiHttp); var fixture = { - 'url': 'simple', + 'url': 'http://hhgg.com/total-perspective-vortex', 'type': 'article', 'headline': 'The Total Perspective Vortex', 'summary': 'You are an insignificant dot on an insignificant dot.', @@ -81,7 +81,7 @@ describe('Asset', () => { // Load the asset to make sure it's really there. chai.request(server) - .get('/api/v1/asset/url/' + fixture.url) + .get('/api/v1/asset?url=' + fixture.url) .end((err, res) => { if (err) { @@ -89,12 +89,15 @@ describe('Asset', () => { } res.should.have.status(200); - res.body.should.be.a('object'); - res.body.should.have.property('id'); + 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(res.body.id); + expect(assetId).to.equal(asset.id); done(); From 53af608419addd1c8f8a3e5a3534fef40347d9b5 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 7 Nov 2016 11:57:34 -0500 Subject: [PATCH 08/21] fix(coral-admin): Adapt to the new schema. Most things are not working yet but at least it bundles --- client/coral-admin/package.json | 6 +- client/coral-admin/public/index.html | 2 +- client/coral-admin/public/manifest.json | 12 ---- client/coral-admin/src/components/App.js | 11 ++-- client/coral-admin/src/components/Comment.js | 17 +++--- .../coral-admin/src/components/CommentList.js | 5 +- client/coral-admin/src/components/Header.js | 10 ++-- .../src/containers/ModerationQueue.js | 8 +-- client/coral-admin/src/reducers/comments.js | 2 +- .../coral-admin/src/services/talk-adapter.js | 59 ++++--------------- client/coral-admin/webpack.config.dev.js | 3 +- 11 files changed, 47 insertions(+), 88 deletions(-) delete mode 100644 client/coral-admin/public/manifest.json diff --git a/client/coral-admin/package.json b/client/coral-admin/package.json index c5a2d4c45..4165e14ce 100644 --- a/client/coral-admin/package.json +++ b/client/coral-admin/package.json @@ -4,14 +4,13 @@ "description": "", "main": "index.js", "scripts": { - "build": "./node_modules/.bin/webpack --config webpack.config.js", + "build": "./node_modules/.bin/webpack --config webpack.config.js --watch", "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --inline --hot --content-base public --port 3142" }, "keywords": [], "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", diff --git a/client/coral-admin/public/index.html b/client/coral-admin/public/index.html index 707b82ebd..c31c22a6d 100644 --- a/client/coral-admin/public/index.html +++ b/client/coral-admin/public/index.html @@ -17,6 +17,6 @@
- + diff --git a/client/coral-admin/public/manifest.json b/client/coral-admin/public/manifest.json deleted file mode 100644 index 5da846121..000000000 --- a/client/coral-admin/public/manifest.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "short_name": "Talk", - "name": "Talk", - "icons": [ - { - "src": "https://coralproject.net/images/icon-coral-white.svg", - "sizes": "150x150" - } - ], - "start_url": "./", - "display": "standalone" -} diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js index 044d00989..545cd80a4 100644 --- a/client/coral-admin/src/components/App.js +++ b/client/coral-admin/src/components/App.js @@ -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 {
- - - - + + + +
diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index ce4f6686e..7406d42db 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -12,14 +12,14 @@ export default props => (
person - {props.comment.get('data').get('name') || lang.t('comment.anon')} - {timeago().format(props.comment.get('data').get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} - {props.comment.get('data').get('flagged') ?

{lang.t('comment.flagged')}

: null} + {props.comment.get('name') || lang.t('comment.anon')} + {timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + {props.comment.get('flagged') ?

{lang.t('comment.flagged')}

: null}
{props.actions.map(action => canShowAction(action, props.comment) ? ( @@ -27,16 +27,17 @@ export default props => (
- {props.comment.get('data').get('body')} + {props.comment.get('body')}
) // 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 diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index b9a1ba944..f56a1349f 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -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 (
    {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} /> diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index 10f414108..8aebe7864 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -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) => (
    - Moderate - Configure + Moderate + Configure
    - Moderate - Configure + Moderate + Configure {props.children} diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index a52ef25eb..f80aac2e2 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -74,7 +74,7 @@ class ModerationQueue extends React.Component { 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 { 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)} diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js index f3b465735..4fd14673a 100644 --- a/client/coral-admin/src/reducers/comments.js +++ b/client/coral-admin/src/reducers/comments.js @@ -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())) } diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 72e121388..2336e0cfd 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -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 })) diff --git a/client/coral-admin/webpack.config.dev.js b/client/coral-admin/webpack.config.dev.js index 010f0c04b..ca3b5cab5 100644 --- a/client/coral-admin/webpack.config.dev.js +++ b/client/coral-admin/webpack.config.dev.js @@ -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' ] }, From dfdd8ea6f82c24764c33e6e9fb1a77612ae1b848 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 7 Nov 2016 12:19:33 -0500 Subject: [PATCH 09/21] fix(coral-admin): updated config --- client/coral-admin/config.sample.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/coral-admin/config.sample.json b/client/coral-admin/config.sample.json index 5d23e2576..f5b9ad5f8 100644 --- a/client/coral-admin/config.sample.json +++ b/client/coral-admin/config.sample.json @@ -1,5 +1,3 @@ { - "basePath": "http://localhost:3142", - "talkHost": "http://localhost:16180", - "xeniaHost": "http://localhost:16180" + "base": "client/coral-admin" } From adb2dc2054a02f0ab0573667828d819ef4a2d027 Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 7 Nov 2016 12:21:27 -0500 Subject: [PATCH 10/21] fix(coral-admin): updated queue endpoint --- client/coral-admin/package.json | 2 +- routes/api/queue/index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/client/coral-admin/package.json b/client/coral-admin/package.json index 4165e14ce..ced6a3512 100644 --- a/client/coral-admin/package.json +++ b/client/coral-admin/package.json @@ -4,7 +4,7 @@ "description": "", "main": "index.js", "scripts": { - "build": "./node_modules/.bin/webpack --config webpack.config.js --watch", + "build": "./node_modules/.bin/webpack --config webpack.config.js", "start": "./node_modules/.bin/webpack-dev-server --config webpack.config.dev.js --inline --hot --content-base public --port 3142" }, "keywords": [], diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js index bf5b8c480..ffaa5c1e1 100644 --- a/routes/api/queue/index.js +++ b/routes/api/queue/index.js @@ -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})]); }); From 91d04e52545a9938f9ce0d19cbfd3dbcb95d9e4c Mon Sep 17 00:00:00 2001 From: Dan Zajdband Date: Mon, 7 Nov 2016 12:26:26 -0500 Subject: [PATCH 11/21] fix(coral-admin): dist files --- .gitignore | 2 ++ dist/coral-admin/index.html | 23 +++++++++++++++++++++++ dist/coral-admin/manifest.json | 12 ++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 dist/coral-admin/index.html create mode 100644 dist/coral-admin/manifest.json diff --git a/.gitignore b/.gitignore index acd1ba775..eff774450 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ node_modules npm-debug.log dist +!dist/coral-admin +dist/coral-admin/bundle.js .DS_Store *.iml .env diff --git a/dist/coral-admin/index.html b/dist/coral-admin/index.html new file mode 100644 index 000000000..3be58a0c8 --- /dev/null +++ b/dist/coral-admin/index.html @@ -0,0 +1,23 @@ + + + + + + Talk - Coral Admin + + + + + + +
    + + + diff --git a/dist/coral-admin/manifest.json b/dist/coral-admin/manifest.json new file mode 100644 index 000000000..5da846121 --- /dev/null +++ b/dist/coral-admin/manifest.json @@ -0,0 +1,12 @@ +{ + "short_name": "Talk", + "name": "Talk", + "icons": [ + { + "src": "https://coralproject.net/images/icon-coral-white.svg", + "sizes": "150x150" + } + ], + "start_url": "./", + "display": "standalone" +} From c84fc24f783952fa9b465f83d0315061cfaec9bd Mon Sep 17 00:00:00 2001 From: David Erwin Date: Mon, 7 Nov 2016 13:07:25 -0500 Subject: [PATCH 12/21] Update based on review --- .eslintignore | 1 + models/asset.js | 9 ++++----- routes/api/asset/index.js | 19 ++++++++----------- swagger.yaml | 5 +---- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/.eslintignore b/.eslintignore index b051c6c57..6cb6a3bc3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ client +dist diff --git a/models/asset.js b/models/asset.js index afae0c677..5864d6409 100644 --- a/models/asset.js +++ b/models/asset.js @@ -26,7 +26,6 @@ const AssetSchema = new Schema({ authors: [String], publication_date: Date },{ - _id: false, versionKey: false, timestamps: { createdAt: 'created_at', @@ -40,7 +39,7 @@ const AssetSchema = new Schema({ */ AssetSchema.statics.search = function(query) { - return Asset.find(query); + return Asset.find(query).exec(); }; @@ -50,7 +49,7 @@ AssetSchema.statics.search = function(query) { */ AssetSchema.statics.findById = function(id) { - return Asset.findOne({id}); + return Asset.findOne({id}).exec(); }; @@ -60,7 +59,7 @@ AssetSchema.statics.findById = function(id) { */ AssetSchema.statics.findByUrl = function(url) { - return Asset.findOne({'url': url}); + return Asset.findOne({'url': url}).exec(); }; @@ -100,7 +99,7 @@ AssetSchema.statics.upsert = function(data) { */ AssetSchema.statics.removeAll = function(query) { - return Asset.remove(query); + return Asset.remove(query).exec(); }; diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 0b3959204..235958aaa 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -3,7 +3,7 @@ const router = express.Router(); const Asset = require('../../../models/asset'); // Search assets. -router.get('/', (req, res) => { +router.get('/', (req, res, next) => { let query = {}; @@ -14,33 +14,30 @@ router.get('/', (req, res) => { Asset.search(query) .then((asset) => { res.json(asset); - }); + }) + .catch(next); }); // Get an asset by id -router.get('/:id', (req, res) => { +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) => { +router.put('/', (req, res, next) => { Asset.upsert(req.body) .then((asset) => { - res.json(asset); - }) - .catch((err) => { - console.error(err); - res.json(err); - }); + .catch(next); }); diff --git a/swagger.yaml b/swagger.yaml index ca7ef29d0..acc696ec6 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -179,7 +179,7 @@ paths: responses: 204: description: OK - /asset/url/{url}: + /asset?url={url}: get: tags: - Asset @@ -192,9 +192,6 @@ paths: responses: 200: description: OK - 404: - description: Not Found - definitions: Item: From 7b2b2eaa77478a9eea5c7297cd8cf614c52feafe Mon Sep 17 00:00:00 2001 From: David Erwin Date: Mon, 7 Nov 2016 13:11:39 -0500 Subject: [PATCH 13/21] Add more review suggestions. --- mongoose.js | 2 +- tests/asset.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mongoose.js b/mongoose.js index 953eb0d11..0aff4c22c 100644 --- a/mongoose.js +++ b/mongoose.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose'); const enabled = require('debug').enabled; -const url = process.env.TALK_MONGO_URL || 'mongodb://localhost/coral-talk'; +const url = process.env.TALK_MONGO_URL || 'mongodb://localhost'; // Use native promises mongoose.Promise = global.Promise; diff --git a/tests/asset.js b/tests/asset.js index a2019966f..f523ce987 100644 --- a/tests/asset.js +++ b/tests/asset.js @@ -2,10 +2,10 @@ const Asset = require('../models/asset'); const expect = require('chai').expect; -let chai = require('chai'); -let chaiHttp = require('chai-http'); -let server = require('../app'); -let should = chai.should(); +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const server = require('../app'); +const should = chai.should(); should; // nullop to satisfy linting chai.use(chaiHttp); From d3213dd33e418afe98e6dd8fd820ff49c68b8531 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Mon, 7 Nov 2016 13:18:46 -0500 Subject: [PATCH 14/21] More review updates --- models/asset.js | 2 +- swagger.yaml | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/models/asset.js b/models/asset.js index 5864d6409..afd446b01 100644 --- a/models/asset.js +++ b/models/asset.js @@ -75,7 +75,7 @@ AssetSchema.statics.upsert = function(data) { } // Perform the upsert against the id field. - let updatePromise = Asset.update({id: data.id}, data, {upsert: true}) + let updatePromise = Asset.update({id: data.id}, data, {upsert: true}).exec() .then(() => { // Pull the freshly minted asset out and return. diff --git a/swagger.yaml b/swagger.yaml index acc696ec6..ba945e2e1 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -3,10 +3,10 @@ info: title: Talk API description: A commenting platform from The Coral Project. https://coralproject.net version: "0.0.1" -host: talk-stg.coralproject.net/api/v1 +host: talk-stg.coralproject.net schemes: - https -basePath: /v1 +basePath: /api/v1 produces: - application/json paths: @@ -170,8 +170,6 @@ paths: responses: 200: description: OK - 404: - description: Not Found put: tags: - Asset From 108c50cb95c14721394b530de23de7269b635caf Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 7 Nov 2016 11:28:09 -0700 Subject: [PATCH 15/21] add tests to settings models/endpoints --- tests/models/setting.js | 32 +++++++++++++++ tests/routes/api/settings/index.js | 62 ++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 tests/models/setting.js create mode 100644 tests/routes/api/settings/index.js diff --git a/tests/models/setting.js b/tests/models/setting.js new file mode 100644 index 000000000..53d0d6386 --- /dev/null +++ b/tests/models/setting.js @@ -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'); + }); + }); + }); + +}); diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js new file mode 100644 index 000000000..fbf55d95f --- /dev/null +++ b/tests/routes/api/settings/index.js @@ -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'); + }); + }); +}); From 4a9c1775f1d4fee255b33e7e03349572160916e0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 7 Nov 2016 11:50:53 -0700 Subject: [PATCH 16/21] Updated linting rules + fixed test layout to standardize --- .eslintrc.json | 15 +++++++- models/action.js | 2 +- models/asset.js | 11 ++---- models/comment.js | 3 +- models/user.js | 2 +- mongoose.js | 2 +- routes/api/asset/index.js | 1 - routes/api/comments/index.js | 1 - routes/api/stream/index.js | 2 +- tests/.eslintrc.json | 5 ++- tests/index.js | 5 --- tests/models/action.js | 11 +++--- tests/{ => models}/asset.js | 41 +++++++------------- tests/models/comment.js | 10 ++--- tests/models/user.js | 13 +++---- tests/routes/api/comments/index.js | 62 +++++++++++++++--------------- tests/routes/api/stream/index.js | 36 +++++++++-------- tests/utils/mongoose.js | 8 ++-- 18 files changed, 106 insertions(+), 124 deletions(-) rename tests/{ => models}/asset.js (74%) diff --git a/.eslintrc.json b/.eslintrc.json index eec982dac..5df3444a6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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} + ] } } diff --git a/models/action.js b/models/action.js index cdbf92e8f..27c63747a 100644 --- a/models/action.js +++ b/models/action.js @@ -12,7 +12,7 @@ const ActionSchema = new Schema({ item_type: String, item_id: String, user_id: String -},{ +}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' diff --git a/models/asset.js b/models/asset.js index afd446b01..de8155131 100644 --- a/models/asset.js +++ b/models/asset.js @@ -25,7 +25,7 @@ const AssetSchema = new Schema({ subsection: String, authors: [String], publication_date: Date -},{ +}, { versionKey: false, timestamps: { createdAt: 'created_at', @@ -33,7 +33,6 @@ const AssetSchema = new Schema({ } }); - /** * Search for assets. Currently only returns all. */ @@ -58,12 +57,11 @@ AssetSchema.statics.findById = function(id) { * @param {String} url identifier of the asset (uuid). */ AssetSchema.statics.findByUrl = function(url) { - + return Asset.findOne({'url': url}).exec(); }; - /** * Upserts an asset. */ @@ -88,7 +86,7 @@ AssetSchema.statics.upsert = function(data) { //return new Promise(); // ??? what do we return on error? }); - + return updatePromise; }; @@ -98,12 +96,11 @@ AssetSchema.statics.upsert = function(data) { * @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; diff --git a/models/comment.js b/models/comment.js index 468478810..d6a814e64 100644 --- a/models/comment.js +++ b/models/comment.js @@ -21,7 +21,7 @@ const CommentSchema = new Schema({ default: '' }, parent_id: String -},{ +}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' @@ -44,7 +44,6 @@ CommentSchema.statics.findByAssetId = function(asset_id) { return Comment.find({asset_id}); }; - const Comment = mongoose.model('Comment', CommentSchema); module.exports = Comment; diff --git a/models/user.js b/models/user.js index 76635abfb..4592ec847 100644 --- a/models/user.js +++ b/models/user.js @@ -11,7 +11,7 @@ const UserProfileSchema = new Schema({ }, display_name: String, auth_user_id: String -},{ +}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' diff --git a/mongoose.js b/mongoose.js index 0aff4c22c..8d9bb3bfb 100644 --- a/mongoose.js +++ b/mongoose.js @@ -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) { diff --git a/routes/api/asset/index.js b/routes/api/asset/index.js index 235958aaa..b261da883 100644 --- a/routes/api/asset/index.js +++ b/routes/api/asset/index.js @@ -41,5 +41,4 @@ router.put('/', (req, res, next) => { }); - module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index df9bbc456..212c47ac6 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -7,7 +7,6 @@ const router = express.Router(); // Routes //============================================================================== - router.get('/', (req, res) => { res.send('Read all of the comments ever'); }); diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index c63bf871d..67216a252 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -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); }); diff --git a/tests/.eslintrc.json b/tests/.eslintrc.json index 97d8c197e..363ef5a28 100644 --- a/tests/.eslintrc.json +++ b/tests/.eslintrc.json @@ -1,9 +1,10 @@ { "env": { "es6": true, - "node": true + "node": true, + "mocha": true }, - "extends": "eslint:recommended", + "extends": "../.eslintrc.json", "rules": { "no-undef": [0] } diff --git a/tests/index.js b/tests/index.js index 51ccc54ee..0c3d947ce 100644 --- a/tests/index.js +++ b/tests/index.js @@ -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', () => { diff --git a/tests/models/action.js b/tests/models/action.js index 8736edcab..34a9f2dca 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -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); }); }); diff --git a/tests/asset.js b/tests/models/asset.js similarity index 74% rename from tests/asset.js rename to tests/models/asset.js index f523ce987..b4d950a93 100644 --- a/tests/asset.js +++ b/tests/models/asset.js @@ -1,16 +1,14 @@ -/* eslint-env node, mocha */ -const Asset = require('../models/asset'); +require('../utils/mongoose'); -const expect = require('chai').expect; const chai = require('chai'); -const chaiHttp = require('chai-http'); -const server = require('../app'); -const should = chai.should(); -should; // nullop to satisfy linting +const expect = chai.expect; +const server = require('../../app'); -chai.use(chaiHttp); +// Setup chai. +chai.should(); +chai.use(require('chai-http')); -var fixture = { +let fixture = { 'url': 'http://hhgg.com/total-perspective-vortex', 'type': 'article', 'headline': 'The Total Perspective Vortex', @@ -19,23 +17,10 @@ var fixture = { 'authors': ['Ford Prefect'] }; +describe('Asset: models', () => { -describe('Asset', () => { - - beforeEach((done) => { - - // TODO: implement asset remove - Asset.removeAll({}) - .then(() => { - done(); - }); - - Asset; // nullop to satisfy linting. - - }); - describe('/GET Asset', () => { - describe.only('#get', () => { + describe('#get', () => { it('It should get an empty array when there are no assets.', (done) => { chai.request(server) @@ -58,7 +43,7 @@ describe('Asset', () => { // This test checks PUT and read describe('/PUT Asset', () => { - describe.only('#put', () => { + describe('#put', () => { it('It should save an asset and load it again.', (done) => { chai.request(server) @@ -81,7 +66,7 @@ describe('Asset', () => { // Load the asset to make sure it's really there. chai.request(server) - .get('/api/v1/asset?url=' + fixture.url) + .get(`/api/v1/asset?url=${encodeURIComponent(fixture.url)}`) .end((err, res) => { if (err) { @@ -94,9 +79,9 @@ describe('Asset', () => { 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. + // This tests the single url per Id concept. expect(assetId).to.equal(asset.id); done(); diff --git a/tests/models/comment.js b/tests/models/comment.js index a8a543183..b3f0c0f30 100644 --- a/tests/models/comment.js +++ b/tests/models/comment.js @@ -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;} }); diff --git a/tests/models/user.js b/tests/models/user.js index e1063e21c..46fc7703c 100644 --- a/tests/models/user.js +++ b/tests/models/user.js @@ -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); }); }); }); - - // }); }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index ad21e54ec..1115a189b 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -4,10 +4,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 Comment = require('../../../../models/comment'); const Action = require('../../../../models/action'); @@ -17,36 +18,36 @@ describe('Post /comments', () => { 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 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') .query({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) .end(function(err, res){ - expect(res).to.have.status(201) - done() - }) - }) + expect(res).to.have.status(201); + done(); + }); + }); -}) +}); describe('Get /:comment_id', () => { const comments = [{ @@ -54,40 +55,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: '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 the right comment for the comment_id', function(done){ chai.request(app) @@ -96,11 +97,8 @@ describe('Get /:comment_id', () => { .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(); }); - }) - - - -}) + }); +}); diff --git a/tests/routes/api/stream/index.js b/tests/routes/api/stream/index.js index a4ca53228..9744abb0c 100644 --- a/tests/routes/api/stream/index.js +++ b/tests/routes/api/stream/index.js @@ -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(); }); - }) -}) + }); +}); diff --git a/tests/utils/mongoose.js b/tests/utils/mongoose.js index 35083a4b3..1549440a6 100644 --- a/tests/utils/mongoose.js +++ b/tests/utils/mongoose.js @@ -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 { From fc80ea2c3ad0f8414c75839388545354f616b48f Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 7 Nov 2016 11:49:02 -0800 Subject: [PATCH 17/21] Check it has body --- tests/routes/api/comments/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index c3694d95e..4945195a0 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -313,6 +313,7 @@ describe('Post /:comment_id/status', () => { .send({'status': 'accepted'}) .end(function(res){ expect(res).to.have.status(200) + expect(res).to.have.body done() }) }) From 601d36f2dfe604c23c5018a908ec13511b13b61b Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 7 Nov 2016 12:54:04 -0700 Subject: [PATCH 18/21] whoops missed some semicolons --- tests/models/setting.js | 4 ++-- tests/routes/api/settings/index.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/models/setting.js b/tests/models/setting.js index 53d0d6386..e2600fb0f 100644 --- a/tests/models/setting.js +++ b/tests/models/setting.js @@ -9,8 +9,8 @@ describe('Setting: model', () => { beforeEach(() => { const defaults = {id: 1, moderation: 'pre'}; - return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}) - }) + return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); + }); describe('#getSettings()', () => { it('should have a moderation field defined', () => { diff --git a/tests/routes/api/settings/index.js b/tests/routes/api/settings/index.js index fbf55d95f..8c2cd221e 100644 --- a/tests/routes/api/settings/index.js +++ b/tests/routes/api/settings/index.js @@ -27,7 +27,7 @@ describe('GET /settings', () => { expect(res.body).to.have.property('moderation'); expect(res.body.moderation).to.equal('pre'); done(err); - }) + }); }); }); From 9e70b86687879313c86eeb0dce74fd40c6bc736a Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 7 Nov 2016 12:29:07 -0800 Subject: [PATCH 19/21] Fix tests. --- tests/routes/api/comments/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 5fb28a8e7..887ad679d 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -308,7 +308,8 @@ describe('Post /:comment_id/status', () => { chai.request(app) .post('/api/v1/comments/abc/status') .send({'status': 'accepted'}) - .end(function(res){ + .end(function(err, res){ + expect(err).to.be.null; expect(res).to.have.status(200); expect(res).to.have.body; done(); @@ -365,7 +366,8 @@ describe('Post /:comment_id/actions', () => { chai.request(app) .post('/api/v1/comments/abc/actions') .send({'user_id': '456', 'action_type': 'flag'}) - .end(function(res){ + .end(function(err, res){ + expect(err).to.be.null; expect(res).to.have.status(200); done(); }); From d911e5b30db562ac04a70ea7a3b23178c7e3acef Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 7 Nov 2016 13:08:23 -0800 Subject: [PATCH 20/21] Improve test. --- routes/api/comments/index.js | 2 +- tests/routes/api/comments/index.js | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index b0458ada7..3acca5b3e 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -27,7 +27,7 @@ router.post('/', (req, res, next) => { const {body, author_id, asset_id, parent_id, status} = req.body; let comment = new Comment({body, author_id, asset_id, parent_id, status}); comment.save().then(({id}) => { - res.status(200).send(id); + res.status(200).send({'id': id}); }).catch(error => { next(error); }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index 887ad679d..f55ac98af 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -14,7 +14,7 @@ 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', @@ -95,6 +95,7 @@ describe('Post /comments', () => { .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) .end(function(err, res){ expect(res).to.have.status(200); + expect(res.body).to.have.property('id'); done(); }); }); @@ -148,7 +149,8 @@ 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(); }); }); @@ -201,7 +203,10 @@ describe('Put /:comment_id', () => { .post('/api/v1/comments/abc') .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_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.'); done(); }); }); @@ -312,6 +317,8 @@ 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'); done(); }); }); @@ -369,6 +376,15 @@ describe('Post /:comment_id/actions', () => { .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(); }); }); From 8154133fe20c235c370b0a7794c87bfc0bf28c4b Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 7 Nov 2016 13:11:43 -0800 Subject: [PATCH 21/21] Improve test to remove comment. --- tests/routes/api/comments/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index f55ac98af..4fbe52e94 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -259,6 +259,9 @@ describe('Delete /:comment_id', () => { .delete('/api/v1/comments/abc') .end(function(err, res){ expect(res).to.have.status(201); + Comment.findById({'id': 'abc'}).then((comment) => { + expect(comment).to.be.null; + }); done(); }); });