Merge remote-tracking branch 'refs/remotes/origin/master' into develop

This commit is contained in:
Baris Usakli
2017-08-15 13:00:18 -04:00
21 changed files with 504 additions and 6 deletions

View File

@@ -4,6 +4,7 @@ var adminController = {
dashboard: require('./admin/dashboard'),
categories: require('./admin/categories'),
tags: require('./admin/tags'),
postQueue: require('./admin/postqueue'),
blacklist: require('./admin/blacklist'),
groups: require('./admin/groups'),
appearance: require('./admin/appearance'),

View File

@@ -0,0 +1,66 @@
'use strict';
var async = require('async');
var db = require('../../database');
var user = require('../../user');
var pagination = require('../../pagination');
var utils = require('../../utils');
var postQueueController = module.exports;
postQueueController.get = function (req, res, next) {
var page = parseInt(req.query.page, 10) || 1;
var postsPerPage = 20;
var pageCount = 0;
var start = (page - 1) * postsPerPage;
var stop = start + postsPerPage - 1;
var postData;
async.waterfall([
function (next) {
async.parallel({
count: function (next) {
db.sortedSetCard('post:queue', next);
},
ids: function (next) {
db.getSortedSetRange('post:queue', start, stop, next);
},
}, next);
},
function (results, next) {
pageCount = Math.ceil(results.count / postsPerPage);
var keys = results.ids.map(function (id) {
return 'post:queue:' + id;
});
db.getObjects(keys, next);
},
function (data, next) {
postData = data;
data.forEach(function (data) {
data.data = JSON.parse(data.data);
data.data.timestampISO = utils.toISOString(data.data.timestamp);
return data;
});
var uids = data.map(function (data) {
return data && data.uid;
});
user.getUsersFields(uids, ['username', 'userslug', 'picture'], next);
},
function (userData) {
postData.forEach(function (postData, index) {
postData.user = userData[index];
});
res.render('admin/manage/post-queue', {
title: '[[pages:post-queue]]',
posts: postData,
pagination: pagination.create(page, pageCount),
});
},
], next);
};

View File

@@ -4,6 +4,7 @@ var async = require('async');
var user = require('../user');
var adminBlacklistController = require('./admin/blacklist');
var adminPostQueueController = require('./admin/postqueue');
var globalModsController = module.exports;
@@ -20,3 +21,17 @@ globalModsController.ipBlacklist = function (req, res, next) {
},
], next);
};
globalModsController.postQueue = function (req, res, next) {
async.waterfall([
function (next) {
user.isAdminOrGlobalMod(req.uid, next);
},
function (isAdminOrGlobalMod, next) {
if (!isAdminOrGlobalMod) {
return next();
}
adminPostQueueController.get(req, res, next);
},
], next);
};

View File

