Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

promisify is part of node 8 update #3

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 105 additions & 71 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
const alexaSDK = require('alexa-sdk');
const awsSDK = require('aws-sdk');
const promisify = require('es6-promisify');

const appId = 'REPLACE WITH YOUR SKILL APPLICATION ID';
const appId = 'amzn1.ask.skill.d4d54bbf-af45-4901-81d9-4c7c85da9c8c';
const recipesTable = 'Recipes';
const docClient = new awsSDK.DynamoDB.DocumentClient();

// convert callback style functions to promises
const dbScan = promisify(docClient.scan, docClient);
const dbGet = promisify(docClient.get, docClient);
const dbPut = promisify(docClient.put, docClient);
const dbDelete = promisify(docClient.delete, docClient);
const docClient = new awsSDK
.DynamoDB
.DocumentClient();

const instructions = `Welcome to Recipe Organizer<break strength="medium" />
The following commands are available: add recipe, get recipe,
Expand All @@ -22,28 +17,25 @@ const handlers = {
/**
* Triggered when the user says "Alexa, open Recipe Organizer.
*/
'LaunchRequest'() {
'LaunchRequest' () {
this.emit(':ask', instructions);
},

/**
* Adds a recipe to the current user's saved recipes.
* Slots: RecipeName, RecipeLocation, LongOrQuick
*/
'AddRecipeIntent'() {
const { userId } = this.event.session.user;
const { slots } = this.event.request.intent;

// prompt for slot values and request a confirmation for each
'AddRecipeIntent' () {
const {userId} = this.event.session.user;
const {slots} = this.event.request.intent;

// RecipeName
// prompt for slot values and request a confirmation for each RecipeName
if (!slots.RecipeName.value) {
const slotToElicit = 'RecipeName';
const speechOutput = 'What is the name of the recipe?';
const repromptSpeech = 'Please tell me the name of the recipe';
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}
else if (slots.RecipeName.confirmationStatus !== 'CONFIRMED') {
} else if (slots.RecipeName.confirmationStatus !== 'CONFIRMED') {

if (slots.RecipeName.confirmationStatus !== 'DENIED') {
// slot status: unconfirmed
Expand All @@ -66,8 +58,7 @@ const handlers = {
const speechOutput = 'Where can the recipe be found?';
const repromptSpeech = 'Please give me a location where the recipe can be found.';
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}
else if (slots.RecipeLocation.confirmationStatus !== 'CONFIRMED') {
} else if (slots.RecipeLocation.confirmationStatus !== 'CONFIRMED') {

if (slots.RecipeLocation.confirmationStatus !== 'DENIED') {
// slot status: unconfirmed
Expand All @@ -90,8 +81,7 @@ const handlers = {
const speechOutput = 'Is this a quick or long recipe to make?';
const repromptSpeech = 'Is this a quick or long recipe to make?';
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}
else if (slots.LongOrQuick.confirmationStatus !== 'CONFIRMED') {
} else if (slots.LongOrQuick.confirmationStatus !== 'CONFIRMED') {

if (slots.LongOrQuick.confirmationStatus !== 'DENIED') {
// slot status: unconfirmed
Expand All @@ -112,7 +102,10 @@ const handlers = {

const name = slots.RecipeName.value;
const location = slots.RecipeLocation.value;
const isQuick = slots.LongOrQuick.value.toLowerCase() === 'quick';
const isQuick = slots
.LongOrQuick
.value
.toLowerCase() === 'quick';
const dynamoParams = {
TableName: recipesTable,
Item: {
Expand All @@ -134,7 +127,9 @@ const handlers = {
console.log('Attempting to add recipe', dynamoParams);

// query DynamoDB to see if the item exists first
dbGet(checkIfRecipeExistsParams)
docClient
.get(checkIfRecipeExistsParams)
.promise()
.then(data => {
console.log('Get item succeeded', data);

Expand All @@ -144,10 +139,15 @@ const handlers = {
const errorMsg = `Recipe ${name} already exists!`;
this.emit(':tell', errorMsg);
throw new Error(errorMsg);
}
else {
} else {
// no match, add the recipe
return dbPut(dynamoParams);
return docClient.put(dynamoParams, function (err, data) {
if (err) {
console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2));
}
});
}
})
.then(data => {
Expand All @@ -164,9 +164,9 @@ const handlers = {
* Lists all saved recipes for the current user. The user can filter by quick or long recipes.
* Slots: GetRecipeQuickOrLong
*/
'GetAllRecipesIntent'() {
const { userId } = this.event.session.user;
const { slots } = this.event.request.intent;
'GetAllRecipesIntent' () {
const {userId} = this.event.session.user;
const {slots} = this.event.request.intent;
let output;

// prompt for slot data if needed
Expand All @@ -177,32 +177,49 @@ const handlers = {
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}

const isQuick = slots.GetRecipeQuickOrLong.value.toLowerCase() === 'quick';
const isLong = slots.GetRecipeQuickOrLong.value.toLowerCase() === 'long';
const isQuick = slots
.GetRecipeQuickOrLong
.value
.toLowerCase() === 'quick';
const isLong = slots
.GetRecipeQuickOrLong
.value
.toLowerCase() === 'long';
const dynamoParams = {
TableName: recipesTable
};

if (isQuick || isLong) {
dynamoParams.FilterExpression = 'UserId = :user_id AND IsQuick = :is_quick';
dynamoParams.ExpressionAttributeValues = { ':user_id': userId, ':is_quick': isQuick };
output = `The following ${isQuick ? 'quick' : 'long'} recipes were found: <break strength="x-strong" />`;
}
else {
dynamoParams.ExpressionAttributeValues = {
':user_id': userId,
':is_quick': isQuick
};
output = `The following ${isQuick
? 'quick'
: 'long'} recipes were found: <break strength="x-strong" />`;
} else {
dynamoParams.FilterExpression = 'UserId = :user_id';
dynamoParams.ExpressionAttributeValues = { ':user_id': userId };
dynamoParams.ExpressionAttributeValues = {
':user_id': userId
};
output = 'The following recipes were found: <break strength="x-strong" />';
}

// query DynamoDB
dbScan(dynamoParams)
docClient
.scan(dynamoParams)
.promise()
.then(data => {
console.log('Read table succeeded!', data);

if (data.Items && data.Items.length) {
data.Items.forEach(item => { output += `${item.Name}<break strength="x-strong" />`; });
}
else {
data
.Items
.forEach(item => {
output += `${item.Name}<break strength="x-strong" />`;
});
} else {
output = 'No recipes found!';
}

Expand All @@ -219,8 +236,8 @@ const handlers = {
* Reads the full info of the selected recipe.
* Slots: RecipeName
*/
'GetRecipeIntent'() {
const { slots } = this.event.request.intent;
'GetRecipeIntent' () {
const {slots} = this.event.request.intent;

// prompt for slot data if needed
if (!slots.RecipeName.value) {
Expand All @@ -230,7 +247,7 @@ const handlers = {
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}

const { userId } = this.event.session.user;
const {userId} = this.event.session.user;
const recipeName = slots.RecipeName.value;
const dynamoParams = {
TableName: recipesTable,
Expand All @@ -243,17 +260,20 @@ const handlers = {
console.log('Attempting to read data');

// query DynamoDB
dbGet(dynamoParams)
docClient
.get(dynamoParams)
.promise()
.then(data => {
console.log('Get item succeeded', data);

const recipe = data.Item;

if (recipe) {
this.emit(':tell', `Recipe ${recipeName} is located in ${recipe.Location} and it
is a ${recipe.IsQuick ? 'Quick' : 'Long'} recipe to make.`);
}
else {
is a ${recipe.IsQuick
? 'Quick'
: 'Long'} recipe to make.`);
} else {
this.emit(':tell', `Recipe ${recipeName} not found!`);
}
})
Expand All @@ -264,8 +284,8 @@ const handlers = {
* Gets a random saved recipe for this user. The user can filter by quick or long recipes.
* Slots: GetRecipeQuickOrLong
*/
'GetRandomRecipeIntent'() {
const { slots } = this.event.request.intent;
'GetRandomRecipeIntent' () {
const {slots} = this.event.request.intent;

// prompt for slot data if needed
if (!slots.GetRecipeQuickOrLong.value) {
Expand All @@ -275,14 +295,19 @@ const handlers = {
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}

const quickOrLongSlotValue = slots.GetRecipeQuickOrLong.value.toLowerCase();
const quickOrLongSlotValue = slots
.GetRecipeQuickOrLong
.value
.toLowerCase();
const isQuick = quickOrLongSlotValue === 'quick';
const isLong = quickOrLongSlotValue === 'long';
const { userId } = this.event.session.user;
const {userId} = this.event.session.user;
const dynamoParams = {
TableName: recipesTable,
FilterExpression: 'UserId = :user_id',
ExpressionAttributeValues: { ':user_id': userId }
ExpressionAttributeValues: {
':user_id': userId
}
};

if (isQuick || isLong) {
Expand All @@ -293,20 +318,23 @@ const handlers = {
console.log('Attempting to read data');

// query DynamoDB
dbScan(dynamoParams)
docClient
.scan(dynamoParams)
.promise()
.then(data => {
console.log('Read table succeeded!', data);

const recipes = data.Items;

if (!recipes.length) {
this.emit(':tell', 'No recipes added.');
}
else {
} else {
const randomNumber = Math.floor(Math.random() * recipes.length);
const recipe = recipes[randomNumber];

this.emit(':tell', `The lucky recipe is ${recipe.Name} <break time="500ms"/> and it is located in ${recipe.Location} and it is a ${recipe.IsQuick ? 'quick' : 'long'} recipe to make.`);
this.emit(':tell', `The lucky recipe is ${recipe.Name} <break time="500ms"/> and it is located in ${recipe.Location} and it is a ${recipe.IsQuick
? 'quick'
: 'long'} recipe to make.`);
}
})
.catch(err => console.error(err));
Expand All @@ -315,17 +343,16 @@ const handlers = {
/**
* Allow the user to delete one of their recipes.
*/
'DeleteRecipeIntent'() {
const { slots } = this.event.request.intent;
'DeleteRecipeIntent' () {
const {slots} = this.event.request.intent;

// prompt for the recipe name if needed and then require a confirmation
if (!slots.RecipeName.value) {
const slotToElicit = 'RecipeName';
const speechOutput = 'What is the name of the recipe you would like to delete?';
const repromptSpeech = 'Please tell me the name of the recipe';
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}
else if (slots.RecipeName.confirmationStatus !== 'CONFIRMED') {
} else if (slots.RecipeName.confirmationStatus !== 'CONFIRMED') {

if (slots.RecipeName.confirmationStatus !== 'DENIED') {
// slot status: unconfirmed
Expand All @@ -342,7 +369,7 @@ const handlers = {
return this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
}

const { userId } = this.event.session.user;
const {userId} = this.event.session.user;
const recipeName = slots.RecipeName.value;
const dynamoParams = {
TableName: recipesTable,
Expand All @@ -355,16 +382,23 @@ const handlers = {
console.log('Attempting to read data');

// query DynamoDB to see if the item exists first
dbGet(dynamoParams)
docClient
.get(dynamoParams)
.promise()
.then(data => {
console.log('Get item succeeded', data);

const recipe = data.Item;

if (recipe) {
console.log('Attempting to delete data', data);

return dbDelete(dynamoParams);
return docClient.delete(dynamoParams, function (err, data) {
if (err) {
console.error("Unable to delete item. Error JSON:", JSON.stringify(err, null, 2));
} else {
console.log("DeleteItem succeeded:", JSON.stringify(data, null, 2));
}
});
}

const errorMsg = `Recipe ${recipeName} not found!`;
Expand All @@ -379,29 +413,29 @@ const handlers = {
.catch(err => console.log(err));
},

'Unhandled'() {
'Unhandled' () {
console.error('problem', this.event);
this.emit(':ask', 'An unhandled problem occurred!');
},

'AMAZON.HelpIntent'() {
'AMAZON.HelpIntent' () {
const speechOutput = instructions;
const reprompt = instructions;
this.emit(':ask', speechOutput, reprompt);
},

'AMAZON.CancelIntent'() {
'AMAZON.CancelIntent' () {
this.emit(':tell', 'Goodbye!');
},

'AMAZON.StopIntent'() {
'AMAZON.StopIntent' () {
this.emit(':tell', 'Goodbye!');
}
};

exports.handler = function handler(event, context) {
const alexa = alexaSDK.handler(event, context);
alexa.APP_ID = appId;
alexa.appId = appId;
alexa.registerHandlers(handlers);
alexa.execute();
};
};