mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 19:46:01 +01:00
Change post owner (#7752)
* feat: #7749, allow array of keys for setObject * feat: sortedSetRemoveBulk * feat: test for bulk remove * feat: #7083, ability to change post ownership * feat: #7083, fix tid:<tid>:posters * feat: #7083, front end * fix: #7752, psql methods * fix: add missing await * fix: maybe psql
This commit is contained in:
committed by
GitHub
parent
5b746d53e7
commit
53e1b349ae
@@ -26,6 +26,7 @@
|
||||
"purge": "Purge",
|
||||
"restore": "Restore",
|
||||
"move": "Move",
|
||||
"change-owner": "Change Owner",
|
||||
"fork": "Fork",
|
||||
"link": "Link",
|
||||
"share": "Share",
|
||||
@@ -75,6 +76,7 @@
|
||||
"thread_tools.move": "Move Topic",
|
||||
"thread_tools.move-posts": "Move Posts",
|
||||
"thread_tools.move_all": "Move All",
|
||||
"thread_tools.change_owner": "Change Owner",
|
||||
"thread_tools.select_category": "Select Category",
|
||||
"thread_tools.fork": "Fork Topic",
|
||||
"thread_tools.delete": "Delete Topic",
|
||||
@@ -114,6 +116,7 @@
|
||||
"delete_posts_instruction": "Click the posts you want to delete/purge",
|
||||
"merge_topics_instruction": "Click the topics you want to merge",
|
||||
"move_posts_instruction": "Click the posts you want to move",
|
||||
"change_owner_instruction": "Click the posts you want to assign to another user",
|
||||
|
||||
"composer.title_placeholder": "Enter your topic title here...",
|
||||
"composer.handle_placeholder": "Name",
|
||||
|
||||
91
public/src/client/topic/change-owner.js
Normal file
91
public/src/client/topic/change-owner.js
Normal file
@@ -0,0 +1,91 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
define('forum/topic/change-owner', [
|
||||
'components',
|
||||
'postSelect',
|
||||
'autocomplete',
|
||||
], function (components, postSelect, autocomplete) {
|
||||
var ChangeOwner = {};
|
||||
|
||||
var modal;
|
||||
var commit;
|
||||
var toUid = 0;
|
||||
ChangeOwner.init = function (postEl) {
|
||||
if (modal) {
|
||||
return;
|
||||
}
|
||||
app.parseAndTranslate('partials/change_owner_modal', {}, function (html) {
|
||||
modal = html;
|
||||
|
||||
commit = modal.find('#change_owner_commit');
|
||||
|
||||
$('body').append(modal);
|
||||
|
||||
modal.find('.close,#change_owner_cancel').on('click', closeModal);
|
||||
modal.find('#username').on('keyup', checkButtonEnable);
|
||||
postSelect.init(onPostToggled, {
|
||||
allowMainPostSelect: true,
|
||||
});
|
||||
showPostsSelected();
|
||||
|
||||
if (postEl) {
|
||||
postSelect.togglePostSelection(postEl, onPostToggled);
|
||||
}
|
||||
|
||||
commit.on('click', function () {
|
||||
changeOwner();
|
||||
});
|
||||
|
||||
autocomplete.user(modal.find('#username'), function (ev, ui) {
|
||||
toUid = ui.item.user.uid;
|
||||
checkButtonEnable();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function showPostsSelected() {
|
||||
if (postSelect.pids.length) {
|
||||
modal.find('#pids').translateHtml('[[topic:fork_pid_count, ' + postSelect.pids.length + ']]');
|
||||
} else {
|
||||
modal.find('#pids').translateHtml('[[topic:fork_no_pids]]');
|
||||
}
|
||||
}
|
||||
|
||||
function checkButtonEnable() {
|
||||
if (toUid && modal.find('#username').length && modal.find('#username').val().length && postSelect.pids.length) {
|
||||
commit.removeAttr('disabled');
|
||||
} else {
|
||||
commit.attr('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
function onPostToggled() {
|
||||
checkButtonEnable();
|
||||
showPostsSelected();
|
||||
}
|
||||
|
||||
function changeOwner() {
|
||||
if (!toUid) {
|
||||
return;
|
||||
}
|
||||
socket.emit('posts.changeOwner', { pids: postSelect.pids, toUid: toUid }, function (err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
ajaxify.refresh();
|
||||
|
||||
closeModal();
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
modal = null;
|
||||
postSelect.disable();
|
||||
}
|
||||
}
|
||||
|
||||
return ChangeOwner;
|
||||
});
|
||||
@@ -207,6 +207,13 @@ define('forum/topic/postTools', [
|
||||
});
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/change-owner"]', function () {
|
||||
var btn = $(this);
|
||||
require(['forum/topic/change-owner'], function (changeOwner) {
|
||||
changeOwner.init(btn.parents('[data-pid]'));
|
||||
});
|
||||
});
|
||||
|
||||
postContainer.on('click', '[component="post/ban-ip"]', function () {
|
||||
var ip = $(this).attr('data-ip');
|
||||
socket.emit('blacklist.addRule', ip, function (err) {
|
||||
|
||||
@@ -7,9 +7,13 @@ define('postSelect', ['components'], function (components) {
|
||||
|
||||
PostSelect.pids = [];
|
||||
|
||||
PostSelect.init = function (_onSelect) {
|
||||
var allowMainPostSelect = false;
|
||||
|
||||
PostSelect.init = function (_onSelect, options) {
|
||||
PostSelect.pids.length = 0;
|
||||
onSelect = _onSelect;
|
||||
options = options || {};
|
||||
allowMainPostSelect = options.allowMainPostSelect || false;
|
||||
$('#content').on('click', '[component="topic"] [component="post"]', onPostClicked);
|
||||
disableClicksOnPosts();
|
||||
};
|
||||
@@ -30,7 +34,7 @@ define('postSelect', ['components'], function (components) {
|
||||
PostSelect.togglePostSelection = function (post) {
|
||||
var newPid = post.attr('data-pid');
|
||||
|
||||
if (parseInt(post.attr('data-index'), 10) === 0) {
|
||||
if (!allowMainPostSelect && parseInt(post.attr('data-index'), 10) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,14 @@ module.exports = function (db, module) {
|
||||
}
|
||||
|
||||
const writeData = helpers.serializeData(data);
|
||||
if (Array.isArray(key)) {
|
||||
var bulk = db.collection('objects').initializeUnorderedBulkOp();
|
||||
key.forEach(key => bulk.find({ _key: key }).upsert().updateOne({ $set: writeData }));
|
||||
await bulk.execute();
|
||||
} else {
|
||||
await db.collection('objects').updateOne({ _key: key }, { $set: writeData }, { upsert: true, w: 1 });
|
||||
}
|
||||
|
||||
cache.delObjectCache(key);
|
||||
};
|
||||
|
||||
|
||||
@@ -49,4 +49,13 @@ module.exports = function (db, module) {
|
||||
|
||||
await db.collection('objects').deleteMany(query);
|
||||
};
|
||||
|
||||
module.sortedSetRemoveBulk = async function (data) {
|
||||
if (!Array.isArray(data) || !data.length) {
|
||||
return;
|
||||
}
|
||||
var bulk = db.collection('objects').initializeUnorderedBulkOp();
|
||||
data.forEach(item => bulk.find({ _key: item[0], value: String(item[1]) }).remove());
|
||||
await bulk.execute();
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@ module.exports = function (db, module) {
|
||||
|
||||
await module.transaction(async function (client) {
|
||||
var query = client.query.bind(client);
|
||||
|
||||
const dataString = JSON.stringify(data);
|
||||
async function setOne(key) {
|
||||
await helpers.ensureLegacyObjectType(client, key, 'hash');
|
||||
await query({
|
||||
name: 'setObject',
|
||||
@@ -23,8 +24,14 @@ INSERT INTO "legacy_hash" ("_key", "data")
|
||||
VALUES ($1::TEXT, $2::TEXT::JSONB)
|
||||
ON CONFLICT ("_key")
|
||||
DO UPDATE SET "data" = "legacy_hash"."data" || $2::TEXT::JSONB`,
|
||||
values: [key, JSON.stringify(data)],
|
||||
values: [key, dataString],
|
||||
});
|
||||
}
|
||||
if (Array.isArray(key)) {
|
||||
await Promise.all(key.map(k => setOne(k)));
|
||||
} else {
|
||||
await setOne(key);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,6 +42,8 @@ DO UPDATE SET "data" = "legacy_hash"."data" || $2::TEXT::JSONB`,
|
||||
|
||||
await module.transaction(async function (client) {
|
||||
var query = client.query.bind(client);
|
||||
const valueString = JSON.stringify(value);
|
||||
async function setOne(key) {
|
||||
await helpers.ensureLegacyObjectType(client, key, 'hash');
|
||||
await query({
|
||||
name: 'setObjectField',
|
||||
@@ -43,8 +52,15 @@ INSERT INTO "legacy_hash" ("_key", "data")
|
||||
VALUES ($1::TEXT, jsonb_build_object($2::TEXT, $3::TEXT::JSONB))
|
||||
ON CONFLICT ("_key")
|
||||
DO UPDATE SET "data" = jsonb_set("legacy_hash"."data", ARRAY[$2::TEXT], $3::TEXT::JSONB)`,
|
||||
values: [key, field, JSON.stringify(value)],
|
||||
values: [key, field, valueString],
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(key)) {
|
||||
await Promise.all(key.map(k => setOne(k)));
|
||||
} else {
|
||||
await setOne(key);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -69,4 +69,27 @@ DELETE FROM "legacy_zset"
|
||||
values: [keys, min, max],
|
||||
});
|
||||
};
|
||||
|
||||
module.sortedSetRemoveBulk = async function (data) {
|
||||
// const keys = [];
|
||||
// const values = [];
|
||||
|
||||
// data.forEach(function (item) {
|
||||
// keys.push(item[0]);
|
||||
// values.push(item[1]);
|
||||
// });
|
||||
|
||||
const promises = data.map(item => module.sortedSetRemove(item[0], item[1]));
|
||||
await Promise.all(promises);
|
||||
|
||||
// TODO
|
||||
// await query({
|
||||
// name: 'sortedSetRemoveBulk',
|
||||
// text: `
|
||||
// DELETE FROM "legacy_zset"
|
||||
// SELECT k, v
|
||||
// FROM UNNEST($1::TEXT[], $2::TEXT[]) vs(k, v)`,
|
||||
// values: [keys, values],
|
||||
// });
|
||||
};
|
||||
};
|
||||
|
||||
@@ -27,7 +27,14 @@ module.exports = function (redisClient, module) {
|
||||
if (!Object.keys(data).length) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(key)) {
|
||||
const batch = redisClient.batch();
|
||||
key.forEach(k => batch.hmset(k, data));
|
||||
await helpers.execBatch(batch);
|
||||
} else {
|
||||
await redisClient.async.hmset(key, data);
|
||||
}
|
||||
|
||||
cache.delObjectCache(key);
|
||||
};
|
||||
|
||||
@@ -35,7 +42,14 @@ module.exports = function (redisClient, module) {
|
||||
if (!field) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(key)) {
|
||||
const batch = redisClient.batch();
|
||||
key.forEach(k => batch.hset(k, field, value));
|
||||
await helpers.execBatch(batch);
|
||||
} else {
|
||||
await redisClient.async.hset(key, field, value);
|
||||
}
|
||||
|
||||
cache.delObjectCache(key);
|
||||
};
|
||||
|
||||
|
||||
@@ -34,4 +34,13 @@ module.exports = function (redisClient, module) {
|
||||
keys.forEach(k => batch.zremrangebyscore(k, min, max));
|
||||
await helpers.execBatch(batch);
|
||||
};
|
||||
|
||||
module.sortedSetRemoveBulk = async function (data) {
|
||||
if (!Array.isArray(data) || !data.length) {
|
||||
return;
|
||||
}
|
||||
const batch = redisClient.batch();
|
||||
data.forEach(item => batch.zrem(item[0], item[1]));
|
||||
await helpers.execBatch(batch);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,7 +4,9 @@ var async = require('async');
|
||||
var validator = require('validator');
|
||||
var _ = require('lodash');
|
||||
|
||||
const db = require('../database');
|
||||
var user = require('../user');
|
||||
const topics = require('../topics');
|
||||
var groups = require('../groups');
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
@@ -121,4 +123,115 @@ module.exports = function (Posts) {
|
||||
const cids = await Posts.getCidsByPids(pids);
|
||||
return await user.isModerator(uid, cids);
|
||||
};
|
||||
|
||||
Posts.changeOwner = async function (pids, toUid) {
|
||||
const exists = user.exists(toUid);
|
||||
if (!exists) {
|
||||
throw new Error('[[error:no-user]]');
|
||||
}
|
||||
const postData = await Posts.getPostsFields(pids, ['pid', 'tid', 'uid', 'timestamp', 'upvotes', 'downvotes']);
|
||||
pids = postData.filter(p => p.pid && p.uid !== parseInt(toUid, 10))
|
||||
.map(p => p.pid);
|
||||
|
||||
const cids = await Posts.getCidsByPids(pids);
|
||||
|
||||
const bulkRemove = [];
|
||||
const bulkAdd = [];
|
||||
let repChange = 0;
|
||||
const postsByUser = {};
|
||||
postData.forEach((post, i) => {
|
||||
post.cid = cids[i];
|
||||
repChange += post.votes;
|
||||
bulkRemove.push(['uid:' + post.uid + ':posts', post.pid]);
|
||||
bulkRemove.push(['cid:' + post.cid + ':uid:' + post.uid + ':pids', post.pid]);
|
||||
bulkRemove.push(['cid:' + post.cid + ':uid:' + post.uid + ':pids:votes', post.pid]);
|
||||
|
||||
bulkAdd.push(['uid:' + toUid + ':posts', post.timestamp, post.pid]);
|
||||
bulkAdd.push(['cid:' + post.cid + ':uid:' + toUid + ':pids', post.timestamp, post.pid]);
|
||||
if (post.votes > 0) {
|
||||
bulkAdd.push(['cid:' + post.cid + ':uid:' + toUid + ':pids:votes', post.votes, post.pid]);
|
||||
}
|
||||
postsByUser[post.uid] = postsByUser[post.uid] || [];
|
||||
postsByUser[post.uid].push(post);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
db.setObjectField(pids.map(pid => 'post:' + pid), 'uid', toUid),
|
||||
db.sortedSetRemoveBulk(bulkRemove),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
user.incrementUserPostCountBy(toUid, pids.length),
|
||||
updateReputation(toUid, repChange),
|
||||
handleMainPidOwnerChange(postData, toUid),
|
||||
reduceCounters(postsByUser),
|
||||
updateTopicPosters(postData, toUid),
|
||||
]);
|
||||
};
|
||||
|
||||
async function reduceCounters(postsByUser) {
|
||||
await async.eachOfSeries(postsByUser, async function (posts, uid) {
|
||||
const repChange = posts.reduce((acc, val) => acc + val.votes, 0);
|
||||
await Promise.all([
|
||||
user.incrementUserPostCountBy(uid, -posts.length),
|
||||
updateReputation(uid, -repChange),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
async function updateTopicPosters(postData, toUid) {
|
||||
const postsByTopic = _.groupBy(postData, p => parseInt(p.tid, 10));
|
||||
await async.eachOf(postsByTopic, async function (posts, tid) {
|
||||
const postsByUser = _.groupBy(posts, p => parseInt(p.uid, 10));
|
||||
await db.sortedSetIncrBy('tid:' + tid + ':posters', posts.length, toUid);
|
||||
await async.eachOf(postsByUser, async function (posts, uid) {
|
||||
await db.sortedSetIncrBy('tid:' + tid + ':posters', -posts.length, uid);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function updateReputation(uid, change) {
|
||||
if (!change) {
|
||||
return;
|
||||
}
|
||||
const newReputation = await user.incrementUserFieldBy(uid, 'reputation', change);
|
||||
await db.sortedSetAdd('users:reputation', newReputation, uid);
|
||||
}
|
||||
|
||||
async function handleMainPidOwnerChange(postData, toUid) {
|
||||
const tids = _.uniq(postData.map(p => p.tid));
|
||||
const topicData = await topics.getTopicsFields(tids, ['mainPid', 'timestamp']);
|
||||
const tidToTopic = _.zipObject(tids, topicData);
|
||||
|
||||
const mainPosts = postData.filter(p => p.pid === tidToTopic[p.tid].mainPid);
|
||||
if (!mainPosts.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bulkAdd = [];
|
||||
const bulkRemove = [];
|
||||
const postsByUser = {};
|
||||
mainPosts.forEach((post) => {
|
||||
bulkRemove.push(['cid:' + post.cid + ':uid:' + post.uid + ':tids', post.tid]);
|
||||
bulkRemove.push(['uid:' + post.uid + ':topics', post.tid]);
|
||||
|
||||
bulkAdd.push(['cid:' + post.cid + ':uid:' + toUid + ':tids', tidToTopic[post.tid].timestamp, post.tid]);
|
||||
bulkAdd.push(['uid:' + toUid + ':topics', tidToTopic[post.tid].timestamp, post.tid]);
|
||||
postsByUser[post.uid] = postsByUser[post.uid] || [];
|
||||
postsByUser[post.uid].push(post);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
db.setObjectField(mainPosts.map(p => 'topic:' + p.tid), 'uid', toUid),
|
||||
db.sortedSetRemoveBulk(bulkRemove),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
user.incrementUserFieldBy(toUid, 'topiccount', mainPosts.length),
|
||||
reduceTopicCounts(postsByUser),
|
||||
]);
|
||||
}
|
||||
|
||||
async function reduceTopicCounts(postsByUser) {
|
||||
await async.eachSeries(Object.keys(postsByUser), async function (uid) {
|
||||
const posts = postsByUser[uid];
|
||||
await user.incrementUserFieldBy(uid, 'topiccount', -posts.length);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,4 +248,16 @@ module.exports = function (SocketPosts) {
|
||||
},
|
||||
}, callback);
|
||||
}
|
||||
|
||||
SocketPosts.changeOwner = async function (socket, data) {
|
||||
if (!data || !Array.isArray(data.pids) || !data.toUid) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
const isAdminOrGlobalMod = user.isAdminOrGlobalMod(socket.uid);
|
||||
if (!isAdminOrGlobalMod) {
|
||||
throw new Error('[[error:no-privileges]]');
|
||||
}
|
||||
|
||||
await posts.changeOwner(data.pids, data.toUid);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -25,6 +25,14 @@ describe('Hash methods', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set two objects to same data', async function () {
|
||||
const data = { foo: 'baz', test: '1' };
|
||||
await db.setObject(['multiObject1', 'multiObject2'], data);
|
||||
const result = await db.getObjects(['multiObject1', 'multiObject2']);
|
||||
assert.deepStrictEqual(result[0], data);
|
||||
assert.deepStrictEqual(result[1], data);
|
||||
});
|
||||
|
||||
it('should do nothing if key is falsy', function (done) {
|
||||
db.setObject('', { foo: 1, derp: 2 }, function (err) {
|
||||
assert.ifError(err);
|
||||
@@ -82,6 +90,14 @@ describe('Hash methods', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should set two objects fields to same data', async function () {
|
||||
const data = { foo: 'baz', test: '1' };
|
||||
await db.setObjectField(['multiObject1', 'multiObject2'], 'myField', '2');
|
||||
const result = await db.getObjects(['multiObject1', 'multiObject2']);
|
||||
assert.deepStrictEqual(result[0].myField, '2');
|
||||
assert.deepStrictEqual(result[1].myField, '2');
|
||||
});
|
||||
|
||||
it('should work for field names with "." in them', function (done) {
|
||||
db.setObjectField('dotObject2', 'my.dot.field', 'foo2', function (err) {
|
||||
assert.ifError(err);
|
||||
|
||||
@@ -978,6 +978,21 @@ describe('Sorted Set methods', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should do a bulk remove', async function () {
|
||||
await db.sortedSetAddBulk([
|
||||
['bulkRemove1', 1, 'value1'],
|
||||
['bulkRemove1', 2, 'value2'],
|
||||
['bulkRemove2', 3, 'value2'],
|
||||
]);
|
||||
await db.sortedSetRemoveBulk([
|
||||
['bulkRemove1', 'value1'],
|
||||
['bulkRemove1', 'value2'],
|
||||
['bulkRemove2', 'value2'],
|
||||
]);
|
||||
const members = await db.getSortedSetsMembers(['bulkRemove1', 'bulkRemove2']);
|
||||
assert.deepStrictEqual(members, [[], []]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortedSetsRemove()', function () {
|
||||
|
||||
@@ -73,6 +73,36 @@ describe('Post\'s', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should change owner of post and topic properly', async function () {
|
||||
const oldUid = await user.create({ username: 'olduser' });
|
||||
const newUid = await user.create({ username: 'newuser' });
|
||||
const postResult = await topics.post({ uid: oldUid, cid: cid, title: 'change owner', content: 'original post' });
|
||||
const postData = await topics.reply({ uid: oldUid, tid: postResult.topicData.tid, content: 'firstReply' });
|
||||
const pid1 = postResult.postData.pid;
|
||||
const pid2 = postData.pid;
|
||||
|
||||
assert.deepStrictEqual(await db.sortedSetScores('tid:' + postResult.topicData.tid + ':posters', [oldUid, newUid]), [2, null]);
|
||||
|
||||
await posts.changeOwner([pid1, pid2], newUid);
|
||||
|
||||
assert.deepStrictEqual(await db.sortedSetScores('tid:' + postResult.topicData.tid + ':posters', [oldUid, newUid]), [0, 2]);
|
||||
|
||||
assert.deepStrictEqual(await posts.isOwner([pid1, pid2], oldUid), [false, false]);
|
||||
assert.deepStrictEqual(await posts.isOwner([pid1, pid2], newUid), [true, true]);
|
||||
|
||||
assert.strictEqual(await user.getUserField(oldUid, 'postcount'), 0);
|
||||
assert.strictEqual(await user.getUserField(newUid, 'postcount'), 2);
|
||||
|
||||
assert.strictEqual(await user.getUserField(oldUid, 'topiccount'), 0);
|
||||
assert.strictEqual(await user.getUserField(newUid, 'topiccount'), 1);
|
||||
|
||||
assert.strictEqual(await db.sortedSetScore('users:postcount', oldUid), 0);
|
||||
assert.strictEqual(await db.sortedSetScore('users:postcount', newUid), 2);
|
||||
|
||||
assert.strictEqual(await topics.isOwner(postResult.topicData.tid, oldUid), false);
|
||||
assert.strictEqual(await topics.isOwner(postResult.topicData.tid, newUid), true);
|
||||
});
|
||||
|
||||
it('should return falsy if post does not exist', function (done) {
|
||||
posts.getPostData(9999, function (err, postData) {
|
||||
assert.ifError(err);
|
||||
|
||||
Reference in New Issue
Block a user