Skip to content

Commit

Permalink
Merge branch 'master' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronschachter committed Jul 16, 2018
2 parents da4757e + e7fd11c commit cf54671
Show file tree
Hide file tree
Showing 24 changed files with 548 additions and 133 deletions.
11 changes: 11 additions & 0 deletions app/routes/broadcasts/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const express = require('express');

const getBroadcastsMiddleware = require('../../../lib/middleware/broadcasts/index/broadcasts-get');

const router = express.Router();

router.use(getBroadcastsMiddleware());

module.exports = router;
11 changes: 11 additions & 0 deletions app/routes/broadcasts/single.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const express = require('express');

const getBroadcastMiddleware = require('../../../lib/middleware/broadcasts/single/broadcast-get');

const router = express.Router({ mergeParams: true });

router.use(getBroadcastMiddleware());

module.exports = router;
8 changes: 8 additions & 0 deletions app/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

// Routes
const broadcastsIndexRoute = require('./broadcasts/index');
const broadcastsSingleRoute = require('./broadcasts/single');
const campaignsIndexRoute = require('./campaigns/index');
const campaignsSingleRoute = require('./campaigns/single');
const campaignActivityRoute = require('./campaignActivity');
Expand Down Expand Up @@ -31,6 +33,12 @@ module.exports = function init(app) {
app.get('/', statusRoute);
app.use('/v1/status', statusRoute);

// Provides data for a chatbot broadcast.
app.use('/v1/broadcasts/:broadcastId', broadcastsSingleRoute);

// Provides list of chatbot broadcasts.
app.use('/v1/broadcasts', broadcastsIndexRoute);

// Provides list of defaultTopicTriggers
app.use('/v1/defaultTopicTriggers', defaultTopicTriggersRoute);

Expand Down
7 changes: 7 additions & 0 deletions config/lib/helpers/broadcast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

module.exports = {
broadcastContentTypes: [
'broadcast',
],
};
4 changes: 4 additions & 0 deletions config/lib/helpers/cache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
'use strict';

module.exports = {
broadcasts: {
name: 'broadcasts',
ttl: parseInt(process.env.GAMBIT_BROADCASTS_CACHE_TTL, 10) || 3600,
},
campaigns: {
name: 'campaigns',
ttl: parseInt(process.env.GAMBIT_CAMPAIGNS_CACHE_TTL, 10) || 3600,
Expand Down
3 changes: 3 additions & 0 deletions documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

Endpoint | Functionality
---------------------------------------------- | --------------------------------------------------------
`GET /v1/broadcasts` | Retrieve broadcasts
`GET /v1/broadcasts/:id` | Retrieve a single broadcast
`GET /v1/campaigns/:id` | [Retrieve a campaign](endpoints/campaigns.md#retrieve-a-campaigns)
`GET /v1/campaigns` | [Retrieve all campaigns with active topics](endpoints/campaigns.md#retrieve-all-campaigns)
`GET /v1/campaigns/:id` | [Retrieve a campaign](endpoints/campaigns.md#retrieve-a-campaigns)
`POST /v1/campaignActivity` | [Parses an inbound message from user as campaign activity](endpoints/campaignActivity.md)
Expand Down
110 changes: 0 additions & 110 deletions documentation/endpoints/campaigns.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,116 +223,6 @@ curl http://localhost:5000/v1/campaigns/7 \
}
}
],
"botConfig": {
"postType": "photo",
"templates": {
"startPhotoPost": {
"raw": "Over 111,000 people have joined the movement to bring positivity to their schools. All it takes is posting encouraging notes in places that can trigger low self-esteem. Take 5 mins to post a note today. \n\nThen, text {{cmd_reportback}} to share a photo of the messages you posted (and you'll be entered to win a $2000 scholarship)!",
"override": true,
"rendered": "Over 111,000 people have joined the movement to bring positivity to their schools. All it takes is posting encouraging notes in places that can trigger low self-esteem. Take 5 mins to post a note today. \n\nThen, text START to share a photo of the messages you posted (and you'll be entered to win a $2000 scholarship)!"
},
"webStartPhotoPost": {
"raw": "Hey - this is Freddie from DoSomething. Thanks for joining a movement to spread positivity in school. You can do something simple to make a big impact for a stranger.\n\nLet's do this: post encouraging notes in places that can trigger low self-esteem, like school bathrooms.\n\nThen, text {{cmd_reportback}} to share a photo of the messages you posted (and you'll be entered to win a $2000 scholarship)!",
"override": true,
"rendered": "Hey - this is Freddie from DoSomething. Thanks for joining a movement to spread positivity in school. You can do something simple to make a big impact for a stranger.\n\nLet's do this: post encouraging notes in places that can trigger low self-esteem, like school bathrooms.\n\nThen, text START to share a photo of the messages you posted (and you'll be entered to win a $2000 scholarship)!"
},
"startPhotoPostAutoReply": {
"raw": "Sorry, I didn't get that.\n\nText {{cmd_reportback}} when you're ready to submit a post for {{title}}.",
"override": false,
"rendered": "Sorry, I didn't get that.\n\nText START when you're ready to submit a post for Mirror Messages."
},
"completedPhotoPost": {
"raw": "Great! We've got you down for {{quantity}} messages posted.\n\nTo submit another post for Mirror Messages, text {{cmd_reportback}}.",
"override": true,
"rendered": "Great! We've got you down for {{quantity}} messages posted.\n\nTo submit another post for Mirror Messages, text START."
},
"completedPhotoPostAutoReply": {
"raw": "Sorry I didn't get that. If you've posted more messages, text {{cmd_reportback}}.\n\nText Q if you have a question, or text MENU to find a different action to take.",
"override": true,
"rendered": "Sorry I didn't get that. If you've posted more messages, text START.\n\nText Q if you have a question, or text MENU to find a different action to take."
},
"askQuantity": {
"raw": "Sweet! First, what's the total number of messages you posted?\n\nBe sure to text in a number not a word (i.e. “4”, not “four”)",
"override": true,
"rendered": "Sweet! First, what's the total number of messages you posted?\n\nBe sure to text in a number not a word (i.e. “4”, not “four”)"
},
"invalidQuantity": {
"raw": "Sorry, that isn't a valid number. What's the total number of messages you posted? Be sure to text in a number not a word (i.e. “4”, not “four”)",
"override": true,
"rendered": "Sorry, that isn't a valid number. What's the total number of messages you posted? Be sure to text in a number not a word (i.e. “4”, not “four”)"
},
"askPhoto": {
"raw": "Send us your best pic of yourself and the messages you posted.",
"override": true,
"rendered": "Send us your best pic of yourself and the messages you posted."
},
"invalidPhoto": {
"raw": "Sorry, I didn't get that.\n\nSend us your best pic of yourself and the messages you posted. \n\nIf you have a question, text Q.",
"override": true,
"rendered": "Sorry, I didn't get that.\n\nSend us your best pic of yourself and the messages you posted. \n\nIf you have a question, text Q."
},
"askCaption": {
"raw": "Got it! Now text back a caption for your photo (think Instagram)! Keep it short & sweet, under 60 characters please.",
"override": false,
"rendered": "Got it! Now text back a caption for your photo (think Instagram)! Keep it short & sweet, under 60 characters please."
},
"invalidCaption": {
"raw": "Sorry, I didn't get that.\n\nText back a caption for your photo -- keep it short & sweet, under 60 characters please. (but more than 3!)",
"override": false,
"rendered": "Sorry, I didn't get that.\n\nText back a caption for your photo -- keep it short & sweet, under 60 characters please. (but more than 3!)"
},
"askWhyParticipated": {
"raw": "Last question: Why was participating in {{title}} important to you? (No need to write an essay, one sentence is good).",
"override": false,
"rendered": "Last question: Why was participating in Mirror Messages important to you? (No need to write an essay, one sentence is good)."
},
"invalidWhyParticipated": {
"raw": "Sorry, I didn't get that.\n\nLast question: Why was participating in {{title}} important to you? (No need to write an essay, one sentence is good).",
"override": false,
"rendered": "Sorry, I didn't get that.\n\nLast question: Why was participating in Mirror Messages important to you? (No need to write an essay, one sentence is good)."
},
"memberSupport": {
"raw": "Text back your question and I'll try to get back to you within 24 hrs.\n\nIf you want to continue {{title}}, text back {{keyword}}",
"override": false,
"rendered": "Text back your question and I'll try to get back to you within 24 hrs.\n\nIf you want to continue Mirror Messages, text back MIRROR"
},
"campaignClosed": {
"raw": "Sorry, {{title}} is no longer available.\n\nText {{cmd_member_support}} for help.",
"override": false,
"rendered": "Sorry, Mirror Messages is no longer available.\n\nText Q for help."
},
"askSignup": {
"raw": "{{tagline}}\n\nWant to join {{title}}?\n\nYes or No",
"override": false,
"rendered": "Boost a stranger's self-esteem with just a sticky note!\n\nWant to join Mirror Messages?\n\nYes or No"
},
"declinedSignup": {
"raw": "Ok! Text MENU if you'd like to find a different action to take.",
"override": false,
"rendered": "Ok! Text MENU if you'd like to find a different action to take."
},
"invalidAskSignupResponse": {
"raw": "Sorry, I didn't get that. Did you want to join {{title}}?\n\nYes or No",
"override": false,
"rendered": "Sorry, I didn't get that. Did you want to join Mirror Messages?\n\nYes or No"
},
"askContinue": {
"raw": "Ready to get back to {{title}}?\n\nYes or No",
"override": false,
"rendered": "Ready to get back to Mirror Messages?\n\nYes or No"
},
"declinedContinue": {
"raw": "Right on, we'll check in with you about {{title}} later.\n\nText MENU if you'd like to find a different action to take.",
"override": false,
"rendered": "Right on, we'll check in with you about Mirror Messages later.\n\nText MENU if you'd like to find a different action to take."
},
"invalidAskContinueResponse": {
"raw": "Sorry, I didn't get that. Did you want to join {{title}}?\n\nYes or No",
"override": false,
"rendered": "Sorry, I didn't get that. Did you want to join Mirror Messages?\n\nYes or No"
}
}
}
}
}
```
Expand Down
14 changes: 14 additions & 0 deletions lib/contentful.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ function fetchByContentTypes(contentTypes) {
logger.debug('fetchByContentTypes', { contentTypes });
const query = module.exports.getQueryBuilder()
.contentTypes(contentTypes)
.orderByDescCreatedAt()
.build();
return module.exports.getClient().getEntries(query)
.then((res) => {
Expand Down Expand Up @@ -139,6 +140,18 @@ function getContentTypeFromContentfulEntry(contentfulEntry) {
return contentfulEntry.sys.contentType.sys.id;
}

/**
* @param {Object} contentfulEntry
* @return {object}
*/
function getAttachmentsFromContentfulEntry(contentfulEntry) {
const attachmentsFieldValue = contentfulEntry.fields.attachments;
if (!attachmentsFieldValue) {
return [];
}
return attachmentsFieldValue.map(assetObject => assetObject.fields.file);
}

/**
* @param {Object} messageObject - A Contentful entry
* @return {String}
Expand Down Expand Up @@ -202,6 +215,7 @@ module.exports = {
createNewClient,
fetchByContentfulId,
fetchByContentTypes,
getAttachmentsFromContentfulEntry,
getCampaignIdFromContentfulEntry,
getClient,
getContentfulIdFromContentfulEntry,
Expand Down
4 changes: 4 additions & 0 deletions lib/contentfulQueryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ class QueryBuilder {
});
return this;
}
orderByDescCreatedAt() {
this.query.order = '-sys.createdAt';
return this;
}
build() {
return this.query;
}
Expand Down
82 changes: 82 additions & 0 deletions lib/helpers/broadcast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
'use strict';

const logger = require('winston');
const contentful = require('../contentful');
const helpers = require('../helpers');
const config = require('../../config/lib/helpers/broadcast');

/**
* @return {Promise}
*/
function fetchAll() {
// TODO: Loop through all pages of query results - currently returns 100.
return contentful.fetchByContentTypes(config.broadcastContentTypes)
.then(contentfulEntries => Promise.all(contentfulEntries.map(module.exports
.parseBroadcastFromContentfulEntry)));
}

/**
* @param {String} broadcastId
* @return {Promise}
*/
function fetchById(broadcastId) {
return contentful.fetchByContentfulId(broadcastId)
.then(contentfulEntry => module.exports.parseBroadcastFromContentfulEntry(contentfulEntry));
}

/**
* @param {String} topicId
* @return {Promise}
*/
function getById(broadcastId) {
return helpers.cache.broadcasts.get(broadcastId)
.then((data) => {
if (data) {
logger.debug('Broadcast cache hit', { broadcastId });
return data;
}
logger.debug('Broadcast cache miss', { broadcastId });
return module.exports.fetchById(broadcastId)
.then(broadcast => helpers.cache.broadcasts.set(broadcastId, broadcast));
});
}

/**
* @param {Object} contentfulEntry
* @return {Promise}
*/
function parseBroadcastFromContentfulEntry(contentfulEntry) {
const data = {
id: contentful.getContentfulIdFromContentfulEntry(contentfulEntry),
name: contentful.getNameTextFromContentfulEntry(contentfulEntry),
createdAt: contentfulEntry.sys.createdAt,
updatedAt: contentfulEntry.sys.updatedAt,
message: {
text: contentfulEntry.fields.message,
attachments: contentful.getAttachmentsFromContentfulEntry(contentfulEntry),
},
};
// Note: We plan to split broadcast content type into two separate content types, so editors
// don't need to remember which fields to include/exclude in order to create a specific type.
const hardcodedTopic = contentfulEntry.fields.topic;
if (hardcodedTopic) {
// Another note: the new broadcast content types will reference a topic Contentful entry, not
// a campaign Contentful entry -- eventually topic will be returned as a nested object.
data.campaignId = null;
data.topic = hardcodedTopic;
data.message.template = 'rivescript';
} else {
const campaignConfigEntry = contentfulEntry.fields.campaign;
data.campaignId = Number(contentful.getCampaignIdFromContentfulEntry(campaignConfigEntry));
data.topic = null;
data.message.template = contentfulEntry.fields.template || 'askSignup';
}
return data;
}

module.exports = {
fetchAll,
fetchById,
getById,
parseBroadcastFromContentfulEntry,
};
12 changes: 12 additions & 0 deletions lib/helpers/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ const redisClient = require('../../config/redis')();
const config = require('../../config/lib/helpers/cache');

const redisEngine = new RedisEngine(redisClient);
const broadcastsCache = new Cacheman(config.broadcasts.name, {
ttl: config.broadcasts.ttl,
engine: redisEngine,
});
const campaignsCache = new Cacheman(config.campaigns.name, {
ttl: config.campaigns.ttl,
engine: redisEngine,
Expand All @@ -24,6 +28,14 @@ function parseSetCacheResponse(res) {
}

module.exports = {
broadcasts: {
get: function get(id) {
return broadcastsCache.get(id);
},
set: function set(id, data) {
return broadcastsCache.set(id, data).then(res => parseSetCacheResponse(res));
},
},
campaigns: {
get: function get(id) {
return campaignsCache.get(id);
Expand Down
2 changes: 2 additions & 0 deletions lib/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const broadcast = require('./broadcast');
const cache = require('./cache');
const campaign = require('./campaign');
const campaignActivity = require('./campaignActivity');
Expand All @@ -10,6 +11,7 @@ const topic = require('./topic');
const util = require('./util');

module.exports = {
broadcast,
cache,
campaign,
campaignActivity,
Expand Down
12 changes: 12 additions & 0 deletions lib/middleware/broadcasts/index/broadcasts-get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

const helpers = require('../../../helpers');

module.exports = function getBroadcasts() {
return (req, res) => helpers.broadcast.fetchAll()
.then((broadcasts) => {
req.data = broadcasts;
return res.send({ data: req.data });
})
.catch(err => helpers.sendErrorResponse(res, err));
};
12 changes: 12 additions & 0 deletions lib/middleware/broadcasts/single/broadcast-get.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
'use strict';

const helpers = require('../../../helpers');

module.exports = function getBroadcast() {
return (req, res) => helpers.broadcast.getById(req.params.broadcastId)
.then((broadcast) => {
req.data = broadcast;
return res.send({ data: req.data });
})
.catch(err => helpers.sendErrorResponse(res, err));
};
Loading

0 comments on commit cf54671

Please sign in to comment.