Skip to content

Commit 7f07205

Browse files
authored
Merge pull request #3479 from topcoder-platform/feature-contentful
TC blog to Thrive bridge
2 parents 611bcf8 + f945794 commit 7f07205

File tree

4 files changed

+130
-10
lines changed

4 files changed

+130
-10
lines changed

.circleci/config.yml

+3-9
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,6 @@ workflows:
176176
only:
177177
- develop
178178
- hot-fix
179-
- feature-contentful
180179
- survey_tc
181180
- fix-groups-updates
182181
- sort_registrations_submissions
@@ -185,21 +184,16 @@ workflows:
185184
context : org-global
186185
filters:
187186
branches:
188-
only:
189-
- develop
187+
only:
188+
- develop
189+
- feature-contentful
190190
# This is beta env for production soft releases
191191
- "build-prod-beta":
192192
context : org-global
193193
filters:
194194
branches:
195195
only:
196196
- develop
197-
- feature-contentful
198-
- survey_tc
199-
- feature-groups-api
200-
- mm-broker-api
201-
- fix-groups-updates
202-
- sort_registrations_submissions
203197
# Production builds are exectuted only on tagged commits to the
204198
# master branch.
205199
- "build-prod":

config/default.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ module.exports = {
150150
COMMUNITY_API: 'http://localhost:8000',
151151
COMMUNITY_APP_GITHUB_ISSUES: 'https://github.com/topcoder-platform/community-app/issues',
152152
EMAIL_VERIFY_URL: 'http://www.topcoder-dev.com/settings/account/changeEmail',
153+
THRIVE_POLL_FEED: 'https://www.topcoder.com/feed',
153154
},
154155

155156
/* Information about Topcoder user groups can be cached in various places.
@@ -259,7 +260,7 @@ module.exports = {
259260
title: 'Switch to BUSINESS',
260261
href: 'https://connect.topcoder-dev.com',
261262
},
262-
// Config for TC EDU
263+
// Config for TC EDU - THRIVE
263264
TC_EDU_BASE_PATH: '/thrive',
264265
TC_EDU_TRACKS_PATH: '/tracks',
265266
TC_EDU_ARTICLES_PATH: '/articles',

src/server/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ import { toJson as xmlToJson } from 'utils/xml2json';
2626
import cdnRouter from './routes/cdn';
2727
import mailChimpRouter from './routes/mailchimp';
2828
import mockDocuSignFactory from './__mocks__/docu-sign-mock';
29+
import { pollArticlesForThrive } from 'server/services/contentful';
2930

3031
/* Dome API for topcoder communities */
3132
import tcCommunitiesDemoApi from './tc-communities';
3233

3334
import webpackConfigFactory from '../../webpack.config';
3435
/* eslint-enable */
3536

37+
// Init TC Blog -> THRIVE poll bridge
38+
pollArticlesForThrive();
39+
3640
global.atob = atob;
3741

3842
const CMS_BASE_URL = `https://app.contentful.com/spaces/${config.SECRET.CONTENTFUL.SPACE_ID}`;

src/server/services/contentful.js

+121
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ import fetch from 'isomorphic-fetch';
1010
// import logger from 'utils/logger';
1111
// import moment from 'moment';
1212
import qs from 'qs';
13+
import { logger } from 'topcoder-react-lib';
1314

1415
const contentful = require('contentful-management');
16+
const xml2json = require('xml2json');
1517

1618
/* Holds Contentful CDN URL. */
1719
const CDN_URL = 'https://cdn.contentful.com/spaces';
@@ -191,6 +193,125 @@ export function articleVote(body) {
191193
.then(entry => entry.publish());
192194
}
193195

