mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-30 18:46:01 +01:00 
			
		
		
		
	* fix: #8954, clear purged replies and toPids * fix: redis test
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							39dae0aaff
						
					
				
				
					commit
					5bb5ec4618
				
			| @@ -149,7 +149,7 @@ module.exports = function (module) { | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	module.deleteObjectFields = async function (key, fields) { | 	module.deleteObjectFields = async function (key, fields) { | ||||||
| 		if (!key || !Array.isArray(fields) || !fields.length) { | 		if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		fields = fields.filter(Boolean); | 		fields = fields.filter(Boolean); | ||||||
| @@ -162,8 +162,12 @@ module.exports = function (module) { | |||||||
| 			field = helpers.fieldToString(field); | 			field = helpers.fieldToString(field); | ||||||
| 			data[field] = ''; | 			data[field] = ''; | ||||||
| 		}); | 		}); | ||||||
|  | 		if (Array.isArray(key)) { | ||||||
|  | 			await module.client.collection('objects').updateMany({ _key: { $in: key } }, { $unset: data }); | ||||||
|  | 		} else { | ||||||
| 			await module.client.collection('objects').updateOne({ _key: key }, { $unset: data }); | 			await module.client.collection('objects').updateOne({ _key: key }, { $unset: data }); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		cache.del(key); | 		cache.del(key); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -247,20 +247,26 @@ SELECT (h."data" ? $2::TEXT AND h."data"->>$2::TEXT IS NOT NULL) b | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	module.deleteObjectFields = async function (key, fields) { | 	module.deleteObjectFields = async function (key, fields) { | ||||||
| 		if (!key || !Array.isArray(fields) || !fields.length) { | 		if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  | 		async function delKey(key, fields) { | ||||||
| 			await module.pool.query({ | 			await module.pool.query({ | ||||||
| 				name: 'deleteObjectFields', | 				name: 'deleteObjectFields', | ||||||
| 				text: ` | 				text: ` | ||||||
| UPDATE "legacy_hash" | 	UPDATE "legacy_hash" | ||||||
| 	   SET "data" = COALESCE((SELECT jsonb_object_agg("key", "value") | 	   SET "data" = COALESCE((SELECT jsonb_object_agg("key", "value") | ||||||
| 								FROM jsonb_each("data") | 								FROM jsonb_each("data") | ||||||
| 							   WHERE "key" <> ALL ($2::TEXT[])), '{}') | 							   WHERE "key" <> ALL ($2::TEXT[])), '{}') | ||||||
| 	 WHERE "_key" = $1::TEXT`, | 	 WHERE "_key" = $1::TEXT`, | ||||||
| 				values: [key, fields], | 				values: [key, fields], | ||||||
| 			}); | 			}); | ||||||
|  | 		} | ||||||
|  | 		if (Array.isArray(key)) { | ||||||
|  | 			await Promise.all(key.map(k => delKey(k, fields))); | ||||||
|  | 		} else { | ||||||
|  | 			await delKey(key, fields); | ||||||
|  | 		} | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	module.incrObjectField = async function (key, field) { | 	module.incrObjectField = async function (key, field) { | ||||||
|   | |||||||
| @@ -150,14 +150,21 @@ module.exports = function (module) { | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	module.deleteObjectFields = async function (key, fields) { | 	module.deleteObjectFields = async function (key, fields) { | ||||||
| 		if (!key || !Array.isArray(fields) || !fields.length) { | 		if (!key || (Array.isArray(key) && !key.length) || !Array.isArray(fields) || !fields.length) { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		fields = fields.filter(Boolean); | 		fields = fields.filter(Boolean); | ||||||
| 		if (!fields.length) { | 		if (!fields.length) { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  | 		if (Array.isArray(key)) { | ||||||
|  | 			const batch = module.client.batch(); | ||||||
|  | 			key.forEach(k => batch.hdel(k, fields)); | ||||||
|  | 			await helpers.execBatch(batch); | ||||||
|  | 		} else { | ||||||
| 			await module.client.async.hdel(key, fields); | 			await module.client.async.hdel(key, fields); | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		cache.del(key); | 		cache.del(key); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -122,13 +122,18 @@ module.exports = function (Posts) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	async function deletePostFromReplies(postData) { | 	async function deletePostFromReplies(postData) { | ||||||
| 		if (!parseInt(postData.toPid, 10)) { | 		const replyPids = await db.getSortedSetMembers('pid:' + postData.pid + ':replies'); | ||||||
| 			return; | 		const promises = [ | ||||||
|  | 			db.deleteObjectFields( | ||||||
|  | 				replyPids.map(pid => 'post:' + pid), ['toPid'] | ||||||
|  | 			), | ||||||
|  | 			db.delete('pid:' + postData.pid + ':replies'), | ||||||
|  | 		]; | ||||||
|  | 		if (parseInt(postData.toPid, 10)) { | ||||||
|  | 			promises.push(db.sortedSetRemove('pid:' + postData.toPid + ':replies', postData.pid)); | ||||||
|  | 			promises.push(db.decrObjectField('post:' + postData.toPid, 'replies')); | ||||||
| 		} | 		} | ||||||
| 		await Promise.all([ | 		await Promise.all(promises); | ||||||
| 			db.sortedSetRemove('pid:' + postData.toPid + ':replies', postData.pid), |  | ||||||
| 			db.decrObjectField('post:' + postData.toPid, 'replies'), |  | ||||||
| 		]); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	async function deletePostFromGroups(postData) { | 	async function deletePostFromGroups(postData) { | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								src/upgrades/1.15.4/clear_purged_replies.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/upgrades/1.15.4/clear_purged_replies.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const _ = require('lodash'); | ||||||
|  | const db = require('../../database'); | ||||||
|  |  | ||||||
|  | const batch = require('../../batch'); | ||||||
|  |  | ||||||
|  | module.exports = { | ||||||
|  | 	name: 'Clear purged replies and toPid', | ||||||
|  | 	timestamp: Date.UTC(2020, 10, 26), | ||||||
|  | 	method: async function () { | ||||||
|  | 		const progress = this.progress; | ||||||
|  |  | ||||||
|  | 		await batch.processSortedSet('posts:pid', async function (pids) { | ||||||
|  | 			progress.incr(pids.length); | ||||||
|  | 			let postData = await db.getObjects(pids.map(pid => 'post:' + pid)); | ||||||
|  | 			postData = postData.filter(p => p && parseInt(p.toPid, 10)); | ||||||
|  | 			if (!postData.length) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			const toPids = postData.map(p => p.toPid); | ||||||
|  | 			const exists = await db.exists(toPids.map(pid => 'post:' + pid)); | ||||||
|  | 			const pidsToDelete = postData.filter((p, index) => !exists[index]).map(p => p.pid); | ||||||
|  | 			await db.deleteObjectFields(pidsToDelete.map(pid => 'post:' + pid), ['toPid']); | ||||||
|  |  | ||||||
|  | 			const repliesToDelete = _.uniq(toPids.filter((pid, index) => !exists[index])); | ||||||
|  | 			await db.deleteAll(repliesToDelete.map(pid => 'pid:' + pid + ':replies')); | ||||||
|  | 		}, { | ||||||
|  | 			progress: progress, | ||||||
|  | 			batchSize: 500, | ||||||
|  | 		}); | ||||||
|  | 	}, | ||||||
|  | }; | ||||||
| @@ -415,6 +415,16 @@ describe('Hash methods', function () { | |||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		it('should delete multiple fields of multiple objects', async function () { | ||||||
|  | 			await db.setObject('deleteFields1', { foo: 'foo1', baz: '2' }); | ||||||
|  | 			await db.setObject('deleteFields2', { foo: 'foo2', baz: '3' }); | ||||||
|  | 			await db.deleteObjectFields(['deleteFields1', 'deleteFields2'], ['baz']); | ||||||
|  | 			const obj1 = await db.getObject('deleteFields1'); | ||||||
|  | 			const obj2 = await db.getObject('deleteFields2'); | ||||||
|  | 			assert.deepStrictEqual(obj1, { foo: 'foo1' }); | ||||||
|  | 			assert.deepStrictEqual(obj2, { foo: 'foo2' }); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
| 		it('should not error if fields is empty array', async () => { | 		it('should not error if fields is empty array', async () => { | ||||||
| 			await db.deleteObjectFields('someKey', []); | 			await db.deleteObjectFields('someKey', []); | ||||||
| 		}); | 		}); | ||||||
|   | |||||||
| @@ -190,6 +190,22 @@ describe('Topic\'s', function () { | |||||||
| 				done(); | 				done(); | ||||||
| 			}); | 			}); | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 		it('should delete nested relies properly', async function () { | ||||||
|  | 			const result = await topics.post({ uid: fooUid, title: 'nested test', content: 'main post', cid: topic.categoryId }); | ||||||
|  | 			const reply1 = await topics.reply({ uid: fooUid, content: 'reply post 1', tid: result.topicData.tid }); | ||||||
|  | 			const reply2 = await topics.reply({ uid: fooUid, content: 'reply post 2', tid: result.topicData.tid, toPid: reply1.pid }); | ||||||
|  | 			let replies = await socketPosts.getReplies({ uid: fooUid }, reply1.pid); | ||||||
|  | 			assert.strictEqual(replies.length, 1); | ||||||
|  | 			assert.strictEqual(replies[0].content, 'reply post 2'); | ||||||
|  | 			let toPid = await posts.getPostField(reply2.pid, 'toPid'); | ||||||
|  | 			assert.strictEqual(parseInt(toPid, 10), parseInt(reply1.pid, 10)); | ||||||
|  | 			await posts.purge(reply1.pid, fooUid); | ||||||
|  | 			replies = await socketPosts.getReplies({ uid: fooUid }, reply1.pid); | ||||||
|  | 			assert.strictEqual(replies.length, 0); | ||||||
|  | 			toPid = await posts.getPostField(reply2.pid, 'toPid'); | ||||||
|  | 			assert.strictEqual(toPid, null); | ||||||
|  | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	describe('Get methods', function () { | 	describe('Get methods', function () { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user