Merging master into asset

This commit is contained in:
David Erwin
2016-11-07 12:00:28 -05:00
25 changed files with 586 additions and 70 deletions
+1
View File
@@ -4,3 +4,4 @@ dist
.DS_Store
*.iml
.env
gaba.cfg
+2 -1
View File
@@ -1,4 +1,4 @@
FROM node
FROM node:7
# Create app directory
RUN mkdir -p /usr/src/app
@@ -6,6 +6,7 @@ WORKDIR /usr/src/app
# Setup the environment
ENV PATH /usr/src/app/bin:$PATH
ENV TALK_PORT 5000
EXPOSE 5000
# Install app dependencies
+1 -1
View File
@@ -1,4 +1,4 @@
# Talk
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
A commenting platform from The Coral Project. [https://coralproject.net](https://coralproject.net)
### Getting Started
+10 -7
View File
@@ -1,15 +1,18 @@
/* This is the entrypoint for the Talk Platform server. */
// Initialize application framework.
const express = require('express');
const bodyParser = require('body-parser');
const morgan = require('morgan');
const path = require('path');
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
// Middleware declarations.
app.use(morgan('dev'));
app.use(bodyParser.json());
// API Routes.
app.use('/api/v1', require('./routes/api'));
app.use('/client/', express.static('./dist'));
// Static Routes.
app.use('/client/', express.static(path.join(__dirname, 'dist')));
module.exports = app;
+9 -4
View File
@@ -1,7 +1,12 @@
const mongoose = require('../mongoose');
const Setting = require('../models/setting');
const defaults = {id: '1', moderation: 'pre'};
Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}).then(() => {
console.log('created settings object.');
// process.exit(); // Removed to satisfy hooks
}).catch(console.error);
Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
.then(() => {
console.log('Created settings object.');
mongoose.disconnect();
}).catch((err) => {
console.error(`failed to create the settings object ${JSON.stringify(err)}`);
throw new Error(err); // just to be safe
});
+21
View File
@@ -0,0 +1,21 @@
machine:
node:
version: 7
services:
- docker
test:
override:
- MOCHA_FILE=$CIRCLE_TEST_REPORTS/junit/test-results.xml ./node_modules/.bin/mocha tests --reporter mocha-junit-reporter
- npm run lint
deployment:
release:
tag: /[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
latest:
branch: master
commands:
- bash ./scripts/deploy.sh
+24
View File
@@ -0,0 +1,24 @@
version: '2'
services:
talk:
image: coralproject/talk:latest
build: .
restart: always
ports:
- "5000:5000"
environment:
- "TALK_PORT=5000"
- "TALK_MONGO_URL=mongodb://mongo"
depends_on:
- mongo
mongo:
image: mongo:3.2
restart: always
volumes:
- mongo:/data/db
volumes:
mongo:
external: false
+10 -3
View File
@@ -1,5 +1,3 @@
'use strict';
const mongoose = require('../mongoose');
const uuid = require('uuid');
const Schema = mongoose.Schema;
@@ -15,7 +13,6 @@ const ActionSchema = new Schema({
item_id: String,
user_id: String
},{
_id: false,
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
@@ -30,6 +27,16 @@ ActionSchema.statics.findById = function(id) {
return Action.findOne({id});
};
/**
* Finds users in an array of ids.
* @param {String} ids array of user identifiers (uuid)
*/
ActionSchema.statics.findByItemIdArray = function(item_ids) {
return Action.find({
'item_id': {$in: item_ids}
});
};
const Action = mongoose.model('Action', ActionSchema);
module.exports = Action;
+11 -5
View File
@@ -1,5 +1,3 @@
'use strict';
const mongoose = require('../mongoose');
const uuid = require('uuid');
const Schema = mongoose.Schema;
@@ -13,7 +11,7 @@ const CommentSchema = new Schema({
body: {
type: String,
required: [true, 'The body is required.'],
minlength: 50
minlength: 2
},
asset_id: String,
author_id: String,
@@ -24,7 +22,6 @@ const CommentSchema = new Schema({
},
parent_id: String
},{
_id: false,
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
@@ -33,12 +30,21 @@ const CommentSchema = new Schema({
/**
* Finds a comment by the id.
* @param {String} id identifier of the comment (uuid)
* @param {String} asset_id identifier of comment (uuid)
*/
CommentSchema.statics.findById = function(id) {
return Comment.findOne({id});
};
/**
* Finds a comment by the asset_id.
* @param {String} asset_id identifier of the asset which owns this comment (uuid)
*/
CommentSchema.statics.findByAssetId = function(asset_id) {
return Comment.find({asset_id});
};
const Comment = mongoose.model('Comment', CommentSchema);
module.exports = Comment;
+10 -1
View File
@@ -12,10 +12,19 @@ const SettingSchema = new Schema({
}
});
/**
* gets the entire settings record and sends it back
* @return {Promise} settings the whole settings record
*/
SettingSchema.statics.getSettings = function () {
return this.findOne();
return this.findOne({id: '1'});
};
/**
* this will update the settings object with whatever you pass in
* @param {object} setting a hash of whatever settings you want to update
* @return {Promise} settings Promise that resolves to the entire (updated) settings object.
*/
SettingSchema.statics.updateSettings = function (setting) {
// there should only ever be one record unless something has gone wrong.
return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true});
+10 -2
View File
@@ -1,4 +1,3 @@
'use strict';
const mongoose = require('../mongoose');
const uuid = require('uuid');
@@ -13,7 +12,6 @@ const UserProfileSchema = new Schema({
display_name: String,
auth_user_id: String
},{
_id: false,
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
@@ -28,6 +26,16 @@ UserProfileSchema.statics.findById = function(id) {
return UserProfile.findOne({id});
};
/**
* Finds users in an array of idd.
* @param {String} idd array of user identifiers (uuid)
*/
UserProfileSchema.statics.findByIdArray = function(ids) {
return UserProfile.find({
'id': {$in: ids}
});
};
// TO DO: methods
// modifications to user as statics
// find by auth user id
+11 -2
View File
@@ -7,7 +7,9 @@
"start": "./bin/www",
"build": "webpack --config ./client/coral-embed-stream/webpack.config.js",
"lint": "eslint .",
"test": "mocha tests",
"pretest": "npm install",
"test": "mocha tests --recursive",
"test-watch": "mocha tests --recursive -w",
"embed-start": "node client/coral-embed-stream/dev-server.js"
},
"config": {
@@ -44,7 +46,9 @@
"chai-http": "^3.0.0",
"debug": "^2.2.0",
"express": "^4.14.0",
"mongoose": "^4.6.5"
"mongoose": "^4.6.5",
"uuid": "^2.0.3",
"morgan": "^1.7.0"
},
"devDependencies": {
"babel-core": "6.14.0",
@@ -59,6 +63,7 @@
"babel-preset-es2015-minimal": "^2.1.0",
"babel-preset-stage-0": "^6.16.0",
"chai": "^3.5.0",
"chai-http": "^1.0.0",
"copy-webpack-plugin": "^3.0.1",
"eslint": "^3.9.1",
"exports-loader": "^0.6.3",
@@ -66,6 +71,7 @@
"imports-loader": "^0.6.5",
"json-loader": "^0.5.4",
"mocha": "^3.1.2",
"mocha-junit-reporter": "^1.12.1",
"pre-git": "^3.10.0",
"pym.js": "^1.1.1",
"react": "15.3.2",
@@ -82,5 +88,8 @@
"webpack-hot-middleware": "^2.12.2",
"webpack-module-hot-accept": "^1.0.4",
"whatwg-fetch": "^1.0.0"
},
"engines": {
"node": ">=7.0.0"
}
}
+26 -4
View File
@@ -1,17 +1,39 @@
const express = require('express');
const Comment = require('../../../models/comment');
const router = express.Router();
//==============================================================================
// Routes
//==============================================================================
router.get('/', (req, res) => {
res.send('Read all of the comments ever');
});
router.get('/:comment_id', (req, res) => {
res.send('Read a comment');
router.get('/:comment_id', (req, res, next) => {
Comment.findById(req.params.comment_id).then((comment) => {
res.status(200).json(comment);
}).catch(error => {
next(error);
});
});
router.post('/', (req, res) => {
res.send('Write a comment');
router.post('/', (req, res, next) => {
let comment = new Comment({
body: req.query.body,
author_id: req.query.author_id,
asset_id: req.query.asset_id,
parent_id: req.query.parent_id,
status: req.query.status
});
comment.save().then(({id}) => {
res.status(201).send(id);
}).catch(error => {
next(error);
});
});
router.put('/:comment_id', (req, res) => {
+2 -2
View File
@@ -3,11 +3,11 @@ const router = express.Router();
const Setting = require('../../../models/setting');
router.get('/', (req, res, next) => {
Setting.getSettings().then(res.json).catch(next);
Setting.getSettings().then(settings => res.json(settings)).catch(next);
});
router.put('/', (req, res, next) => {
Setting.updateSettings(req.body).then(res.json).catch(next);
Setting.updateSettings(req.body).then(() => res.status(204).end()).catch(next);
});
module.exports = router;
+19 -26
View File
@@ -1,33 +1,26 @@
const express = require('express');
const Comment = require('../../../models/comment');
const User = require('../../../models/user');
const Action = require('../../../models/action');
const router = express.Router();
router.get('/', (req, res) => {
console.log('Stream endpoint has been hit with asset_id ', req.query.asset_id);
res.json([
{
'id': 'abc',
'type': 'comment',
'body': 'Sample comment',
'created_at': new Date().getTime(),
'asset_id': 'assetTest'
},
{
'id': 'xyz',
'type': 'comment',
'body': 'Sample reply',
'created_at': new Date().getTime() - 600000,
'parent_id': 'abc',
'asset_id': 'assetTest'
},
{
'id': 'def',
'type': 'comment',
'body': 'Another comment',
'created_at': new Date().getTime() - 400000,
'asset_id': 'assetTest'
}
]);
router.get('/', (req, res, next) => {
const commentsPromise = Comment.findByAssetId(req.query.asset_id);
commentsPromise.then(comments => {
return Promise.all([
comments,
User.findByIdArray(comments.map((comment) => comment.author_id)),
Action.findByItemIdArray(comments.map((comment) => comment.id))
]);
}).then(([comments, users, actions]) => {
res.json([...comments,...users,...actions]);
}).catch(error => {
next(error);
});
});
module.exports = router;
+55
View File
@@ -0,0 +1,55 @@
#!/bin/bash
set -e
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
# Sourced from https://segment.com/blog/ci-at-segment/
deploy_tag() {
# Find our individual versions from the tags
if [ -n "$(echo $CIRCLE_TAG | grep -E '.*\..*\..*')" ]
then
major=$(echo $CIRCLE_TAG | cut -d. -f1)
minor=$(echo $CIRCLE_TAG | cut -d. -f2)
patch=$(echo $CIRCLE_TAG | cut -d. -f3)
major_version_tag=$major
minor_version_tag=$major.$minor
patch_version_tag=$major.$minor.$patch
tag_list="$major_version_tag $minor_version_tag $patch_version_tag"
else
tag_list=$CIRCLE_TAG
fi
# Tag the new image with major, minor and patch version tags.
for version in $tag_list
do
echo "==> tagging $version"
docker tag coralproject/talk:latest coralproject/talk:$version
done
# Push each of the tags to docker hub, including latest
for version in $tag_list latest
do
echo "==> pushing $version"
docker push coralproject/talk:$version
done
}
deploy_latest() {
echo "==> pushing latest"
docker push coralproject/talk:latest
}
# build the repo
docker build -t coralproject/talk .
# deploy based on the env
if [ -n "$CIRCLE_TAG" ]
then
deploy_tag
else
deploy_latest
fi
+18 -11
View File
@@ -150,7 +150,11 @@ paths:
description: Settings
responses:
200:
description: OK
description: Get Setting
schema:
type: array
items:
$ref: '#/definitions/Setting'
put:
tags:
- Settings
@@ -192,7 +196,6 @@ paths:
description: Not Found
definitions:
Item:
type: object
@@ -244,6 +247,19 @@ definitions:
type: string
user_id:
type: string
moderation:
type: string
enum:
- pre
- post
created_at:
type: string
format: date-time
description: Creation Date-Time
updated_at:
type: string
format: date-time
description: Updated Date-Time
Asset:
type: object
properties:
@@ -275,12 +291,3 @@ definitions:
publication_date:
type: date
desctipion: When this asset was published.
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"rules": {
"no-undef": [0]
}
}
+3 -1
View File
@@ -1,10 +1,12 @@
/* eslint-env node, mocha */
'use strict';
// require('./utils/mongoose')
const expect = require('chai').expect;
describe('Comment', () => {
describe.only('#add', () => {
describe('#add', () => {
it('should add a comment', () => {
expect(0).to.be.equal(0);
});
+40
View File
@@ -0,0 +1,40 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const Action = require('../../models/action');
const expect = require('chai').expect;
describe('Action: models', () => {
var 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) => {
mockActions = actions;
});
});
describe('#findById()', () => {
it('should find an action by id', () => {
return Action.findById(mockActions[0].id).then((result) => {
expect(result).to.have.property('action_type')
.and.to.equal('flag');
});
});
});
describe('#findByItemIdArray()', () => {
it('should find an array of actions from an array of item_ids', () => {
return Action.findByItemIdArray(['123','456']).then((result) => {
expect(result).to.have.length(2);
});
});
});
});
+51
View File
@@ -0,0 +1,51 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const Comment = require('../../models/comment');
const expect = require('chai').expect;
describe('Comment: models', () => {
var 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) => {
mockComments = comments;
});
});
describe('#findById()', () => {
it('should find a comment by id', () => {
return Comment.findById(mockComments[0].id).then((result) => {
expect(result).to.have.property('body')
.and.to.equal('comment 10');
});
});
});
describe('#findByAssetId()', () => {
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) => {
if (a.body < b.body) {return -1;}
else {return 1;}
});
expect(result[0]).to.have.property('body')
.and.to.equal('comment 10');
expect(result[1]).to.have.property('body')
.and.to.equal('comment 20');
});
});
});
// });
});
+40
View File
@@ -0,0 +1,40 @@
/* eslint-env node, mocha */
require('../utils/mongoose');
const User = require('../../models/user');
const expect = require('chai').expect;
describe('User: models', () => {
var mockUsers;
beforeEach(() => {
return User.create([{
display_name: 'Stampi',
},{
display_name: 'Sockmonster',
},{
display_name: 'Marvel',
}]).then((users) => {
mockUsers = users;
});
});
describe('#findById()', () => {
it('should find a user by id', () => {
return User.findById(mockUsers[0].id).then((result) => {
expect(result).to.have.property('display_name')
.and.to.equal('Stampi');
});
});
});
describe('#findByIdArray()', () => {
it('should find an array of users from an array of ids', () => {
const ids = mockUsers.map((user) => user.id)
return User.findByIdArray(ids).then((result) => {
expect(result).to.have.length(3);
});
});
});
// });
});
+106
View File
@@ -0,0 +1,106 @@
process.env.NODE_ENV = 'test';
require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
var expect = chai.expect;
const Comment = require('../../../../models/comment');
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
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)
})
})
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()
})
})
})
describe('Get /:comment_id', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
const users = [{
id: '123',
display_name: '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)
}).then(() => {
return Action.create(actions)
})
})
it('should return the right comment for the comment_id', function(done){
chai.request(app)
.get('/api/v1/comments')
.query({'comment_id': 'abc'})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
if (err) return done(err);
done();
});
})
})
+65
View File
@@ -0,0 +1,65 @@
require('../../../utils/mongoose');
const app = require('../../../../app');
const chai = require('chai');
const chaiHttp = require('chai-http');
chai.use(chaiHttp);
var expect = chai.expect;
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
const Comment = require('../../../../models/comment');
describe('api/stream: routes', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
},{
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
},{
id: 'hij',
body: 'comment 30',
asset_id: '456'
}]
const users = [{
id: '123',
display_name: '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)
}).then(() => {
return Action.create(actions)
})
})
it('should return a stream with comments, users and actions', function(done){
chai.request(app)
.get('/api/v1/stream')
.query({'asset_id': 'asset'})
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
if (err) return done(err);
done();
});
})
})
+31
View File
@@ -0,0 +1,31 @@
var mongoose = require('mongoose');
// Ensure the NODE_ENV is set to 'test',
// this is helpful when you would like to change behavior when testing.
process.env.NODE_ENV = 'test';
beforeEach(function (done) {
function clearDB() {
for (var 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) {
if (err) {
throw err;
}
return clearDB();
});
} else {
return clearDB();
}
});
after(function (done) {
mongoose.disconnect();
return done();
});