196+
/**
197+
* This function fetches TC RSS feed to create draft articles in THRIVE for the new posted blogs.
198+
* It calls itself by interval to poll for new blogs.
199+
* Runs on server side only.
200+
*/
201+
export async function pollArticlesForThrive() {
202+
// make this function execute only when in production
203+
if (process.env.BABEL_ENV !== 'production') return;
204+
logger.log('polling blog articles for THRIVE -> INIT');
205+
// Create Contentful client to work with
206+
const client = contentful.createClient({
207+
accessToken: config.SECRET.CONTENTFUL.MANAGEMENT_TOKEN,
208+
});
209+
// connect to Thrive space
210+
const env = await client.getSpace(config.SECRET.CONTENTFUL.EDU.SPACE_ID)
211+
.then(space => space.getEnvironment('master'));
212+
// fetch the poll interval
213+
const pollConf = await env.getEntry('7n1HT3MhUhgjvCzd36Nymd');
214+
const pollInt = pollConf ? pollConf.fields.timeInSeconds : 86400; // default to day if not found
215+
// fetch the RSS feed and parse it
216+
const feedXML = await fetch(config.URL.THRIVE_POLL_FEED);
217+
if (feedXML.ok) {
218+
let feed = await feedXML.text();
219+
feed = await Promise.resolve(xml2json.toJson(feed, { object: true }));
220+
if (feed) {
221+
// when feed loaded and parsed ok
222+
// apply filter first based on tags work only with 'Community Stories'
223+
feed.rss.channel.item = _.filter(
224+
feed.rss.channel.item,
225+
blogItem => blogItem.category.indexOf('Community Stories') !== -1,
226+
);
227+
// loop all feed items and check if those exists in space
228+
// if not existing then create
229+
Promise.all(
230+
_.map(feed.rss.channel.item,
231+
blogPost => env.getEntries({
232+
content_type: 'article',
233+
'fields.title[match]': blogPost.title,
234+
})
235+
.then((queryData) => {
236+
if (queryData.total === 0) {
237+
// article not found in Contentful space
238+
// will create it with payload...
239+
const article = {
240+
fields: {
241+
title: { 'en-US': blogPost.title },
242+
type: { 'en-US': 'Article' },
243+
tags: { 'en-US': blogPost.category },
244+
creationDate: { 'en-US': new Date(blogPost.pubDate) },
245+
content: { 'en-US': blogPost.description },
246+
externalArticle: { 'en-US': true },
247+
contentUrl: { 'en-US': blogPost.link },
248+
},
249+
};
250+
// check if author exists
251+
// if yes link it if no create it?
252+
return env.getEntries({
253+
content_type: 'person',
254+
query: blogPost['dc:creator'],
255+
})
256+
.then((author) => {
257+
// found an author that matches link it
258+
if (author.total) {
259+
article.fields.contentAuthor = {
260+
'en-US': [{
261+
sys: {
262+
type: 'Link', linkType: 'Entry', id: author.items[0].sys.id,
263+
},
264+
}],
265+
};
266+
}
267+
// try get the page to extract its featured image
268+
return fetch(blogPost.link)
269+
.then(rsp => rsp.text())
270+
.then((HTMLsrc) => {
271+
const match = /<image [^>]*href="([^"]+)"/gm.exec(HTMLsrc);
272+
if (match && match[1]) {
273+
// create the asset in Contentful
274+
return env.createAsset({
275+
fields: {
276+
title: { 'en-US': blogPost.title },
277+
file: {
278+
'en-US': { fileName: blogPost.title, contentType: 'image', upload: match[1] },
279+
},
280+
},
281+
})
282+
.then(asset => asset.processForAllLocales())
283+
.then((asset) => {
284+
article.fields.featuredImage = {
285+
'en-US': {
286+
sys: {
287+
type: 'Link', linkType: 'Asset', id: asset.sys.id,
288+
},
289+
},
290+
};
291+
return env.createEntry('article', article);
292+
});
293+
}
294+
// could not find image
295+
// just create the article without it...
296+
return env.createEntry('article', article);
297+
});
298+
});
299+
}
300+
// Article exists in Contentful space
301+
// do nothing...
302+
return 1;
303+
})),
304+
)
305+
.then(() => logger.log('polling blog articles for THRIVE -> DONE'));
306+
}
307+
}
308+
// schedule a repeat each TC_EDU_POLL_TIME
309+
setTimeout(
310+
pollArticlesForThrive,
311+
pollInt * 1000 + (Math.floor(Math.random() * Math.floor(5)) * 60 * 1000),
312+
).unref();
313+
}
314+
194315
// /* Contentful CDN service. */
195316
// export const cdnService = new ApiService(CDN_URL, CDN_KEY);
196317

0 commit comments

Comments
 (0)