@@ -24,6 +24,7 @@ require('./posts/recent')(Posts);
require('./posts/tools')(Posts);
require('./posts/votes')(Posts);
require('./posts/bookmarks')(Posts);
require('./posts/queue')(Posts);
Posts.exists = function (pid, callback) {
db.isSortedSetMember('posts:pid', pid, callback);

153
src/posts/queue.js Normal file
View File

@@ -0,0 +1,153 @@
'use strict';
var async = require('async');
var db = require('../database');
var user = require('../user');
var meta = require('../meta');
var topics = require('../topics');
var privileges = require('../privileges');
var socketHelpers = require('../socket.io/helpers');
module.exports = function (Posts) {
Posts.shouldQueue = function (uid, data, callback) {
async.waterfall([
function (next) {
user.getUserFields(uid, ['reputation', 'postcount'], next);
},
function (userData, next) {
var shouldQueue = parseInt(meta.config.postQueue, 10) === 1 && (!parseInt(uid, 10) || (parseInt(userData.reputation, 10) <= 0 && parseInt(userData.postcount, 10) <= 0));
next(null, shouldQueue);
},
], callback);
};
Posts.addToQueue = function (data, callback) {
var type = data.title ? 'topic' : 'reply';
var id = type + '-' + Date.now();
async.waterfall([
function (next) {
canPost(type, data, next);
},
function (next) {
db.sortedSetAdd('post:queue', Date.now(), id, next);
},
function (next) {
db.setObject('post:queue:' + id, {
id: id,
uid: data.uid,
type: type,
data: JSON.stringify(data),
}, next);
},
function (next) {
user.setUserField(data.uid, 'lastposttime', Date.now(), next);
},
function (next) {
next(null, {
queued: true,
message: '[[success:post-queued]]',
});
},
], callback);
};
function canPost(type, data, callback) {
async.waterfall([
function (next) {
if (type === 'topic') {
next(null, data.cid);
} else if (type === 'reply') {
topics.getTopicField(data.tid, 'cid', next);
}
},
function (cid, next) {
async.parallel({
canPost: function (next) {
if (type === 'topic') {
privileges.categories.can('topics:create', data.cid, data.uid, next);
} else if (type === 'reply') {
privileges.categories.can('topics:reply', cid, data.uid, next);
}
},
isReadyToPost: function (next) {
user.isReadyToPost(data.uid, cid, next);
},
}, next);
},
function (results, next) {
if (!results.canPost) {
return next(new Error('[[error:no-privileges]]'));
}
next();
},
], callback);
}
Posts.removeFromQueue = function (id, callback) {
async.waterfall([
function (next) {
db.sortedSetRemove('post:queue', id, next);
},
function (next) {
db.delete('post:queue:' + id, next);
},
], callback);
};
Posts.submitFromQueue = function (id, callback) {
async.waterfall([
function (next) {
db.getObject('post:queue:' + id, next);
},
function (data, next) {
if (!data) {
return callback();
}
try {
data.data = JSON.parse(data.data);
} catch (err) {
return next(err);
}
if (data.type === 'topic') {
createTopic(data.data, next);
} else if (data.type === 'reply') {
createReply(data.data, next);
}
},
function (next) {
Posts.removeFromQueue(id, next);
},
], callback);
};
function createTopic(data, callback) {
async.waterfall([
function (next) {
topics.post(data, next);
},
function (result, next) {
socketHelpers.notifyNew(data.uid, 'newTopic', { posts: [result.postData], topic: result.topicData });
next();
},
], callback);
}
function createReply(data, callback) {
async.waterfall([
function (next) {
topics.reply(data, next);
},
function (postData, next) {
var result = {
posts: [postData],
'reputation:disabled': parseInt(meta.config['reputation:disabled'], 10) === 1,
'downvote:disabled': parseInt(meta.config['downvote:disabled'], 10) === 1,
};
socketHelpers.notifyNew(data.uid, 'newPost', result);
next();
},
], callback);
}
};

View File

@@ -56,6 +56,7 @@ function addRoutes(router, middleware, controllers) {
router.get('/manage/categories/:category_id/analytics', middlewares, controllers.admin.categories.getAnalytics);
router.get('/manage/tags', middlewares, controllers.admin.tags.get);
router.get('/manage/post-queue', middlewares, controllers.admin.postQueue.get);
router.get('/manage/ip-blacklist', middlewares, controllers.admin.blacklist.get);
router.get('/manage/users', middlewares, controllers.admin.users.sortByJoinDate);

View File

@@ -44,6 +44,7 @@ function modRoutes(app, middleware, controllers) {
}
function globalModRoutes(app, middleware, controllers) {
setupPageRoute(app, '/post-queue', middleware, [], controllers.globalMods.postQueue);
setupPageRoute(app, '/ip-blacklist', middleware, [], controllers.globalMods.ipBlacklist);
}

View File

@@ -30,6 +30,21 @@ SocketPosts.reply = function (socket, data, callback) {
data.req = websockets.reqFromSocket(socket);
data.timestamp = Date.now();
async.waterfall([
function (next) {
posts.shouldQueue(socket.uid, data, next);
},
function (shouldQueue, next) {
if (shouldQueue) {
posts.addToQueue(data, next);
} else {
postReply(socket, data, next);
}
},
], callback);
};
function postReply(socket, data, callback) {
async.waterfall([
function (next) {
topics.reply(data, next);
@@ -50,7 +65,7 @@ SocketPosts.reply = function (socket, data, callback) {
socketHelpers.notifyNew(socket.uid, 'newPost', result);
},
], callback);
};
}
SocketPosts.getRawPost = function (socket, pid, callback) {
async.waterfall([
@@ -152,3 +167,26 @@ SocketPosts.getReplies = function (socket, pid, callback) {
},
], callback);
};
SocketPosts.accept = function (socket, data, callback) {
acceptOrReject(posts.submitFromQueue, socket, data, callback);
};
SocketPosts.reject = function (socket, data, callback) {
acceptOrReject(posts.removeFromQueue, socket, data, callback);
};
function acceptOrReject(method, socket, data, callback) {
async.waterfall([
function (next) {
user.isAdminOrGlobalMod(socket.uid, next);
},
function (isAdminOrGlobalMod, next) {
if (!isAdminOrGlobalMod) {
return callback(new Error('[[error:no-privileges]]'));
}
method(data.id, next);
},
], callback);
}

View File

@@ -3,6 +3,7 @@
var async = require('async');
var topics = require('../topics');
var posts = require('../posts');
var websockets = require('./index');
var user = require('../user');
var apiController = require('../controllers/api');
@@ -25,6 +26,21 @@ SocketTopics.post = function (socket, data, callback) {
data.req = websockets.reqFromSocket(socket);
data.timestamp = Date.now();
async.waterfall([
function (next) {
posts.shouldQueue(socket.uid, data, next);
},
function (shouldQueue, next) {
if (shouldQueue) {
posts.addToQueue(data, next);
} else {
postTopic(socket, data, next);
}
},
], callback);
};
function postTopic(socket, data, callback) {
async.waterfall([
function (next) {
topics.post(data, next);
@@ -38,7 +54,7 @@ SocketTopics.post = function (socket, data, callback) {
socketHelpers.notifyNew(socket.uid, 'newTopic', { posts: [result.postData], topic: result.topicData });
},
], callback);
};
}
SocketTopics.postcount = function (socket, tid, callback) {
topics.getTopicField(tid, 'postcount', callback);

View File

@@ -0,0 +1,59 @@
<div class="row">
<div class="col-xs-12">
<div class="post-queue panel panel-primary">
<div class="panel-heading">
[[admin/manage/post-queue:post-queue]]
</div>
<!-- IF !posts.length -->
<p class="panel-body">
[[admin/manage/post-queue:description, {config.relative_path}/admin/settings/post#posting-restrictions]]
</p>
<!-- ENDIF !posts.length -->
<div class="table-responsive">
<table class="table table-striped posts-list">
<thead>
<tr>
<th>[[admin/manage/post-queue:user]]</th>
<th>[[admin/manage/post-queue:title]]</th>
<th>[[admin/manage/post-queue:content]]</th>
<th>[[admin/manage/post-queue:posted]]</th>
<th></th>
</tr>
</thead>
<tbody>
<!-- BEGIN posts -->
<tr data-id="{posts.id}">
<td class="col-md-1">
<!-- IF posts.user.userslug -->
<a href="/uid/{posts.user.uid}">{posts.user.username}</a>
<!-- ELSE -->
{posts.user.username}
<!-- ENDIF posts.user.userslug -->
</td>
<td class="col-md-2">
{posts.data.title}
</td>
<td class="col-md-7">
{posts.data.content}
</td>
<td class="col-md-1">
<span class="timeago" title={posts.data.timestampISO}></span>
</td>
<td class="col-md-1">
<div class="btn-group pull-right">
<button class="btn btn-success btn-xs" data-action="accept"><i class="fa fa-check"></i></button>
<button class="btn btn-danger btn-xs" data-action="delete"><i class="fa fa-times"></i></button>
</div>
</td>
</tr>
<!-- END posts -->
</tbody>
</table>
</div>
<!-- IMPORT partials/paginator.tpl -->
</div>
</div>
</div>

View File

@@ -19,6 +19,7 @@
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
</ul>
</section>
@@ -191,6 +192,7 @@
<li><a href="{relative_path}/admin/manage/groups">[[admin/menu:manage/groups]]</a></li>
<li><a href="{relative_path}/admin/manage/tags">[[admin/menu:manage/tags]]</a></li>
<li><a href="{relative_path}/admin/manage/registration">[[admin/menu:manage/registration]]</a></li>
<li><a href="{relative_path}/admin/manage/post-queue">[[admin/menu:manage/post-queue]]</a></li>
<li><a href="{relative_path}/admin/manage/ip-blacklist">[[admin/menu:manage/ip-blacklist]]</a></li>
</ul>
</li>

View File

@@ -28,6 +28,17 @@
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/post:restrictions]]</div>
<div class="col-sm-10 col-xs-12">
<form>
<div class="form-group">
<div class="checkbox">
<label class="mdl-switch mdl-js-switch mdl-js-ripple-effect">
<input class="mdl-switch__input" type="checkbox" data-field="postQueue">
<span class="mdl-switch__label"><strong>[[admin/settings/post:restrictions.post-queue]]</strong></span>
</label>
</div>
<p class="help-block">
[[admin/settings/post:restrictions.post-queue-help]]
</p>
</div>
<div class="form-group">
<label for="postDelay">[[admin/settings/post:restrictions.seconds-between]]</label>
<input id="postDelay" type="text" class="form-control" value="10" data-field="postDelay">