mirror of
https://github.com/wassname/talk.git
synced 2026-07-16 11:22:16 +08:00
Merge branch 'master' into guides
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
||||
dist
|
||||
dist
|
||||
client/lib
|
||||
|
||||
@@ -22,7 +22,10 @@ if (app.get('env') !== 'test') {
|
||||
//==============================================================================
|
||||
|
||||
app.set('trust proxy', 1);
|
||||
app.use(helmet());
|
||||
// We disable frameward on helmet to allow crossdomain injection of the embed
|
||||
app.use(helmet({
|
||||
frameguard: false
|
||||
}));
|
||||
app.use(bodyParser.json());
|
||||
app.use('/client', express.static(path.join(__dirname, 'dist')));
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
|
||||
@@ -12,9 +12,11 @@ process.env.DEBUG = process.env.TALK_DEBUG;
|
||||
|
||||
const program = require('commander');
|
||||
const pkg = require('../package.json');
|
||||
const parseDuration = require('parse-duration');
|
||||
const Table = require('cli-table');
|
||||
const Asset = require('../models/asset');
|
||||
const mongoose = require('../mongoose');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('../util');
|
||||
|
||||
// Register the shutdown criteria.
|
||||
@@ -55,6 +57,37 @@ function listAssets() {
|
||||
});
|
||||
}
|
||||
|
||||
function refreshAssets(ageString) {
|
||||
const now = new Date().getTime();
|
||||
const ageMs = parseDuration(ageString);
|
||||
const age = new Date(now - ageMs);
|
||||
|
||||
Asset.find({
|
||||
$or: [
|
||||
{
|
||||
scraped: {
|
||||
$lte: age
|
||||
}
|
||||
},
|
||||
{
|
||||
scraped: null
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// Queue all the assets for scraping.
|
||||
.then((assets) => Promise.all(assets.map(scraper.create)))
|
||||
|
||||
.then(() => {
|
||||
console.log('Assets were queued to be scraped');
|
||||
util.shutdown();
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
util.shutdown(1);
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Setting up the program command line arguments.
|
||||
//==============================================================================
|
||||
@@ -67,6 +100,11 @@ program
|
||||
.description('list all the assets in the database')
|
||||
.action(listAssets);
|
||||
|
||||
program
|
||||
.command('refresh <age>')
|
||||
.description('queues the assets that exceed the age requested')
|
||||
.action(refreshAssets);
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
// If there is no command listed, output help.
|
||||
|
||||
@@ -14,11 +14,15 @@ const program = require('commander');
|
||||
const scraper = require('../services/scraper');
|
||||
const util = require('../util');
|
||||
const mongoose = require('../mongoose');
|
||||
const kue = require('../kue');
|
||||
|
||||
util.onshutdown([
|
||||
() => mongoose.disconnect()
|
||||
]);
|
||||
|
||||
/**
|
||||
* Starts the job processor.
|
||||
*/
|
||||
function processJobs() {
|
||||
|
||||
// Start the processor.
|
||||
@@ -31,6 +35,65 @@ function processJobs() {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single job.
|
||||
* @param {Object} job the job to be removed
|
||||
* @return {Promise}
|
||||
*/
|
||||
function removeJob(job) {
|
||||
return new Promise((resolve, reject) => job.remove((err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
return resolve(job);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the jobs passed in and returns a promise.
|
||||
* @param {Array} jobs array of jobs
|
||||
* @return {Promise}
|
||||
*/
|
||||
function removeJobs(jobs) {
|
||||
return Promise.all(jobs.map(removeJob));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the top n jobs with a specific state.
|
||||
* @param {String} [state='complete'] state to list jobs by
|
||||
* @param {Number} limit limit of jobs to load
|
||||
* @return {Promise}
|
||||
*/
|
||||
function rangeJobsByState(state = 'complete', limit) {
|
||||
return new Promise((resolve, reject) => {
|
||||
kue.Job.rangeByState(state, 0, limit, 'asc', (err, jobs) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(jobs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the jobs that are in the queue.
|
||||
*/
|
||||
function cleanupJobs(options) {
|
||||
const n = 100;
|
||||
|
||||
Promise.all([
|
||||
rangeJobsByState('complete', n),
|
||||
options.stuck ? rangeJobsByState('failed', n) : false
|
||||
])
|
||||
.then((joblists) => joblists.filter((jobs) => jobs).map(removeJobs))
|
||||
.then(() => {
|
||||
util.shutdown();
|
||||
console.log('Removed old jobs');
|
||||
});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Setting up the program command line arguments.
|
||||
//==============================================================================
|
||||
@@ -40,6 +103,12 @@ program
|
||||
.description('starts job processing')
|
||||
.action(processJobs);
|
||||
|
||||
program
|
||||
.command('cleanup')
|
||||
.option('-s, --stuck', 'cleans up jobs that have been stuck', false)
|
||||
.description('cleans up inactive jobs')
|
||||
.action(cleanupJobs);
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
// If there is no command listed, output help.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as actions from '../constants/auth';
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
// Check Login
|
||||
|
||||
@@ -9,8 +9,7 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
dispatch(checkLoginRequest());
|
||||
fetch(`${base}/auth`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth')
|
||||
.then(user => {
|
||||
const isAdmin = !!user.roles.filter(i => i === 'admin').length;
|
||||
dispatch(checkLoginSuccess(user, isAdmin));
|
||||
@@ -26,8 +25,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
|
||||
|
||||
export const logout = () => dispatch => {
|
||||
dispatch(logOutRequest());
|
||||
fetch(`${base}/auth`, getInit('DELETE'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth', {method: 'DELETE'})
|
||||
.then(() => dispatch(logOutSuccess()))
|
||||
.catch(error => dispatch(logOutFailure(error)));
|
||||
};
|
||||
|
||||
@@ -9,12 +9,11 @@ import {
|
||||
SET_ROLE
|
||||
} from '../constants/community';
|
||||
|
||||
import {base, getInit, handleResp} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
export const fetchCommenters = (query = {}) => dispatch => {
|
||||
dispatch(requestFetchCommenters());
|
||||
fetch(`${base}/user?${qs.stringify(query)}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi(`/user?${qs.stringify(query)}`)
|
||||
.then(({result, page, count, limit, totalPages}) =>
|
||||
dispatch({
|
||||
type: FETCH_COMMENTERS_SUCCESS,
|
||||
@@ -42,7 +41,7 @@ export const newPage = () => ({
|
||||
});
|
||||
|
||||
export const setRole = (id, role) => dispatch => {
|
||||
return fetch(`${base}/user/${id}/role`, getInit('POST', {role}))
|
||||
return coralApi(`/user/${id}/role`, {method: 'POST', body: {role}})
|
||||
.then(() => {
|
||||
return dispatch({type: SET_ROLE, id, role});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
export const SETTINGS_LOADING = 'SETTINGS_LOADING';
|
||||
export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED';
|
||||
@@ -12,8 +12,7 @@ export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED';
|
||||
|
||||
export const fetchSettings = () => dispatch => {
|
||||
dispatch({type: SETTINGS_LOADING});
|
||||
fetch(`${base}/settings`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
coralApi('/settings')
|
||||
.then(settings => {
|
||||
dispatch({type: SETTINGS_RECEIVED, settings});
|
||||
})
|
||||
@@ -29,8 +28,7 @@ export const updateSettings = settings => {
|
||||
export const saveSettingsToServer = () => (dispatch, getState) => {
|
||||
const settings = getState().settings.toJS().settings;
|
||||
dispatch({type: SAVE_SETTINGS_LOADING});
|
||||
fetch(`${base}/settings`, getInit('PUT', settings))
|
||||
.then(handleResp)
|
||||
coralApi('/settings', {method: 'PUT', body: settings})
|
||||
.then(() => {
|
||||
dispatch({type: SAVE_SETTINGS_SUCCESS, settings});
|
||||
})
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
export const base = '/api/v1';
|
||||
|
||||
export const getInit = (method, body) => {
|
||||
let init = {
|
||||
method,
|
||||
headers: new Headers({
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}),
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
|
||||
if (method.toLowerCase() !== 'get') {
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
return init;
|
||||
};
|
||||
|
||||
export const handleResp = res => {
|
||||
if (res.status === 401) {
|
||||
throw new Error('Not Authorized to make this request');
|
||||
} else if (res.status > 399) {
|
||||
throw new Error('Error! Status ', res.status);
|
||||
} else if (res.status === 204) {
|
||||
return res.text();
|
||||
} else {
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import {base, handleResp, getInit} from '../../../coral-framework/helpers/response';
|
||||
import coralApi from '../../../coral-framework/helpers/response';
|
||||
|
||||
/**
|
||||
* The adapter is a redux middleware that interecepts the actions that need
|
||||
@@ -33,14 +33,13 @@ export default store => next => action => {
|
||||
const fetchModerationQueueComments = store =>
|
||||
|
||||
Promise.all([
|
||||
fetch(`${base}/queue/comments/pending`, getInit('GET')),
|
||||
fetch(`${base}/comments?status=rejected`, getInit('GET')),
|
||||
fetch(`${base}/comments?action_type=flag`, getInit('GET'))
|
||||
coralApi('/queue/comments/pending'),
|
||||
coralApi('/comments?status=rejected'),
|
||||
coralApi('/comments?action_type=flag')
|
||||
])
|
||||
.then(res => Promise.all(res.map(handleResp)))
|
||||
.then(res => {
|
||||
res[2] = res[2].map(comment => { comment.flagged = true; return comment; });
|
||||
return res.reduce((prev, curr) => prev.concat(curr), []);
|
||||
.then(([pending, rejected, flagged]) => {
|
||||
flagged.forEach(comment => comment.flagged = true);
|
||||
return [...pending, ...rejected, ...flagged];
|
||||
})
|
||||
.then(res => store.dispatch({type: 'COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS',
|
||||
comments: res}))
|
||||
@@ -49,8 +48,7 @@ Promise.all([
|
||||
// Update a comment. Now to update a comment we need to send back the whole object
|
||||
|
||||
const updateComment = (store, comment) => {
|
||||
fetch(`${base}/comments/${comment.get('id')}/status`, getInit('PUT', {status: comment.get('status')}))
|
||||
.then(handleResp)
|
||||
coralApi(`/comments/${comment.get('id')}/status`, {method: 'PUT', body: {status: comment.get('status')}})
|
||||
.then(res => store.dispatch({type: 'COMMENT_UPDATE_SUCCESS', res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_UPDATE_FAILED', error}));
|
||||
};
|
||||
@@ -63,8 +61,7 @@ const createComment = (store, name, comment) => {
|
||||
name: name,
|
||||
createdAt: Date.now()
|
||||
};
|
||||
return fetch(`${base}/comments`, getInit('POST', body))
|
||||
.then(handleResp)
|
||||
return coralApi('/comments', {method: 'POST', body})
|
||||
.then(res => store.dispatch({type: 'COMMENT_CREATE_SUCCESS', comment: res}))
|
||||
.catch(error => store.dispatch({type: 'COMMENT_CREATE_FAILED', error}));
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut lobortis sollicitudin eros a ornare. Curabitur dignissim vestibulum massa non rhoncus. Cras laoreet ante vel nunc hendrerit, ac imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Pellentesque interdum nec elit sed tincidunt. Donec volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non enim nec neque congue faucibus porttitor sit amet dui.</p>
|
||||
<p>Nunc pharetra orci id diam feugiat, vitae rutrum magna efficitur. Morbi porttitor blandit lorem, et facilisis tellus luctus at. Morbi tincidunt eget nisl id placerat. Nullam consectetur quam vel mauris lacinia, non consectetur est faucibus. Duis cursus auctor nulla nec sagittis. Aenean sem erat, ultrices a hendrerit consectetur, accumsan non lorem. Integer ac neque sed magna sodales vulputate at quis neque. Praesent eget ornare lacus. Donec ultricies, dolor eget commodo faucibus, arcu velit ullamcorper tellus, in cursus tellus elit sed urna. Suspendisse in consequat magna. Duis vel ullamcorper tortor, vel cursus libero. Proin et nisi luctus ligula faucibus luctus. Morbi pulvinar, justo ac feugiat elementum, libero tellus congue justo, pharetra ultrices felis felis id leo. Integer mattis quam tempus libero porta, ac pretium ligula elementum.</p>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script type='text/javascript' src='/client/js/lib/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', 'index.html', {title: 'comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})</script>
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
itemActions,
|
||||
Notification,
|
||||
notificationActions,
|
||||
authActions
|
||||
authActions,
|
||||
} from '../../coral-framework';
|
||||
import {connect} from 'react-redux';
|
||||
import CommentBox from '../../coral-plugin-commentbox/CommentBox';
|
||||
@@ -19,6 +19,8 @@ import LikeButton from '../../coral-plugin-likes/LikeButton';
|
||||
import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton';
|
||||
import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
|
||||
import UserBox from '../../coral-sign-in/components/UserBox';
|
||||
import {TabBar, Tab, TabContent} from '../../coral-ui';
|
||||
import SettingsContainer from '../../coral-settings/containers/SettingsContainer';
|
||||
|
||||
const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions;
|
||||
const {addNotification, clearNotification} = notificationActions;
|
||||
@@ -46,11 +48,27 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
},
|
||||
appendItemArray: (item, property, value, addToFront, itemType) =>
|
||||
dispatch(appendItemArray(item, property, value, addToFront, itemType)),
|
||||
logout: () => dispatch(logout())
|
||||
logout: () => dispatch(logout()),
|
||||
});
|
||||
|
||||
class CommentStream extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
activeTab: 0
|
||||
};
|
||||
|
||||
this.changeTab = this.changeTab.bind(this);
|
||||
}
|
||||
|
||||
changeTab (tab) {
|
||||
this.setState({
|
||||
activeTab: tab
|
||||
});
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
items: PropTypes.object.isRequired,
|
||||
addItem: PropTypes.func.isRequired,
|
||||
@@ -60,13 +78,32 @@ class CommentStream extends Component {
|
||||
componentDidMount () {
|
||||
// Set up messaging between embedded Iframe an parent component
|
||||
// Using recommended Pym init code which violates .eslint standards
|
||||
const pym = new Pym.Child({polling: 100});
|
||||
this.pym = new Pym.Child({polling: 100});
|
||||
|
||||
if (/https?\:\/\/([^?]+)/.test(pym.parentUrl)) {
|
||||
this.props.getStream(pym.parentUrl);
|
||||
} else {
|
||||
this.props.getStream(window.location);
|
||||
}
|
||||
const path = this.pym.parentUrl.split('#')[0];
|
||||
|
||||
this.props.getStream(path || window.location);
|
||||
this.path = path;
|
||||
|
||||
this.pym.sendMessage('childReady');
|
||||
|
||||
this.pym.onMessage('DOMContentLoaded', hash => {
|
||||
// the comment ids can start with numbers, which is invalid for DOM id attributes
|
||||
const commentId = hash.replace('#', 'c_');
|
||||
let count = 0;
|
||||
const interval = setInterval(() => {
|
||||
if (document.getElementById(commentId)) {
|
||||
window.clearInterval(interval);
|
||||
this.pym.scrollParentToChildEl(commentId);
|
||||
}
|
||||
|
||||
if (++count > 100) { // ~10 seconds
|
||||
// give up waiting for the comments to load.
|
||||
// it would be weird for the page to jump after that long.
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
@@ -90,137 +127,148 @@ class CommentStream extends Component {
|
||||
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
|
||||
const {actions, users, comments} = this.props.items;
|
||||
const {loggedIn, user, showSignInDialog} = this.props.auth;
|
||||
const {activeTab} = this.state;
|
||||
|
||||
return <div className={showSignInDialog ? 'expandForSignin' : ''}>
|
||||
{
|
||||
rootItem
|
||||
? <div>
|
||||
<div id="commentBox">
|
||||
<InfoBox
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}/>
|
||||
<Count
|
||||
id={rootItemId}
|
||||
items={this.props.items}/>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
reply={false}
|
||||
author={user}
|
||||
/>
|
||||
{!loggedIn && <SignInContainer />}
|
||||
</div>
|
||||
{
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = comments[commentId];
|
||||
return <div className="comment" key={commentId}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[comment.author_id]}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
like={actions[comment.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
flag={actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
comment_id={commentId}
|
||||
asset_id={comment.asset_id}/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
comment.children &&
|
||||
comment.children.map((replyId) => {
|
||||
let reply = this.props.items.comments[replyId];
|
||||
return <div className="reply" key={replyId}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[reply.author_id]}/>
|
||||
<PubDate created_at={reply.created_at}/>
|
||||
<Content body={reply.body}/>
|
||||
<div className="replyActionsLeft">
|
||||
<ReplyButton
|
||||
? <div className="commentStream">
|
||||
<TabBar onChange={this.changeTab} activeTab={activeTab}>
|
||||
<Tab><Count id={rootItemId} items={this.props.items}/></Tab>
|
||||
<Tab>Settings</Tab>
|
||||
</TabBar>
|
||||
|
||||
<TabContent show={activeTab === 0}>
|
||||
<div id="commentBox">
|
||||
<InfoBox
|
||||
content={this.props.config.infoBoxContent}
|
||||
enable={this.props.config.infoBoxEnable}
|
||||
/>
|
||||
{loggedIn && <UserBox user={user} logout={this.props.logout} />}
|
||||
<CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
reply={false}
|
||||
author={user}
|
||||
/>
|
||||
{!loggedIn && <SignInContainer />}
|
||||
</div>
|
||||
{
|
||||
rootItem.comments && rootItem.comments.map((commentId) => {
|
||||
const comment = comments[commentId];
|
||||
return <div className="comment" key={commentId} id={`c_${commentId}`}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[comment.author_id]}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={commentId}
|
||||
showReply={comment.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
like={actions[comment.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
flag={actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={commentId}
|
||||
articleURL={this.path}/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
comment.children &&
|
||||
comment.children.map((replyId) => {
|
||||
let reply = this.props.items.comments[replyId];
|
||||
return <div className="reply" key={replyId} id={`c_${replyId}`}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[reply.author_id]}/>
|
||||
<PubDate created_at={reply.created_at}/>
|
||||
<Content body={reply.body}/>
|
||||
<div className="replyActionsLeft">
|
||||
<ReplyButton
|
||||
updateItem={this.props.updateItem}
|
||||
id={replyId}
|
||||
showReply={reply.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
like={this.props.items.actions[reply.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
commentId={reply.parent_id}
|
||||
articleURL={this.path}
|
||||
/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={replyId}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={reply.showReply}/>
|
||||
<LikeButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
like={this.props.items.actions[reply.like]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PermalinkButton
|
||||
comment_id={reply.parent_id}
|
||||
asset_id={rootItemId}
|
||||
/>
|
||||
</div>
|
||||
<ReplyBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
showReply={reply.showReply}/>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}/>
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
</div>;
|
||||
})
|
||||
}
|
||||
<Notification
|
||||
notifLength={4500}
|
||||
clearNotification={this.props.clearNotification}
|
||||
notification={this.props.notification}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent show={activeTab === 1}>
|
||||
<SettingsContainer/>
|
||||
</TabContent>
|
||||
</div>
|
||||
: 'Loading'
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ hr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.commentStream {
|
||||
/* prevent absolutely positioned final permalink popover from being clipped */
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
/* Comment Box Styles */
|
||||
.coral-plugin-commentbox-container {
|
||||
display: flex;
|
||||
@@ -106,6 +111,7 @@ hr {
|
||||
/* Comment styles */
|
||||
.comment {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.coral-plugin-commentcontent-text {
|
||||
@@ -139,9 +145,8 @@ hr {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.commentActionsLeft .material-icons,.commentActionsRight .material-icons,
|
||||
.replyActionsLeft .material-icons, .replyActionsRight .material-icons {
|
||||
font-size: 12px;
|
||||
.material-icons {
|
||||
font-size: 12px !important;
|
||||
margin-left: 3px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -154,12 +159,37 @@ hr {
|
||||
color: #F00;
|
||||
}
|
||||
|
||||
/* Comment count styles */
|
||||
.coral-plugin-comment-count-text {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.coral-plugin-pubdate-text {
|
||||
color: #CCC;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-container {
|
||||
/*position: relative;*/
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-popover {
|
||||
display: none;
|
||||
background-color: white;
|
||||
border: 1px solid black;
|
||||
width: calc(100% - 15px);
|
||||
position: absolute;
|
||||
top: 70px;
|
||||
right: 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-popover.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-copy-field {
|
||||
display: block;
|
||||
width: calc(100% - 5px);
|
||||
}
|
||||
|
||||
.coral-plugin-permalinks-copied-text {
|
||||
float: right;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from './../translations';
|
||||
const lang = new I18n(translations);
|
||||
import * as actions from '../constants/auth';
|
||||
import {base, handleResp, getInit} from '../helpers/response';
|
||||
import coralApi, {base} from '../helpers/response';
|
||||
import {addItem} from './items';
|
||||
|
||||
// Dialog Actions
|
||||
@@ -25,8 +25,7 @@ const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
|
||||
|
||||
export const fetchSignIn = (formData) => dispatch => {
|
||||
dispatch(signInRequest());
|
||||
fetch(`${base}/auth/local`, getInit('POST', formData))
|
||||
.then(handleResp)
|
||||
coralApi('/auth/local', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
dispatch(hideSignInDialog());
|
||||
dispatch(signInSuccess(user));
|
||||
@@ -74,8 +73,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
|
||||
|
||||
export const fetchSignUp = formData => dispatch => {
|
||||
dispatch(signUpRequest());
|
||||
fetch(`${base}/user`, getInit('POST', formData))
|
||||
.then(handleResp)
|
||||
coralApi('/user', {method: 'POST', body: formData})
|
||||
.then(({user}) => {
|
||||
dispatch(signUpSuccess(user));
|
||||
setTimeout(() =>{
|
||||
@@ -93,8 +91,7 @@ const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILU
|
||||
|
||||
export const fetchForgotPassword = email => dispatch => {
|
||||
dispatch(forgotPassowordRequest(email));
|
||||
fetch(`${base}/user/request-password-reset`, getInit('POST', {email}))
|
||||
.then(handleResp)
|
||||
coralApi('/user/request-password-reset', {method: 'POST', body: {email}})
|
||||
.then(() => dispatch(forgotPassowordSuccess()))
|
||||
.catch(error => dispatch(forgotPassowordFailure(error)));
|
||||
};
|
||||
@@ -107,8 +104,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
|
||||
|
||||
export const logout = () => dispatch => {
|
||||
dispatch(logOutRequest());
|
||||
fetch(`${base}/auth`, getInit('DELETE'))
|
||||
.then(handleResp)
|
||||
coralApi('/auth', {method: 'DELETE'})
|
||||
.then(() => dispatch(logOutSuccess()))
|
||||
.catch(error => dispatch(logOutFailure(error)));
|
||||
};
|
||||
@@ -126,14 +122,13 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
dispatch(checkLoginRequest());
|
||||
fetch(`${base}/auth`, getInit('GET'))
|
||||
.then((res) => {
|
||||
if (res.status !== 200) {
|
||||
coralApi('/auth')
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
throw new Error('not logged in');
|
||||
}
|
||||
|
||||
return res.json();
|
||||
dispatch(checkLoginSuccess(user));
|
||||
})
|
||||
.then(user => dispatch(checkLoginSuccess(user)))
|
||||
.catch(error => dispatch(checkLoginFailure(error)));
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {getInit, base, handleResp} from '../../coral-framework/helpers/response';
|
||||
import coralApi from '../helpers/response';
|
||||
import {fromJS} from 'immutable';
|
||||
/* Item Actions */
|
||||
|
||||
@@ -95,8 +95,7 @@ export const appendItemArray = (id, property, value, add_to_front, item_type) =>
|
||||
*/
|
||||
export function getStream (assetUrl) {
|
||||
return (dispatch) => {
|
||||
return fetch(`${base}/stream?asset_url=${encodeURIComponent(assetUrl)}`)
|
||||
.then(handleResp)
|
||||
return coralApi(`/stream?asset_url=${encodeURIComponent(assetUrl)}`)
|
||||
.then((json) => {
|
||||
|
||||
/* Add items to the store */
|
||||
@@ -166,8 +165,7 @@ export function getStream (assetUrl) {
|
||||
|
||||
export function getItemsArray (ids) {
|
||||
return (dispatch) => {
|
||||
return fetch(`${base}/item/${ids}`, getInit('GET'))
|
||||
.then(handleResp)
|
||||
return coralApi(`/item/${ids}`)
|
||||
.then((json) => {
|
||||
for (let i = 0; i < json.items.length; i++) {
|
||||
dispatch(addItem(json.items[i]));
|
||||
@@ -196,8 +194,7 @@ export function postItem (item, type, id) {
|
||||
if (id) {
|
||||
item.id = id;
|
||||
}
|
||||
return fetch(`${base}/${type}`, getInit('POST', item))
|
||||
.then(handleResp)
|
||||
return coralApi(`/${type}`, {method: 'POST', body: item})
|
||||
.then((json) => {
|
||||
dispatch(addItem({...item, id:json.id}, type));
|
||||
return json.id;
|
||||
@@ -227,8 +224,7 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
user_id
|
||||
};
|
||||
|
||||
return fetch(`${base}/${item_type}/${item_id}/actions`, getInit('POST', action))
|
||||
.then(handleResp);
|
||||
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -249,7 +245,6 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
|
||||
export function deleteAction (action_id) {
|
||||
return () => {
|
||||
return fetch(`${base}/actions/${action_id}`, {method: 'DELETE'})
|
||||
.then(handleResp);
|
||||
return coralApi(`/actions/${action_id}`, {method: 'DELETE'});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
export const base = '/api/v1';
|
||||
|
||||
export const getInit = (method, body) => {
|
||||
let init = {
|
||||
method,
|
||||
const buildOptions = (inputOptions = {}) => {
|
||||
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
const options = Object.assign({}, defaultOptions, inputOptions);
|
||||
|
||||
if (method.toLowerCase() !== 'get') {
|
||||
init.body = JSON.stringify(body);
|
||||
if (options.method.toLowerCase() !== 'get') {
|
||||
options.body = JSON.stringify(options.body);
|
||||
}
|
||||
|
||||
return init;
|
||||
return options;
|
||||
};
|
||||
|
||||
export const handleResp = res => {
|
||||
const handleResp = res => {
|
||||
if (res.status === 401) {
|
||||
throw new Error('Not Authorized to make this request');
|
||||
} else if (res.status > 399) {
|
||||
@@ -28,3 +30,7 @@ export const handleResp = res => {
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
|
||||
export default (url, options) => {
|
||||
return fetch(`${base}${url}`, buildOptions(options)).then(handleResp);
|
||||
};
|
||||
|
||||
@@ -11,5 +11,5 @@ export {
|
||||
itemActions,
|
||||
I18n,
|
||||
notificationActions,
|
||||
authActions
|
||||
authActions,
|
||||
};
|
||||
|
||||
@@ -14,5 +14,5 @@ export default combineReducers({
|
||||
config,
|
||||
items,
|
||||
notification,
|
||||
auth
|
||||
auth,
|
||||
});
|
||||
|
||||
@@ -9,8 +9,8 @@ const lang = new I18n(translations);
|
||||
class PermalinkButton extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
asset_id: PropTypes.string.isRequired,
|
||||
comment_id: PropTypes.string.isRequired
|
||||
articleURL: PropTypes.string.isRequired,
|
||||
commentId: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
@@ -43,29 +43,27 @@ class PermalinkButton extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const publisherUrl = `${location.protocol}//${location.host}/`;
|
||||
|
||||
return (
|
||||
<div className={`${name}-container`} style={styles}>
|
||||
<div className={`${name}-container`}>
|
||||
<button onClick={this.toggle} className={`${name}-button`}>
|
||||
<i className={`${name}-icon material-icons`} aria-hidden={true}>link</i>
|
||||
{lang.t('permalink.permalink')}
|
||||
</button>
|
||||
<div
|
||||
style={styles.popover(this.state.popoverOpen)}
|
||||
className={`${name}-popover`}>
|
||||
className={`${name}-popover ${this.state.popoverOpen ? 'active' : ''}`}>
|
||||
<input
|
||||
className={`${name}-copy-field`}
|
||||
type='text'
|
||||
ref={input => this.permalinkInput = input}
|
||||
value={`${publisherUrl}${this.props.asset_id}#${this.props.comment_id}`}
|
||||
value={`${this.props.articleURL}#${this.props.commentId}`}
|
||||
onChange={() => {}} />
|
||||
<button className={`${name}-copy-button`} onClick={this.copyPermalink}>Copy</button>
|
||||
{
|
||||
this.state.copySuccessful ? <p>copied to clipboard</p> : null
|
||||
this.state.copySuccessful ? <p className={`${name}-copied-text`}>copied to clipboard</p> : null
|
||||
}
|
||||
{
|
||||
this.state.copyFailure
|
||||
? <p>copying to clipboard not supported in this browser. Use Cmd + C.</p>
|
||||
? <p className={`${name}-copied-error`}>copying to clipboard not supported in this browser. Use Cmd + C.</p>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
@@ -75,20 +73,3 @@ class PermalinkButton extends React.Component {
|
||||
}
|
||||
|
||||
export default onClickOutside(PermalinkButton);
|
||||
|
||||
const styles = {
|
||||
position: 'relative',
|
||||
|
||||
popover: active => {
|
||||
return {
|
||||
display: active ? 'block' : 'none',
|
||||
backgroundColor: 'white',
|
||||
border: '1px solid black',
|
||||
minWidth: 400,
|
||||
position: 'absolute',
|
||||
top: 30,
|
||||
right: 0,
|
||||
padding: 5
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.bio textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
min-height: 100px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #d8d8d8;
|
||||
}
|
||||
|
||||
.bio h1 {
|
||||
font-size: 16px;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.bio p {
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: right;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import styles from './Bio.css';
|
||||
import {Button} from '../../coral-ui';
|
||||
|
||||
export default () => (
|
||||
<div className={styles.bio}>
|
||||
<h1>Bio</h1>
|
||||
<p>Tell the community about yourself</p>
|
||||
<textarea />
|
||||
<div className={styles.actions}>
|
||||
<Button cStyle='cancel' raised>Cancel</Button>
|
||||
<Button cStyle='success'>Save Changes</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import styles from './CommentHistory.css';
|
||||
|
||||
export default ({comments = []}) => (
|
||||
<div className={styles.header}>
|
||||
<h1>Comments</h1>
|
||||
<ul>
|
||||
{comments.map(() => (
|
||||
<li>
|
||||
{/* Comment Data*/}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,8 @@
|
||||
.header h1 {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import styles from './SettingsHeader.css';
|
||||
|
||||
export default () => (
|
||||
<div className={styles.header}>
|
||||
<h1>Jackson</h1>
|
||||
<h2>jackson_persona@gmail.com</h2>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {TabBar, Tab} from '../../coral-ui';
|
||||
|
||||
import Bio from '../components/Bio';
|
||||
import CommentHistory from '../components/CommentHistory';
|
||||
import SettingsHeader from '../components/SettingsHeader';
|
||||
|
||||
class SignInContainer extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeTab: 0
|
||||
};
|
||||
|
||||
this.handleTabChange = this.handleTabChange.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
// Get Bio
|
||||
// Fetch commentHistory
|
||||
}
|
||||
|
||||
handleTabChange(tab) {
|
||||
this.setState({
|
||||
activeTab: tab
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
//const {embedStream} = this.props;
|
||||
const {activeTab} = this.state;
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader />
|
||||
<TabBar onChange={this.handleTabChange} activeTab={activeTab} cStyle='material'>
|
||||
<Tab>All Comments (120)</Tab>
|
||||
<Tab>Profile Settings</Tab>
|
||||
</TabBar>
|
||||
{ activeTab === 0 && <CommentHistory {...this.props}/> }
|
||||
{ activeTab === 1 && <Bio {...this.props}/> }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = () => ({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
getBio: () => dispatch(),
|
||||
getHistory: () => dispatch(),
|
||||
handleSaveChanges: () => dispatch(),
|
||||
handleCancel: () => dispatch()
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(SignInContainer);
|
||||
@@ -34,7 +34,7 @@ class ForgotContent extends React.Component {
|
||||
id="email"
|
||||
name="email" />
|
||||
</div>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton}>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton} full>
|
||||
{lang.t('signIn.recoverPassword')}
|
||||
</Button>
|
||||
{
|
||||
@@ -44,7 +44,7 @@ class ForgotContent extends React.Component {
|
||||
}
|
||||
{
|
||||
passwordRequestFailure
|
||||
? <p className={styles.attention}>{passwordRequestFailure}</p>
|
||||
? <p className={styles.passwordRequestFailure}>{passwordRequestFailure}</p>
|
||||
: null
|
||||
}
|
||||
</form>
|
||||
|
||||
@@ -16,7 +16,7 @@ const SignInContent = ({handleChange, formData, ...props}) => (
|
||||
</h1>
|
||||
</div>
|
||||
<div className={styles.socialConnections}>
|
||||
<Button cStyle="facebook" onClick={props.fetchSignInFacebook}>
|
||||
<Button cStyle="facebook" onClick={props.fetchSignInFacebook} full>
|
||||
{lang.t('signIn.facebookSignIn')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -44,7 +44,7 @@ const SignInContent = ({handleChange, formData, ...props}) => (
|
||||
<div className={styles.action}>
|
||||
{
|
||||
!props.auth.isLoading ?
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton}>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton} full>
|
||||
{lang.t('signIn.signIn')}
|
||||
</Button>
|
||||
:
|
||||
|
||||
@@ -17,7 +17,7 @@ const SignUpContent = ({handleChange, formData, ...props}) => (
|
||||
</h1>
|
||||
</div>
|
||||
<div className={styles.socialConnections}>
|
||||
<Button cStyle="facebook" onClick={props.fetchSignInFacebook}>
|
||||
<Button cStyle="facebook" onClick={props.fetchSignInFacebook} full>
|
||||
{lang.t('signIn.facebookSignUp')}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -69,7 +69,7 @@ const SignUpContent = ({handleChange, formData, ...props}) => (
|
||||
/>
|
||||
<div className={styles.action}>
|
||||
{ !props.auth.isLoading && !props.auth.successSignUp && (
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton}>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton} full>
|
||||
{lang.t('signIn.signUp')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -138,5 +138,6 @@ input.error{
|
||||
|
||||
.passwordRequestFailure {
|
||||
border: 1px solid orange;
|
||||
background-color: 1px solid coral
|
||||
background-color: 1px solid coral;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ class SignInContainer extends Component {
|
||||
const {auth, showSignInDialog} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={showSignInDialog}>
|
||||
<Button onClick={showSignInDialog} full>
|
||||
Sign in to comment
|
||||
</Button>
|
||||
<SignDialog
|
||||
|
||||
@@ -23,8 +23,7 @@
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.type--black {
|
||||
@@ -46,4 +45,34 @@
|
||||
.type--facebook:hover {
|
||||
background-color: #365899;
|
||||
border-color: #365899;
|
||||
}
|
||||
}
|
||||
|
||||
.type--success {
|
||||
color: white;
|
||||
background: #2376d8;
|
||||
}
|
||||
|
||||
.type--success:hover {
|
||||
color: white;
|
||||
background: #2782ee;
|
||||
}
|
||||
|
||||
.type--cancel {
|
||||
color: white;
|
||||
background: #2E343B;
|
||||
}
|
||||
|
||||
.type--cancel:hover {
|
||||
color: white;
|
||||
background: #4f5c67;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.raised {
|
||||
background: rgba(158,158,158,.2);
|
||||
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import React from 'react';
|
||||
import styles from './Button.css';
|
||||
|
||||
const Button = ({cStyle = 'local', children, className, ...props}) => (
|
||||
const Button = ({cStyle = 'local', children, className, raised, full, ...props}) => (
|
||||
<button
|
||||
className={`${styles.button} ${styles[`type--${cStyle}`]} ${className}`}
|
||||
className={`
|
||||
${styles.button}
|
||||
${styles[`type--${cStyle}`]}
|
||||
${className}
|
||||
${full && styles.full}
|
||||
${raised && styles.button}
|
||||
`}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
li.base--active {
|
||||
background: white;
|
||||
}
|
||||
|
||||
li.material--active {
|
||||
font-weight: bold;
|
||||
border-bottom: solid 2px black;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import styles from './Tab.css';
|
||||
|
||||
export default ({children, tabId, active, onTabClick, cStyle = 'base'}) => (
|
||||
<li
|
||||
key={tabId}
|
||||
className={active ? styles[`${cStyle}--active`] : ''}
|
||||
onClick={() => onTabClick(tabId)}
|
||||
>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
.base {
|
||||
list-style: none;
|
||||
border-bottom: solid 1px #D8D8D8;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.base li {
|
||||
color: #4E5259;
|
||||
border: solid 1px #D8D8D8;
|
||||
background: #F0F0F0;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
display: inline-block;
|
||||
border-bottom: none;
|
||||
padding: 8px 10px;
|
||||
margin-right: -1px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.base li:hover {
|
||||
background: #f3f3f3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.material {
|
||||
list-style: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.material li {
|
||||
color: black;
|
||||
border: none;
|
||||
border-bottom: solid 2px white;
|
||||
background: white;
|
||||
padding: 8px 0;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
.material li:hover {
|
||||
background: white;
|
||||
border-bottom: solid 2px grey;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import styles from './TabBar.css';
|
||||
|
||||
export class TabBar extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClickTab = this.handleClickTab.bind(this);
|
||||
}
|
||||
|
||||
handleClickTab(tabId) {
|
||||
if (this.props.onChange) {
|
||||
this.props.onChange(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children, activeTab, cStyle = 'base'} = this.props;
|
||||
return (
|
||||
<div>
|
||||
<ul className={`${styles.base} ${cStyle ? styles[cStyle] : ''}`}>
|
||||
{React.Children.map(children, (child, tabId) =>
|
||||
React.cloneElement(child, {
|
||||
tabId,
|
||||
active: tabId === activeTab,
|
||||
onTabClick: this.handleClickTab,
|
||||
cStyle
|
||||
})
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default TabBar;
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export default ({children, show = true}) => (
|
||||
show ? <div>{children}</div> : null
|
||||
);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export {default as Dialog} from './components/Dialog';
|
||||
export {default as CoralLogo} from './components/CoralLogo';
|
||||
export {default as FabButton} from './components/FabButton';
|
||||
export {default as TabBar} from './components/TabBar';
|
||||
export {default as Tab} from './components/Tab';
|
||||
export {default as TabContent} from './components/TabContent';
|
||||
export {default as Button} from './components/Button';
|
||||
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
+46
-5
@@ -1,7 +1,8 @@
|
||||
const mongoose = require('../mongoose');
|
||||
const uuid = require('uuid');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const uuid = require('uuid');
|
||||
|
||||
const AssetSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
@@ -22,6 +23,10 @@ const AssetSchema = new Schema({
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
settings: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: null
|
||||
},
|
||||
title: String,
|
||||
description: String,
|
||||
image: String,
|
||||
@@ -38,21 +43,32 @@ const AssetSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
AssetSchema.index({
|
||||
title: 'text',
|
||||
url: 'text',
|
||||
description: 'text',
|
||||
section: 'text',
|
||||
subsection: 'text',
|
||||
author: 'text'
|
||||
}, {
|
||||
background: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Search for assets. Currently only returns all.
|
||||
*/
|
||||
*/
|
||||
AssetSchema.statics.search = (query) => Asset.find(query);
|
||||
|
||||
/**
|
||||
* Finds an asset by its id.
|
||||
* @param {String} id identifier of the asset (uuid).
|
||||
*/
|
||||
*/
|
||||
AssetSchema.statics.findById = (id) => Asset.findOne({id});
|
||||
|
||||
/**
|
||||
* Finds a asset by its url.
|
||||
* @param {String} url identifier of the asset (uuid).
|
||||
*/
|
||||
*/
|
||||
AssetSchema.statics.findByUrl = (url) => Asset.findOne({url});
|
||||
|
||||
/**
|
||||
@@ -65,7 +81,8 @@ AssetSchema.statics.findByUrl = (url) => Asset.findOne({url});
|
||||
* is not possible with the mongoose driver.
|
||||
*
|
||||
* @param {String} url identifier of the asset (uuid).
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, {url}, {
|
||||
|
||||
// Ensure that if it's new, we return the new object created.
|
||||
@@ -78,6 +95,30 @@ AssetSchema.statics.findOrCreateByUrl = (url) => Asset.findOneAndUpdate({url}, {
|
||||
setDefaultsOnInsert: true
|
||||
});
|
||||
|
||||
/**
|
||||
* Updates the settings for the asset.
|
||||
* @param {[type]} id [description]
|
||||
* @param {[type]} settings [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
AssetSchema.statics.overrideSettings = (id, settings) => Asset.update({id}, {
|
||||
$set: {
|
||||
settings
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds assets matching keywords on the model. If `value` is an empty string,
|
||||
* then it will not even perform a text search query.
|
||||
* @param {String} value string to search by.
|
||||
* @return {Promise}
|
||||
*/
|
||||
AssetSchema.statics.search = (value) => value.length === 0 ? Asset.find({}) : Asset.find({
|
||||
$text: {
|
||||
$search: value
|
||||
}
|
||||
});
|
||||
|
||||
const Asset = mongoose.model('Asset', AssetSchema);
|
||||
|
||||
module.exports = Asset;
|
||||
|
||||
@@ -9,6 +9,7 @@ const SALT_ROUNDS = 10;
|
||||
|
||||
// USER_ROLES is the array of roles that is permissible as a user role.
|
||||
const USER_ROLES = [
|
||||
'',
|
||||
'admin',
|
||||
'moderator'
|
||||
];
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
const mongoose = require('mongoose');
|
||||
const debug = require('debug')('talk:db');
|
||||
const enabled = require('debug').enabled;
|
||||
const url = process.env.TALK_MONGO_URL || 'mongodb://localhost';
|
||||
const url = process.env.TALK_MONGO_URL || (process.env.NODE_ENV === 'test' ? 'mongodb://localhost/test' : 'mongodb://localhost/talk');
|
||||
|
||||
// Use native promises
|
||||
mongoose.Promise = global.Promise;
|
||||
|
||||
+14
-3
@@ -16,8 +16,13 @@
|
||||
"config": {
|
||||
"pre-git": {
|
||||
"commit-msg": [],
|
||||
"pre-commit": ["npm run lint", "npm test"],
|
||||
"pre-push": ["npm test"],
|
||||
"pre-commit": [
|
||||
"npm run lint",
|
||||
"npm test"
|
||||
],
|
||||
"pre-push": [
|
||||
"npm test"
|
||||
],
|
||||
"post-commit": [],
|
||||
"post-merge": []
|
||||
}
|
||||
@@ -26,7 +31,12 @@
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/coralproject/talk.git"
|
||||
},
|
||||
"keywords": ["talk", "coral", "coralproject", "ask"],
|
||||
"keywords": [
|
||||
"talk",
|
||||
"coral",
|
||||
"coralproject",
|
||||
"ask"
|
||||
],
|
||||
"author": "",
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
@@ -52,6 +62,7 @@
|
||||
"morgan": "^1.7.0",
|
||||
"natural": "^0.4.0",
|
||||
"nodemailer": "^2.6.4",
|
||||
"parse-duration": "^0.1.1",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-local": "^1.0.0",
|
||||
|
||||
@@ -11,17 +11,20 @@ router.get('/', (req, res, next) => {
|
||||
limit = 20,
|
||||
skip = 0,
|
||||
sort = 'asc',
|
||||
field = 'created_at'
|
||||
field = 'created_at',
|
||||
search = ''
|
||||
} = req.query;
|
||||
|
||||
// Find all the assets.
|
||||
Promise.all([
|
||||
Asset
|
||||
.find({})
|
||||
.search(search)
|
||||
.sort({[field]: (sort === 'asc') ? 1 : -1})
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Asset.count()
|
||||
Asset
|
||||
.search(search)
|
||||
.count()
|
||||
])
|
||||
.then(([result, count]) => {
|
||||
|
||||
@@ -78,4 +81,19 @@ router.post('/:asset_id/scrape', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/:asset_id/settings', (req, res, next) => {
|
||||
|
||||
// Override the settings for the asset.
|
||||
Asset
|
||||
.overrideSettings(req.params.asset_id, req.body)
|
||||
.then(() => {
|
||||
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -30,16 +30,36 @@ router.get('/', (req, res, next) => {
|
||||
Setting.getModerationSetting()
|
||||
])
|
||||
.then(([asset, settings]) => {
|
||||
// Get the sitewide moderation setting and return the appropriate comments
|
||||
|
||||
// Merge the asset specific settings with the returned settings object in
|
||||
// the event that the asset that was returned also had settings.
|
||||
if (asset.settings) {
|
||||
settings = Object.assign(settings, asset.settings);
|
||||
}
|
||||
|
||||
// Fetch the appropriate comments stream.
|
||||
let comments;
|
||||
|
||||
if (settings.moderation === 'pre') {
|
||||
comments = Comment.findAcceptedByAssetId(asset.id);
|
||||
} else {
|
||||
comments = Comment.findAcceptedAndNewByAssetId(asset.id);
|
||||
}
|
||||
|
||||
return Promise.all([comments, asset, settings]);
|
||||
return Promise.all([
|
||||
|
||||
// This is the promised component... Fetch the comments based on the
|
||||
// moderation settings.
|
||||
comments,
|
||||
|
||||
// Send back the reference to the asset.
|
||||
asset,
|
||||
|
||||
// Send back the settings to the stream.
|
||||
settings
|
||||
]);
|
||||
})
|
||||
// Get all the users and actions for those comments.
|
||||
.then(([comments, asset, settings]) => {
|
||||
|
||||
// Get the user id's from the author id's as a unique array that gets
|
||||
@@ -73,14 +93,16 @@ router.get('/', (req, res, next) => {
|
||||
// The users who wrote those comments
|
||||
users,
|
||||
|
||||
// The actions on the above items
|
||||
// And all actions about the asset, comments, and users.
|
||||
actions,
|
||||
|
||||
// And the relevant settings
|
||||
// Pass back the settings that we loaded.
|
||||
settings
|
||||
]);
|
||||
})
|
||||
.then(([asset, comments, users, actions, settings]) => {
|
||||
|
||||
// Send back the payload containing all this data.
|
||||
res.json({
|
||||
assets: [asset],
|
||||
comments,
|
||||
|
||||
+8
-2
@@ -6,11 +6,17 @@ router.use('/admin', require('./admin'));
|
||||
router.use('/embed', require('./embed'));
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.render('article', {title: 'Coral Talk'});
|
||||
return res.render('article', {
|
||||
title: 'Coral Talk',
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/assets/:asset_title', (req, res) => {
|
||||
res.render('article', {title: req.params.asset_title.split('-').join(' ')});
|
||||
return res.render('article', {
|
||||
title: req.params.asset_title.split('-').join(' '),
|
||||
basePath: '/client/embed/stream'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -335,6 +335,28 @@ paths:
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
|
||||
/asset/{asset_id}/settings:
|
||||
put:
|
||||
parameters:
|
||||
- name: asset_id
|
||||
in: path
|
||||
required: true
|
||||
type: string
|
||||
format: uuid
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Settings'
|
||||
responses:
|
||||
204:
|
||||
description: The asset settings were updated.
|
||||
404:
|
||||
description: The asset was not found.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
|
||||
/stream:
|
||||
get:
|
||||
|
||||
+27
-1
@@ -1,5 +1,10 @@
|
||||
const Asset = require('../../models/asset');
|
||||
const expect = require('chai').expect;
|
||||
|
||||
const chai = require('chai');
|
||||
const expect = chai.expect;
|
||||
|
||||
// Use the chai should.
|
||||
chai.should();
|
||||
|
||||
describe('Asset: model', () => {
|
||||
|
||||
@@ -53,6 +58,27 @@ describe('Asset: model', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#overrideSettings', () => {
|
||||
it('should update the settings', () => {
|
||||
return Asset
|
||||
.findOrCreateByUrl('https://override.test.com/asset')
|
||||
.then((asset) => {
|
||||
expect(asset).to.have.property('settings');
|
||||
expect(asset.settings).to.be.null;
|
||||
|
||||
return Asset.overrideSettings(asset.id, {moderation: 'pre'});
|
||||
})
|
||||
.then(() => {
|
||||
return Asset.findOrCreateByUrl('https://override.test.com/asset');
|
||||
})
|
||||
.then((asset) => {
|
||||
expect(asset).to.have.property('settings');
|
||||
expect(asset.settings).is.an('object');
|
||||
expect(asset.settings).to.have.property('moderation', 'pre');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findOrCreateByUrl', ()=> {
|
||||
it('should find an asset by a url', () => {
|
||||
return Asset.findOrCreateByUrl('http://test.com')
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('Setting: model', () => {
|
||||
expect(settings).to.have.property('moderation').and.to.equal('pre');
|
||||
});
|
||||
});
|
||||
|
||||
it('should have two infoBox fields defined', () => {
|
||||
return Setting.getSettings().then(settings => {
|
||||
expect(settings).to.have.property('infoBoxEnable').and.to.equal(false);
|
||||
@@ -26,6 +27,7 @@ describe('Setting: model', () => {
|
||||
it('should update the settings with a passed object', () => {
|
||||
const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'};
|
||||
return Setting.updateSettings(mockSettings).then(updatedSettings => {
|
||||
expect(updatedSettings).to.be.an('object');
|
||||
expect(updatedSettings).to.have.property('moderation').and.to.equal('post');
|
||||
expect(updatedSettings).to.have.property('infoBoxEnable', true);
|
||||
expect(updatedSettings).to.have.property('infoBoxContent', 'yeah');
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@ const mongoose = require('../mongoose');
|
||||
|
||||
beforeEach(function (done) {
|
||||
function clearDB() {
|
||||
for (let i in mongoose.connection.collections) {
|
||||
mongoose.connection.collections[i].remove(function() {});
|
||||
for (let collection in mongoose.connection.collections) {
|
||||
mongoose.connection.collections[collection].remove(function() {});
|
||||
}
|
||||
return done();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,84 @@
|
||||
describe('/assets', () => {
|
||||
const passport = require('../../../passport');
|
||||
|
||||
describe('GET', () => {
|
||||
const app = require('../../../../app');
|
||||
const chai = require('chai');
|
||||
const expect = chai.expect;
|
||||
|
||||
it('should return assets that we search for');
|
||||
it('should not return assets that we do not search for');
|
||||
// Setup chai.
|
||||
chai.should();
|
||||
chai.use(require('chai-http'));
|
||||
|
||||
const Asset = require('../../../../models/asset');
|
||||
|
||||
describe('/api/v1/assets', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
return Asset.create([
|
||||
{
|
||||
url: 'https://coralproject.net/news/asset1',
|
||||
title: 'Asset 1',
|
||||
description: 'term1'
|
||||
},
|
||||
{
|
||||
url: 'https://coralproject.net/news/asset2',
|
||||
title: 'Asset 2',
|
||||
description: 'term2'
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
describe('#get', () => {
|
||||
|
||||
it('should return all assets without a search query', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
|
||||
expect(body).to.have.property('count', 2);
|
||||
expect(body).to.have.property('result');
|
||||
|
||||
const assets = body.result;
|
||||
|
||||
expect(assets).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return assets that we search for', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset?search=term2')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
|
||||
expect(body).to.have.property('count', 1);
|
||||
expect(body).to.have.property('result');
|
||||
|
||||
const assets = body.result;
|
||||
|
||||
expect(assets).to.have.length(1);
|
||||
|
||||
const asset = assets[0];
|
||||
|
||||
expect(asset).to.have.property('url', 'https://coralproject.net/news/asset2');
|
||||
expect(asset).to.have.property('title', 'Asset 2');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return assets that we do not search for', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/asset?search=term3')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
const body = res.body;
|
||||
|
||||
expect(body).to.have.property('count', 0);
|
||||
expect(body).to.have.property('result');
|
||||
|
||||
expect(body.result).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -6,32 +6,47 @@ chai.use(require('chai-http'));
|
||||
|
||||
const User = require('../../../../models/user');
|
||||
|
||||
describe('POST /auth/local', () => {
|
||||
describe('/api/v1/auth', () => {
|
||||
describe('#get', () => {
|
||||
it('should return nothing when no user is logged in', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/auth')
|
||||
.then((res) => {
|
||||
expect(res.status).to.be.equal(204);
|
||||
expect(res.body).to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/api/v1/auth/local', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
return User.createLocalUser('maria@gmail.com', 'password!', 'Maria');
|
||||
});
|
||||
|
||||
it('should send back the user on a successful login', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/auth/local')
|
||||
.send({email: 'maria@gmail.com', password: 'password!'})
|
||||
.catch((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.have.property('displayName', 'Maria');
|
||||
});
|
||||
});
|
||||
describe('#post', () => {
|
||||
it('should send back the user on a successful login', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/auth/local')
|
||||
.send({email: 'maria@gmail.com', password: 'password!'})
|
||||
.catch((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('user');
|
||||
expect(res.body.user).to.have.property('displayName', 'Maria');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not send back the user on a unsuccessful login', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/auth/local')
|
||||
.send({email: 'maria@gmail.com', password: 'password!3'})
|
||||
.catch((err) => {
|
||||
expect(err).to.not.be.null;
|
||||
expect(err.response).to.have.status(401);
|
||||
expect(err.response.body).to.have.property('message', 'not authorized');
|
||||
});
|
||||
it('should not send back the user on a unsuccessful login', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/auth/local')
|
||||
.send({email: 'maria@gmail.com', password: 'password!3'})
|
||||
.catch((err) => {
|
||||
expect(err).to.not.be.null;
|
||||
expect(err.response).to.have.status(401);
|
||||
expect(err.response.body).to.have.property('message', 'not authorized');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+156
-303
@@ -16,11 +16,7 @@ const User = require('../../../../models/user');
|
||||
const Setting = require('../../../../models/setting');
|
||||
const settings = {id: '1', moderation: 'pre'};
|
||||
|
||||
beforeEach(() => {
|
||||
return Setting.create(settings);
|
||||
});
|
||||
|
||||
describe('Get /comments', () => {
|
||||
describe('/api/v1/comments', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
@@ -32,61 +28,11 @@ describe('Get /comments', () => {
|
||||
asset_id: 'asset',
|
||||
author_id: '456'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456'
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get comments by status and action', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'def',
|
||||
id: 'def-rejected',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456'
|
||||
author_id: '456',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
@@ -117,109 +63,100 @@ describe('Get comments by status and action', () => {
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=rejected')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the approved comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=accepted')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'hij');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the new comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=new')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the flagged comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?action_type=flag')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
|
||||
expect(res.body.length).to.equal(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post /comments', () => {
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions),
|
||||
wordlist.insert([
|
||||
'bad words'
|
||||
])
|
||||
]),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create a comment', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res.body).to.have.property('id');
|
||||
});
|
||||
describe('#get', () => {
|
||||
it('should return all the comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=rejected')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def-rejected');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the approved comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=accepted')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.length(1);
|
||||
expect(res.body[0]).to.have.property('id', 'hij');
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the new comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=new')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.length(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the flagged comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?action_type=flag')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
|
||||
expect(res.body).to.have.length(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a comment with a rejected status if it contains a bad word', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res.body).to.have.property('id');
|
||||
expect(res.body).to.have.property('status', 'rejected');
|
||||
});
|
||||
describe('#post', () => {
|
||||
|
||||
it('should create a comment', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res.body).to.have.property('id');
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a comment with a rejected status if it contains a bad word', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res.body).to.have.property('id');
|
||||
expect(res.body).to.have.property('status', 'rejected');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Get /:comment_id', () => {
|
||||
describe('/api/v1/comments/:comment_id', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
@@ -264,79 +201,65 @@ describe('Get /:comment_id', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the right comment for the comment_id', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments/abc')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.property('body');
|
||||
expect(res.body).to.have.property('body', 'comment 10');
|
||||
describe('#get', () => {
|
||||
|
||||
});
|
||||
it('should return the right comment for the comment_id', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments/abc')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.property('body');
|
||||
expect(res.body).to.have.property('body', 'comment 10');
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#delete', () => {
|
||||
it('it should remove comment', () => {
|
||||
return chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
return Comment.findById('abc');
|
||||
})
|
||||
.then((comment) => {
|
||||
expect(comment).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#put', () => {
|
||||
it('it should update status', function() {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/comments/abc/status')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
expect(res.body).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('it should not allow a non-admin to update status', () => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/comments/abc/status')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.be.empty;
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('status', 401);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Remove /: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 = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should remove comment', () => {
|
||||
return chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
return Comment.findById('abc');
|
||||
})
|
||||
.then((comment) => {
|
||||
expect(comment).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Put /:comment_id/status', () => {
|
||||
describe('/api/v1/comments/:comment_id/actions', () => {
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
@@ -383,90 +306,20 @@ describe('Put /:comment_id/status', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should update status', function() {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/comments/abc/status')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
expect(res.body).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
it('it should not allow a non-admin to update status', () => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/comments/abc/status')
|
||||
.set(passport.inject({roles: []}))
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.be.empty;
|
||||
})
|
||||
.catch((err) => {
|
||||
expect(err).to.have.property('status', 401);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Post /:comment_id/actions', () => {
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
asset_id: 'asset',
|
||||
author_id: '123',
|
||||
status: ''
|
||||
}, {
|
||||
id: 'def',
|
||||
body: 'comment 20',
|
||||
asset_id: 'asset',
|
||||
author_id: '456',
|
||||
status: 'rejected'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 30',
|
||||
asset_id: '456',
|
||||
status: 'accepted'
|
||||
}];
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
const actions = [{
|
||||
action_type: 'flag',
|
||||
item_id: 'abc'
|
||||
}, {
|
||||
action_type: 'like',
|
||||
item_id: 'hij'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should update actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/actions')
|
||||
.set(passport.inject({id: '456', roles: ['admin']}))
|
||||
.send({'user_id': '456', 'action_type': 'flag'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.body;
|
||||
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');
|
||||
});
|
||||
describe('#post', () => {
|
||||
it('it should update actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/actions')
|
||||
.set(passport.inject({id: '456', roles: ['admin']}))
|
||||
.send({'user_id': '456', 'action_type': 'flag'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.body;
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,11 +15,7 @@ const User = require('../../../../models/user');
|
||||
const Setting = require('../../../../models/setting');
|
||||
const settings = {id: '1', moderation: 'pre'};
|
||||
|
||||
beforeEach(() => {
|
||||
return Setting.create(settings);
|
||||
});
|
||||
|
||||
describe('Get moderation queues rejected, pending, flags', () => {
|
||||
describe('/api/v1/queue', () => {
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
body: 'comment 10',
|
||||
@@ -62,19 +58,22 @@ describe('Get moderation queues rejected, pending, flags', () => {
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
Action.create(actions),
|
||||
Setting.create(settings)
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the pending comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/queue/comments/pending')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.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();
|
||||
});
|
||||
describe('#get', () => {
|
||||
it('should return all the pending comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/queue/comments/pending')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,49 +10,42 @@ chai.use(require('chai-http'));
|
||||
const Setting = require('../../../../models/setting');
|
||||
const defaults = {id: '1', moderation: 'pre'};
|
||||
|
||||
describe('GET /settings', () => {
|
||||
describe('/api/v1/settings', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
|
||||
beforeEach(() => Setting.create(defaults));
|
||||
|
||||
describe('#get', () => {
|
||||
|
||||
it('should return a settings object', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/settings')
|
||||
.set(passport.inject({
|
||||
roles: ['admin']
|
||||
}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('moderation', 'pre');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a settings object', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/settings')
|
||||
.set(passport.inject({
|
||||
roles: ['admin']
|
||||
}))
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.be.json;
|
||||
expect(res.body).to.have.property('moderation', 'pre');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#put', () => {
|
||||
|
||||
// update the settings.
|
||||
describe('update settings', () => {
|
||||
it('should respond ok to a PUT', () => {
|
||||
return Setting
|
||||
.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true})
|
||||
.then(() => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/settings')
|
||||
.set(passport.inject({
|
||||
roles: ['admin']
|
||||
}))
|
||||
.send({moderation: 'post'});
|
||||
})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(204);
|
||||
it('should update the settings', () => {
|
||||
return chai.request(app)
|
||||
.put('/api/v1/settings')
|
||||
.set(passport.inject({roles: ['admin']}))
|
||||
.send({moderation: 'post'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
return Setting.getSettings();
|
||||
})
|
||||
.then(settings => {
|
||||
|
||||
// confirm updated settings in db
|
||||
expect(settings).to.have.property('moderation');
|
||||
expect(settings.moderation).to.equal('post');
|
||||
});
|
||||
return Setting.getSettings();
|
||||
})
|
||||
.then((settings) => {
|
||||
expect(settings).to.have.property('moderation', 'post');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -13,9 +13,12 @@ const Asset = require('../../../../models/asset');
|
||||
|
||||
const Setting = require('../../../../models/setting');
|
||||
|
||||
describe('api/stream: routes', () => {
|
||||
describe('/api/v1/stream', () => {
|
||||
|
||||
const settings = {id: '1', moderation: 'pre'};
|
||||
const settings = {
|
||||
id: '1',
|
||||
moderation: 'post'
|
||||
};
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
@@ -35,7 +38,7 @@ describe('api/stream: routes', () => {
|
||||
asset_id: 'asset',
|
||||
author_id: '456',
|
||||
parent_id: '',
|
||||
status: ''
|
||||
status: 'accepted'
|
||||
}, {
|
||||
id: 'hij',
|
||||
body: 'comment 40',
|
||||
@@ -65,15 +68,26 @@ describe('api/stream: routes', () => {
|
||||
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Asset.findOrCreateByUrl('http://test.com')
|
||||
Asset.findOrCreateByUrl('http://test.com'),
|
||||
Asset
|
||||
.findOrCreateByUrl('http://coralproject.net/asset2')
|
||||
.then((asset) => {
|
||||
return Asset
|
||||
.overrideSettings(asset.id, {moderation: 'pre'})
|
||||
.then(() => asset);
|
||||
})
|
||||
])
|
||||
.then(([users, asset]) => {
|
||||
.then(([users, asset1, asset2]) => {
|
||||
|
||||
comments[0].author_id = users[0].id;
|
||||
comments[1].author_id = users[1].id;
|
||||
comments[2].author_id = users[0].id;
|
||||
comments[3].author_id = users[1].id;
|
||||
|
||||
comments[0].asset_id = asset.id;
|
||||
comments[1].asset_id = asset.id;
|
||||
comments[0].asset_id = asset1.id;
|
||||
comments[1].asset_id = asset1.id;
|
||||
comments[2].asset_id = asset2.id;
|
||||
comments[3].asset_id = asset2.id;
|
||||
|
||||
return Promise.all([
|
||||
Comment.create(comments),
|
||||
@@ -83,17 +97,32 @@ describe('api/stream: routes', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a stream with comments, users and actions for an existing asset', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
.query({'asset_url': 'http://test.com'})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body.assets[0]).to.have.property('url');
|
||||
expect(res.body.comments[0]).to.have.property('body');
|
||||
expect(res.body.users[0]).to.have.property('displayName');
|
||||
expect(res.body.actions[0]).to.have.property('action_type');
|
||||
expect(res.body.settings).to.have.property('moderation');
|
||||
});
|
||||
describe('#get', () => {
|
||||
it('should return a stream with comments, users and actions for an existing asset', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
.query({'asset_url': 'http://test.com'})
|
||||
.then(res => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body.assets.length).to.equal(1);
|
||||
expect(res.body.comments.length).to.equal(2);
|
||||
expect(res.body.users.length).to.equal(2);
|
||||
expect(res.body.actions.length).to.equal(1);
|
||||
expect(res.body.settings).to.have.property('moderation', 'post');
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge the settings when the asset contains settings to override it with', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/stream')
|
||||
.query({'asset_url': 'http://coralproject.net/asset2'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body.assets.length).to.equal(1);
|
||||
expect(res.body.comments.length).to.equal(1);
|
||||
expect(res.body.users.length).to.equal(1);
|
||||
expect(res.body.settings).to.have.property('moderation', 'pre');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+20
-8
@@ -32,13 +32,25 @@
|
||||
<div id='coralStreamEmbed'></div>
|
||||
</main>
|
||||
|
||||
<!--- Script --->
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {
|
||||
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
|
||||
});
|
||||
</script>
|
||||
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var ready = false;
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'})
|
||||
pymParent.onMessage('childReady', function () {
|
||||
var interval = setInterval(function () {
|
||||
if (ready) {
|
||||
window.clearInterval(interval);
|
||||
pymParent.sendMessage('DOMContentLoaded', window.location.hash);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// wait till images and other iframes are loaded before scrolling the page.
|
||||
// or do we want to be more aggressive and scroll when we hit DOM ready?
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
ready = true;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+19
-7
@@ -19,14 +19,26 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id='coralStreamEmbed'></div>
|
||||
|
||||
<!--- Script --->
|
||||
<script type='text/javascript' src='https://pym.nprapps.org/pym.v1.min.js'></script>
|
||||
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
|
||||
<script>
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {
|
||||
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
|
||||
});
|
||||
var ready = false;
|
||||
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream', {title: 'Talk Comments'});
|
||||
pymParent.onMessage('height', function(height) {document.querySelector('#coralStreamEmbed iframe').height = height + 'px'});
|
||||
pymParent.onMessage('childReady', function () {
|
||||
var interval = setInterval(function () {
|
||||
if (ready) {
|
||||
window.clearInterval(interval);
|
||||
pymParent.sendMessage('DOMContentLoaded', window.location.hash);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// wait till images and other iframes are loaded before scrolling the page.
|
||||
// or do we want to be more aggressive and scroll when we hit DOM ready?
|
||||
document.addEventListener('DOMContentLoaded', function (e) {
|
||||
ready = true;
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-4
@@ -74,10 +74,16 @@ module.exports = {
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Copy(buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
}))),
|
||||
new Copy([
|
||||
...buildEmbeds.map(embed => ({
|
||||
from: path.join(__dirname, 'client', `coral-embed-${embed}`, 'style'),
|
||||
to: path.join(__dirname, 'dist', 'embed', embed)
|
||||
})),
|
||||
{
|
||||
from: path.join(__dirname, 'client', 'lib'),
|
||||
to: path.join(__dirname, 'dist', 'embed', 'stream')
|
||||
}
|
||||
]),
|
||||
autoprefixer,
|
||||
precss,
|
||||
new webpack.ProvidePlugin({
|
||||
|
||||
Reference in New Issue
Block a user