mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 19:40:35 +08:00
Merge branch 'master' into admin
This commit is contained in:
+8
-1
@@ -32,7 +32,7 @@ This is a guide to have a common language to talk about "Talk".
|
||||
* Protected Profile: information about users that only moderators and admins can see
|
||||
|
||||
* Queue - Group of items based on a query, aka - moderation queue
|
||||
* Target - The item/s on which an action is performed..
|
||||
* Target - The item/s on which an action is performed
|
||||
|
||||
## Actions
|
||||
|
||||
@@ -64,3 +64,10 @@ Postmoderation means that comments appear on the site _before_ any moderation ac
|
||||
|
||||
* New comments appear in comment streams immediately.
|
||||
* New comments do not appear in moderation queues unless they are flagged by other users.
|
||||
|
||||
### Word lists
|
||||
|
||||
* Banned words - words that the site never allows in a comment
|
||||
* Suspect words - words whose usage needs to be approved by a moderator before being shown in the stream
|
||||
* Approved words - words that are usually Banned or Suspect sitewide, but approved for use in a specific article stream
|
||||
|
||||
|
||||
@@ -13,33 +13,30 @@ process.env.DEBUG = process.env.TALK_DEBUG;
|
||||
const app = require('../app');
|
||||
const debug = require('debug')('talk:server');
|
||||
const http = require('http');
|
||||
const initPromise = require('../init');
|
||||
const init = require('../init');
|
||||
const port = normalizePort(process.env.TALK_PORT || '3000');
|
||||
|
||||
let server;
|
||||
|
||||
initPromise
|
||||
.then(() => {
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
init().then(() => {
|
||||
|
||||
app.set('port', port);
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
server = http.createServer(app);
|
||||
|
||||
server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
});
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
});
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
|
||||
@@ -34,7 +34,11 @@ export default store => next => action => {
|
||||
// Get comments to fill each of the three lists on the mod queue
|
||||
const fetchModerationQueueComments = store =>
|
||||
|
||||
Promise.all([fetch('/api/v1/queue/comments/pending'), fetch('/api/v1/comments/status/rejected'), fetch('/api/v1/comments/action/flag')])
|
||||
Promise.all([
|
||||
fetch('/api/v1/queue/comments/pending'),
|
||||
fetch('/api/v1/comments?status=rejected'),
|
||||
fetch('/api/v1/comments?action=flag')
|
||||
])
|
||||
.then(res => Promise.all(res.map(r => r.json())))
|
||||
.then(res => {
|
||||
res[2] = res[2].map(comment => { comment.flagged = true; return comment; });
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,9 +87,9 @@ const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUE
|
||||
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
|
||||
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
|
||||
|
||||
export const fetchForgotPassword = () => dispatch => {
|
||||
dispatch(forgotPassowordRequest());
|
||||
fetch(`${base}/user/request-password-reset`, getInit('POST'))
|
||||
export const fetchForgotPassword = email => dispatch => {
|
||||
dispatch(forgotPassowordRequest(email));
|
||||
fetch(`${base}/user/request-password-reset`, getInit('POST', {email}))
|
||||
.then(handleResp)
|
||||
.then(() => dispatch(forgotPassowordSuccess()))
|
||||
.catch(error => dispatch(forgotPassowordFailure(error)));
|
||||
|
||||
@@ -15,7 +15,7 @@ const getInit = (method, body) => {
|
||||
};
|
||||
|
||||
const init = {method, headers};
|
||||
if (method.toLowerCase() !== 'get') {
|
||||
if (body) {
|
||||
init.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ const getInit = (method, body) => {
|
||||
};
|
||||
|
||||
const responseHandler = response => {
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
return response.ok ? response.json() : Promise.reject(`${response.status} ${response.statusText}`);
|
||||
};
|
||||
/**
|
||||
@@ -199,8 +202,6 @@ export function postItem (item, type, id) {
|
||||
};
|
||||
}
|
||||
|
||||
//http://localhost:16180/v1/action/flag/user/user_89654/on/item/87e418c5-aafb-4eb7-9ce4-78f28793782a
|
||||
|
||||
/*
|
||||
* PostAction
|
||||
* Posts an action to an item
|
||||
@@ -243,14 +244,9 @@ export function postAction (item_id, action_type, user_id, item_type) {
|
||||
*
|
||||
*/
|
||||
|
||||
export function deleteAction (item_id, action_type, user_id, item_type) {
|
||||
export function deleteAction (action_id) {
|
||||
return () => {
|
||||
const action = {
|
||||
action_type,
|
||||
user_id
|
||||
};
|
||||
|
||||
return fetch(`/api/v1/${item_type}/${item_id}/actions`, getInit('DELETE', action))
|
||||
return fetch(`/api/v1/actions/${action_id}`, {method: 'DELETE'})
|
||||
.then(responseHandler);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ const initialState = Map({
|
||||
showSignInDialog: false,
|
||||
view: 'SIGNIN',
|
||||
error: '',
|
||||
passwordRequestSuccess: null,
|
||||
passwordRequestFailure: null,
|
||||
successSignUp: false
|
||||
});
|
||||
|
||||
@@ -22,6 +24,8 @@ export default function auth (state = initialState, action) {
|
||||
showSignInDialog: false,
|
||||
view: 'SIGNIN',
|
||||
error: '',
|
||||
passwordRequestFailure: null,
|
||||
passwordRequestSuccess: null,
|
||||
successSignUp: false
|
||||
}));
|
||||
case actions.CHANGE_VIEW :
|
||||
@@ -79,6 +83,14 @@ export default function auth (state = initialState, action) {
|
||||
case actions.VALID_FORM:
|
||||
return state
|
||||
.set('error', '');
|
||||
case actions.FETCH_FORGOT_PASSWORD_SUCCESS:
|
||||
return state
|
||||
.set('passwordRequestFailure', null)
|
||||
.set('passwordRequestSuccess', 'If you have a registered account, a password reset link was sent to that email');
|
||||
case actions.FETCH_FORGOT_PASSWORD_FAILURE:
|
||||
return state
|
||||
.set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
|
||||
.set('passwordRequestSuccess', null);
|
||||
default :
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -10,12 +10,13 @@ const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, ad
|
||||
if (!flagged) {
|
||||
postAction(id, 'flag', '123', 'comments')
|
||||
.then((action) => {
|
||||
addItem({...action, current_user:true}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, action.id, 'comments');
|
||||
let id = `${action.action_type}_${action.item_id}`;
|
||||
addItem({id, current_user: action, count: flag ? flag.count + 1 : 1}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, id, 'comments');
|
||||
});
|
||||
addNotification('success', lang.t('flag-notif'));
|
||||
} else {
|
||||
deleteAction(id, 'flag', '123', 'comments')
|
||||
deleteAction(flagged.id)
|
||||
.then(() => {
|
||||
updateItem(id, 'flag', '', 'comments');
|
||||
});
|
||||
|
||||
@@ -10,11 +10,12 @@ const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem}) =
|
||||
if (!liked) {
|
||||
postAction(id, 'like', '123', 'comments')
|
||||
.then((action) => {
|
||||
addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, action.id, 'comments');
|
||||
let id = `${action.action_type}_${action.item_id}`;
|
||||
addItem({id, current_user: action, count: like ? like.count + 1 : 1}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, id, 'comments');
|
||||
});
|
||||
} else {
|
||||
deleteAction(id, 'like', '123', 'comments')
|
||||
deleteAction(liked.id)
|
||||
.then(() => {
|
||||
updateItem(like.id, 'count', like.count - 1, 'actions');
|
||||
updateItem(like.id, 'current_user', false, 'actions');
|
||||
|
||||
@@ -5,25 +5,56 @@ import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import translations from '../translations';
|
||||
const lang = new I18n(translations);
|
||||
|
||||
const ForgotContent = ({changeView, ...props}) => (
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h1>{lang.t('signIn.recoverPassword')}</h1>
|
||||
</div>
|
||||
<form onSubmit={(e) => {e.preventDefault(); props.fetchForgotPassword();}}>
|
||||
<div className={styles.formField}>
|
||||
<label htmlFor="email">{lang.t('signIn.email')}</label>
|
||||
<input type="text" id="email" />
|
||||
class ForgotContent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleSubmit (e) {
|
||||
e.preventDefault();
|
||||
this.props.fetchForgotPassword(this.emailInput.value);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {changeView, auth} = this.props;
|
||||
const {passwordRequestSuccess, passwordRequestFailure} = auth;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h1>{lang.t('signIn.recoverPassword')}</h1>
|
||||
</div>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className={styles.formField}>
|
||||
<label htmlFor="email">{lang.t('signIn.email')}</label>
|
||||
<input
|
||||
ref={input => this.emailInput = input}
|
||||
type="text"
|
||||
id="email"
|
||||
name="email" />
|
||||
</div>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton}>
|
||||
{lang.t('signIn.recoverPassword')}
|
||||
</Button>
|
||||
{
|
||||
passwordRequestSuccess
|
||||
? <p className={styles.passwordRequestSuccess}>{passwordRequestSuccess}</p>
|
||||
: null
|
||||
}
|
||||
{
|
||||
passwordRequestFailure
|
||||
? <p className={styles.attention}>{passwordRequestFailure}</p>
|
||||
: null
|
||||
}
|
||||
</form>
|
||||
<div className={styles.footer}>
|
||||
<span>{lang.t('signIn.needAnAccount')} <a onClick={() => changeView('SIGNUP')}>{lang.t('signIn.register')}</a></span>
|
||||
<span>{lang.t('signIn.alreadyHaveAnAccount')} <a onClick={() => changeView('SIGNIN')}>{lang.t('signIn.signIn')}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="submit" cStyle="black" className={styles.signInButton}>
|
||||
{lang.t('signIn.recoverPassword')}
|
||||
</Button>
|
||||
</form>
|
||||
<div className={styles.footer}>
|
||||
<span>{lang.t('signIn.needAnAccount')} <a onClick={() => changeView('SIGNUP')}>{lang.t('signIn.register')}</a></span>
|
||||
<span>{lang.t('signIn.alreadyHaveAnAccount')} <a onClick={() => changeView('SIGNIN')}>{lang.t('signIn.signIn')}</a></span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ForgotContent;
|
||||
|
||||
@@ -128,4 +128,16 @@ input.error{
|
||||
|
||||
.action {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.passwordRequestSuccess {
|
||||
border: 1px solid green;
|
||||
background-color: lightgreen;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.passwordRequestFailure {
|
||||
border: 1px solid orange;
|
||||
background-color: 1px solid coral
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
const Setting = require('./models/setting');
|
||||
const wordlist = require('./services/wordlist');
|
||||
|
||||
const defaults = {id: '1', moderation: 'pre'};
|
||||
module.exports = Setting.init(defaults);
|
||||
module.exports = () => Promise.all([
|
||||
|
||||
// presumably this file will grow, which is why I've broken it out.
|
||||
// Upsert the settings object.
|
||||
Setting
|
||||
.init({id: '1', moderation: 'pre'})
|
||||
.then(() => {
|
||||
|
||||
// Load in the wordlist now that settings have been init'd.
|
||||
return wordlist.init();
|
||||
})
|
||||
|
||||
]);
|
||||
|
||||
+1
-3
@@ -90,9 +90,7 @@ ActionSchema.statics.findCommentsIdByActionType = function(action_type, item_typ
|
||||
return Action.find({
|
||||
'action_type': action_type,
|
||||
'item_type': item_type
|
||||
},
|
||||
'item_id'
|
||||
);
|
||||
}, 'item_id');
|
||||
};
|
||||
|
||||
const Action = mongoose.model('Action', ActionSchema);
|
||||
|
||||
+44
-36
@@ -17,7 +17,6 @@ const CommentSchema = new Schema({
|
||||
},
|
||||
asset_id: String,
|
||||
author_id: String,
|
||||
username: String,
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['accepted', 'rejected', ''],
|
||||
@@ -31,19 +30,6 @@ const CommentSchema = new Schema({
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// New Statics
|
||||
//==============================================================================
|
||||
|
||||
/**
|
||||
* Create a comment.
|
||||
* @param {String} body content of comment
|
||||
*/
|
||||
CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, status, username) {
|
||||
let comment = new Comment({body, author_id, asset_id, parent_id, status, username});
|
||||
return comment.save();
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Find Statics
|
||||
//==============================================================================
|
||||
@@ -51,7 +37,8 @@ CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, statu
|
||||
/**
|
||||
* Finds a comment by the id.
|
||||
* @param {String} id identifier of comment (uuid)
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findById = function(id) {
|
||||
return Comment.findOne({'id': id});
|
||||
};
|
||||
@@ -59,7 +46,8 @@ CommentSchema.statics.findById = function(id) {
|
||||
/**
|
||||
* Finds ALL the comments by the asset_id.
|
||||
* @param {String} asset_id identifier of the asset which owns this comment (uuid)
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findByAssetId = function(asset_id) {
|
||||
return Comment.find({asset_id});
|
||||
};
|
||||
@@ -68,7 +56,8 @@ CommentSchema.statics.findByAssetId = function(asset_id) {
|
||||
* Finds the accepted comments by the asset_id.
|
||||
* get the comments that are accepted.
|
||||
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findAcceptedByAssetId = function(asset_id) {
|
||||
return Comment.find({asset_id: asset_id, status:'accepted'});
|
||||
};
|
||||
@@ -76,7 +65,8 @@ CommentSchema.statics.findAcceptedByAssetId = function(asset_id) {
|
||||
/**
|
||||
* Finds the new and accepted comments by the asset_id.
|
||||
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
|
||||
return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}});
|
||||
};
|
||||
@@ -84,7 +74,8 @@ CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
|
||||
/**
|
||||
* Find comments by an action that was performed on them.
|
||||
* @param {String} action_type the type of action that was performed on the comment
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findByActionType = function(action_type) {
|
||||
return Action
|
||||
.findCommentsIdByActionType(action_type, 'comment')
|
||||
@@ -99,50 +90,54 @@ CommentSchema.statics.findByActionType = function(action_type) {
|
||||
* Find not moderated comments by an action that was performed on them.
|
||||
* @param {String} action_type the type of action that was performed on the comment
|
||||
* @param {String} status the status of the comment to search for
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findByStatusByActionType = function(status, action_type) {
|
||||
return Action
|
||||
.findCommentsIdByActionType(action_type, 'comment')
|
||||
.then((actions) => {
|
||||
|
||||
return Comment.find({
|
||||
'status': status,
|
||||
'id': {
|
||||
'$in': actions.map(a => {
|
||||
return a.item_id;
|
||||
})
|
||||
status: status,
|
||||
id: {
|
||||
$in: actions.map(a => a.item_id)
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments by their status.
|
||||
* @param {String} status the status of the comment to search for
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.findByStatus = function(status) {
|
||||
return Comment.find({'status': status});
|
||||
return Comment.find({
|
||||
status: status === 'new' ? '' : status
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find comments that need to be moderated (aka moderation queue).
|
||||
* @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings.
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.moderationQueue = function(moderation) {
|
||||
switch(moderation){
|
||||
|
||||
// Pre-moderation: New comments are shown in the moderator queues immediately.
|
||||
case 'pre':
|
||||
return Comment.findByStatus('').then((comments) => {
|
||||
return comments;
|
||||
});
|
||||
|
||||
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
|
||||
case 'post':
|
||||
return Comment.findByStatusByActionType('', 'flag').then((comments) => {
|
||||
return comments;
|
||||
});
|
||||
|
||||
default:
|
||||
throw new Error('Moderation setting not found.');
|
||||
return Promise.reject(Error('Moderation setting not found.'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,16 +149,18 @@ CommentSchema.statics.moderationQueue = function(moderation) {
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.changeStatus = function(id, status) {
|
||||
return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}}, {upsert: false, new: true});
|
||||
return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an action to the comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} action the new action to the comment
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.addAction = function(id, user_id, action_type) {
|
||||
// check that the comment exist
|
||||
let action = new Action({
|
||||
@@ -183,7 +180,8 @@ CommentSchema.statics.addAction = function(id, user_id, action_type) {
|
||||
* Change the status of a comment.
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} status the new status of the comment
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.removeById = function(id) {
|
||||
return Comment.remove({'id': id});
|
||||
};
|
||||
@@ -193,7 +191,8 @@ CommentSchema.statics.removeById = function(id) {
|
||||
* @param {String} id identifier of the comment (uuid)
|
||||
* @param {String} action_type the type of the action to be removed
|
||||
* @param {String} user_id the id of the user performing the action
|
||||
*/
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.removeAction = function(item_id, user_id, action_type) {
|
||||
return Action.remove({
|
||||
action_type,
|
||||
@@ -203,6 +202,15 @@ CommentSchema.statics.removeAction = function(item_id, user_id, action_type) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all the comments in the collection.
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.all = () => {
|
||||
return Comment.find();
|
||||
};
|
||||
|
||||
// Comment model.
|
||||
const Comment = mongoose.model('Comment', CommentSchema);
|
||||
|
||||
module.exports = Comment;
|
||||
|
||||
+3
-2
@@ -2,7 +2,7 @@ const mongoose = require('../mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
/**
|
||||
* this Schema manages application settings that get used on front- and backend
|
||||
* this Schema manages application settings that get used on front and backend
|
||||
* NOTE: when you set a setting here, it will not automatically be exposed to
|
||||
* the front end. You must add it to the whitelist in the settings route
|
||||
* in /routes/api/settings/index.js
|
||||
@@ -12,7 +12,8 @@ const SettingSchema = new Schema({
|
||||
id: {type: String, default: '1'},
|
||||
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'},
|
||||
infoBoxEnable: {type: Boolean, default: false},
|
||||
infoBoxContent: {type: String, default: ''}
|
||||
infoBoxContent: {type: String, default: ''},
|
||||
wordlist: [String]
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
|
||||
+4
-14
@@ -16,13 +16,8 @@
|
||||
"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": []
|
||||
}
|
||||
@@ -31,12 +26,7 @@
|
||||
"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": {
|
||||
@@ -56,13 +46,13 @@
|
||||
"helmet": "^3.1.0",
|
||||
"jsonwebtoken": "^7.1.9",
|
||||
"lodash": "^4.16.6",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"mongoose": "^4.6.5",
|
||||
"morgan": "^1.7.0",
|
||||
"nodemailer": "^2.6.4",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"natural": "^0.4.0",
|
||||
"prompt": "^1.0.0",
|
||||
"react-linkify": "^0.1.3",
|
||||
"redis": "^2.6.3",
|
||||
|
||||
@@ -5,9 +5,12 @@ router.get('/embed/stream/preview', (req, res) => {
|
||||
res.render('embed-stream', {basePath: '/client/embed/stream'});
|
||||
});
|
||||
|
||||
router.get('/password-reset/:token', (req, res, next) => {
|
||||
// render a page or something?
|
||||
res.send('ok');
|
||||
// this route is expecting there to be a token in the hash
|
||||
// see /views/password-reset-email.ejs
|
||||
router.get('/password-reset', (req, res, next) => {
|
||||
// TODO: store the redirect uri in the token or something fancy
|
||||
// admins and regular users should probably be redirected to different places.
|
||||
res.render('password-reset', {redirectUri: process.env.TALK_ROOT_URL});
|
||||
});
|
||||
|
||||
router.get('*', (req, res) => {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
const express = require('express');
|
||||
const Action = require('../../../models/action');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.delete('/:action_id', (req, res, next) => {
|
||||
Action
|
||||
.findOneAndRemove({
|
||||
id: req.params.action_id
|
||||
})
|
||||
.then(() => {
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
+79
-115
@@ -1,147 +1,111 @@
|
||||
const express = require('express');
|
||||
const Comment = require('../../../models/comment');
|
||||
const wordlist = require('../../../services/wordlist');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
//==============================================================================
|
||||
// Get Routes
|
||||
//==============================================================================
|
||||
|
||||
router.get('/', (req, res, next) => {
|
||||
Comment.find({}).then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
let query;
|
||||
|
||||
if (req.query.status) {
|
||||
query = Comment.findByStatus(req.query.status);
|
||||
} else if (req.query.action_type) {
|
||||
query = Comment.findByActionType(req.query.action_type);
|
||||
} else {
|
||||
query = Comment.all();
|
||||
}
|
||||
|
||||
query.then(comments => {
|
||||
res.json(comments);
|
||||
})
|
||||
.catch(next);
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/', wordlist.filter('body'), (req, res, next) => {
|
||||
|
||||
const {
|
||||
body,
|
||||
asset_id,
|
||||
parent_id,
|
||||
author_id
|
||||
} = req.body;
|
||||
|
||||
Comment
|
||||
.create({
|
||||
body,
|
||||
asset_id,
|
||||
parent_id,
|
||||
status: req.wordlist.matched ? 'rejected' : '',
|
||||
author_id
|
||||
})
|
||||
.then((comment) => {
|
||||
|
||||
res.status(201).send(comment);
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:comment_id', (req, res, next) => {
|
||||
Comment
|
||||
.findById(req.params.comment_id)
|
||||
.then(comment => {
|
||||
if (!comment) {
|
||||
res.status(404).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json(comment);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Get all the comments that have an action_type over them.
|
||||
router.get('/action/:action_type', (req, res, next) => {
|
||||
Comment
|
||||
.findByActionType(req.params.action_type)
|
||||
.then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Get all the comments that were rejected.
|
||||
router.get('/status/rejected', (req, res, next) => {
|
||||
Comment.findByStatus('rejected').then(comments => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(next);
|
||||
});
|
||||
|
||||
// Get all the comments that were accepted.
|
||||
router.get('/status/accepted', (req, res, next) => {
|
||||
Comment.findByStatus('accepted').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
// Get all the not moderated comments.
|
||||
router.get('/status/new', (req, res, next) => {
|
||||
Comment.findByStatus('').then((comments) => {
|
||||
res.status(200).json(comments);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Post Routes
|
||||
//==============================================================================
|
||||
|
||||
router.post('/', (req, res, next) => {
|
||||
|
||||
const {body, author_id, asset_id, parent_id, status, username} = req.body;
|
||||
|
||||
Comment
|
||||
.new(body, author_id, asset_id, parent_id, status, username)
|
||||
.then((comment) => {
|
||||
res.status(200).send({'id': comment.id});
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id', (req, res, next) => {
|
||||
Comment
|
||||
.findById(req.params.comment_id)
|
||||
.then((comment) => {
|
||||
comment.body = req.body.body;
|
||||
comment.author_id = req.body.author_id;
|
||||
comment.asset_id = req.body.asset_id;
|
||||
comment.parent_id = req.body.parent_id;
|
||||
comment.status = req.body.status;
|
||||
return comment.save();
|
||||
})
|
||||
.then((comment) => {
|
||||
res.status(200).send(comment);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id/status', (req, res, next) => {
|
||||
|
||||
Comment
|
||||
.changeStatus(req.params.comment_id, req.body.status)
|
||||
.then(comment => res.status(200).send(comment))
|
||||
.catch(error => next(error));
|
||||
|
||||
});
|
||||
|
||||
router.post('/:comment_id/actions', (req, res, next) => {
|
||||
Comment
|
||||
.addAction(req.params.comment_id, req.body.user_id, req.body.action_type)
|
||||
.then((action) => {
|
||||
res.status(200).send(action);
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
});
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// Delete Routes
|
||||
//==============================================================================
|
||||
|
||||
router.delete('/:comment_id', (req, res, next) => {
|
||||
Comment
|
||||
.removeById(req.params.comment_id)
|
||||
.then(() => {
|
||||
res.status(201).send({});
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:comment_id/actions', (req, res, next) => {
|
||||
console.log(req.params);
|
||||
router.put('/:comment_id/status', (req, res, next) => {
|
||||
|
||||
const {
|
||||
status
|
||||
} = req.body;
|
||||
|
||||
Comment
|
||||
.removeAction(req.params.comment_id, req.body.user_id, req.body.action_type)
|
||||
.changeStatus(req.params.comment_id, status)
|
||||
.then(() => {
|
||||
res.status(201).send({});
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch(error => {
|
||||
next(error);
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:comment_id/actions', (req, res, next) => {
|
||||
|
||||
const {
|
||||
user_id,
|
||||
action_type
|
||||
} = req.body;
|
||||
|
||||
Comment
|
||||
.addAction(req.params.comment_id, user_id, action_type)
|
||||
.then((action) => {
|
||||
res.status(201).json(action);
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,5 +9,6 @@ router.use('/queue', require('./queue'));
|
||||
router.use('/settings', require('./settings'));
|
||||
router.use('/stream', require('./stream'));
|
||||
router.use('/user', require('./user'));
|
||||
router.use('/actions', require('./actions'));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const express = require('express');
|
||||
const _ = require('lodash');
|
||||
|
||||
const Comment = require('../../../models/comment');
|
||||
const User = require('../../../models/user');
|
||||
@@ -25,9 +26,9 @@ router.get('/', (req, res, next) => {
|
||||
case 'pre':
|
||||
return Promise.all([Comment.findAcceptedByAssetId(asset.id), asset]);
|
||||
case 'post':
|
||||
return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]);
|
||||
return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]);
|
||||
default:
|
||||
throw new Error('Moderation setting not found.');
|
||||
return Promise.reject(new Error('Moderation setting not found.'));
|
||||
}
|
||||
})
|
||||
// Get all the users and actions for those comments.
|
||||
@@ -35,8 +36,12 @@ router.get('/', (req, res, next) => {
|
||||
return Promise.all([
|
||||
[asset],
|
||||
comments,
|
||||
User.findByIdArray(comments.map((comment) => comment.author_id)),
|
||||
Action.getActionSummaries(comments.map((comment) => comment.id))
|
||||
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
|
||||
Action.getActionSummaries(_.uniq([
|
||||
asset.id,
|
||||
...comments.map((comment) => comment.id),
|
||||
...comments.map((comment) => comment.author_id)
|
||||
]))
|
||||
]);
|
||||
})
|
||||
.then(([assets, comments, users, actions]) => {
|
||||
|
||||
@@ -79,6 +79,10 @@ router.post('/', (req, res, next) => {
|
||||
router.post('/update-password', (req, res, next) => {
|
||||
const {token, password} = req.body;
|
||||
|
||||
if (!password || password.length < 8) {
|
||||
return res.status(400).send('Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
User.verifyPasswordResetToken(token)
|
||||
.then(user => {
|
||||
return User.changePassword(user.id, password);
|
||||
@@ -100,7 +104,7 @@ router.post('/request-password-reset', (req, res, next) => {
|
||||
const {email} = req.body;
|
||||
|
||||
if (!email) {
|
||||
return next();
|
||||
return next('you must submit an email when requesting a password.');
|
||||
}
|
||||
|
||||
User
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
const debug = require('debug')('talk:services:wordlist');
|
||||
const _ = require('lodash');
|
||||
const natural = require('natural');
|
||||
const tokenizer = new natural.WordTokenizer();
|
||||
const Setting = require('../models/setting');
|
||||
|
||||
/**
|
||||
* The root wordlist object.
|
||||
* @type {Object}
|
||||
*/
|
||||
const wordlist = {
|
||||
list: [],
|
||||
enabled: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads wordlists in from the naughty-words package based on languages
|
||||
* selected.
|
||||
* @param {Array} languages language codes to add to the wordlist
|
||||
*/
|
||||
wordlist.init = () => {
|
||||
return Setting
|
||||
.getSettings()
|
||||
.then((settings) => {
|
||||
|
||||
// Insert the settings wordlist.
|
||||
wordlist.insert(settings.wordlist);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Inserts the wordlist data and enables the wordlist.
|
||||
* @param {Array} list list of words to be added to the wordlist
|
||||
*/
|
||||
wordlist.insert = (list) => {
|
||||
|
||||
// Add the words to this array, but also lowercase the words so that an
|
||||
// easy comparison can take place.
|
||||
wordlist.list = _.uniq(wordlist.list.concat(list.map((word) => {
|
||||
return tokenizer.tokenize(word.toLowerCase());
|
||||
})));
|
||||
|
||||
debug(`Added ${list.length} words to the wordlist, now the wordlist is ${wordlist.list.length} entries long.`);
|
||||
|
||||
// Enable the wordlist.
|
||||
wordlist.enabled = true;
|
||||
|
||||
return Promise.resolve(wordlist);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests the phrase to see if it contains any of the defined blockwords.
|
||||
* @param {String} phrase value to check for blockwords.
|
||||
* @return {Boolean} true if a blockword is found, false otherwise.
|
||||
*/
|
||||
wordlist.match = (phrase) => {
|
||||
|
||||
// Lowercase the word to ensure that we don't miss a match due to
|
||||
// capitalization.
|
||||
let lowerPhraseWords = tokenizer.tokenize(phrase.toLowerCase());
|
||||
|
||||
// This will return true in the event that at least one blockword is found
|
||||
// in the phrase.
|
||||
return wordlist.list.some((blockphrase) => {
|
||||
|
||||
// First, let's see if we can find the first word in the blockphrase in the
|
||||
// source phrase.
|
||||
let idx = lowerPhraseWords.indexOf(blockphrase[0]);
|
||||
|
||||
if (idx === -1) {
|
||||
|
||||
// The first blockword in the blockphrase did not match the source phrase
|
||||
// anywhere.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here we'll quick respond with true in the event that the blockphrase was
|
||||
// just a single word.
|
||||
if (blockphrase.length === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We found the first word in the source phrase! Lets ensure it matches the
|
||||
// rest of the blockphrase...
|
||||
|
||||
// Check to see if it even has the length to support this word!
|
||||
if (lowerPhraseWords.length < idx + blockphrase.length - 1) {
|
||||
|
||||
// We couldn't possibly have the entire phrase here because we don't have
|
||||
// enough entries!
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 1; i < blockphrase.length; i++) {
|
||||
|
||||
// Check to see if the next word also matches!
|
||||
if (lowerPhraseWords[idx + i] !== blockphrase[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We've walked over all the words of the blockphrase, and haven't had a
|
||||
// mismatch... It does contain the whole word!
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
// ErrContainsProfanity is returned in the event that the middleware detects
|
||||
// profanity/wordlisted words in the payload.
|
||||
const ErrContainsProfanity = new Error('contains profanity');
|
||||
ErrContainsProfanity.status = 400;
|
||||
|
||||
/**
|
||||
* Connect middleware for scanning request bodies for wordlisted words and
|
||||
* attaching a ErrContainsProfanity to the req.wordlisted parameter, otherwise
|
||||
* it will just set that parameter to false.
|
||||
* @param {Array} fields selectors for the body to extract the fields to be
|
||||
* tested
|
||||
* @return {Function} the Connect middleware
|
||||
*/
|
||||
wordlist.filter = (...fields) => (req, res, next) => {
|
||||
|
||||
// Start with the sensible default that the content does not contain
|
||||
// profanity.
|
||||
req.wordlist = {
|
||||
matched: false
|
||||
};
|
||||
|
||||
// If the wordlist isn't enabled, then don't actually perform checking and
|
||||
// forward the request!
|
||||
if (!wordlist.enabled) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Loop over all the fields from the body that we want to check.
|
||||
const containsProfanity = fields.some((field) => {
|
||||
let phrase = _.get(req.body, field, false);
|
||||
|
||||
// If the field doesn't exist in the body, then it can't be profane!
|
||||
if (!phrase) {
|
||||
|
||||
// Return that there wasn't a profane word here.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the field contains a profane word.
|
||||
if (wordlist.match(phrase)) {
|
||||
debug(`the field "${field}" contained a phrase "${phrase}" which contained a wordlisted word/phrase`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// The body could contain some profanity, address that here.
|
||||
if (containsProfanity) {
|
||||
req.wordlist.matched = ErrContainsProfanity;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = wordlist;
|
||||
module.exports.ErrContainsProfanity = ErrContainsProfanity;
|
||||
+260
-156
@@ -7,221 +7,320 @@ host: talk-stg.coralproject.net
|
||||
schemes:
|
||||
- https
|
||||
basePath: /api/v1
|
||||
consumes:
|
||||
- application/json
|
||||
produces:
|
||||
- application/json
|
||||
|
||||
paths:
|
||||
/comments:
|
||||
# get:
|
||||
# tags:
|
||||
# - Comments
|
||||
# produces:
|
||||
# - application/json
|
||||
# summary: Comment Types
|
||||
# description: |
|
||||
# This endpoint retrieves comments
|
||||
# parameters:
|
||||
# - name: id
|
||||
# in: query
|
||||
# description: Comment by id
|
||||
# required: false
|
||||
# type: string
|
||||
# responses:
|
||||
# 200:
|
||||
# description: An array of comments
|
||||
# schema:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/definitions/Comment'
|
||||
get:
|
||||
tags:
|
||||
- Comments
|
||||
parameters:
|
||||
- name: status
|
||||
in: query
|
||||
description: Performs a search based on the comment's status.
|
||||
type: string
|
||||
enum:
|
||||
- flag
|
||||
- name: action_type
|
||||
in: query
|
||||
description: Performs a search based on the actions that have been added to it.
|
||||
type: string
|
||||
enum:
|
||||
- rejected
|
||||
- accepted
|
||||
- new
|
||||
responses:
|
||||
200:
|
||||
description: Comments matching the query.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
- $ref: '#/definitions/Comment'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
post:
|
||||
description: Add a new comment
|
||||
tags:
|
||||
- Comments
|
||||
parameters:
|
||||
- name: body
|
||||
in: body
|
||||
description: Body
|
||||
required: true
|
||||
description: The comment to create.
|
||||
schema:
|
||||
$ref: '#/definitions/Comment'
|
||||
responses:
|
||||
201:
|
||||
description: "OK: Comment Added"
|
||||
description: The comment that was created.
|
||||
schema:
|
||||
$ref: '#/definitions/Comment'
|
||||
$ref: '#/definitions/Comment'
|
||||
500:
|
||||
description: "Error"
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/comments/{comment_id}:
|
||||
get:
|
||||
tags:
|
||||
- Comments
|
||||
parameters:
|
||||
- name: comment_id
|
||||
in: path
|
||||
description: The id of the comment to retrieve.
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: The comment was found.
|
||||
schema:
|
||||
$ref: '#/definitions/Comment'
|
||||
404:
|
||||
description: The comment was not found.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
delete:
|
||||
tags:
|
||||
- Comments
|
||||
parameters:
|
||||
- name: comment_id
|
||||
in: path
|
||||
description: The id of the comment to delete.
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: The comment was deleted.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/comments/{comment_id}/status:
|
||||
put:
|
||||
tags:
|
||||
- Comments
|
||||
- Moderation
|
||||
parameters:
|
||||
- name: comment_id
|
||||
in: path
|
||||
description: The id of the comment to retrieve.
|
||||
type: string
|
||||
required: true
|
||||
- name: body
|
||||
in: body
|
||||
description: The status to update to.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
description: The status to update to.
|
||||
responses:
|
||||
204:
|
||||
description: The comment status was updated.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/comments/{comment_id}/actions:
|
||||
post:
|
||||
tags:
|
||||
- Comments
|
||||
description: Add a action
|
||||
- Actions
|
||||
parameters:
|
||||
- name: comment_id
|
||||
in: path
|
||||
description: Comment ID
|
||||
required: true
|
||||
description: The id of the comment to retrieve.
|
||||
type: string
|
||||
required: true
|
||||
- name: body
|
||||
in: body
|
||||
description: comment
|
||||
description: The action to add.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/Action'
|
||||
type: object
|
||||
properties:
|
||||
action_type:
|
||||
type: string
|
||||
description: The action to add
|
||||
responses:
|
||||
201:
|
||||
description: Action Added
|
||||
description: The action created.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Comment'
|
||||
/comments/{comment_id}/status:
|
||||
post:
|
||||
$ref: '#/definitions/Action'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/actions/{action_id}:
|
||||
delete:
|
||||
tags:
|
||||
- Comments
|
||||
description: Add a new status
|
||||
- Actions
|
||||
parameters:
|
||||
- name: comment_id
|
||||
- name: action_id
|
||||
in: path
|
||||
description: Comment ID
|
||||
required: true
|
||||
description: The id of the action to delete.
|
||||
type: string
|
||||
- name: body
|
||||
in: body
|
||||
description: comment
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/ModerationAction'
|
||||
responses:
|
||||
204:
|
||||
description: ModerationAction Added
|
||||
/queue:
|
||||
description: The action was deleted.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/auth:
|
||||
get:
|
||||
tags:
|
||||
- Queue
|
||||
description: Queue
|
||||
parameters:
|
||||
- name: type
|
||||
in: query
|
||||
description:
|
||||
"pending: no status | flagged: flagged action + no status | rejected: rejected status"
|
||||
required: true
|
||||
type: string
|
||||
enum:
|
||||
- pending
|
||||
- flagged
|
||||
- rejected
|
||||
- name: limit
|
||||
in: query
|
||||
description: Queue limit
|
||||
required: false
|
||||
type: integer
|
||||
- name: skip
|
||||
in: query
|
||||
description: Skip
|
||||
required: false
|
||||
type: integer
|
||||
- Auth
|
||||
description: Retrieves the current authentication credentials.
|
||||
responses:
|
||||
200:
|
||||
description: ModerationAction Added
|
||||
description: The current user.
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
delete:
|
||||
tags:
|
||||
- Auth
|
||||
description: Logs out the current authenticated user.
|
||||
responses:
|
||||
204:
|
||||
description: The current user has been logged out.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/auth/local:
|
||||
post:
|
||||
tags:
|
||||
- Auth
|
||||
parameters:
|
||||
- name: body
|
||||
in: body
|
||||
required: true
|
||||
description: The login credentials.
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
description: The email address of the current user.
|
||||
password:
|
||||
type: string
|
||||
description: The password of the current user.
|
||||
responses:
|
||||
200:
|
||||
description: The user has authenticated sucesfully.
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
||||
401:
|
||||
description: The authentication error.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/auth/facebook:
|
||||
get:
|
||||
tags:
|
||||
- Auth
|
||||
responses:
|
||||
302:
|
||||
description: Redirects the user to perform external facebook authentication.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/queue/comments/pending:
|
||||
get:
|
||||
tags:
|
||||
- Comments
|
||||
- Moderation
|
||||
responses:
|
||||
200:
|
||||
description: The comments that are not moderated.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ModerationAction'
|
||||
- $ref: '#/definitions/Comment'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/stream:
|
||||
get:
|
||||
tags:
|
||||
- Stream
|
||||
description: Stream
|
||||
- Actions
|
||||
- Assets
|
||||
- Comments
|
||||
- Users
|
||||
parameters:
|
||||
- name: asset_id
|
||||
- name: asset_url
|
||||
in: query
|
||||
description: Description
|
||||
required: true
|
||||
description: The asset url to get the comment stream from.
|
||||
type: string
|
||||
format: url
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
description: The comment stream.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Item'
|
||||
type: object
|
||||
properties:
|
||||
assets:
|
||||
type: array
|
||||
items:
|
||||
- $ref: '#/definitions/Asset'
|
||||
comments:
|
||||
type: array
|
||||
items:
|
||||
- $ref: '#/definitions/Comment'
|
||||
users:
|
||||
type: array
|
||||
items:
|
||||
- $ref: '#/definitions/User'
|
||||
actions:
|
||||
type: array
|
||||
items:
|
||||
- $ref: '#/definitions/Actions'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
/settings:
|
||||
get:
|
||||
tags:
|
||||
- Settings
|
||||
description: Settings
|
||||
responses:
|
||||
200:
|
||||
description: Get Setting
|
||||
description: The settings.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Setting'
|
||||
$ref: '#/definitions/Settings'
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
put:
|
||||
tags:
|
||||
- Settings
|
||||
description: Settings
|
||||
responses:
|
||||
204:
|
||||
description: OK
|
||||
|
||||
/user/request-password-reset:
|
||||
post:
|
||||
tags:
|
||||
- Users
|
||||
description: trigger a reset password email. sends a success code whether email was found or no.
|
||||
responses:
|
||||
204:
|
||||
description: OK
|
||||
|
||||
/user/update-password:
|
||||
post:
|
||||
tags:
|
||||
- Users
|
||||
description: Update existing user password
|
||||
parameters:
|
||||
- name: token
|
||||
type: string
|
||||
in: body
|
||||
description: JSON Web token taken taken from emailed link
|
||||
required: true
|
||||
- name: password
|
||||
type: string
|
||||
in: body
|
||||
description: new password to be settings
|
||||
required: true
|
||||
responses:
|
||||
204:
|
||||
description: OK
|
||||
|
||||
/asset:
|
||||
get:
|
||||
tags:
|
||||
- Asset
|
||||
description: Get an asset by id.
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
put:
|
||||
tags:
|
||||
- Asset
|
||||
description: Upsert an asset.
|
||||
responses:
|
||||
204:
|
||||
description: OK
|
||||
/asset?url={url}:
|
||||
get:
|
||||
tags:
|
||||
- Asset
|
||||
parameters:
|
||||
- name: url
|
||||
in: query
|
||||
description: The url of the asset.
|
||||
required: true
|
||||
description: Get an asset by its url.
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
|
||||
description: The settings were updated.
|
||||
500:
|
||||
description: An error occured.
|
||||
schema:
|
||||
$ref: '#/definitions/Error'
|
||||
definitions:
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
description: The error that occured.
|
||||
Item:
|
||||
type: object
|
||||
ModerationAction:
|
||||
@@ -314,5 +413,10 @@ definitions:
|
||||
type: string
|
||||
description: An array of the authors for this asset.
|
||||
publication_date:
|
||||
type: date
|
||||
desctipion: When this asset was published.
|
||||
type: string
|
||||
format: datetime
|
||||
description: When this asset was published.
|
||||
User:
|
||||
type: object
|
||||
Settings:
|
||||
type: object
|
||||
|
||||
@@ -173,7 +173,7 @@ describe('itemActions', () => {
|
||||
fetchMock.delete('*', {});
|
||||
return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch)
|
||||
.then(response => {
|
||||
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions');
|
||||
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/actions/abc');
|
||||
expect(response).to.deep.equal({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ const expect = chai.expect;
|
||||
chai.should();
|
||||
chai.use(require('chai-http'));
|
||||
|
||||
const wordlist = require('../../../../services/wordlist');
|
||||
const Comment = require('../../../../models/comment');
|
||||
const Action = require('../../../../models/action');
|
||||
const User = require('../../../../models/user');
|
||||
@@ -64,13 +65,13 @@ describe('Get /comments', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the comments', function(done){
|
||||
chai.request(app)
|
||||
it('should return all the comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.then((res) => {
|
||||
|
||||
expect(res).to.have.status(200);
|
||||
done();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -122,48 +123,42 @@ describe('Get comments by status and action', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return all the rejected comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/rejected')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
it('should return all the rejected comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=rejected')
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the approved comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/accepted')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
it('should return all the approved comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=accepted')
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'hij');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the new comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/status/new')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
it('should return all the new comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?status=new')
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body[0]).to.have.property('id', 'def');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return all the flagged comments', function(done){
|
||||
chai.request(app)
|
||||
.get('/api/v1/comments/action/flag')
|
||||
.end(function(err, res){
|
||||
it('should return all the flagged comments', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments?action_type=flag')
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(err).to.be.null;
|
||||
|
||||
expect(res.body.length).to.equal(1);
|
||||
expect(res.body[0]).to.have.property('id', 'abc');
|
||||
done();
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -190,18 +185,31 @@ describe('Post /comments', () => {
|
||||
beforeEach(() => {
|
||||
return Promise.all([
|
||||
User.createLocalUsers(users),
|
||||
Action.create(actions)
|
||||
Action.create(actions),
|
||||
wordlist.insert([
|
||||
'bad words'
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
it('it should create a comment', function(done) {
|
||||
chai.request(app)
|
||||
it('should create a comment', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.end(function(err, res){
|
||||
expect(res).to.have.status(200);
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res.body).to.have.property('id');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a comment with a rejected status if it contains a bad word', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments')
|
||||
.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');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -251,72 +259,14 @@ describe('Get /:comment_id', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return the right comment for the comment_id', function(done){
|
||||
chai.request(app)
|
||||
it('should return the right comment for the comment_id', () => {
|
||||
return chai.request(app)
|
||||
.get('/api/v1/comments/abc')
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.property('body');
|
||||
expect(res.body).to.have.property('body', 'comment 10');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Put /: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 update comment', function(done) {
|
||||
chai.request(app)
|
||||
.post('/api/v1/comments/abc')
|
||||
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''})
|
||||
.end(function(err, res){
|
||||
expect(err).to.be.null;
|
||||
expect(res).to.have.status(200);
|
||||
expect(res.body).to.have.property('body', 'Something body.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -369,7 +319,7 @@ describe('Remove /:comment_id', () => {
|
||||
return chai.request(app)
|
||||
.delete('/api/v1/comments/abc')
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.status(204);
|
||||
|
||||
return Comment.findById('abc');
|
||||
})
|
||||
@@ -384,7 +334,7 @@ process.on('unhandledRejection', (reason) => {
|
||||
console.error(reason);
|
||||
});
|
||||
|
||||
describe('Post /:comment_id/status', () => {
|
||||
describe('Put /:comment_id/status', () => {
|
||||
|
||||
const comments = [{
|
||||
id: 'abc',
|
||||
@@ -433,12 +383,11 @@ describe('Post /:comment_id/status', () => {
|
||||
|
||||
it('it should update status', function() {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/status')
|
||||
.put('/api/v1/comments/abc/status')
|
||||
.send({status: 'accepted'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('status', 'accepted');
|
||||
expect(res).to.have.status(204);
|
||||
expect(res.body).to.be.empty;
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -495,7 +444,7 @@ describe('Post /:comment_id/actions', () => {
|
||||
.post('/api/v1/comments/abc/actions')
|
||||
.send({'user_id': '456', 'action_type': 'flag'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(200);
|
||||
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');
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
const expect = require('chai').expect;
|
||||
|
||||
const wordlist = require('../../services/wordlist');
|
||||
|
||||
describe('wordlist: services', () => {
|
||||
|
||||
before(() => wordlist.insert([
|
||||
'BAD',
|
||||
'bad',
|
||||
'how to murder',
|
||||
'how to kill'
|
||||
]));
|
||||
|
||||
beforeEach(() => {
|
||||
expect(wordlist.list).to.not.be.empty;
|
||||
expect(wordlist.enabled).to.be.true;
|
||||
});
|
||||
|
||||
describe('#init', () => {
|
||||
|
||||
it('has entries', () => {
|
||||
expect(wordlist.list).to.not.be.empty;
|
||||
expect(wordlist.enabled).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#match', () => {
|
||||
|
||||
it('does match on a bad word', () => {
|
||||
[
|
||||
'how to kill',
|
||||
'what is bad',
|
||||
'bad',
|
||||
'BAD.',
|
||||
'how to murder',
|
||||
'How To mUrDer'
|
||||
].forEach((word) => {
|
||||
expect(wordlist.match(word)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('does not match on a good word', () => {
|
||||
[
|
||||
'how to',
|
||||
'kill',
|
||||
'bads',
|
||||
'how to be a great person?',
|
||||
'how to not kill?'
|
||||
].forEach((word) => {
|
||||
expect(wordlist.match(word)).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('#filter', () => {
|
||||
|
||||
it('matches on bodies containing bad words', (done) => {
|
||||
|
||||
let req = {
|
||||
body: {
|
||||
content: 'how to kill?'
|
||||
}
|
||||
};
|
||||
|
||||
wordlist.filter('content')(req, {}, (err) => {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req).to.have.property('wordlist');
|
||||
expect(req.wordlist).to.have.property('matched');
|
||||
expect(req.wordlist.matched).to.be.equal(wordlist.ErrContainsProfanity);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('does not match on bodies not containing bad words', (done) => {
|
||||
|
||||
let req = {
|
||||
body: {
|
||||
content: 'how to be a great person?'
|
||||
}
|
||||
};
|
||||
|
||||
wordlist.filter('content')(req, {}, (err) => {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req).to.have.property('wordlist');
|
||||
expect(req.wordlist).to.have.property('matched');
|
||||
expect(req.wordlist.matched).to.be.false;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('does not match on bodies not containing the bad word field', (done) => {
|
||||
|
||||
let req = {
|
||||
body: {
|
||||
author: 'how to kill?',
|
||||
content: 'how to be a great person?'
|
||||
}
|
||||
};
|
||||
|
||||
wordlist.filter('content')(req, {}, (err) => {
|
||||
expect(err).to.be.undefined;
|
||||
expect(req).to.have.property('wordlist');
|
||||
expect(req.wordlist).to.have.property('matched');
|
||||
expect(req.wordlist.matched).to.be.false;
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- extremely naive implementation of a password reset email -->
|
||||
<p>We received a request to reset your password. If you did not request this change, you can ignore this email.<br />
|
||||
If you did, <a href="<%= rootURL %>/admin/password-reset/<%= token %>">please click here to reset password</a>.</p>
|
||||
If you did, <a href="<%= rootURL %>/admin/password-reset#<%= token %>">please click here to reset password</a>.</p>
|
||||
<% if (process.env.NODE_ENV !== 'production') { %>
|
||||
<p style="color: red"><%= token %></p>
|
||||
<% } %>
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
|
||||
<title>Password Reset</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
|
||||
|
||||
<style media="screen">
|
||||
body, #root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#root form {
|
||||
max-width: 300px;
|
||||
border: 1px solid lightgrey;
|
||||
box-shadow: 0px 10px 24px 2px rgba(0,0,0,0.2);
|
||||
margin: 50px auto;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 3px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
small {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
input {
|
||||
border-radius: 4px;
|
||||
margin-top: 3px;
|
||||
border: 1px solid lightgrey;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
padding: 14px;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.submit-password-reset {
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
display: block;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-top: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error-console {
|
||||
display: none;
|
||||
margin-top: 10px;
|
||||
border-radius: 4px;
|
||||
background-color: pink;
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.error-console.active {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
<form id="reset-password-form">
|
||||
<legend class="legend">Set new password</legend>
|
||||
<label for="password">
|
||||
New password
|
||||
<input type="password" name="password" placeholder="new password" />
|
||||
<p><small>Password must be at least 8 characters</small></p>
|
||||
</label>
|
||||
|
||||
<label for="confirm-password">
|
||||
Confirm password
|
||||
<input type="password" name="confirm-password" placeholder="confirm password" />
|
||||
</label>
|
||||
<button class="submit-password-reset" type="submit">Apply</button>
|
||||
<div class="error-console">foo</div>
|
||||
</form>
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
|
||||
<script>
|
||||
$(function () {
|
||||
function showError(message) {
|
||||
$('.error-console').text(message).addClass('active');
|
||||
}
|
||||
|
||||
function handleSubmit (e) {
|
||||
e.preventDefault();
|
||||
$('.error-console').removeClass('active');
|
||||
|
||||
var password = $('[name="password"]').val();
|
||||
var confirm = $('[name="confirm-password"]').val();
|
||||
|
||||
if (password !== confirm || password === '' || password.length < 8) {
|
||||
showError('passwords must match and be 8 characters.');
|
||||
return false;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/api/v1/user/update-password',
|
||||
contentType: 'application/json',
|
||||
method: 'POST',
|
||||
data: JSON.stringify({password: password, token: location.hash.replace('#', '')})
|
||||
}).then(function (success) {
|
||||
location.href = '<%= redirectUri %>';
|
||||
}).catch(function (error) {
|
||||
showError(error.responseText);
|
||||
});
|
||||
}
|
||||
|
||||
$('#reset-password-form').on('submit', handleSubmit);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user