From 33cd7a439850475711185e9199725415bb79431f Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:00:00 -0400 Subject: [PATCH 01/19] Adds one state variable per user_id to save last command --- classes/Dispatch.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/classes/Dispatch.js b/classes/Dispatch.js index 6142f24..4346394 100644 --- a/classes/Dispatch.js +++ b/classes/Dispatch.js @@ -14,6 +14,7 @@ class Dispatch { this.templates = reqDir('../routes/messages/Templates/'); this.db = reqDir('../models/db/'); this.helpers = reqDir('../routes/messages/helpers'); + this.state = {}; } async execute(Event, override) { @@ -85,6 +86,17 @@ class Dispatch { return this.import('helpers', ...args); } + saveState(Event) { + // Save a single Event object to be pulled later by the app + // ie: the user is going through onboarding, save the Event + // that triggered the onboarding + this.state[Event.user_id] = Event; + } + + getState(Event) { + return this.state[Event.user_id]; + } + async sendTemplate(name, ...args) { // If you want to immediately run a template instead of importing it then running it, // run this with any arguments the template wants, everything separated by a comma. From bd23ff7ff988a07e52ac91d31633a00f8a59a2d3 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:00:17 -0400 Subject: [PATCH 02/19] Removes diag log --- classes/WebhookEvent.js | 1 - 1 file changed, 1 deletion(-) diff --git a/classes/WebhookEvent.js b/classes/WebhookEvent.js index a3c1728..05d92f0 100644 --- a/classes/WebhookEvent.js +++ b/classes/WebhookEvent.js @@ -153,7 +153,6 @@ module.exports = class WebhookEvent { setOverride(command) { // The first override should receive priority if (!this.isOverridden && !this.override) { - console.log('Overriding to: ', command); this.isOverridden = true; this.override = command; } From 49b5715a49dfc8dc0b76942aac2845e5d1f21cdf Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:00:28 -0400 Subject: [PATCH 03/19] Updates to use new command style --- jobs/timedMessages.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/jobs/timedMessages.js b/jobs/timedMessages.js index da2b26e..4f9adcf 100644 --- a/jobs/timedMessages.js +++ b/jobs/timedMessages.js @@ -1,7 +1,7 @@ const TimedMessages = require('../models/db/timedMessages.js'); const Users = require('models/db/users.js'); const Pages = require('models/db/pages.js'); -const Message = require('classes/WebhookEvent.js'); +const MessageQueue = require('classes/MessageQueue.js'); const GenericTemplate = require('routes/messages/Templates/Generic.js'); module.exports = setInterval(cycleMessages, 1000 * 60 * 30); @@ -16,7 +16,7 @@ async function cycleMessages() { const user = await Users.retrieve({ id: m.user_id }).first(); // This gets us around having to add a page_id column to the timed_messages // table but likely won't work long term if books lose the page_id column - const page = await Pages.retrieve({ id: m.page_id }).first(); + const { access_token } = await Pages.retrieve({ id: m.page_id }).first(); const psid = user.facebook_id; const response = GenericTemplate([ @@ -34,16 +34,14 @@ async function cycleMessages() { } ]); - const timed = new Message( - { - sender: psid, - page, - command: 'timed_message' - }, - [response] - ); + const timed = new MessageQueue({ + sender_id: psid, + access_token, + validatedCommand: 'timed_message' + }); - await timed.respond(); + timed.add(response); + timed.send(); await TimedMessages.remove(m.id); console.log('Sent timed response'); } From 9888b6cbb8141668ed722e60e233b867b8d5fed9 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:05:25 -0400 Subject: [PATCH 04/19] Removes no longer used job --- server.js | 1 - 1 file changed, 1 deletion(-) diff --git a/server.js b/server.js index b5451cf..2be78d6 100644 --- a/server.js +++ b/server.js @@ -4,7 +4,6 @@ const cors = require('cors'); // Jobs -- Tasks running on a schedule require('./jobs/timedMessages.js'); -// require('./jobs/bookRatings.js')(); // Import routes const messageRouter = require('./routes/messages/'); From 1f3ed538849a2efaf3234c683125d717d8c58753 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:23:35 -0400 Subject: [PATCH 05/19] Adds state methods --- classes/Dispatch.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/classes/Dispatch.js b/classes/Dispatch.js index 4346394..7de0576 100644 --- a/classes/Dispatch.js +++ b/classes/Dispatch.js @@ -86,7 +86,7 @@ class Dispatch { return this.import('helpers', ...args); } - saveState(Event) { + setStates(Event) { // Save a single Event object to be pulled later by the app // ie: the user is going through onboarding, save the Event // that triggered the onboarding @@ -94,7 +94,16 @@ class Dispatch { } getState(Event) { - return this.state[Event.user_id]; + // Remove the item from state and return it. + // Note: it is destructive + const state = this.state[Event.user_id]; + delete this.state[Event.user_id]; + return state; + } + + hasOpenState(Event) { + // Boolean return for if a user has data in state + return !this.state[Event.user_id]; } async sendTemplate(name, ...args) { From 2af4cbd528557a6b641522c72e2f8ecb72bd5e94 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:23:43 -0400 Subject: [PATCH 06/19] Adds override methods --- classes/WebhookEvent.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/classes/WebhookEvent.js b/classes/WebhookEvent.js index 05d92f0..958bce1 100644 --- a/classes/WebhookEvent.js +++ b/classes/WebhookEvent.js @@ -89,8 +89,14 @@ module.exports = class WebhookEvent { return this.bookCount > 1; } - overrideOnUserMessage(command) { - if (this.type === 'message' && !this.isOverridden) { + overrideOnUserMessage(command = 'get_started') { + if (this.type === 'message') { + this.setOverride(command); + } + } + + overrideOnMissingProperty(prop, command = 'get_started') { + if (!this[prop]) { this.setOverride(command); } } @@ -113,7 +119,7 @@ module.exports = class WebhookEvent { const { user_id } = this; const userCategories = await UserCategories.retrieve({ user_id }); if (userCategories.length < 3) { - return this.setOverride('pick_category'); + return this.setOverride('request_categories'); } else if (!this.user.email) { return this.setOverride('request_email'); } else if (this.user.prefersLongSummaries === null) { From 6c0e52f5d8768f609c0d882b364c65c8837b23c2 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:24:06 -0400 Subject: [PATCH 07/19] Renames 'pick_category' for consistency sake --- .../{pick_category.js => request_categories.js} | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) rename routes/messages/commands/{pick_category.js => request_categories.js} (55%) diff --git a/routes/messages/commands/pick_category.js b/routes/messages/commands/request_categories.js similarity index 55% rename from routes/messages/commands/pick_category.js rename to routes/messages/commands/request_categories.js index 10c1612..163657e 100644 --- a/routes/messages/commands/pick_category.js +++ b/routes/messages/commands/request_categories.js @@ -1,7 +1,6 @@ const UserCategories = require('models/db/userCategories.js'); -const QRT = require('../Templates/QuickReply.js'); -module.exports = async Event => { +module.exports = async function(Event) { const { user_id } = Event; const userCategories = await UserCategories.retrieve({ user_id }); @@ -9,26 +8,18 @@ module.exports = async Event => { // Only show categories the user has not saved const categories = await Event.getNewCategoriesForUser(); - // If command given is this command, push them toward browse as a default behavior - console.log('VAL: ', Event.validatedCommand); - const redirect = - Event.validatedCommand === 'pick_category' - ? 'browse' - : Event.validatedCommand; - const quick_replies = categories.map(c => { return { title: c.name, payload: JSON.stringify({ command: 'save_category', - category_id: c.id, - redirect + category_id: c.id }) }; }); const intro = - Event.command === 'browse' ? 'To get started, please ' : 'Please '; + Event.validatedCommand === 'browse' ? 'To get started, please ' : 'Please '; const text = !userCategories.length ? intro + 'select 3 categories' @@ -36,5 +27,5 @@ module.exports = async Event => { ? '2 more to go...' : 'Last one!'; - return QRT(text, quick_replies); + return this.sendTemplate('QuickReply', text, quick_replies); }; From ec6fbbb316653a8843e47062b4b5cb834f5314a8 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:24:54 -0400 Subject: [PATCH 08/19] Removes unused commands --- routes/messages/commands/request_email.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/routes/messages/commands/request_email.js b/routes/messages/commands/request_email.js index 2eea1dc..db2e0ad 100644 --- a/routes/messages/commands/request_email.js +++ b/routes/messages/commands/request_email.js @@ -1,17 +1,4 @@ module.exports = async Event => { - // const textResponses = { - // pick_category: - // "Great, I have what I need to make some suggestions! Though first, I'd like to make an account for you so I can remember your preferences across platforms. What email address can I attach to your account?", - // get_started: - // 'I have some suggestions ready to go for you, just respond with a valid email and I can get them right to you!', - // default: - // "We use an email address to identify you across platforms. Type a valid email address at any time and I'll save it to your account so you can proceed!" - // }; - - // const text = textResponses[Event.command] - // ? textResponses[Event.command] - // : textResponses.default; - return [ { text: "And what's your email address?" From c383bd27d8fc44c936c4f6a784363a8552b515a7 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:25:06 -0400 Subject: [PATCH 09/19] Updates to use new override method --- routes/messages/conditions/browse.js | 7 ++++++- routes/messages/conditions/get_synopsis.js | 6 +----- routes/messages/conditions/save_category.js | 6 ++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/routes/messages/conditions/browse.js b/routes/messages/conditions/browse.js index 3a40cb2..6a5e3ea 100644 --- a/routes/messages/conditions/browse.js +++ b/routes/messages/conditions/browse.js @@ -1 +1,6 @@ -module.exports = async Event => Event.overrideIfNotOnboarded(); +module.exports = async function(Event) { + if (this.hasOpenState(Event)) { + this.setState(Event); + } + Event.overrideIfNotOnboarded(); +}; diff --git a/routes/messages/conditions/get_synopsis.js b/routes/messages/conditions/get_synopsis.js index 4ae328f..c91aea2 100644 --- a/routes/messages/conditions/get_synopsis.js +++ b/routes/messages/conditions/get_synopsis.js @@ -1,8 +1,4 @@ module.exports = async function(Event) { - console.log('Synopsis Override'); Event.overrideOnUserMessage('get_started'); - - if (!Event.book_id) { - Event.setOverride('get_started'); - } + Event.overrideOnMissingProperty('book_id', 'get_started'); }; diff --git a/routes/messages/conditions/save_category.js b/routes/messages/conditions/save_category.js index 180ed0d..482abd1 100644 --- a/routes/messages/conditions/save_category.js +++ b/routes/messages/conditions/save_category.js @@ -1,5 +1,3 @@ -module.exports = ({ category_id }) => { - if (!category_id) { - Event.setOverride('get_started'); - } +module.exports = function(Event) { + Event.overrideOnMissingProperty('category_id', 'get_started'); }; From d5c44c791c8f96ddc01082669123f90c2699f215 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 18:28:21 -0400 Subject: [PATCH 10/19] Fixes typo in method name --- classes/Dispatch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Dispatch.js b/classes/Dispatch.js index 7de0576..76cee63 100644 --- a/classes/Dispatch.js +++ b/classes/Dispatch.js @@ -86,7 +86,7 @@ class Dispatch { return this.import('helpers', ...args); } - setStates(Event) { + setState(Event) { // Save a single Event object to be pulled later by the app // ie: the user is going through onboarding, save the Event // that triggered the onboarding From f0c26a51c5e550ebc7c9e36fa29e64bddfe647c3 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:01:38 -0400 Subject: [PATCH 11/19] Modifies the getState to override properties on current Event --- classes/Dispatch.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/classes/Dispatch.js b/classes/Dispatch.js index 76cee63..9858094 100644 --- a/classes/Dispatch.js +++ b/classes/Dispatch.js @@ -90,7 +90,9 @@ class Dispatch { // Save a single Event object to be pulled later by the app // ie: the user is going through onboarding, save the Event // that triggered the onboarding - this.state[Event.user_id] = Event; + + // NOTE: Does NOT save across server crashes/restarts at this time + this.state[Event.user_id] = { ...Event }; } getState(Event) { @@ -98,7 +100,13 @@ class Dispatch { // Note: it is destructive const state = this.state[Event.user_id]; delete this.state[Event.user_id]; - return state; + + Event.insertPreviousState(state); + return Event; + } + + clearState(Event) { + delete this.state[Event.user_id]; } hasOpenState(Event) { @@ -106,6 +114,10 @@ class Dispatch { return !this.state[Event.user_id]; } + isUsingState(Event) { + return this.state[Event.user_id]; + } + async sendTemplate(name, ...args) { // If you want to immediately run a template instead of importing it then running it, // run this with any arguments the template wants, everything separated by a comma. From d1428c1659d79880a86d30e8dc394bc09a9ab56e Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:01:54 -0400 Subject: [PATCH 12/19] Adds method to override current properties to clone previous state --- classes/WebhookEvent.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/classes/WebhookEvent.js b/classes/WebhookEvent.js index 99463c8..6b6accc 100644 --- a/classes/WebhookEvent.js +++ b/classes/WebhookEvent.js @@ -91,13 +91,19 @@ module.exports = class WebhookEvent { overrideOnUserMessage(command = 'get_started') { if (this.type === 'message') { - this.setOverride(command); + return this.setOverride(command); } } overrideOnMissingProperty(prop, command = 'get_started') { if (!this[prop]) { - this.setOverride(command); + return this.setOverride(command); + } + } + + insertPreviousState(state) { + for (let property in state) { + this[property] = state[property]; } } @@ -174,6 +180,7 @@ module.exports = class WebhookEvent { if (!this.isOverridden && !this.override) { this.isOverridden = true; this.override = command; + return this.override; } } From 9a150ee80e9c24de29fc50079d28b3f8101d86aa Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:02:38 -0400 Subject: [PATCH 13/19] Removes tempoarily check for the user to be onboarded. Now sends through onboarding to save --- routes/messages/Templates/Book.js | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/routes/messages/Templates/Book.js b/routes/messages/Templates/Book.js index 881a399..f86ecab 100644 --- a/routes/messages/Templates/Book.js +++ b/routes/messages/Templates/Book.js @@ -41,19 +41,15 @@ module.exports = async (Event, books) => { lib => lib.id === parseInt(book_id, 10) ); - if (awaitEvent.isUserOnboarded()) { - // Temporary measure. Don't let the user save to their - // library if they aren't onboarded - buttons.push({ - type: 'postback', - title: isInLibrary ? 'Remove from Library' : 'Add to Library', - payload: JSON.stringify({ - command: 'toggle_in_library', - book_id, - isAdding: !isInLibrary - }) - }); - } + buttons.push({ + type: 'postback', + title: isInLibrary ? 'Remove from Library' : 'Add to Library', + payload: JSON.stringify({ + command: 'toggle_in_library', + book_id, + isAdding: !isInLibrary + }) + }); } buttons.push({ From 4caf29cb6215cf9ee41ab2417314ea5bfed05467 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:03:04 -0400 Subject: [PATCH 14/19] Updates redirect to bounce back to previous command --- routes/messages/commands/request_categories.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/messages/commands/request_categories.js b/routes/messages/commands/request_categories.js index 163657e..5d9b07c 100644 --- a/routes/messages/commands/request_categories.js +++ b/routes/messages/commands/request_categories.js @@ -13,7 +13,8 @@ module.exports = async function(Event) { title: c.name, payload: JSON.stringify({ command: 'save_category', - category_id: c.id + category_id: c.id, + redirect: Event.validatedCommand }) }; }); From ddc01b8a3e6b9db69aace8882c37b9e409fe2c31 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:03:25 -0400 Subject: [PATCH 15/19] Adds a check for previous state --- .../messages/commands/request_summary_preference.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/routes/messages/commands/request_summary_preference.js b/routes/messages/commands/request_summary_preference.js index bd2ef80..522fb94 100644 --- a/routes/messages/commands/request_summary_preference.js +++ b/routes/messages/commands/request_summary_preference.js @@ -1,19 +1,24 @@ // Part of onboarding, requires the user to specify if they want long or short summaries. // The value is saved in the users table under "prefersLongSummaries" inside of the "browse" command -module.exports = async function() { + +module.exports = async function(Event) { + // If Event was previously saved to onboard, restore the Event + if (this.isUsingState(Event)) { + Event = this.getState(Event); + } const text = 'Would you like to read 2-minute or 10-15 minute summaries?'; const buttons = [ { title: '2-minute', payload: JSON.stringify({ - command: 'browse', + command: Event.validatedCommand, prefersLongSummaries: false }) }, { title: '10-minute', payload: JSON.stringify({ - command: 'browse', + command: Event.validatedCommand, prefersLongSummaries: true }) } From 015c0482023ee3a68bf024046598b3e0dcb27209 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:04:38 -0400 Subject: [PATCH 16/19] Checks for previous state and restores it --- routes/messages/commands/save_email.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/routes/messages/commands/save_email.js b/routes/messages/commands/save_email.js index ed35fec..a547deb 100644 --- a/routes/messages/commands/save_email.js +++ b/routes/messages/commands/save_email.js @@ -4,8 +4,18 @@ module.exports = async function(Event) { // When the user types a valid email address, save it to their account. // Then redirect them to 'browse' const { user_id: id } = Event; - Event.user = await Users.edit({ id }, { email: Event.command }); + const email = Event.command; + + const wasPreviousCommand = this.isUsingState(Event); + if (wasPreviousCommand) { + // Override Event to previous state, if it exists + this.getState(Event); + } + Event.user = await Users.edit({ id }, { email }); + + const nextCommand = wasPreviousCommand ? Event.validatedCommand : 'browse'; + console.log('nextCommand: ', nextCommand); Event.isMultiBookPage() - ? this.redirectTo(Event, 'browse') + ? this.redirectTo(Event, nextCommand) : this.redirectTo(Event, 'get_started'); }; From 67f7b92885ecac23466928b6b69109aa284235c5 Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 19:05:08 -0400 Subject: [PATCH 17/19] Removes diagnostic log --- routes/messages/commands/save_email.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/messages/commands/save_email.js b/routes/messages/commands/save_email.js index a547deb..d9babb5 100644 --- a/routes/messages/commands/save_email.js +++ b/routes/messages/commands/save_email.js @@ -14,7 +14,7 @@ module.exports = async function(Event) { Event.user = await Users.edit({ id }, { email }); const nextCommand = wasPreviousCommand ? Event.validatedCommand : 'browse'; - console.log('nextCommand: ', nextCommand); + Event.isMultiBookPage() ? this.redirectTo(Event, nextCommand) : this.redirectTo(Event, 'get_started'); From 4612e88fe49984b1fbb6ded4d1617e3ac8d9e69d Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 21:11:07 -0400 Subject: [PATCH 18/19] Fixes bugs related to state saving correctly --- classes/Dispatch.js | 13 ++--- classes/WebhookEvent.js | 51 ++++++++----------- routes/messages/Templates/Book.js | 6 +-- .../messages/commands/request_categories.js | 22 ++++---- .../commands/request_summary_preference.js | 8 +-- routes/messages/commands/save_email.js | 2 +- .../commands/save_summary_preferences.js | 15 ++++++ routes/messages/commands/toggle_in_library.js | 24 ++++++--- routes/messages/conditions/browse.js | 9 +++- .../conditions/save_summary_preference.js | 5 ++ .../messages/conditions/toggle_in_library.js | 13 ++++- 11 files changed, 104 insertions(+), 64 deletions(-) create mode 100644 routes/messages/commands/save_summary_preferences.js create mode 100644 routes/messages/conditions/save_summary_preference.js diff --git a/classes/Dispatch.js b/classes/Dispatch.js index 9858094..f631737 100644 --- a/classes/Dispatch.js +++ b/classes/Dispatch.js @@ -92,15 +92,14 @@ class Dispatch { // that triggered the onboarding // NOTE: Does NOT save across server crashes/restarts at this time + console.log('SAVING STATE'); this.state[Event.user_id] = { ...Event }; } getState(Event) { - // Remove the item from state and return it. - // Note: it is destructive + console.log('GETTING STATE'); const state = this.state[Event.user_id]; - delete this.state[Event.user_id]; - + // Can either simply run the method or set Event to the return Event.insertPreviousState(state); return Event; } @@ -110,11 +109,12 @@ class Dispatch { } hasOpenState(Event) { - // Boolean return for if a user has data in state + // Returns true is state for user is empty return !this.state[Event.user_id]; } - isUsingState(Event) { + hasState(Event) { + // Returns truthy if user has data in state return this.state[Event.user_id]; } @@ -131,6 +131,7 @@ class Dispatch { redirectTo(Event, command) { Event.validatedCommand = command; + Event.type = 'redirect'; this.execute(Event, true); } diff --git a/classes/WebhookEvent.js b/classes/WebhookEvent.js index 6b6accc..db5fbb5 100644 --- a/classes/WebhookEvent.js +++ b/classes/WebhookEvent.js @@ -89,6 +89,10 @@ module.exports = class WebhookEvent { return this.bookCount > 1; } + isPostback() { + return this.type === 'postback'; + } + overrideOnUserMessage(command = 'get_started') { if (this.type === 'message') { return this.setOverride(command); @@ -101,26 +105,7 @@ module.exports = class WebhookEvent { } } - insertPreviousState(state) { - for (let property in state) { - this[property] = state[property]; - } - } - async overrideIfNotOnboarded() { - // User has completed onboarding - if (this.hasOwnProperty('prefersLongSummaries')) { - // Event.prefersLongSummaries will exist on the postback - // from request_summary_preference (in English: the user sent the final - // piece needed to complete onboarding and should progress) - const { prefersLongSummaries } = this; - const updatedUser = await Users.edit( - { id: this.user_id }, - { prefersLongSummaries } - ); - this.user = updatedUser; - } - if (!this.isOverridden) { const { user_id } = this; const userCategories = await UserCategories.retrieve({ user_id }); @@ -178,6 +163,7 @@ module.exports = class WebhookEvent { setOverride(command) { // The first override should receive priority if (!this.isOverridden && !this.override) { + this.type = 'redirect'; this.isOverridden = true; this.override = command; return this.override; @@ -228,11 +214,13 @@ module.exports = class WebhookEvent { this.response = response; } - setUser(u) { + setUser(u, addTimeUser) { const { id, ...user } = u; this.user = user; this.user_id = id; - this.addTimedMessage(); + if (addTimeUser) { + this.addTimedMessage(); + } } setValidatedCommand(command) { @@ -276,6 +264,12 @@ module.exports = class WebhookEvent { return allCategories.filter(c => userCategoryIDs.indexOf(c.id) === -1); } + insertPreviousState(state) { + for (let property in state) { + this[property] = state[property]; + } + } + async addTimedMessage() { const { user_id, page_id } = this; if (!user_id || !page_id) { @@ -322,7 +316,6 @@ module.exports = class WebhookEvent { } async _parseUserAction(entry) { - console.log('Received user action'); const { Event } = entry; const message = entry.messaging[0]; @@ -338,7 +331,7 @@ module.exports = class WebhookEvent { Event.isNewUser = true; } - this.setUser(user); + this.setUser(user, true); // So many commands currently rely on how many books are available // for the page, we've added the bookCount as a default @@ -366,14 +359,14 @@ module.exports = class WebhookEvent { isReferral ? this.setEventData({ + type: 'referral', ...parsed_data, - ...this._handleReferral(message.postback.referral.ref), - type: 'referral' + ...this._handleReferral(message.postback.referral.ref) }) : this.setEventData({ + type: 'postback', ...parsed_data, - ...JSON.parse(payload), - type: 'postback' + ...JSON.parse(payload) }); } else if (message.referral) { // This block will fire under one condition: @@ -383,9 +376,9 @@ module.exports = class WebhookEvent { // http://m.me/109461977131004?ref=command=start_book,book_id=1 this.setEventData({ + type: 'referral', ...parsed_data, - ...this._handleReferral(message.referral.ref), - type: 'referral' + ...this._handleReferral(message.referral.ref) }); } else if (message && message.message) { // Fires whenever the user types something at the bot diff --git a/routes/messages/Templates/Book.js b/routes/messages/Templates/Book.js index f86ecab..a410f40 100644 --- a/routes/messages/Templates/Book.js +++ b/routes/messages/Templates/Book.js @@ -37,17 +37,17 @@ module.exports = async (Event, books) => { if (Event.isMultiBookPage()) { const usersLibrary = await UserLibraries.retrieve({ user_id }); - const isInLibrary = usersLibrary.find( + const isAdding = !usersLibrary.find( lib => lib.id === parseInt(book_id, 10) ); buttons.push({ type: 'postback', - title: isInLibrary ? 'Remove from Library' : 'Add to Library', + title: isAdding ? 'Add to Library' : 'Remove from Library', payload: JSON.stringify({ command: 'toggle_in_library', book_id, - isAdding: !isInLibrary + isAdding }) }); } diff --git a/routes/messages/commands/request_categories.js b/routes/messages/commands/request_categories.js index 5d9b07c..d01974b 100644 --- a/routes/messages/commands/request_categories.js +++ b/routes/messages/commands/request_categories.js @@ -1,6 +1,10 @@ const UserCategories = require('models/db/userCategories.js'); module.exports = async function(Event) { + if (this.hasState(Event)) { + this.getState(Event); + } + const { user_id } = Event; const userCategories = await UserCategories.retrieve({ user_id }); @@ -8,16 +12,14 @@ module.exports = async function(Event) { // Only show categories the user has not saved const categories = await Event.getNewCategoriesForUser(); - const quick_replies = categories.map(c => { - return { - title: c.name, - payload: JSON.stringify({ - command: 'save_category', - category_id: c.id, - redirect: Event.validatedCommand - }) - }; - }); + const quick_replies = categories.map(c => ({ + title: c.name, + payload: JSON.stringify({ + command: 'save_category', + category_id: c.id, + redirect: Event.validatedCommand + }) + })); const intro = Event.validatedCommand === 'browse' ? 'To get started, please ' : 'Please '; diff --git a/routes/messages/commands/request_summary_preference.js b/routes/messages/commands/request_summary_preference.js index 522fb94..f3baaf3 100644 --- a/routes/messages/commands/request_summary_preference.js +++ b/routes/messages/commands/request_summary_preference.js @@ -3,22 +3,22 @@ module.exports = async function(Event) { // If Event was previously saved to onboard, restore the Event - if (this.isUsingState(Event)) { - Event = this.getState(Event); + if (this.hasState(Event)) { + this.getState(Event); } const text = 'Would you like to read 2-minute or 10-15 minute summaries?'; const buttons = [ { title: '2-minute', payload: JSON.stringify({ - command: Event.validatedCommand, + command: 'save_summary_preferences', prefersLongSummaries: false }) }, { title: '10-minute', payload: JSON.stringify({ - command: Event.validatedCommand, + command: 'save_summary_preferences', prefersLongSummaries: true }) } diff --git a/routes/messages/commands/save_email.js b/routes/messages/commands/save_email.js index d9babb5..632517b 100644 --- a/routes/messages/commands/save_email.js +++ b/routes/messages/commands/save_email.js @@ -6,7 +6,7 @@ module.exports = async function(Event) { const { user_id: id } = Event; const email = Event.command; - const wasPreviousCommand = this.isUsingState(Event); + const wasPreviousCommand = this.hasState(Event); if (wasPreviousCommand) { // Override Event to previous state, if it exists this.getState(Event); diff --git a/routes/messages/commands/save_summary_preferences.js b/routes/messages/commands/save_summary_preferences.js new file mode 100644 index 0000000..a60ee66 --- /dev/null +++ b/routes/messages/commands/save_summary_preferences.js @@ -0,0 +1,15 @@ +module.exports = async function(Event) { + // User has submitted their preference for short or long summaries + const { prefersLongSummaries } = Event; + const [Users] = this.withDBs('users'); + if (this.hasState(Event)) { + this.getState(Event); + } + const updatedUser = await Users.edit( + { id: Event.user_id }, + { prefersLongSummaries } + ); + Event.setUser(updatedUser); + console.log('REDIRECT TO: ', Event.validatedCommand); + this.redirectTo(Event, Event.validatedCommand); +}; diff --git a/routes/messages/commands/toggle_in_library.js b/routes/messages/commands/toggle_in_library.js index 8bf7a8b..64c8113 100644 --- a/routes/messages/commands/toggle_in_library.js +++ b/routes/messages/commands/toggle_in_library.js @@ -1,11 +1,18 @@ -const UserLibrary = require('models/db/userLibraries.js'); - -module.exports = async Event => { +module.exports = async function(Event) { + if (this.hasState(Event)) { + // If an onboarding detour was taken, retrieve original state then clear it + this.getState(Event); + this.clearState(Event); + } const { user_id, book_id, isAdding } = Event; - const currentLibrary = await UserLibrary.retrieve({ 'ul.user_id': user_id }); - const exists = currentLibrary.find(b => b.id === book_id); + const [Books, UserLibraries] = this.withDBs('books', 'userLibraries'); + const book = Books.retrieve({ 'b.id': book_id }).first(); + const currentLibrary = await UserLibraries.retrieve({ + 'ul.user_id': user_id + }); + const exists = currentLibrary.find(b => b.id === book_id); // Two conditions something will happen: // 1) It's a new item and the user is trying to add it // 2) The user previous saved it and they want to remove it @@ -14,8 +21,11 @@ module.exports = async Event => { !exists && isAdding ? 'add' : exists && !isAdding ? 'remove' : null; if (method) { - await UserLibrary[method]({ user_id, book_id }); + await UserLibraries[method]({ user_id, book_id }); + const firstWord = method === 'add' ? 'Saved' : 'Removed'; + const preposition = method === 'add' ? 'to' : 'from'; + return { text: `${firstWord} ${book.title} ${preposition} your library!` }; } - return { text: 'Saved!' }; + return { text: `That has already been ${isAdding ? 'saved' : 'removed'}` }; }; diff --git a/routes/messages/conditions/browse.js b/routes/messages/conditions/browse.js index 6a5e3ea..95fc3a5 100644 --- a/routes/messages/conditions/browse.js +++ b/routes/messages/conditions/browse.js @@ -1,6 +1,11 @@ module.exports = async function(Event) { - if (this.hasOpenState(Event)) { + if (Event.isPostback()) { this.setState(Event); } - Event.overrideIfNotOnboarded(); + const isOnboarding = Event.overrideIfNotOnboarded(); + + if (!isOnboarding) { + // Clear state if we don't onboard + this.clearState(Event); + } }; diff --git a/routes/messages/conditions/save_summary_preference.js b/routes/messages/conditions/save_summary_preference.js new file mode 100644 index 0000000..acbaae0 --- /dev/null +++ b/routes/messages/conditions/save_summary_preference.js @@ -0,0 +1,5 @@ +module.exports = async function(Event) { + Event.overrideOnUserMessage(); + + Event.overrideOnMissingProperty('prefersLongSummaries'); +}; diff --git a/routes/messages/conditions/toggle_in_library.js b/routes/messages/conditions/toggle_in_library.js index a5d13ce..22e9a81 100644 --- a/routes/messages/conditions/toggle_in_library.js +++ b/routes/messages/conditions/toggle_in_library.js @@ -1,4 +1,13 @@ -module.exports = async Event => { +module.exports = async function(Event) { Event.overrideOnUserMessage(); - await Event.overrideIfNotOnboarded(); + if (Event.isPostback()) { + // If it's not a postback but it's a valid request, + // it's been redirected. In that case, we don't want to override state + this.setState(Event); + } + const isOnboarding = await Event.overrideIfNotOnboarded(); + if (!isOnboarding) { + // If we didn't onboard the user, clear state + this.clearState(Event); + } }; From d843659e3a632244a1c554f7cf5cda39507f01de Mon Sep 17 00:00:00 2001 From: AJ Brush <19802883+ajb85@users.noreply.github.com> Date: Fri, 1 Nov 2019 21:28:03 -0400 Subject: [PATCH 19/19] Removes useless code --- server.js | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/server.js b/server.js index 2be78d6..d4ceee9 100644 --- a/server.js +++ b/server.js @@ -46,33 +46,3 @@ server.get('*', function(req, res) { server.use(errorHandler); module.exports = server; - -server.put('/api/users/:id', async (req, res) => { - const { id } = req.params; - const body = req.body; - - if (!body.name || !body.bio) { - return res - .status(400) - .json({ errorMessage: 'Please provide name and bio for the user' }); - } - - const user = await db.findById(id); - - if (!user) { - return res - .status(404) - .json({ message: 'The user with the specified ID does not exist.' }); - } - - const updated = await db.update(id, body); - - if (!updated) { - return res - .status(500) - .json({ error: 'The user information could not be modified' }); - } - - const updatedUser = await db.findById(id); - return res.status(200).json(updatedUser); -});