mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-06-20 03:31:14 +02:00
* feat: allow both callback&and await
* feat: ignore async key
* feat: callbackify and promisify in same file
* Revert "feat: callbackify and promisify in same file"
This reverts commit cea206a9b8.
* feat: no need to store .callbackify
* feat: change getTopics to async
* feat: remove .async
* fix: byScore
* feat: rewrite topics/index and social with async/await
* fix: rewrite topics/data.js
fix issue with async.waterfall, only pass result if its not undefined
* feat: add callbackify to redis/psql
* feat: psql use await
* fix: redis 🌋
* feat: less returns
* feat: more await rewrite
* fix: redis tests
* feat: convert sortedSetAdd
rewrite psql transaction to async/await
* feat: 🐶
* feat: test
* feat: log client and query
* feat: log bind
* feat: more logs
* feat: more logs
* feat: check perform
* feat: dont callbackify transaction
* feat: remove logs
* fix: main functions
* feat: more logs
* fix: increment
* fix: rename
* feat: remove cls
* fix: remove console.log
* feat: add deprecation message to .async usage
* feat: update more dbal methods
* fix: redis :voodoo:
* feat: fix redis zrem, convert setObject
* feat: upgrade getObject methods
* fix: psql getObjectField
* fix: redis tests
* feat: getObjectKeys
* feat: getObjectValues
* feat: isObjectField
* fix: add missing return
* feat: delObjectField
* feat: incrObjectField
* fix: add missing await
* feat: remove exposed helpers
* feat: list methods
* feat: flush/empty
* feat: delete
* fix: redis delete all
* feat: get/set
* feat: incr/rename
* feat: type
* feat: expire
* feat: setAdd
* feat: setRemove
* feat: isSetMember
* feat: getSetMembers
* feat: setCount, setRemoveRandom
* feat: zcard,zcount
* feat: sortedSetRank
* feat: isSortedSetMember
* feat: zincrby
* feat: sortedSetLex
* feat: processSortedSet
* fix: add mising await
* feat: debug psql
* fix: psql test
* fix: test
* fix: another test
* fix: test fix
* fix: psql tests
* feat: remove logs
* feat: user arrow func
use builtin async promises
* feat: topic bookmarks
* feat: topic.delete
* feat: topic.restore
* feat: topics.purge
* feat: merge
* feat: suggested
* feat: topics/user.js
* feat: topics modules
* feat: topics/follow
* fix: deprecation msg
* feat: fork
* feat: topics/posts
* feat: sorted/recent
* feat: topic/teaser
* feat: topics/tools
* feat: topics/unread
* feat: add back node versions
disable deprecation notice
wrap async controllers in try/catch
* feat: use db directly
* feat: promisify in place
* fix: redis/psql
* feat: deprecation message
logs for psql
* feat: more logs
* feat: more logs
* feat: logs again
* feat: more logs
* fix: call release
* feat: restore travis, remove logs
* fix: loops
* feat: remove .async. usage
251 lines
7.5 KiB
JavaScript
251 lines
7.5 KiB
JavaScript
|
|
'use strict';
|
|
|
|
var _ = require('lodash');
|
|
var validator = require('validator');
|
|
|
|
var db = require('../database');
|
|
var utils = require('../utils');
|
|
var plugins = require('../plugins');
|
|
var analytics = require('../analytics');
|
|
var user = require('../user');
|
|
var meta = require('../meta');
|
|
var posts = require('../posts');
|
|
var privileges = require('../privileges');
|
|
var categories = require('../categories');
|
|
|
|
module.exports = function (Topics) {
|
|
Topics.create = async function (data) {
|
|
// This is an internal method, consider using Topics.post instead
|
|
var timestamp = data.timestamp || Date.now();
|
|
await Topics.resizeAndUploadThumb(data);
|
|
|
|
const tid = await db.incrObjectField('global', 'nextTid');
|
|
|
|
let topicData = {
|
|
tid: tid,
|
|
uid: data.uid,
|
|
cid: data.cid,
|
|
mainPid: 0,
|
|
title: data.title,
|
|
slug: tid + '/' + (utils.slugify(data.title) || 'topic'),
|
|
timestamp: timestamp,
|
|
lastposttime: 0,
|
|
postcount: 0,
|
|
viewcount: 0,
|
|
locked: 0,
|
|
deleted: 0,
|
|
pinned: 0,
|
|
};
|
|
if (data.thumb) {
|
|
topicData.thumb = data.thumb;
|
|
}
|
|
const result = await plugins.fireHook('filter:topic.create', { topic: topicData, data: data });
|
|
topicData = result.topic;
|
|
await db.setObject('topic:' + topicData.tid, topicData);
|
|
|
|
await Promise.all([
|
|
db.sortedSetsAdd([
|
|
'topics:tid',
|
|
'cid:' + topicData.cid + ':tids',
|
|
'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
|
|
], timestamp, topicData.tid),
|
|
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', 0, topicData.tid),
|
|
categories.updateRecentTid(topicData.cid, topicData.tid),
|
|
user.addTopicIdToUser(topicData.uid, topicData.tid, timestamp),
|
|
db.incrObjectField('category:' + topicData.cid, 'topic_count'),
|
|
db.incrObjectField('global', 'topicCount'),
|
|
Topics.createTags(data.tags, topicData.tid, timestamp),
|
|
]);
|
|
|
|
plugins.fireHook('action:topic.save', { topic: _.clone(topicData), data: data });
|
|
return topicData.tid;
|
|
};
|
|
|
|
Topics.post = async function (data) {
|
|
var uid = data.uid;
|
|
data.title = String(data.title).trim();
|
|
data.tags = data.tags || [];
|
|
if (data.content) {
|
|
data.content = utils.rtrim(data.content);
|
|
}
|
|
check(data.title, meta.config.minimumTitleLength, meta.config.maximumTitleLength, 'title-too-short', 'title-too-long');
|
|
check(data.tags, meta.config.minimumTagsPerTopic, meta.config.maximumTagsPerTopic, 'not-enough-tags', 'too-many-tags');
|
|
check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long');
|
|
|
|
const [categoryExists, canCreate, canTag] = await Promise.all([
|
|
categories.exists(data.cid),
|
|
privileges.categories.can('topics:create', data.cid, data.uid),
|
|
privileges.categories.can('topics:tag', data.cid, data.uid),
|
|
]);
|
|
|
|
if (!categoryExists) {
|
|
throw new Error('[[error:no-category]]');
|
|
}
|
|
|
|
if (!canCreate || (!canTag && data.tags.length)) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
|
|
await guestHandleValid(data);
|
|
await user.isReadyToPost(data.uid, data.cid);
|
|
const filteredData = await plugins.fireHook('filter:topic.post', data);
|
|
data = filteredData;
|
|
const tid = await Topics.create(data);
|
|
|
|
let postData = data;
|
|
postData.tid = tid;
|
|
postData.ip = data.req ? data.req.ip : null;
|
|
postData.isMain = true;
|
|
postData = await posts.create(postData);
|
|
postData = await onNewPost(postData, data);
|
|
|
|
const [settings, topics] = await Promise.all([
|
|
user.getSettings(uid),
|
|
Topics.getTopicsByTids([postData.tid], uid),
|
|
]);
|
|
|
|
if (!Array.isArray(topics) || !topics.length) {
|
|
throw new Error('[[error:no-topic]]');
|
|
}
|
|
|
|
if (settings.followTopicsOnCreate) {
|
|
await Topics.follow(postData.tid, uid);
|
|
}
|
|
const topicData = topics[0];
|
|
topicData.unreplied = 1;
|
|
topicData.mainPost = postData;
|
|
postData.index = 0;
|
|
|
|
analytics.increment(['topics', 'topics:byCid:' + topicData.cid]);
|
|
plugins.fireHook('action:topic.post', { topic: topicData, post: postData, data: data });
|
|
|
|
if (parseInt(uid, 10)) {
|
|
user.notifications.sendTopicNotificationToFollowers(uid, topicData, postData);
|
|
}
|
|
|
|
return {
|
|
topicData: topicData,
|
|
postData: postData,
|
|
};
|
|
};
|
|
|
|
Topics.reply = async function (data) {
|
|
var tid = data.tid;
|
|
var uid = data.uid;
|
|
|
|
const topicData = await Topics.getTopicData(tid);
|
|
if (!topicData) {
|
|
throw new Error('[[error:no-topic]]');
|
|
}
|
|
|
|
data.cid = topicData.cid;
|
|
|
|
const [canReply, isAdminOrMod] = await Promise.all([
|
|
privileges.topics.can('topics:reply', tid, uid),
|
|
privileges.categories.isAdminOrMod(data.cid, uid),
|
|
]);
|
|
|
|
if (topicData.locked && !isAdminOrMod) {
|
|
throw new Error('[[error:topic-locked]]');
|
|
}
|
|
|
|
if (topicData.deleted && !isAdminOrMod) {
|
|
throw new Error('[[error:topic-deleted]]');
|
|
}
|
|
|
|
if (!canReply) {
|
|
throw new Error('[[error:no-privileges]]');
|
|
}
|
|
|
|
await guestHandleValid(data);
|
|
await user.isReadyToPost(uid, data.cid);
|
|
await plugins.fireHook('filter:topic.reply', data);
|
|
if (data.content) {
|
|
data.content = utils.rtrim(data.content);
|
|
}
|
|
check(data.content, meta.config.minimumPostLength, meta.config.maximumPostLength, 'content-too-short', 'content-too-long');
|
|
|
|
data.ip = data.req ? data.req.ip : null;
|
|
let postData = await posts.create(data);
|
|
postData = await onNewPost(postData, data);
|
|
|
|
const settings = await user.getSettings(uid);
|
|
if (settings.followTopicsOnReply) {
|
|
await Topics.follow(postData.tid, uid);
|
|
}
|
|
|
|
if (parseInt(uid, 10)) {
|
|
user.setUserField(uid, 'lastonline', Date.now());
|
|
}
|
|
|
|
Topics.notifyFollowers(postData, uid);
|
|
analytics.increment(['posts', 'posts:byCid:' + data.cid]);
|
|
plugins.fireHook('action:topic.reply', { post: _.clone(postData), data: data });
|
|
|
|
return postData;
|
|
};
|
|
|
|
async function onNewPost(postData, data) {
|
|
var tid = postData.tid;
|
|
var uid = postData.uid;
|
|
await Topics.markAsUnreadForAll(tid);
|
|
await Topics.markAsRead([tid], uid);
|
|
const [
|
|
userInfo,
|
|
topicInfo,
|
|
] = await Promise.all([
|
|
posts.getUserInfoForPosts([postData.uid], uid),
|
|
Topics.getTopicFields(tid, ['tid', 'uid', 'title', 'slug', 'cid', 'postcount', 'mainPid']),
|
|
Topics.addParentPosts([postData]),
|
|
posts.parsePost(postData),
|
|
]);
|
|
|
|
postData.user = userInfo[0];
|
|
postData.topic = topicInfo;
|
|
postData.index = topicInfo.postcount - 1;
|
|
|
|
// Username override for guests, if enabled
|
|
if (meta.config.allowGuestHandles && postData.uid === 0 && data.handle) {
|
|
postData.user.username = validator.escape(String(data.handle));
|
|
}
|
|
|
|
postData.votes = 0;
|
|
postData.bookmarked = false;
|
|
postData.display_edit_tools = true;
|
|
postData.display_delete_tools = true;
|
|
postData.display_moderator_tools = true;
|
|
postData.display_move_tools = true;
|
|
postData.selfPost = false;
|
|
postData.timestampISO = utils.toISOString(postData.timestamp);
|
|
postData.topic.title = String(postData.topic.title);
|
|
|
|
return postData;
|
|
}
|
|
|
|
function check(item, min, max, minError, maxError) {
|
|
// Trim and remove HTML (latter for composers that send in HTML, like redactor)
|
|
if (typeof item === 'string') {
|
|
item = utils.stripHTMLTags(item).trim();
|
|
}
|
|
|
|
if (item === null || item === undefined || item.length < parseInt(min, 10)) {
|
|
throw new Error('[[error:' + minError + ', ' + min + ']]');
|
|
} else if (item.length > parseInt(max, 10)) {
|
|
throw new Error('[[error:' + maxError + ', ' + max + ']]');
|
|
}
|
|
}
|
|
|
|
async function guestHandleValid(data) {
|
|
if (meta.config.allowGuestHandles && parseInt(data.uid, 10) === 0 && data.handle) {
|
|
if (data.handle.length > meta.config.maximumUsernameLength) {
|
|
throw new Error('[[error:guest-handle-invalid]]');
|
|
}
|
|
const exists = await user.existsBySlug(utils.slugify(data.handle));
|
|
if (exists) {
|
|
throw new Error('[[error:username-taken]]');
|
|
}
|
|
}
|
|
}
|
|
};
|