From de8fc604fc28c848b66d76e34c2ce554603718e6 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Wed, 17 Nov 2021 21:17:09 +0800 Subject: [PATCH 1/8] [scripts] init commit for blockUser script --- src/scripts/blockUser.js | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/scripts/blockUser.js diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js new file mode 100644 index 00000000..1e79f2ba --- /dev/null +++ b/src/scripts/blockUser.js @@ -0,0 +1,51 @@ +import 'dotenv/config'; +import yargs from 'yargs'; +// import { SingleBar } from 'cli-progress'; +import client from 'util/client'; + +/** + * Given userId & block reason, blocks the user and marks all their existing ArticleReply, + * ArticleReplyFeedback, ReplyRequest, ArticleCategory, ArticleCategoryFeedback as BLOCKED. + * + * @param {object} args + */ +async function main({ userId, blockedReason } = {}) { + const { + body: { result: setBlockedReasonResult }, + } = await client.update({ + index: 'users', + type: 'doc', + id: userId, + body: { + doc: { blockedReason }, + }, + }); + + /* istanbul ignore if */ + if (setBlockedReasonResult === 'noop') { + throw new Error(`Cannot set user ${userId}'s blocked reason'`); + } +} + +export default main; + +if (require.main === module) { + const argv = yargs + .options({ + userId: { + alias: 'u', + description: 'The user ID to block', + type: 'string', + demandOption: true, + }, + blockedReason: { + alias: 'r', + description: 'The URL to the annoucement that blocks this user', + type: 'string', + demandOption: true, + }, + }) + .help('help').argv; + + main(argv).catch(console.error); +} From d5f149fc5318b0ea62911ffadeffc27a3f25135b Mon Sep 17 00:00:00 2001 From: MrOrz Date: Thu, 18 Nov 2021 10:08:28 +0800 Subject: [PATCH 2/8] [scripts] add test on blockUser --- src/scripts/__fixtures__/blockUser.js | 12 ++++++++++ src/scripts/__tests__/blockUser.js | 21 ++++++++++++++++++ src/scripts/blockUser.js | 32 ++++++++++++++++----------- 3 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 src/scripts/__fixtures__/blockUser.js create mode 100644 src/scripts/__tests__/blockUser.js diff --git a/src/scripts/__fixtures__/blockUser.js b/src/scripts/__fixtures__/blockUser.js new file mode 100644 index 00000000..fce46ba6 --- /dev/null +++ b/src/scripts/__fixtures__/blockUser.js @@ -0,0 +1,12 @@ +export default { + '/users/doc/user-to-block': { + id: 'user-to-block', + name: 'Naughty spammer', + }, + + '/users/doc/already-blocked': { + id: 'already-blocked', + name: 'Blocked spammer', + blockedReason: 'Some announcement', + }, +}; diff --git a/src/scripts/__tests__/blockUser.js b/src/scripts/__tests__/blockUser.js new file mode 100644 index 00000000..fa486f70 --- /dev/null +++ b/src/scripts/__tests__/blockUser.js @@ -0,0 +1,21 @@ +import { loadFixtures, unloadFixtures } from 'util/fixtures'; +// import client from 'util/client'; +import blockUser from '../blockUser'; +import fixtures from '../__fixtures__/blockUser'; + +beforeEach(() => loadFixtures(fixtures)); +afterEach(() => unloadFixtures(fixtures)); + +it('fails if userId is not valid', async () => { + expect( + blockUser({ userId: 'not-exist', blockedReason: 'announcement url' }) + ).rejects.toMatchInlineSnapshot( + `[Error: User with ID=not-exist does not exist]` + ); +}); + +it('Cannot block with identical reason', async () => { + expect( + blockUser({ userId: 'already-blocked', blockedReason: 'Some annoucement' }) + ).rejects.toMatchInlineSnapshot(); +}); diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index 1e79f2ba..7d2b2ed4 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -10,20 +10,26 @@ import client from 'util/client'; * @param {object} args */ async function main({ userId, blockedReason } = {}) { - const { - body: { result: setBlockedReasonResult }, - } = await client.update({ - index: 'users', - type: 'doc', - id: userId, - body: { - doc: { blockedReason }, - }, - }); + try { + const { + body: { result: setBlockedReasonResult }, + } = await client.update({ + index: 'users', + type: 'doc', + id: userId, + body: { + doc: { blockedReason }, + }, + }); + if (setBlockedReasonResult === 'noop') { + throw new Error(`Cannot set user ${userId}'s blocked reason'`); + } + } catch (e) { + if (e.message === 'document_missing_exception') { + throw new Error(`User with ID=${userId} does not exist`); + } - /* istanbul ignore if */ - if (setBlockedReasonResult === 'noop') { - throw new Error(`Cannot set user ${userId}'s blocked reason'`); + throw e; } } From b54b719a9d60e59f1a7c0b44339e220949b40c43 Mon Sep 17 00:00:00 2001 From: Johnson Liang Date: Thu, 18 Nov 2021 13:37:53 +0800 Subject: [PATCH 3/8] [scripts] test blockUser writes blockedReason --- src/scripts/__fixtures__/blockUser.js | 2 -- src/scripts/__tests__/blockUser.js | 29 ++++++++++++++++++++++----- src/scripts/blockUser.js | 6 +++++- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/scripts/__fixtures__/blockUser.js b/src/scripts/__fixtures__/blockUser.js index fce46ba6..f545a77f 100644 --- a/src/scripts/__fixtures__/blockUser.js +++ b/src/scripts/__fixtures__/blockUser.js @@ -1,11 +1,9 @@ export default { '/users/doc/user-to-block': { - id: 'user-to-block', name: 'Naughty spammer', }, '/users/doc/already-blocked': { - id: 'already-blocked', name: 'Blocked spammer', blockedReason: 'Some announcement', }, diff --git a/src/scripts/__tests__/blockUser.js b/src/scripts/__tests__/blockUser.js index fa486f70..7d1870b3 100644 --- a/src/scripts/__tests__/blockUser.js +++ b/src/scripts/__tests__/blockUser.js @@ -1,5 +1,5 @@ import { loadFixtures, unloadFixtures } from 'util/fixtures'; -// import client from 'util/client'; +import client from 'util/client'; import blockUser from '../blockUser'; import fixtures from '../__fixtures__/blockUser'; @@ -14,8 +14,27 @@ it('fails if userId is not valid', async () => { ); }); -it('Cannot block with identical reason', async () => { - expect( - blockUser({ userId: 'already-blocked', blockedReason: 'Some annoucement' }) - ).rejects.toMatchInlineSnapshot(); +it('correctly sets the block reason and updates status of their works', async () => { + await blockUser({ + userId: 'user-to-block', + blockedReason: 'announcement url', + }); + + const { + body: { _source: blockedUser }, + } = await client.get({ + index: 'users', + type: 'doc', + id: 'user-to-block', + }); + + // Assert that blockedReason is written on the user + expect(blockedUser).toMatchInlineSnapshot(` + Object { + "blockedReason": "announcement url", + "name": "Naughty spammer", + } + `); }); + +// it('still updates statuses of blocked user even if they are blocked previously') diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index 7d2b2ed4..326fe9ee 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -21,8 +21,12 @@ async function main({ userId, blockedReason } = {}) { doc: { blockedReason }, }, }); + + /* istanbul ignore if */ if (setBlockedReasonResult === 'noop') { - throw new Error(`Cannot set user ${userId}'s blocked reason'`); + console.log( + `Info: user ID ${userId} already has set the same blocked reason.` + ); } } catch (e) { if (e.message === 'document_missing_exception') { From ffc3b0880355c300fbf57ac96c25fb8841dbea41 Mon Sep 17 00:00:00 2001 From: Johnson Liang Date: Thu, 18 Nov 2021 13:48:24 +0800 Subject: [PATCH 4/8] [scripts] separate blockUser script to functions --- src/scripts/blockUser.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index 326fe9ee..b77d3365 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -1,15 +1,20 @@ +/** + * Given userId & block reason, blocks the user and marks all their existing ArticleReply, + * ArticleReplyFeedback, ReplyRequest, ArticleCategory, ArticleCategoryFeedback as BLOCKED. + */ + import 'dotenv/config'; import yargs from 'yargs'; // import { SingleBar } from 'cli-progress'; import client from 'util/client'; /** - * Given userId & block reason, blocks the user and marks all their existing ArticleReply, - * ArticleReplyFeedback, ReplyRequest, ArticleCategory, ArticleCategoryFeedback as BLOCKED. + * Update user to write blockedReason. Throws if user does not exist. * - * @param {object} args + * @param {string} userId + * @param {string} blockedReason */ -async function main({ userId, blockedReason } = {}) { +async function writeBlockedReasonToUser(userId, blockedReason) { try { const { body: { result: setBlockedReasonResult }, @@ -37,6 +42,21 @@ async function main({ userId, blockedReason } = {}) { } } +/** + * Convert all article replies with NORMAL status by the user to BLOCKED status. + * + * @param {userId} userId + */ +async function processArticleReplies(/* userId */) {} + +/** + * @param {object} args + */ +async function main({ userId, blockedReason } = {}) { + await writeBlockedReasonToUser(userId, blockedReason); + await processArticleReplies(userId); +} + export default main; if (require.main === module) { From 75b089fe83ed823a03b85136a198260e0ca9507f Mon Sep 17 00:00:00 2001 From: MrOrz Date: Fri, 19 Nov 2021 03:09:35 +0800 Subject: [PATCH 5/8] [blockUser] implement processReplyRequests --- src/scripts/blockUser.js | 89 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index b77d3365..78587640 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -5,8 +5,9 @@ import 'dotenv/config'; import yargs from 'yargs'; -// import { SingleBar } from 'cli-progress'; +import { SingleBar } from 'cli-progress'; import client from 'util/client'; +import getAllDocs from 'util/getAllDocs'; /** * Update user to write blockedReason. Throws if user does not exist. @@ -42,19 +43,101 @@ async function writeBlockedReasonToUser(userId, blockedReason) { } } +/** + * Convert all reply requests with NORMAL status by the user to BLOCKED status. + * + * @param {userId} userId + */ +async function processReplyRequests(userId) { + const NORMAL_REPLY_REQUEST_QUERY = { + bool: { + must: [{ term: { status: 'NORMAL' } }, { term: { userId } }], + }, + }; + + const articleIdsWithNormalReplyRequests = []; + + for await (const { _id } of getAllDocs( + 'articles', + NORMAL_REPLY_REQUEST_QUERY + )) { + articleIdsWithNormalReplyRequests.push(_id); + } + + /* Bulk update reply reqeuests status */ + const { body: updateByQueryResult } = await client.updateByQuery({ + index: 'replyrequests', + type: 'doc', + body: { + query: NORMAL_REPLY_REQUEST_QUERY, + script: { + lang: 'painless', + source: `ctx._source.status = 'BLOCKED';`, + }, + }, + refresh: true, + }); + + console.log('Reply request status update result', updateByQueryResult); + + /* Bulk update articles' replyRequestCount & lastRequestedAt */ + console.log( + `Updating ${articleIdsWithNormalReplyRequests.length} articles...` + ); + const bar = new SingleBar({ stopOnComplete: true }); + bar.start(articleIdsWithNormalReplyRequests.length, 0); + + for (const articleId of articleIdsWithNormalReplyRequests) { + const { + body: { + hits: { total }, + aggregations: { lastRequestedAt }, + }, + } = await client.search({ + index: 'articlerequests', + size: 0, + body: { + query: { + bool: { + must: [{ term: { status: 'NORMAL' } }, { term: { articleId } }], + }, + }, + aggs: { + lastRequestedAt: { max: { field: 'createdAt' } }, + }, + }, + }); + + await client.update({ + index: 'articles', + type: 'doc', + id: articleId, + body: { + doc: { + lastRequestedAt, + replyRequestCount: total, + }, + }, + }); + + bar.increment(); + } +} + /** * Convert all article replies with NORMAL status by the user to BLOCKED status. * * @param {userId} userId */ -async function processArticleReplies(/* userId */) {} +// async function processArticleReplies(/* userId */) {} /** * @param {object} args */ async function main({ userId, blockedReason } = {}) { await writeBlockedReasonToUser(userId, blockedReason); - await processArticleReplies(userId); + await processReplyRequests(userId); + // await processArticleReplies(userId); } export default main; From 20a0a75323671f4ebdbb148df3156f461883fe4b Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 21 Nov 2021 19:25:19 +0800 Subject: [PATCH 6/8] [blockUser] Add unit test for reply request handling and fix implementation --- src/scripts/__fixtures__/blockUser.js | 31 ++++++++++++++++++++ src/scripts/__tests__/blockUser.js | 41 +++++++++++++++++++++++++++ src/scripts/blockUser.js | 28 ++++++++++-------- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/scripts/__fixtures__/blockUser.js b/src/scripts/__fixtures__/blockUser.js index f545a77f..ac467e80 100644 --- a/src/scripts/__fixtures__/blockUser.js +++ b/src/scripts/__fixtures__/blockUser.js @@ -7,4 +7,35 @@ export default { name: 'Blocked spammer', blockedReason: 'Some announcement', }, + + '/replyrequests/doc/already-blocked': { + articleId: 'some-article', + userId: 'user-to-block', + status: 'BLOCKED', + createdAt: '2021-11-11T00:00:00.000Z', + }, + + '/articles/doc/some-article': { + replyRequestCount: 1, + lastRequestedAt: '2021-01-01T00:00.000Z', + }, + + '/replyrequests/doc/replyrequest-to-block': { + articleId: 'modified-article', + userId: 'user-to-block', + status: 'NORMAL', + createdAt: '2021-11-11T11:00:00.000Z', + }, + + '/replyrequests/doc/valid-reply-request': { + articleId: 'modified-article', + userId: 'valid-user', + status: 'NORMAL', + createdAt: '2021-10-10T00:00:00.000Z', + }, + + '/articles/doc/modified-article': { + replyRequestCount: 2, + lastRequestedAt: '2021-01-01T00:00:01.000Z', + }, }; diff --git a/src/scripts/__tests__/blockUser.js b/src/scripts/__tests__/blockUser.js index 7d1870b3..aa7f829d 100644 --- a/src/scripts/__tests__/blockUser.js +++ b/src/scripts/__tests__/blockUser.js @@ -35,6 +35,47 @@ it('correctly sets the block reason and updates status of their works', async () "name": "Naughty spammer", } `); + + // + // Check reply requests + // + + // Assert reply requests that is already blocked are not selected to update + // + const { + body: { _source: someArticleWithAlreadyBlockedReplyRequest }, + } = await client.get({ + index: 'articles', + type: 'doc', + id: 'some-article', + }); + expect(someArticleWithAlreadyBlockedReplyRequest).toMatchObject( + fixtures['/articles/doc/some-article'] + ); + + // Assert normal reply requests being blocked and article being updated + // + const { + body: { _source: replyRequestToBlock }, + } = await client.get({ + index: 'replyrequests', + type: 'doc', + id: 'replyrequest-to-block', + }); + expect(replyRequestToBlock.status).toEqual('BLOCKED'); + const { + body: { _source: modifiedArticle }, + } = await client.get({ + index: 'articles', + type: 'doc', + id: 'modified-article', + }); + expect(modifiedArticle).toMatchObject({ + // Only replyrequests/doc/valid-reply-request + replyRequestCount: 1, + lastRequestedAt: + fixtures['/replyrequests/doc/valid-reply-request'].createdAt, + }); }); // it('still updates statuses of blocked user even if they are blocked previously') diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index 78587640..cff2966a 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -5,7 +5,6 @@ import 'dotenv/config'; import yargs from 'yargs'; -import { SingleBar } from 'cli-progress'; import client from 'util/client'; import getAllDocs from 'util/getAllDocs'; @@ -57,11 +56,10 @@ async function processReplyRequests(userId) { const articleIdsWithNormalReplyRequests = []; - for await (const { _id } of getAllDocs( - 'articles', - NORMAL_REPLY_REQUEST_QUERY - )) { - articleIdsWithNormalReplyRequests.push(_id); + for await (const { + _source: { articleId }, + } of getAllDocs('replyrequests', NORMAL_REPLY_REQUEST_QUERY)) { + articleIdsWithNormalReplyRequests.push(articleId); } /* Bulk update reply reqeuests status */ @@ -84,17 +82,19 @@ async function processReplyRequests(userId) { console.log( `Updating ${articleIdsWithNormalReplyRequests.length} articles...` ); - const bar = new SingleBar({ stopOnComplete: true }); - bar.start(articleIdsWithNormalReplyRequests.length, 0); - for (const articleId of articleIdsWithNormalReplyRequests) { + for (let i = 0; i < articleIdsWithNormalReplyRequests.length; i += 1) { + const articleId = articleIdsWithNormalReplyRequests[i]; + const { body: { hits: { total }, - aggregations: { lastRequestedAt }, + aggregations: { + lastRequestedAt: { value_as_string: lastRequestedAt }, + }, }, } = await client.search({ - index: 'articlerequests', + index: 'replyrequests', size: 0, body: { query: { @@ -120,7 +120,11 @@ async function processReplyRequests(userId) { }, }); - bar.increment(); + console.log( + `[${i + 1}/${ + articleIdsWithNormalReplyRequests.length + }] article ${articleId}: changed to ${total} reply requests, last requested at ${lastRequestedAt}` + ); } } From 6b7d0e2e083e6122c686ccb022bf4eddbf309e51 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Sun, 21 Nov 2021 19:33:03 +0800 Subject: [PATCH 7/8] [blockUser] do not need to cover "else" for exception --- src/scripts/blockUser.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/blockUser.js b/src/scripts/blockUser.js index cff2966a..d1b359a6 100644 --- a/src/scripts/blockUser.js +++ b/src/scripts/blockUser.js @@ -34,6 +34,7 @@ async function writeBlockedReasonToUser(userId, blockedReason) { ); } } catch (e) { + /* istanbul ignore else */ if (e.message === 'document_missing_exception') { throw new Error(`User with ID=${userId} does not exist`); } From b2cb9e226c817107ee22a1ad1280ca5564a2a591 Mon Sep 17 00:00:00 2001 From: MrOrz Date: Mon, 22 Nov 2021 00:10:08 +0800 Subject: [PATCH 8/8] Add documentation for blockUser script --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 919aaf3a..a16ffe4b 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,15 @@ $ node build/scripts/removeArticleReply.js --userId= --articleId= --blockedReason= +``` + +- For more options, run the above script with `--help` or see the file level comments. + ## One-off migration scripts