-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #267 from cofacts/block-user-script
Script that sets blockedReason and blocks reply rquests
- Loading branch information
Showing
4 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export default { | ||
'/users/doc/user-to-block': { | ||
name: 'Naughty spammer', | ||
}, | ||
|
||
'/users/doc/already-blocked': { | ||
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', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
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('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", | ||
} | ||
`); | ||
|
||
// | ||
// 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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
/** | ||
* 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 client from 'util/client'; | ||
import getAllDocs from 'util/getAllDocs'; | ||
|
||
/** | ||
* Update user to write blockedReason. Throws if user does not exist. | ||
* | ||
* @param {string} userId | ||
* @param {string} blockedReason | ||
*/ | ||
async function writeBlockedReasonToUser(userId, blockedReason) { | ||
try { | ||
const { | ||
body: { result: setBlockedReasonResult }, | ||
} = await client.update({ | ||
index: 'users', | ||
type: 'doc', | ||
id: userId, | ||
body: { | ||
doc: { blockedReason }, | ||
}, | ||
}); | ||
|
||
/* istanbul ignore if */ | ||
if (setBlockedReasonResult === 'noop') { | ||
console.log( | ||
`Info: user ID ${userId} already has set the same blocked reason.` | ||
); | ||
} | ||
} catch (e) { | ||
/* istanbul ignore else */ | ||
if (e.message === 'document_missing_exception') { | ||
throw new Error(`User with ID=${userId} does not exist`); | ||
} | ||
|
||
throw e; | ||
} | ||
} | ||
|
||
/** | ||
* 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 { | ||
_source: { articleId }, | ||
} of getAllDocs('replyrequests', NORMAL_REPLY_REQUEST_QUERY)) { | ||
articleIdsWithNormalReplyRequests.push(articleId); | ||
} | ||
|
||
/* 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...` | ||
); | ||
|
||
for (let i = 0; i < articleIdsWithNormalReplyRequests.length; i += 1) { | ||
const articleId = articleIdsWithNormalReplyRequests[i]; | ||
|
||
const { | ||
body: { | ||
hits: { total }, | ||
aggregations: { | ||
lastRequestedAt: { value_as_string: lastRequestedAt }, | ||
}, | ||
}, | ||
} = await client.search({ | ||
index: 'replyrequests', | ||
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, | ||
}, | ||
}, | ||
}); | ||
|
||
console.log( | ||
`[${i + 1}/${ | ||
articleIdsWithNormalReplyRequests.length | ||
}] article ${articleId}: changed to ${total} reply requests, last requested at ${lastRequestedAt}` | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* 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 processReplyRequests(userId); | ||
// await processArticleReplies(userId); | ||
} | ||
|
||
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); | ||
} |