Related
I need to build a tree like structure using data from an API.
The structure i start with is as follows:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [],
}
There will always be a root group as the base of the tree.
I have a function named getMembersInGroup(groupId) which is an API call and returns something like:
[
{
"type": "group",
"id": 77,
"name": "IT group",
},
{
"type": "user",
"id": 40,
"name": "John"
}
]
Members can either be of type user or another group. So a user would look like:
{
"type": "user",
"id": 40,
"name": "John"
}
If it's another group it needs to recursively fetch those until there are only users or empty array left in members.
Any group can have users at any level with the tree.
A mock of getMembersInGroup:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
The end result should look like this:
{
"type": "group",
"id": 1,
"name": "rootGroup",
"members": [
{
"type": "group",
"id": 88,
"name": "Some group",
"members": [
{
"type": "user",
"id": 231,
"name": "SALLY"
},
{
"type": "user",
"id": 232,
"name": "Henry"
}
]
},
{
"type": "user",
"id": 41,
"name": "Chris"
}
],
}
I need help with the algorithm to create the tree.
Your getMembersInGroup function could look like this:
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi(`/groups/${groupId}/members`)) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
Call it like this:
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
loadTree().then(result =>
console.log(result);
// Work with the result ...
).catch(error =>
console.log("error: ", error)
);
Demo with a mock implementation of fetch.callApi:
// Mock for fetch.callApi
const fetch = {
mockData: [0,[2,3,4],[5,6,7],[8,9],0,0,0,[10],0,0,[11,12],0,0],
callApi(url) {
return new Promise((resolve, reject) => {
const groupId = +url.split("/")[2];
const children = this.mockData[groupId];
if (!children) return reject("not found: " + groupId);
const result = children.map(id => {
const type = this.mockData[id] ? "group" : "user";
return {type, id, name: type + "_" + id};
});
setTimeout(() => resolve(result), 50);
});
}
}
async function loadTree() {
return {
type: "group",
id: 1,
name: "rootGroup",
members: await getMembersInGroup(1)
};
}
const getMembersInGroup = async (groupId) => {
const members = (await fetch.callApi('/groups/' + groupId + '/members')) ?? [];
for (const member of members) {
if (member.type == "group") {
member.members = await getMembersInGroup(member.id);
}
}
return members;
}
loadTree().then(result =>
console.log(JSON.stringify(result, null, 2))
).catch(error =>
console.log("error: ", error)
);
You can do something like:
const getMembersInGroup = async (groupId) => {
try {
const members = await fetch.callApi('/groups/' + groupId + '/members');
if (members) {
foreach(member in members) {
if (member.type == 'groups') {
member.members = getMembersInGroup(member.groupid)
}
}
return members;
}
else {
return [];
}
} catch (error) {
return { error };
}
}
So you have the recursion only if it's a group type, otherwise the member is returned as is.
My setup: Apollo server with express.js
MongoDB with Mongoose
My problem: When I run a query, my resolver is not fetching all of the data, just part of it.
Here is my resolver code:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
contentOut.entry = [{ entryVideos: [] }];
content.entry.map(async (val) => {
let file = await Asset.findById(val, 'uri');
if (file && file.uri) {
contentOut.entry[0].entryVideos.push(
file.uri.split('/').slice(-1)[0]
);
}
});
// EQUIPMENT
contentOut.equipment = [];
content.equipment.map(async (val) => {
let equipment = await MarsEquipment.findById(
val.id,
'name thumbnail background'
);
if (equipment) {
contentOut.equipment.push({
id: val.id,
name: equipment.name,
panelImage: equipment.thumbnail.split('/').slice(-1)[0],
productImage: equipment.background.split('/').slice(-1)[0],
});
}
});
// EXERCISES
contentOut.exercises = [];
content.exercises.map(async (val, index) => {
contentOut.exercises.push({
equipment: val.equipment,
content: [],
});
val.content.map(async (valC) => {
let exercise = await MarsExercise.findById(
valC.id,
'name level text thumbnail video'
);
if (exercise) {
let instructions = [];
for (const [key, value] of Object.entries(
JSON.parse(exercise.text)
)) {
instructions.push(value);
}
contentOut.exercises[index].content.push({
id: valC.id,
position: valC.position,
name: exercise.name,
level: exercise.level,
instructions: instructions,
panelImage: exercise.thumbnail.split('/').slice(-1)[0],
programVideo: exercise.video.split('/').slice(-1)[0],
});
}
});
});
// OPTIONS
contentOut.options = [];
let bgImage = await Asset.findById(content.options[0].bgImage, 'uri');
bgImage = bgImage.uri.split('/').slice(-1)[0];
contentOut.options = [
{
bgImage: bgImage,
cards: [],
},
];
content.options[0].cards.map(async (val, index) => {
let cardImg = await Asset.findById(val.panelImage, 'uri');
if (cardImg) {
contentOut.options[0].cards.push({
name: val.name,
panelImage: cardImg.uri.split('/').slice(-1)[0],
subheading: val.subheading,
action: val.action,
});
if (val.overlay) {
contentOut.options[0].cards[index].overlay = val.overlay;
}
if (
val.externalApp &&
val.externalApp.appName &&
val.externalApp.playStoreId
) {
contentOut.options[0].cards[index].externalApp = {
appName: val.externalApp.appName,
playStoreId: val.externalApp.playStoreId,
};
}
}
});
// WORKOUTS
contentOut.workouts = [];
content.workouts.map(async (val) => {
let workout = await MarsWorkout.findById(
val.id,
'name thumbnail video text required'
);
if (workout) {
contentOut.workouts.push({
id: val.id,
position: val.position,
name: workout.name,
panelImage: workout.thumbnail.split('/').slice(-1)[0],
programVideo: workout.video.split('/').slice(-1)[0],
instructions: workout.text,
required: workout.required,
});
}
});
// FILES
contentOut.files = [];
content.files.map(async (val) => {
let file = await Asset.findById(val, 'uri updated_at');
if (file) {
contentOut.files.push({
id: val,
uri: file.uri,
filename: file.uri.split('/').slice(-1)[0],
timestamp: file.updated_at,
});
}
});
return contentOut;
} else {
return {};
}
}
}
Here is the query I'm running in the Playground:
query {
getMarsContentForScreen(screen: "GS123123123123", token: "token-here") {
id
name
entry {
entryVideos
}
equipment {
id
name
position
panelImage
productImage
}
exercises {
equipment
content {
id
position
name
level
panelImage
programVideo
instructions
}
}
options {
bgImage
cards {
name
panelImage
subheading
action
overlay
externalApp {
appName
playStoreId
}
}
}
workouts {
id
position
name
panelImage
programVideo
required
instructions
}
files {
id
filename
uri
timestamp
}
}
}
And here is the output of what I'm getting:
{
"data": {
"getMarsContentForScreen": {
"id": "6203d63f54a0bd82832288c5",
"name": "sdfgsdfg",
"entry": [
{
"entryVideos": [
"6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"9b1628af-e69e-4d0e-9d53-b472a963a1ec.mp4",
"830b0258-70f1-4206-b07b-fb60508e33c5.mp4"
]
}
],
"equipment": [
{
"id": "62025aa4237005069c569d63",
"name": "dsfgsdfg",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025afa237005069c569d99",
"name": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025af4237005069c569d92",
"name": "sdfgsdfgsdfgdsf",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
}
],
"exercises": [
{
"equipment": "dsfgsdfg",
"content": [
{
"id": "62025b27237005069c569dc0",
"position": 1,
"name": "sdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfgsdfg",
"sdfg"
]
},
{
"id": "62025b30237005069c569dc7",
"position": 2,
"name": "sdfgsdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfg",
"hgfjgh"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgdsf",
"content": [
{
"id": "62025b80237005069c569e13",
"position": 1,
"name": "sdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfg",
"sdfgsdfg",
"sdfgdf"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"content": [
{
"id": "62025b88237005069c569e1a",
"position": 1,
"name": "uitytyui",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"ytuityui",
"tyui",
"tyuityuityui"
]
}
]
}
],
"options": [
{
"bgImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"cards": []
}
],
"workouts": [],
"files": []
}
}
}
As you can see, everything from "options" : [{"cards"}] is empty, but it shouldn't be, as there is the data in the database for it.
What is even more interesting, is that when I console.log the contentOut object inside the last .map function (content.files.map()) I'm getting the full response.
Basically it looks like my resolver is returning the content before all of it is gathered.
If I add some if statement to check if all of my content is in the contentOut object, I'm getting empty response, just like the resolver couldn't be bothered to wait for all of the content...
Any ideas?
Many thanks in advance!
Ok, so after more Googling and fighting with it, I've re-write the whole code and use Promise.all for each part of the function in order to make sure that it will wait for the outcome of each await, before returning the value.
Now the code looks like this:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
const getEntryVideos = async (content) => {
let result = [{ entryVideos: [] }];
await Asset.find({ _id: { $in: content } }, 'uri').then((response) =>
response.map((val) => {
result[0].entryVideos.push(val.uri.split('/').slice(-1)[0]);
})
);
return result;
};
const getEquipment = async (content) => {
let result = [];
const ids = content.map((val) => {
return val.id;
});
await MarsEquipment.find(
{ _id: { $in: ids } },
'id name thumbnail background'
).then((response) =>
response.map((val) => {
result.push({
id: val.id,
name: val.name,
panelImage: val.thumbnail.split('/').slice(-1)[0],
productImage: val.background.split('/').slice(-1)[0],
});
})
);
return result;
};
const getExercises = async (content) => {
let result = [];
const ids = [].concat(
...content.map((val) => {
result.push({
equipment: val.equipment,
content: [],
});
return val.content.map((valC) => {
return valC.id;
});
})
);
await MarsExercise.find(
{ _id: { $in: ids } },
'id name level text thumbnail video product'
).then((response) =>
response.map((exer) => {
let instructions = [];
const index = result.indexOf(
result.find((equip) => equip.equipment === exer.product)
);
for (const [key, value] of Object.entries(JSON.parse(exer.text))) {
instructions.push(value);
}
result[index].content.push({
id: exer.id,
position: exer.position,
name: exer.name,
level: exer.level,
instructions: instructions,
panelImage: exer.thumbnail.split('/').slice(-1)[0],
programVideo: exer.video.split('/').slice(-1)[0],
});
})
);
return result;
};
const getOptions = async (content) => {
let result = content;
const ids = content[0].cards.map((val) => {
return val.panelImage;
});
await Asset.findById(content[0].bgImage, 'uri').then((response) => {
result[0].bgImage = response.uri.split('/').slice(-1)[0];
});
await Asset.find({ _id: { $in: ids } }, 'id uri').then((response) =>
response.map((val) => {
let index = result[0].cards.indexOf(
result[0].cards.find((card) => card.panelImage === val.id)
);
result[0].cards[index].panelImage = val.uri.split('/').slice(-1)[0];
})
);
return result;
};
const getWorkouts = async (content) => {
let result = content;
const ids = content.map((val) => {
return val.id;
});
await MarsWorkout.find(
{ _id: { $in: ids } },
'id name thumbnail video text required'
).then((response) => {
response.map((val) => {
let index = result.indexOf(
result.find((work) => work.id === val.id)
);
result[index].panelImage = val.thumbnail.split('/').slice(-1)[0];
result[index].programVideo = val.video.split('/').slice(-1)[0];
});
});
return result;
};
const getFiles = async (content) => {
let result = [];
await Asset.find({ _id: { $in: content } }, 'id uri updated_at').then(
(response) => {
response.map((val) => {
result.push({
id: val.id,
uri: val.uri,
filename: val.uri.split('/').slice(-1)[0],
timestamp: val.updated_at,
});
});
}
);
return result;
};
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
const entry = getEntryVideos(content.entry);
// EQUIPMENT
const equipment = getEquipment(content.equipment);
// EXERCISES
const exercises = getExercises(content.exercises);
// OPTIONS
const options = getOptions(content.options);
// WORKOUTS
const workouts = getWorkouts(content.workouts);
// FILES
const files = getFiles(content.files);
// PROMISE
const results = await Promise.all([
entry,
equipment,
exercises,
options,
workouts,
files,
]);
//console.log(results);
return {
id: content.id,
name: content.name,
entry: results[0],
equipment: results[1],
exercises: results[2],
options: results[3],
workouts: results[4],
files: results[5],
};
} else {
return {};
}
}
},
I want to pass down the variables I create when hitting the /pay route to the /success_api route. Namely I need to pass down 'content', 'productSelectTotal', 'productSubTotal', and 'shipping'. When I trigger /pay the first time, it sets content which is then passed down when I am redirected to '/success', however on all subsequent requests, the value of content, productSelectTotal, productSubTotal, and shipping in the 'success_api' get function remain the same.
Any ideas how to make sure they are being refreshed every time the value of the variables in the parent function is updated?
router.post('/pay', (req, res, next) => {
fs.readFile('ProductDB.json', function (error, data) {
if (error) {
res.status(500).end()
} else {
const productsJson = JSON.parse(data)
console.log(req.body.items)
let content = [];
let productSelect = req.body.items.map(product => {
const itemJson = productsJson.find(function (i) {
return i.productName === product.productName
}).variations.find(function (i) {
return i.id === product.id
})
product.id != null ?
content.push({
// PUSH TO CONTENT HERE
"name": product.productName,
"sku": product.id,
"price": itemJson.variationPrice / 100,
"currency": "USD",
"quantity": product.qty
}) :
false
})
let tariffObj = {
"uk": 3.6,
"us": 11.6,
"rest-of-world": 11.6,
"eu": 8.2
}
let postalCategory = req.body.postalCategory
let postalTarrif = tariffObj[postalCategory]
let shipping = postalTarrif;
let productSelectPrices = content.map(item => item.price * item.quantity);
let productSelectTotal = productSelectPrices.reduce((acc, val) => acc + val, shipping).toFixed(2)
let productSubTotal = productSelectPrices.reduce((acc, val) => acc + val, 0).toFixed(2)
const create_payment_json = {
"intent": "sale",
"payer": {
"payment_method": "paypal"
},
"redirect_urls": {
"return_url": `http://localhost:3000/success`,
"cancel_url": `http://localhost:3000/cancel`
},
"transactions": [{
"item_list": {
"items": content
},
"amount": {
"currency": "USD",
"total": productSelectTotal,
"details": {
"subtotal": productSubTotal,
"shipping": shipping
}
},
"description": "first tea added to the store"
}]
}
paypal.payment.create(create_payment_json, function (error, payment) {
if (error) {
throw error;
} else {
console.log("Create Payment Response");
console.log(payment);
for (let i = 0; i < payment.links.length; i++) {
if (payment.links[i].rel === "approval_url") {
let redirectURL = payment.links[i].href
res.json({
redirectUrl: redirectURL
})
}
}
}
});
/*
After creating the response the user is redirected to paypal, where they enter their payment information after which they are returned to the website where the / success_api call is made
*/
router.route('/success_api').get((req, res) => {
//CONTENT NOT UPDATED WITHIN THIS FUNCTION ON RE - RUNS
console.log('re routed')
const payerId = req.query.PayerID
const paymentId = req.query.paymentId
const execute_payment_json = {
"payer_id": payerId,
"transactions": [{
"item_list": {
"items": content
},
"amount": {
"currency": "USD",
"total": productSelectTotal,
"details": {
"subtotal": productSubTotal,
"shipping": shipping
}
}
}]
}
paypal.payment.execute(paymentId, execute_payment_json, function (error, payment) {
if (error) {
console.log(error.response);
res.json({
message: 'payment error'
})
} else {
console.log(JSON.stringify(payment));
res.json({
message: 'Payment Successful'
})
}
});
})
}
})
})
I keep getting an error when I try to test my basic skill (I'm trying to learn how to create them).
Here is the error from the log:
2018-11-21T16:10:55.759Z 06a36441-eda8-11e8-a421-f996bf66c592
Unexpected exception 'TypeError: Cannot read property 'value' of
undefined':
TypeError: Cannot read property 'value' of undefined at
Object.getSuggestion (/var/task/index.js:31:54) at emitNone
(events.js:86:13) at AlexaRequestEmitter.emit (events.js:185:7) at
AlexaRequestEmitter.EmitEvent
(/var/task/node_modules/alexa-sdk/lib/alexa.js:216:10) at
AlexaRequestEmitter.ValidateRequest
(/var/task/node_modules/alexa-sdk/lib/alexa.js:181:23) at
AlexaRequestEmitter.HandleLambdaEvent
(/var/task/node_modules/alexa-sdk/lib/alexa.js:126:25) at
AlexaRequestEmitter.value
(/var/task/node_modules/alexa-sdk/lib/alexa.js:100:31)
at exports.handler (/var/task/index.js:52:9)
How can I figure this out?
Here is my code:
var Alexa = require('alexa-sdk');
const APP_ID = 'amzn1.ask.skill.ab07421a-0a92-4c2b-b3bd-998e14286xxx';
const skillData = [
{
city: "Austin",
suggestion: "Austin has some of the most outstanding people."
},
{
city: "San Antonio",
suggestion: "San Antonio has some of the most outstanding people."
},
{
city: "Dallas",
suggestion: "The Dallas metroplex is one of the hottest places."
}
];
var number = 0;
while(number<3){
var handlers = {
'LaunchRequest': function () {
this.emit(':ask', 'Tell me the name of the major city you are closest to'
},
'Unhandled': function () {
this.emit(':ask', 'Try saying a city name like Austin, San Antonio, or Dallas');
},
'getSuggestion': function() {
var city = this.event.request.intent.slots.City.value;
this.emit(':ask', getSuggestion(skillData,'city', city.toUpperCase()).suggestion + '. Give me another city and I\'ll hook you up with the best peeps.');
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', "What can I help you with?", "How can I help?");
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', "Okay!");
},
'AMAZON.StopIntent': function () {
this.emit(':tell', "Goodbye!");
},
};
number = number+1;
}
exports.handler = function(event, context, callback){
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
function getSuggestion(arr, propName, cityName) {
for (var i=0; i < arr.length; i++) {
if (arr[i][propName] == cityName) {
return arr[i];
}
}
}
Update
I've made some changes that were suggested below, however, I am still getting an error after the initial response.
"errorMessage": "Cannot read property 'city' of undefined"
Please look at my new code and help me figure this out:
var Alexa = require('alexa-sdk');
const APP_ID = 'amzn1.ask.skill.ab07421a-0a92-4c2b-b3bd-998e14286xxx';
const skillData = [
{
city: 'Austin',
suggestion: "Austin is blahblahblahblahlshflashdfasldfha blah."
},
{
city: 'San Antonio',
suggestion: "San Antonio has blahblahblahblahlshflashdfasldfha blah."
},
{
city: 'Dallas',
suggestion: "The Dallas metroplex is one of the hottest blahblahblahbla blahblahblahblahblahblah."
}
];
var number = 0;
while(number<3){
var handlers = {
'LaunchRequest': function () {
this.emit(':ask', 'Tell me the name of the major city you are closest to!', 'Which major city are you closest to?');
},
'Unhandled': function () {
this.emit(':ask', "Try saying a city name like Austin, San Antonio, or Dallas");
},
'getSuggestion': function() {
var city = this.event.request.intent.slots.city.value;
this.emit(':ask', getSuggestion(skillData,'city', city.toUpperCase()).suggestion + '. Give me another city and I\'ll hook you up with the best peeps.');
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', "What can I help you with?", "How can I help?");
},
'AMAZON.CancelIntent': function () {
this.emit(':tell', "Okay!");
},
'AMAZON.StopIntent': function () {
this.emit(':tell', "Goodbye!");
},
};
number = number+1;
}
exports.handler = function(event, context, callback){
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
function getSuggestion(arr, propName, cityName) {
for (var i=0; i < arr.length; i++) {
var prop = arr[i][propName];
prop = prop.toUpperCase();
if (prop == cityName) {
return arr[i];
}
}
}
UPDATE #2
After a lot of trying different things, I've gotten the skill to run with the help of bal simpson!
However, the skill still errors out when the user utters a city name. There must be an error in my Interaction Model, which is below:
{
"interactionModel": {
"languageModel": {
"invocationName": "city picker",
"intents": [
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": [
"stop"
]
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "getSuggestion",
"slots": [],
"samples": [
"san antonio",
"dallas",
"austin"
]
}
],
"types": []
}
}
}
Getting close!!
As a last-ditch effort:
Here is my index.js housed in Lambda. Would anyone mind looking at this?
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
handle(handlerInput) {
console.log("LaunchRequestHandler");
let speechText = 'Lets get you into your new home. Tell me the name of the major city you are closest to!';
let prompt = 'Which major city are you closest to?';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(prompt)
.getResponse();
}
};
const GetSuggestionIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "getSuggestion"
);
},
handle(handlerInput) {
let intent = handlerInput.requestEnvelope.request.intent;
let city = intent.slot.city.value;
let suggestion = getSuggestion(skillData,'city', city.toUpperCase()).suggestion;
return handlerInput.responseBuilder
.speak(suggestion)
.reprompt('prompt')
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "AMAZON.HelpIntent"
);
},
handle(handlerInput) {
const speechText = "Try saying a city name like Austin, San Antonio, or Dallas";
const promptText = "How can I help?";
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(promptText)
// .withSimpleCard("City Details", speechText)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
(handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.CancelIntent" ||
handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.StopIntent" ||
handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.PauseIntent")
);
},
handle(handlerInput) {
const speechText = `Seeya later!`;
return (
handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(true)
.getResponse()
);
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder.getResponse();
},
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
return handlerInput.responseBuilder
.speak('Sorry, I can\'t understand the command. Try saying a city name like Austin, San Antonio, or Dallas')
.reprompt('Try saying a city name like Austin, San Antonio, or Dallas')
.getResponse();
},
};
const SystemExceptionHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type ===
"System.ExceptionEncountered"
);
},
handle(handlerInput) {
console.log(
`System exception encountered: ${
handlerInput.requestEnvelope.request.reason
}`
);
}
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
GetSuggestionIntentHandler,
CancelAndStopIntentHandler,
HelpIntentHandler,
SystemExceptionHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.lambda();
function getSuggestion(arr, propName, cityName) {
for (var i=0; i < arr.length; i++) {
var prop = arr[i][propName];
prop = prop.toUpperCase();
if (prop == cityName) {
return arr[i];
}
}
}
And here is my Interaction Model from the Developer Portal:
{
"interactionModel": {
"languageModel": {
"invocationName": "city picker",
"intents": [
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": [
"stop"
]
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "getSuggestion",
"slots": [
{
"name": "city",
"type": "CITY_NAMES"
}
],
"samples": [
"{city}"
]
}
],
"types": [
{
"name": "CITY_NAMES",
"values": [
{
"name": {
"value": "dallas"
}
},
{
"name": {
"value": "san antonio"
}
},
{
"name": {
"value": "austin"
}
}
]
}
]
}
}
}
ok. alexa-sdk has been deprecated link. To get the new SDK, do this.
1 - Create a new function in Lambda.
2 - Choose AWS Serverless Application Repository.
3 - Choose alexa-skills-kit-nodejs-factskill.
4 - Click on deploy. Once deployed, click on functions and you should see the new function you just created with a name like aws-serverless-repository-alexaskillskitnodejsfact-NR8HPILH8WNI.
5 - Delete the code and replace the code with this.
const Alexa = require('ask-sdk');
const skillData = [
{
city: 'Austin',
suggestion: "Austin is blahblahblahblahlshflashdfasldfha blah."
},
{
city: 'San Antonio',
suggestion: "San Antonio has blahblahblahblahlshflashdfasldfha blah."
},
{
city: 'Dallas',
suggestion: "The Dallas metroplex is one of the hottest blahblahblahbla blahblahblahblahblahblah."
}
];
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === "LaunchRequest";
},
handle(handlerInput) {
console.log("LaunchRequestHandler");
let speechText = 'Tell me the name of the major city you are closest to!';
let prompt = 'Which major city are you closest to?';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(prompt)
.getResponse();
}
};
const GetSuggestionIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "getSuggestion"
);
},
handle(handlerInput) {
let intent = handlerInput.requestEnvelope.request.intent;
let city = intent.slots.city.value;
let suggestion = getSuggestion(skillData,'city', city.toUpperCase()).suggestion;
return handlerInput.responseBuilder
.speak(suggestion)
.reprompt('prompt')
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
handlerInput.requestEnvelope.request.intent.name === "AMAZON.HelpIntent"
);
},
handle(handlerInput) {
const speechText = "Try saying a city name like Austin, San Antonio, or Dallas";
const promptText = "How can I help?";
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(promptText)
// .withSimpleCard("City Details", speechText)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type === "IntentRequest" &&
(handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.CancelIntent" ||
handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.StopIntent" ||
handlerInput.requestEnvelope.request.intent.name ===
"AMAZON.PauseIntent")
);
},
handle(handlerInput) {
const speechText = `Goodbye`;
return (
handlerInput.responseBuilder
.speak(speechText)
.withShouldEndSession(true)
.getResponse()
);
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder.getResponse();
},
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
return handlerInput.responseBuilder
.speak('Sorry, I can\'t understand the command. Try saying a city name like Austin, San Antonio, or Dallas')
.reprompt('Try saying a city name like Austin, San Antonio, or Dallas')
.getResponse();
},
};
const SystemExceptionHandler = {
canHandle(handlerInput) {
return (
handlerInput.requestEnvelope.request.type ===
"System.ExceptionEncountered"
);
},
handle(handlerInput) {
console.log(
`System exception encountered: ${
handlerInput.requestEnvelope.request.reason
}`
);
}
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
GetSuggestionIntentHandler,
CancelAndStopIntentHandler,
HelpIntentHandler,
SystemExceptionHandler,
SessionEndedRequestHandler
)
.addErrorHandlers(ErrorHandler)
.lambda();
function getSuggestion(arr, propName, cityName) {
for (var i=0; i < arr.length; i++) {
var prop = arr[i][propName];
prop = prop.toUpperCase();
if (prop == cityName) {
return arr[i];
}
}
}
6 - Go to developer.amazon.com and change your Alexa Skill endpoint to the new lambda ARN.
To add a slot type:
Specify slot in your sample phrases like this:
Change your slot name to city:
So instead of musicStations it will be city. Make sure you have entered the three values in your slot values like this:
Add your custom slot values to CITY_NAMES:
If you have done it right, your interaction model should be something like this:
"name": "getSuggestion",
"slots": [
{
"name": "city",
"type": "CITY_NAMES"
}
],
"samples": [
"city name is {city}",
"{city}"
]
Testing Lambda code
In the drop down menu, choose 'configure test events'.
Create new test event with JSON from your developer test portal which should look like this.
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "amzn1.echo-api.session.XXXXXX",
"application": {
"applicationId": "amzn1.ask.skill.XXXXXX"
},
"user": {
"userId": "amzn1.ask.account.XXXXXX"
}
},
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.XXXXXX"
},
"user": {
"userId": "amzn1.ask.account.XXXXXX"
},
"device": {
"deviceId": "amzn1.ask.device.XXXXXX",
"supportedInterfaces": {
"AudioPlayer": {}
}
},
"apiEndpoint": "https://api.eu.amazonalexa.com",
"apiAccessToken": "ACCESS_TOKEN"
},
},
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.XXXX",
"timestamp": "2018-12-03T20:28:29Z",
"locale": "en-IN",
"intent": {
"name": "PlayRadioIntent",
"confirmationStatus": "NONE",
"slots": {
"musicStation": {
"name": "musicStation",
"value": "classic rock",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "amzn1.er-authority.XXXX.RadioStations",
"status": {
"code": "ER_SUCCESS_MATCH"
},
"values": [
{
"value": {
"name": "Classic Rock",
"id": "b8a5bd97a8a02691f9f81dcfb12184dd"
}
}
]
}
]
},
"confirmationStatus": "NONE",
"source": "USER"
}
}
}
}
Click on test button
Check Logs
Does the test result look like this? To see the logs, click on logs. It might have additional error details.
I have an array with countries with the following structure:
{
"code": "ZW",
"name": "Zimbabwe",
"zipPattern": "[\\s\\S]*",
"states": [
{
"name": "Bulawayo"
},
{
"name": "Harare"
},
{
"name": "Manicaland"
},
{
"name": "Mashonaland Central"
},
{
"name": "Mashonaland East"
},
{
"name": "Mashonaland West"
},
{
"name": "Masvingo"
},
{
"name": "Matabeleland North"
},
{
"name": "Matabeleland South"
},
{
"name": "Midlands"
}
]
}
I am trying to insert them into MongoDb using MongoSkin with the following code
var countries = require('./mongo/ready/Countries');
db.collection('countries').find().toArray(function (err, result) {
if (result.length === 0) {
for (var i = 0; i < countries.length; i++) {
var obj = countries[i];
var states = obj.states;
db.collection('countries').insert({
name: obj.name,
code: obj.code,
zipPattern: obj.zipPattern
}, function (error, countryResult) {
var id = countryResult[0]._id;
for (var j = 0; j < states.length; j++) {
var state = states[j];
db.collection('states').insert({
countryId: id,
name: state.name
}, function (stateError, stateResult) {
if (stateError) console.log(stateError);
console.log(stateResult);
});
}
});
}
}
});
but the code inserts the states of the last country in the array (Zimbabwe) for each country in the array instead of the correct states. How can I fix it?
Generally we don't use async query(insert) between sync loop(simple for loop). Its give us abnoramal results. Node provides async loop to overcome this.
First of all require async module for this.
var async = require('async');
Now you can use following code for insertion of countries and their respective states
async.each(countries, function(obj, callback) {
var states = obj.states;
db.collection('countries').insert({
name: obj.name,
code: obj.code,
zipPattern: obj.zipPattern
}, function(error, countryResult) {
if (error) {
callback(error);
} else {
var id = countryResult[0]._id;
async.each(states, function(state, callback) {
db.collection('states').insert({
countryId: id,
name: state.name
}, function(stateError, stateResult) {
if (stateError) {
callback(stateError);
} else {
callback();
}
});
});
callback();
}
}); }, function(err) {
if (err) {
// handle error here
} else {
// do stuff on completion of insertion
} });
Thanks