Skip to content

Commit

Permalink
Merge pull request #294 from cofacts/modify-media
Browse files Browse the repository at this point in the history
Add script to modify media of an article
  • Loading branch information
MrOrz authored Jan 20, 2023
2 parents 7b49289 + 5a141c9 commit a4e24ec
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 14 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ $ node build/scripts/blockUser.js --userId=<userId> --blockedReason=<Announcemen

- For more options, run the above script with `--help` or see the file level comments.

### Replace the media of an article
- This command replaces all the variants of a media article's file on GCS with the variants of the new file.

```
$ node build/script/replaceMedia.js --articleId=<articleId> --url=<new-file-url>
```

### Generating a spreadsheet of new article-categories for human review
- To retrieve a spreadsheet of article categories of interest after a specific timestamp, run:
```
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 89 additions & 0 deletions src/graphql/__tests__/media-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import gql from 'util/GraphQL';
import client from 'util/client';
import delayForMs from 'util/delayForMs';
import { getReplyRequestId } from 'graphql/mutations/CreateOrUpdateReplyRequest';
import replaceMedia from 'scripts/replaceMedia';

if (process.env.GCS_CREDENTIALS && process.env.GCS_BUCKET_NAME) {
// File server serving test input file in __fixtures__/media-integration
Expand Down Expand Up @@ -163,4 +164,92 @@ if (process.env.GCS_CREDENTIALS && process.env.GCS_BUCKET_NAME) {
},
15000
);

it(
'can replace media for article',
async () => {
// Simulates user login
const context = {
user: { id: 'foo', appId: 'WEBSITE' },
};

const createMediaArticleResult = await gql`
mutation($mediaUrl: String!) {
CreateMediaArticle(
mediaUrl: $mediaUrl
articleType: IMAGE
reference: { type: LINE }
) {
id
}
}
`(
{
mediaUrl: `${serverUrl}/small.jpg`,
},
context
);

const articleId = createMediaArticleResult.data.CreateMediaArticle.id;

const articleBeforeReplace = await gql`
query($articleId: String!) {
GetArticle(id: $articleId) {
thumbnailUrl: attachmentUrl(variant: THUMBNAIL)
}
}
`({ articleId }, context);

// Wait until thumbnail is fully uploaded
{
let resp;
while (
!resp ||
resp.headers.get('Content-Type').startsWith('application/xml') // GCS returns XML for error
) {
resp = await fetch(articleBeforeReplace.data.GetArticle.thumbnailUrl);
await delayForMs(1000); // Wait for upload to finish
}
}

await replaceMedia({ articleId, url: `${serverUrl}/replaced.jpg` });

const resp = await fetch(
articleBeforeReplace.data.GetArticle.thumbnailUrl
);
expect(resp.status).toBe(404);

const articleAfterReplace = await gql`
query($articleId: String!) {
GetArticle(id: $articleId) {
attachmentHash
}
}
`({ articleId }, context);

expect(
articleAfterReplace.data.GetArticle.attachmentHash
).toMatchInlineSnapshot(
`"image.vDjh4g.__-AD6SDgAeTEcED___AA_Ej8y_Dg8EDgAOAA4P_8_8"`
);

// Cleanup
await client.delete({
index: 'articles',
type: 'doc',
id: articleId,
});

await client.delete({
index: 'replyrequests',
type: 'doc',
id: getReplyRequestId({
articleId,
userId: context.user.id,
appId: context.user.appId,
}),
});
},
15000
);
}
46 changes: 32 additions & 14 deletions src/graphql/mutations/CreateMediaArticle.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,14 @@ const VALID_ARTICLE_TYPE_TO_MEDIA_TYPE = {
};

/**
* Creates a new article in ElasticSearch,
* or return an article which attachment.hash is similar to mediaUrl
* Upload media of specified article type from the given mediaUrl
*
* @param {object} param
* @param {string} param.mediaUrl
* @param {ArticleTypeEnum} param.articleType
* @param {ArticleReferenceInput} param.reference
* @param {string} param.userId
* @param {string} param.appId
* @returns {Promise<string>} the new article's ID
* @returns {Promise<MediaEntry>}
*/
async function createNewMediaArticle({
mediaUrl,
articleType,
reference: originalReference,
userId,
appId,
}) {
export async function uploadMedia({ mediaUrl, articleType }) {
const mappedMediaType = VALID_ARTICLE_TYPE_TO_MEDIA_TYPE[articleType];
const mediaEntry = await mediaManager.insert({
url: mediaUrl,
Expand Down Expand Up @@ -93,6 +83,28 @@ async function createNewMediaArticle({
},
});

return mediaEntry;
}

/**
* Creates a new article in ElasticSearch,
* or return an article which attachment.hash is similar to mediaUrl
*
* @param {object} param
* @param {MediaEntry} param.mediaEntry
* @param {ArticleTypeEnum} param.articleType
* @param {ArticleReferenceInput} param.reference
* @param {string} param.userId
* @param {string} param.appId
* @returns {Promise<string>} the new article's ID
*/
async function createNewMediaArticle({
mediaEntry,
articleType,
reference: originalReference,
userId,
appId,
}) {
const attachmentHash = mediaEntry.id;
const text = '';
const now = new Date().toISOString();
Expand Down Expand Up @@ -168,9 +180,15 @@ export default {
{ user }
) {
assertUser(user);
const articleId = await createNewMediaArticle({

const mediaEntry = await uploadMedia({
mediaUrl,
articleType,
});

const articleId = await createNewMediaArticle({
mediaEntry,
articleType,
reference,
userId: user.id,
appId: user.appId,
Expand Down
1 change: 1 addition & 0 deletions src/scripts/blockUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ async function main({ userId, blockedReason } = {}) {

export default main;

/* istanbul ignore if */
if (require.main === module) {
const argv = yargs
.options({
Expand Down
87 changes: 87 additions & 0 deletions src/scripts/replaceMedia.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Given articleId and URL, replace the media with the content the URL points to.
*/

import 'dotenv/config';
import yargs from 'yargs';
import client from 'util/client';
import mediaManager from 'util/mediaManager';
import { uploadMedia } from 'graphql/mutations/CreateMediaArticle';

/**
* @param {object} args
*/
async function replaceMedia({ articleId, url } = {}) {
const {
body: { _source: article },
} = await client.get({ index: 'articles', type: 'doc', id: articleId });

/* istanbul ignore if */
if (!article) throw new Error(`Article ${articleId} is not found`);

const oldMediaEntry = await mediaManager.get(article.attachmentHash);

/* istanbul ignore if */
if (!oldMediaEntry)
throw new Error(
`Article ${articleId}'s attachment hash "${
article.attachmentHash
}" has no corresponding media entry`
);

const newMediaEntry = await uploadMedia({
mediaUrl: url,
articleType: article.articleType,
});

console.info(
`Article ${articleId} attachment hash: ${oldMediaEntry.id} --> ${
newMediaEntry.id
}`
);
await client.update({
index: 'articles',
type: 'doc',
id: articleId,
body: {
doc: {
attachmentHash: newMediaEntry.id,
},
},
});

return Promise.all(
oldMediaEntry.variants.map(variant =>
oldMediaEntry
.getFile(variant)
.delete()
.then(() => {
console.info(`Old media entry variant=${variant} deleted`);
})
)
);
}

export default replaceMedia;

/* istanbul ignore if */
if (require.main === module) {
const argv = yargs
.options({
articleId: {
alias: 'a',
description: 'The article ID',
type: 'string',
demandOption: true,
},
url: {
alias: 'u',
description: 'The URL to the content to replace',
type: 'string',
demandOption: true,
},
})
.help('help').argv;

replaceMedia(argv).catch(console.error);
}

0 comments on commit a4e24ec

Please sign in to comment.