From b80843cb97bb2effb7508eab68522800be38173f Mon Sep 17 00:00:00 2001 From: GotPPay Date: Fri, 12 Jan 2018 01:56:17 +0100 Subject: [PATCH] ... --- README.md | 102 +++++++- backend/SaburlySkillInteractionModel.json | 60 ----- backend/components/alexa.js | 205 ++++++++++++++++ backend/{ => config}/config.js | 0 backend/config/constants.js | 25 ++ backend/express.js | 268 --------------------- backend/helpers/amazon.js | 8 +- backend/helpers/database.js | 25 +- backend/server.js | 122 ++++++++++ web/README.md | 82 ------- web/src/App.js | 9 +- web/src/components/Contact.js | 2 +- web/src/components/IntentDetails.js | 2 +- web/src/components/IntentItem.js | 2 +- web/src/components/IntentList.js | 2 +- web/src/components/LaunchRequest.js | 2 +- web/src/config/config.js | 3 + web/src/{config.js => config/constants.js} | 7 +- web/src/lib/api.js | 2 +- web/src/logo.svg | 7 - 20 files changed, 482 insertions(+), 453 deletions(-) delete mode 100644 backend/SaburlySkillInteractionModel.json create mode 100644 backend/components/alexa.js rename backend/{ => config}/config.js (100%) create mode 100644 backend/config/constants.js delete mode 100644 backend/express.js create mode 100644 backend/server.js delete mode 100644 web/README.md create mode 100644 web/src/config/config.js rename web/src/{config.js => config/constants.js} (89%) delete mode 100644 web/src/logo.svg diff --git a/README.md b/README.md index ec7ab2c..caec0bf 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,98 @@ -first terminal -cd web -npm install -npm start +To obtain new Auth Code : -second terminal -cd backend -npm install -node express.js +https://www.amazon.com/ap/oa?client_id=amzn1.application-oa2-client.c748ca56ded04a95b236979898585ff7&scope=alexa::ask:skills:readwrite alexa::ask:models:readwrite alexa::ask:skills:test&response_type=code&redirect_uri=https://layla.amazon.com/api/skill/link/M2ODJY6EXOY6KO +Response URL (Decoded) : + +https://layla.amazon.com/api/skill/link/M2ODJY6EXOY6KO?code=ANCgZUfEFdlRRkpSNFuA&scope=alexa::ask:skills:readwrite alexa::ask:models:readwrite alexa::ask:skills:test + +Code : ANCgZUfEFdlRRkpSNFuA +======================================= + +Now to get Access Token : + +Send a POST request to https://api.amazon.com/auth/o2/token with the following parameters + + - HTTP Header Parameters + + Content-Type: application/x-www-form-urlencoded + + - HTTP Body Parameters + + grant_type: authorization_code + code: The authorization code that was returned in the response. + client_id: The product’s client ID. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. + client_secret: The product’s client secret. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. + redirect_uri: One of the return URIs that you added to your app’s security profile when signing up. + +Response : + +{ + "access_token": "Atza|IwEBIBe6gDqrrowEEav6N-_6s4NztYeP3oG8PGWmu8ZiZw6lbOh3wNla3TK6pY-VEpT1d8an-dVf_n3kXJzVFsNo_4xBfZyFHGoCTDTFjs3yBRul4PVdBOhwwiH3-sgRLcUofZbe2oE06GmTcbfYtaStfXpQI5dfpldfnsJg_CvhSA6AHb_snJT3F6lyXzbV076d_3cYUMJxFldJGnYcviNHHxjjmuQTD06hhGzCbAxxe9eBmkuopRsNfyedLT2UlKP_ublah9CUGA3AdIX_3Iuke82jMwGnNl9gv7pbaDNEjAbj7IQSl3B08uuREtJq-oTBOjALNXRvFxTJmQjZwXNf9eHC7fSHJDdEPdZQU0AcffRQObAyAkUuL6Jv39OHzhb3Q64-zzoyODqnJyLP5SQZ2JVF53Kc_cTBqjIc9pXljqe7yEVk6JDs7q1zKbBibx_AQm57TO79IzWyLBzBMlYL5HdTsqEfRzLeDw2tws-hGMgkx2HWfdbYnmf5Qb4SyIhzvmmdfPLg3MVKTxjIBu1rx0xf3n0PLZP1EO6jsJPoMRPg77Gm4oit5Zp6s37ek3A3Vxh-ntoASpkrkxGTG9kVtRNt", + "refresh_token": "Atzr|IwEBICA3kDhfSJxlwvnQp9AD1o115AC_KBbFd5GBg8oN_QHWn2or5xFQ09BruuK6a07tGHtTt_9q2Y21mnOMH4RDtYXTVG9ADgLE6mHWKZFxYVwt3kHMiUJdY5lJcsOtWLoblrS-bJ0BEXXK5nVDt_bSI5IB7NUf-9QVZxhovRH_ANSxdTjJT0_rMIAZY3WEj68FEap49q_pg72BhnxHVZD2TC3zvX96_DN65HE5SoSgT7OiMAeiJewB_SyemW_HxBwaB-_X-G1ifOtnrzZ4gXTpOrEUlHI2YPuvRMBMtmz1h-nXDZYv3vwU3RA57Qj_ZNVcScj8_RXf2xq8w48v0PzZFXYBSalfnqPq6eUvSSbAJUp6bB8y596JlvR5dFQe_Z--X0Gkfo85IcyrI9D44vK9sJhrGhCVi2FDDa8pHczmNSen99JYZvDif-zpYzgbcymAkOV0gC7JvYMxlZfETT3NTBy7eVA7fJI1SZaeA_qW49xRcBkZBu5gkqTpeGWUU1cGr2aXRVVmXGM22NfV5E7KzvEBsCeHml_tCfxZeKY8Msd8hJb0Cd59u-_hsuc8oNjsOpIdFF976dY3uTmAgHWpG2PH", + "token_type": "bearer", + "expires_in": 3600 +} + +================================ + +Now to get new Access token using refresh token : + +Send a POST request to https://api.amazon.com/auth/o2/token with the following parameters: + + - HTTP Header Parameters + + Content-Type: application/x-www-form-urlencoded + + - HTTP Body Parameters + + grant_type: refresh_token + refresh_token: The URL-encoded refresh token returned with the last request for a new access token. + client_id: To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. + client_secret: The website’s client secret. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. + +Response : + +{ + "access_token": "Atza|IwEBILtBe3hrHovrMx7Oivng-RB2EKzvCm_epXJE2HXPMQzXTFqK10Zrqt-Z8paeRoLQBqbLCmqWvcr5RTNgw9qjtfzOTsOrXC1VKqKmxpqHTrJyn2TLGsCzFjBDfADNjCyufWTf2ZlsSzjxW2GiqCHlwoPSd9pFrLavtRThrm1J-5KvnFrj-yD-tYTSwrgX5W5p2SrjQxoE3aP5b96z6p8GvCL9lM1pddafAxkHb22A3IzR-pYGmEijb4ksRuaIf4WCNwssWV6GBIB2oJA5CU-Dtd2mOZZ5-dYpSSeCHyGumTYecTxxMVSdiVjCqB8WT6AtvvutWFQQoldHjJmIwBsTZP-iQcl-UyajOZJ03GqRUym5Hp-49uByzVG-MfR_Z5qVmYjjsLQEOLCY9kPVnmRGnOTj6YPjrHXibd6P8TQOMh4VTcgFpg-afKKABP6EeDwok1t2ivuYh5OJju-B1A6gzhMi4vQJYKq107e0QMYBBhrf_OqCgMbfnQZ8j40qocVGID5YWv8uk5wKyI61LrbzrTltmzxzNemzqbSBzwAlfNS6GW-jVjg8svsi1lb_EVRbhyOoWJWX3mEd-5GDYyUcyInleiAR0aIHVP94pZxqdiCamA", + "refresh_token": "Atzr|IwEBICA3kDhfSJxlwvnQp9AD1o115AC_KBbFd5GBg8oN_QHWn2or5xFQ09BruuK6a07tGHtTt_9q2Y21mnOMH4RDtYXTVG9ADgLE6mHWKZFxYVwt3kHMiUJdY5lJcsOtWLoblrS-bJ0BEXXK5nVDt_bSI5IB7NUf-9QVZxhovRH_ANSxdTjJT0_rMIAZY3WEj68FEap49q_pg72BhnxHVZD2TC3zvX96_DN65HE5SoSgT7OiMAeiJewB_SyemW_HxBwaB-_X-G1ifOtnrzZ4gXTpOrEUlHI2YPuvRMBMtmz1h-nXDZYv3vwU3RA57Qj_ZNVcScj8_RXf2xq8w48v0PzZFXYBSalfnqPq6eUvSSbAJUp6bB8y596JlvR5dFQe_Z--X0Gkfo85IcyrI9D44vK9sJhrGhCVi2FDDa8pHczmNSen99JYZvDif-zpYzgbcymAkOV0gC7JvYMxlZfETT3NTBy7eVA7fJI1SZaeA_qW49xRcBkZBu5gkqTpeGWUU1cGr2aXRVVmXGM22NfV5E7KzvEBsCeHml_tCfxZeKY8Msd8hJb0Cd59u-_hsuc8oNjsOpIdFF976dY3uTmAgHWpG2PH", + "token_type": "bearer", + "expires_in": 3600 +} + +======================= +======================= + +Prerequests for step 3 (run on server): requires running mongodb service + Database (tellall) with collection (skill_list) + * Insert dummy skill with : db.skill_list.insert({"skillID" : "amzn1.ask.skill.efbf0564-a732-4ba9-958f-57939138adae", "intents" : [ { "intentName" : "GetFirstQuestion", "questions" : [ "tell me something about projects", "tell me all about projects" ], "answer" : "blablabla bla bla" }, { "intentName" : "GetThirdQuestion", "questions" : [ "Give me third question" ], "answer" : "This is answer to the third question" } ], "invocationName" : "Saburly", "invocationAnswer" : "We are Saburly team one" }) + + *obtain _id and change in web/src/App.js, and also skill_db_id in backend/config.js + *enter web/ dir and run "npm run build" + + Database (tellall) with collection (token_list) + * Insert tokens with : db.token_list.insert({"id" : 1, "refresh_token" : "...", "access_token" : "...", "expires_in" : 1515173601.754 }) + (Change refresh_token and access_token dots with real ones) + + Set skill_id, client_id and client_secret to appropriate values in backend/config.js + Set base_url to "tellall.saburly.com" in web/src/config. + + Start backend service from backend/ running "node express.js" + + +====== +for local testing : + +first terminal : + *cd web + *npm start + +second terminal : + * cd backend + *npm install + *node express.js + + + diff --git a/backend/SaburlySkillInteractionModel.json b/backend/SaburlySkillInteractionModel.json deleted file mode 100644 index ff9fdd7..0000000 --- a/backend/SaburlySkillInteractionModel.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "languageModel": { - "intents": [ - { - "name": "AMAZON.CancelIntent", - "samples": [] - }, - { - "name": "AMAZON.HelpIntent", - "samples": [] - }, - { - "name": "AMAZON.StopIntent", - "samples": [] - }, - { - "name": "GetProcessIntent", - "samples": [ - "tell me your process", - "what is your process", - "how do you work", - "about your proces" - ], - "slots": [] - }, - { - "name": "GetProjectsIntent", - "samples": [ - "list me your projects", - "show me what you have done", - "what did you do so far", - "what are your projects", - "your project" - ], - "slots": [] - }, - { - "name": "GetServicesIntent", - "samples": [ - "what are your services", - "what services do you ofer", - "what you do", - "your service", - "tell me service" - ], - "slots": [] - }, - { - "name": "GetTechnologiesIntent", - "samples": [ - "list me your technologies", - "give me your technologies", - "what you know" - ], - "slots": [] - } - ], - "invocationName": "saburly" - } -} \ No newline at end of file diff --git a/backend/components/alexa.js b/backend/components/alexa.js new file mode 100644 index 0000000..93bbcc2 --- /dev/null +++ b/backend/components/alexa.js @@ -0,0 +1,205 @@ +var alexa = require ('alexa-app'); +const config = require ('../config/config'); +var databaseHelper = require ('../helpers/database'); + +//User data for sending message, this is skill-related and will be in some skill container +var Name = null; +var Email = null; +var Message = null; +var State = 0; // states should be defined in seperate file. (Not sending message, Waiting for name, Waiting for email, Waiting for message) +//For now (this is not long term solution) +// 0 : Not sending Message +// 1 : Waiting for name +// 2 : Waiting for email +// 3 : Waiting for message + +var alexaApp = new alexa.app ('saburly'); // this means we still work with one skill + +module.exports = { + init: function (express) { + + alexaApp.express ({ + expressApp: express, + + // verifies requests come from amazon alexa. Must be enabled for production. + // You can disable this if you're running a dev environment and want to POST + // things to test behavior. enabled by default. + checkCert: false, + + // sets up a GET route when set to true. This is handy for testing in + // development, but not recommended for production. disabled by default + debug: true, + }); + }, + + updateIntentsJSON: function () { + databaseHelper + .loadSkill (config.SKILL_DB_ID) + .then (skill => { + skill.intents.map (intent => { + alexaApp.intent ( + intent.intentName, + { + slots: [], + utterances: intent.questions, + }, + function (request, response) { + return response.say (intent.answer).shouldEndSession (false); + } + ); + }); + + alexaApp.launch ((request, response) => { + return response.say (skill.invocationAnswer).shouldEndSession (false); + }); + + alexaApp.intent ( + 'EmailIntentLaunch', + { + slots: [], + utterances: [ + 'I want to send a message', + 'I would like to send a message', + 'I would like to leave a message', + 'Leave a message', + ], + }, + function (request, response) { + Name = null; + Email = null; + Message = null; + State = 1; + + return response + .say ('Ok. What is your name') + .shouldEndSession (false); + } + ); + + //TODO : Watch out for this intent. It will make trouble with other regular intents + //if other intents have utterance with just one slot like {Data} + //It should be taken care somwhere before this, to check if Email Intent is invoked + //This is problem only if we introduce slot options for regular intents for users + alexaApp.intent ( + 'EmailIntent', + { + slots: { + Name: 'AMAZON.US_FIRST_NAME', + Email: 'AMAZON.LITERAL', + Message: 'AMAZON.LITERAL', + Data: 'AMAZON.LITERAL', + }, + utterances: [ + 'My name is {-|Name}', + 'I am {-|Name}', + '{dawdw at dwd wdw|Data}', + 'My email is {wadwwdw at wadwwd wdw|Email}', + 'Send replay to {fkofkeofe at dppfam wd|Email}', + 'My message is {Quick brown fox jumps over lazy dog|Message}', + ], + }, + function (request, response) { + let Data = undefined; + try { + if (!Name) Name = request.slot ('Name'); + } catch (e) { + console.log ('Error. No name slot '); + Name = undefined; + } + try { + if (!Email) Email = request.slot ('Email'); + } catch (e) { + console.log ('Error. No Email slot'); + Email = undefined; + } + try { + if (!Message) Message = request.slot ('Message'); + } catch (e) { + console.log ('Error. No Message slot'); + Message = undefined; + } + try { + Data = request.slot ('Data'); + } catch (e) { + console.log ('Error. No Data slot'); + Data = undefined; + } + console.log ('State : ' + State); + + //TODO : Responses could be configurable for each skill ? + + if (State === 1) { + //Was waiting for name, so if Name is null, name is probably in Data + if ((!Name && Data) || Name) { + //got the name, let's continue for the email + if (!Name) Name = Data; + State = 2; + return response + .say ('Ok ' + Name + '. What is your email ?') + .shouldEndSession (false); + } else { + //Something is wrong, ask for name again + return response + .say ( + 'Sorry, I didnt understand your name. Can you say it again ?' + ) + .shouldEndSession (false); + } + } else if (State === 2) { + //was waiting for email, so if Email is null, email is probably in Data + if ((!Email && Data) || Email) { + //Got the email, first verify email and than continue to message + if (!Email) Email = Data; + //TODO : verify email + State = 3; + return response + .say ('Great. Whats the message ?') + .shouldEndSession (false); + } else { + //Something is wrong, ask for the email again + return response + .say ( + 'Sorry, I didnt understan you email. Can you say it again ?' + ) + .shouldEndSession (false); + } + } else if (State === 3) { + //Was waiting for message, so if Message is null, message is probably in Data + if ((!Message && Data) || Message) { + //Ok, we got all informations. Exit email intent + if (!Message) Message = Data; + State = 0; + //TODO : Send email + console.log ( + 'Name : ' + + Name + + ' | Email : ' + + Email + + ' | Message : ' + + Message + ); + return response.say ( + 'Message sent. Someone will contact you ASAP' + ); + } else { + //Something is wrong, ask for the message again + return response + .say ( + 'Sorry, I didnt understand your message. Can you say it again ?' + ) + .shouldEndSession (false); + } + } else { + console.log ('State strange ! ' + State); + } + } + ); + }) + .catch (err => { + console.log (err); + alexaApp.launch ((request, response) => { + return response.say ('Sorry, there was no skill with that name'); + }); + }); + }, +}; diff --git a/backend/config.js b/backend/config/config.js similarity index 100% rename from backend/config.js rename to backend/config/config.js diff --git a/backend/config/constants.js b/backend/config/constants.js new file mode 100644 index 0000000..5895516 --- /dev/null +++ b/backend/config/constants.js @@ -0,0 +1,25 @@ +const constants = {}; + +constants.amazonResultCodes = { + ok:200, + accepted:202, + badRequest:400, + unauthorized:401, + notFound:404, + conflict:409, + payloadTooLarge:413 +} + +constants.apiResultCodes = { + genericError : -1, + ok:0, + amazonError:1, + databaseError:2, + +} + +constants.skillIDLength = 24; + + + +module.exports = constants; \ No newline at end of file diff --git a/backend/express.js b/backend/express.js deleted file mode 100644 index 91154fc..0000000 --- a/backend/express.js +++ /dev/null @@ -1,268 +0,0 @@ -var amazonHelper = require ('./helpers/amazon'); -var databaseHelper = require ('./helpers/database'); -const config = require ('./config'); -require ('isomorphic-fetch'); -var express = require ('express'); -var alexa = require ('alexa-app'); -var bodyParser = require ('body-parser'); -var MongoClient = require ('mongodb').MongoClient; -var ObjectID = require ('mongodb').ObjectID; - -const router = express.Router (); - -var app = express (); - -//User data for sending message, this is skill-related and will be in some skill container -var Name = null; -var Email = null; -var Message = null; -var State = 0; // states should be defined in seperate file. (Not sending message, Waiting for name, Waiting for email, Waiting for message) -//For now : -// 0 : Not sending Message -// 1 : Waiting for name -// 2 : Waiting for email -// 3 : Waiting for message - -// ALWAYS setup the alexa app and attach it to express before anything else. -var alexaApp = new alexa.app ('saburly'); // this means we still work with one skill - -alexaApp.express ({ - expressApp: app, - - // verifies requests come from amazon alexa. Must be enabled for production. - // You can disable this if you're running a dev environment and want to POST - // things to test behavior. enabled by default. - checkCert: false, - - // sets up a GET route when set to true. This is handy for testing in - // development, but not recommended for production. disabled by default - debug: true, -}); - -// now POST calls to /test in express will be handled by the app.request() function - -// from here on you can setup any other express routes or middlewares as nor - -app.set ('view engine', 'ejs'); - -var updateIntentsJSON = function () { - databaseHelper - .loadSkill (config.SKILL_DB_ID) - .then (skill => { - skill.intents.map (intent => { - - alexaApp.intent ( - intent.intentName, - { - slots: [], - utterances: intent.questions, - }, - function (request, response) { - return response.say (intent.answer).shouldEndSession(false); - } - ); - }); - - alexaApp.launch ((request, response) => { - return response.say (skill.invocationAnswer).shouldEndSession(false); - }); - - alexaApp.intent('EmailIntentLaunch',{ - slots:[], - utterances: [ - 'I want to send a message', - 'I would like to send a message', - 'I would like to leave a message', - 'Leave a message' - ] - }, - function(request,response){ - Name = null; - Email = null; - Message = null; - State = 1; - - return response.say('Ok. What is your name').shouldEndSession(false); - } - ); - - //TODO : Watch out for this intent. It will make trouble with other regular intents - //if other intents have utterance with just one slot like {Data} - //It should be taken care somwhere before this, to check if Email Intent is invoked - //This is problem only if we introduce slot options for regular intents for users - alexaApp.intent('EmailIntent',{ - slots:{ - 'Name':'AMAZON.US_FIRST_NAME', - 'Email' : 'AMAZON.LITERAL', - 'Message': 'AMAZON.LITERAL', - 'Data': 'AMAZON.LITERAL' - }, - utterances: [ - 'My name is {-|Name}', - 'I am {-|Name}', - '{dawdw at dwd wdw|Data}', - 'My email is {wadwwdw at wadwwd wdw|Email}', - 'Send replay to {fkofkeofe at dppfam wd|Email}', - 'My message is {Quick brown fox jumps over lazy dog|Message}' - ] - }, - function(request,response){ - let Data = undefined; - try{if (!Name) Name = request.slot('Name');}catch(e){console.log('Error. No name slot ');Name=undefined;} - try{if (!Email) Email = request.slot('Email');}catch(e){console.log('Error. No Email slot');Email=undefined;} - try{if (!Message) Message = request.slot('Message');}catch(e){console.log('Error. No Message slot');Message=undefined;} - try{Data = request.slot('Data');}catch(e){console.log('Error. No Data slot');Data=undefined;} - console.log('State : ' + State); - - //TODO : Responses could be configurable for each skill ? - - if (State === 1){ - //Was waiting for name, so if Name is null, name is probably in Data - if ((!Name && Data) || Name){ - //got the name, let's continue for the email - if (!Name) Name = Data; - State = 2; - return response.say('Ok ' + Name + '. What is your email ?').shouldEndSession(false); - }else{ - //Something is wrong, ask for name again - return response.say('Sorry, I didnt understand your name. Can you say it again ?').shouldEndSession(false); - } - }else if (State === 2){ - //was waiting for email, so if Email is null, email is probably in Data - if ((!Email && Data) || Email){ - //Got the email, first verify email and than continue to message - if (!Email) Email = Data; - //TODO : verify email - State = 3; - return response.say('Great. Whats the message ?').shouldEndSession(false); - }else{ - //Something is wrong, ask for the email again - return response.say('Sorry, I didnt understan you email. Can you say it again ?').shouldEndSession(false); - } - }else if (State === 3){ - //Was waiting for message, so if Message is null, message is probably in Data - if ((!Message && Data) || Message){ - //Ok, we got all informations. Exit email intent - if (!Message) Message=Data; - State = 0; - //TODO : Send email - console.log('Name : ' + Name + ' | Email : ' + Email + ' | Message : ' + Message); - return response.say('Message sent. Someone will contact you ASAP'); - }else{ - //Something is wrong, ask for the message again - return response.say('Sorry, I didnt understand your message. Can you say it again ?').shouldEndSession(false); - } - }else{ - console.log('State strange ! ' + State); - } - - } - ); - - }) - .catch (err => { - console.log (err); - alexaApp.launch ((request, response) => { - return response.say ('Sorry, there was no skill with that name'); - }); - }); -}; - -router.get ('/getSkill/:id', async (req, res, next) => { - const id = req.params.id; - - if (id.length !== 24) { - res.json ([]); - } else { - databaseHelper - .getSkill (id) - .then (result => { - res.json (result); - }) - .catch (err => { - res.json ([]); - }); - } -}); - -router.get ('/deleteSkill/:skillID', async (req, res, next) => { - databaseHelper - .deleteSkill (req.params.id) - .then (result => { - res.json (result); - }) - .catch (err => { - res.json (err); - }); -}); - -router.post ('/updateSkill/:id', async (req, res, next) => { - let id = req.params.id; - let dataFromWeb = JSON.stringify(req.body); - let skill = JSON.parse(dataFromWeb); - let updateOnAmazon = skill.updateOnAmazon; - delete skill.updateOnAmazon; - delete skill._id; - console.log('id = ' + id); - if (id !== '-1') { - if (updateOnAmazon){ - amazonHelper.updateSkill(skill).then(amazonResult=>{ - console.log('Amazon : ' + amazonResult); - if (amazonResult === 200 || amazonResult === 202) { - //Skill uploaded, it's ok to update databaseI - databaseHelper - .updateSkill (id, skill) - .then (result => { - res.json ({result: 0, message: 'ok'}); - updateIntentsJSON (); - }) - .catch (e => { - res.json ({result: -1, message: 'error'}); - }); - }else{ - res.json({result: -1, message: 'amazon error : ' + amazonResult}); - } - }).catch(e=>{ - res.json ({result: -1, message: e}); - }); - }else{ - databaseHelper - .updateSkill (id, skill) - .then (result => { - res.json ({result: 0, message: 'ok'}); - updateIntentsJSON (); - }) - .catch (e => { - res.json ({result: -1, message: 'error'}); - }); - } - - } else { - //no new skills for now - } -}); - -app.use (function (req, res, next) { - res.header ('Access-Control-Allow-Origin', '*'); - res.header ('Access-Control-Allow-Headers', 'Origin, Content-Type'); - res.header ('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); - res.header ('Access-Control-Allow-Credentials', 'true'); - next (); -}); - -app.use (bodyParser.json ()); -app.use ('/', router); - -MongoClient.connect (config.dbURL) - .then (database => { - databaseHelper.initModule (database); - - app.listen (config.PORT, () => { - console.log ('Express server running on port ' + config.PORT); - updateIntentsJSON (); - databaseHelper.loadTokens (); - }); - }) - .catch (e => { - console.log ('error : ' + e); - }); diff --git a/backend/helpers/amazon.js b/backend/helpers/amazon.js index d182c1c..ab31000 100644 --- a/backend/helpers/amazon.js +++ b/backend/helpers/amazon.js @@ -1,11 +1,10 @@ require ('isomorphic-fetch'); -const config = require ('../config'); +const config = require ('../config/config'); var request = require ('request'); var databaseHelper = require ('./database'); var getBuildStatus = function (skillID) { - try { - fetch ( + fetch ( `https://api.amazonalexa.com/v0/skills/${skillID}/interactionModel/locales/en-US/status`, { method: 'GET', @@ -18,9 +17,6 @@ var getBuildStatus = function (skillID) { .then (result => { return result; }); - } catch (e) { - console.log ('err : ' + e); - } }; var refreshTokens = function () { diff --git a/backend/helpers/database.js b/backend/helpers/database.js index 0b504a8..bd662a2 100644 --- a/backend/helpers/database.js +++ b/backend/helpers/database.js @@ -1,4 +1,4 @@ -const config = require ('../config'); +const config = require ('../config/config'); var ObjectID = require ('mongodb').ObjectID; var db = null; @@ -71,11 +71,10 @@ module.exports = { db .collection ('skill_list') .update ({_id: ObjectID (id)}, skill, {upsert: true}, (err, result) => { - if (JSON.parse (result).nModified === 1) { - //database update ok - resolve (); - } else { - reject (); + if (err){ + reject(); + }else{ + resolve(); } }); }); @@ -86,8 +85,11 @@ module.exports = { db .collection ('skill_list') .remove ({_id: ObjectID (id)}, (err, result) => { - if (err) reject (err); - resolve (result); + if (err){ + reject (err); + }else{ + resolve (result); + } }); }); }, @@ -98,8 +100,11 @@ module.exports = { .collection ('skill_list') .find ({_id: ObjectID (id)}) .toArray ((err, result) => { - if (err) reject (err); - resolve (result); + if (err) { + reject (err); + }else{ + resolve (result); + } }); }); }, diff --git a/backend/server.js b/backend/server.js new file mode 100644 index 0000000..03df6f8 --- /dev/null +++ b/backend/server.js @@ -0,0 +1,122 @@ +var amazonHelper = require ('./helpers/amazon'); +var databaseHelper = require ('./helpers/database'); +const config = require ('./config/config'); +const constants = require ('./config/constants'); +require ('isomorphic-fetch'); +var express = require ('express'); +var alexa = require('./components/alexa'); +var bodyParser = require ('body-parser'); +var MongoClient = require ('mongodb').MongoClient; +var ObjectID = require ('mongodb').ObjectID; + +const router = express.Router (); +var app = express (); + +// ALWAYS setup the alexa app and attach it to express before anything else. +alexa.init(app); + +// from here on you can setup any other express routes or middlewares as nor + +app.set ('view engine', 'ejs'); + +/* +router.get ('/deleteSkill/:skillID', async (req, res, next) => { + databaseHelper + .deleteSkill (req.params.id) + .then (result => { + res.json (result); + }) + .catch (err => { + res.json (err); + }); +}); +*/ + +router.get ('/getSkill/:id', async (req, res, next) => { + const id = req.params.id; + + if (id.length !== constants.skillIDLength) { + res.json ([]); + } else { + databaseHelper + .getSkill (id) + .then (result => { + res.json (result); + }) + .catch (err => { + res.json ([]); + }); + } +}); + +router.post ('/updateSkill/:id', async (req, res, next) => { + let id = req.params.id; + let dataFromWeb = JSON.stringify(req.body); + let skill = JSON.parse(dataFromWeb); + let updateOnAmazon = skill.updateOnAmazon; + delete skill.updateOnAmazon; + delete skill._id; + console.log('id = ' + id); + if (id !== '-1') { + if (updateOnAmazon){ + amazonHelper.updateSkill(skill).then(amazonResult=>{ + console.log('Amazon : ' + amazonResult); + if (amazonResult === constants.amazonResultCodes.ok || amazonResult === constants.amazonResultCodes.accepted) { + //Skill uploaded, it's ok to update databaseI + databaseHelper + .updateSkill (id, skill) + .then (result => { + res.json ({result: constants.apiResultCodes.ok, message: ''}); + alexa.updateIntentsJSON (); + }) + .catch (e => { + res.json ({result: constants.apiResultCodes.databaseError, message: ''}); + }); + }else{ + res.json({result: constants.apiResultCodes.amazonError, message: amazonResult}); + } + }).catch(e=>{ + res.json ({result: constants.apiResultCodes.amazonError, message: 'unknown'}); + }); + }else{ + databaseHelper + .updateSkill (id, skill) + .then (result => { + res.json ({result: constants.apiResultCodes.ok, message: ''}); + alexa.updateIntentsJSON (); + }) + .catch (e => { + res.json ({result: constants.apiResultCodes.databaseError, message: ''}); + }); + } + + } else { + //no new skills for now + } +}); + +app.use (function (req, res, next) { + res.header ('Access-Control-Allow-Origin', '*'); + res.header ('Access-Control-Allow-Headers', 'Origin, Content-Type'); + res.header ('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.header ('Access-Control-Allow-Credentials', 'true'); + next (); +}); + +app.use (bodyParser.json ()); +app.use ('/', router); + +MongoClient.connect (config.dbURL) + .then (database => { + databaseHelper.initModule (database); + + app.listen (config.PORT, () => { + console.log ('Express server running on port ' + config.PORT); + alexa.updateIntentsJSON (); + databaseHelper.loadTokens (); + amazonHelper.getStatus(config.SKILL_ID); + }); + }) + .catch (e => { + console.log ('error : ' + e); + }); diff --git a/web/README.md b/web/README.md deleted file mode 100644 index c13f2f0..0000000 --- a/web/README.md +++ /dev/null @@ -1,82 +0,0 @@ -To obtain new Auth Code : - -https://www.amazon.com/ap/oa?client_id=amzn1.application-oa2-client.c748ca56ded04a95b236979898585ff7&scope=alexa::ask:skills:readwrite alexa::ask:models:readwrite alexa::ask:skills:test&response_type=code&redirect_uri=https://layla.amazon.com/api/skill/link/M2ODJY6EXOY6KO - -Response URL (Decoded) : - -https://layla.amazon.com/api/skill/link/M2ODJY6EXOY6KO?code=ANCgZUfEFdlRRkpSNFuA&scope=alexa::ask:skills:readwrite alexa::ask:models:readwrite alexa::ask:skills:test - -Code : ANCgZUfEFdlRRkpSNFuA - -======================================= - -Now to get Access Token : - -Send a POST request to https://api.amazon.com/auth/o2/token with the following parameters - - - HTTP Header Parameters - - Content-Type: application/x-www-form-urlencoded - - - HTTP Body Parameters - - grant_type: authorization_code - code: The authorization code that was returned in the response. - client_id: The product’s client ID. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. - client_secret: The product’s client secret. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. - redirect_uri: One of the return URIs that you added to your app’s security profile when signing up. - -Response : - -{ - "access_token": "Atza|IwEBIBe6gDqrrowEEav6N-_6s4NztYeP3oG8PGWmu8ZiZw6lbOh3wNla3TK6pY-VEpT1d8an-dVf_n3kXJzVFsNo_4xBfZyFHGoCTDTFjs3yBRul4PVdBOhwwiH3-sgRLcUofZbe2oE06GmTcbfYtaStfXpQI5dfpldfnsJg_CvhSA6AHb_snJT3F6lyXzbV076d_3cYUMJxFldJGnYcviNHHxjjmuQTD06hhGzCbAxxe9eBmkuopRsNfyedLT2UlKP_ublah9CUGA3AdIX_3Iuke82jMwGnNl9gv7pbaDNEjAbj7IQSl3B08uuREtJq-oTBOjALNXRvFxTJmQjZwXNf9eHC7fSHJDdEPdZQU0AcffRQObAyAkUuL6Jv39OHzhb3Q64-zzoyODqnJyLP5SQZ2JVF53Kc_cTBqjIc9pXljqe7yEVk6JDs7q1zKbBibx_AQm57TO79IzWyLBzBMlYL5HdTsqEfRzLeDw2tws-hGMgkx2HWfdbYnmf5Qb4SyIhzvmmdfPLg3MVKTxjIBu1rx0xf3n0PLZP1EO6jsJPoMRPg77Gm4oit5Zp6s37ek3A3Vxh-ntoASpkrkxGTG9kVtRNt", - "refresh_token": "Atzr|IwEBICA3kDhfSJxlwvnQp9AD1o115AC_KBbFd5GBg8oN_QHWn2or5xFQ09BruuK6a07tGHtTt_9q2Y21mnOMH4RDtYXTVG9ADgLE6mHWKZFxYVwt3kHMiUJdY5lJcsOtWLoblrS-bJ0BEXXK5nVDt_bSI5IB7NUf-9QVZxhovRH_ANSxdTjJT0_rMIAZY3WEj68FEap49q_pg72BhnxHVZD2TC3zvX96_DN65HE5SoSgT7OiMAeiJewB_SyemW_HxBwaB-_X-G1ifOtnrzZ4gXTpOrEUlHI2YPuvRMBMtmz1h-nXDZYv3vwU3RA57Qj_ZNVcScj8_RXf2xq8w48v0PzZFXYBSalfnqPq6eUvSSbAJUp6bB8y596JlvR5dFQe_Z--X0Gkfo85IcyrI9D44vK9sJhrGhCVi2FDDa8pHczmNSen99JYZvDif-zpYzgbcymAkOV0gC7JvYMxlZfETT3NTBy7eVA7fJI1SZaeA_qW49xRcBkZBu5gkqTpeGWUU1cGr2aXRVVmXGM22NfV5E7KzvEBsCeHml_tCfxZeKY8Msd8hJb0Cd59u-_hsuc8oNjsOpIdFF976dY3uTmAgHWpG2PH", - "token_type": "bearer", - "expires_in": 3600 -} - -================================ - -Now to get new Access token using refresh token : - -Send a POST request to https://api.amazon.com/auth/o2/token with the following parameters: - - - HTTP Header Parameters - - Content-Type: application/x-www-form-urlencoded - - - HTTP Body Parameters - - grant_type: refresh_token - refresh_token: The URL-encoded refresh token returned with the last request for a new access token. - client_id: To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. - client_secret: The website’s client secret. To access this information, navigate to Amazon’s Developer Console. After you’ve logged in, click Get Started > under Alexa Voice Service, then click Edit next to a registered product (or create a new one). From the left navigation select Security Profile. This page contains both Client ID and Client Secret for your product. - -Response : - -{ - "access_token": "Atza|IwEBILtBe3hrHovrMx7Oivng-RB2EKzvCm_epXJE2HXPMQzXTFqK10Zrqt-Z8paeRoLQBqbLCmqWvcr5RTNgw9qjtfzOTsOrXC1VKqKmxpqHTrJyn2TLGsCzFjBDfADNjCyufWTf2ZlsSzjxW2GiqCHlwoPSd9pFrLavtRThrm1J-5KvnFrj-yD-tYTSwrgX5W5p2SrjQxoE3aP5b96z6p8GvCL9lM1pddafAxkHb22A3IzR-pYGmEijb4ksRuaIf4WCNwssWV6GBIB2oJA5CU-Dtd2mOZZ5-dYpSSeCHyGumTYecTxxMVSdiVjCqB8WT6AtvvutWFQQoldHjJmIwBsTZP-iQcl-UyajOZJ03GqRUym5Hp-49uByzVG-MfR_Z5qVmYjjsLQEOLCY9kPVnmRGnOTj6YPjrHXibd6P8TQOMh4VTcgFpg-afKKABP6EeDwok1t2ivuYh5OJju-B1A6gzhMi4vQJYKq107e0QMYBBhrf_OqCgMbfnQZ8j40qocVGID5YWv8uk5wKyI61LrbzrTltmzxzNemzqbSBzwAlfNS6GW-jVjg8svsi1lb_EVRbhyOoWJWX3mEd-5GDYyUcyInleiAR0aIHVP94pZxqdiCamA", - "refresh_token": "Atzr|IwEBICA3kDhfSJxlwvnQp9AD1o115AC_KBbFd5GBg8oN_QHWn2or5xFQ09BruuK6a07tGHtTt_9q2Y21mnOMH4RDtYXTVG9ADgLE6mHWKZFxYVwt3kHMiUJdY5lJcsOtWLoblrS-bJ0BEXXK5nVDt_bSI5IB7NUf-9QVZxhovRH_ANSxdTjJT0_rMIAZY3WEj68FEap49q_pg72BhnxHVZD2TC3zvX96_DN65HE5SoSgT7OiMAeiJewB_SyemW_HxBwaB-_X-G1ifOtnrzZ4gXTpOrEUlHI2YPuvRMBMtmz1h-nXDZYv3vwU3RA57Qj_ZNVcScj8_RXf2xq8w48v0PzZFXYBSalfnqPq6eUvSSbAJUp6bB8y596JlvR5dFQe_Z--X0Gkfo85IcyrI9D44vK9sJhrGhCVi2FDDa8pHczmNSen99JYZvDif-zpYzgbcymAkOV0gC7JvYMxlZfETT3NTBy7eVA7fJI1SZaeA_qW49xRcBkZBu5gkqTpeGWUU1cGr2aXRVVmXGM22NfV5E7KzvEBsCeHml_tCfxZeKY8Msd8hJb0Cd59u-_hsuc8oNjsOpIdFF976dY3uTmAgHWpG2PH", - "token_type": "bearer", - "expires_in": 3600 -} - -======================= -======================= - -Prerequests for step 3 : - - Database (tellall) with collection (skill_list) - Insert dummy skill with : db.skill_list.insert({"skillID" : "amzn1.ask.skill.efbf0564-a732-4ba9-958f-57939138adae", "intents" : [ { "intentName" : "GetFirstQuestion", "questions" : [ "tell me something about projects", "tell me all about projects" ], "answer" : "blablabla bla bla" }, { "intentName" : "GetThirdQuestion", "questions" : [ "Give me third question" ], "answer" : "This is answer to the third question" } ], "invocationName" : "Saburly", "invocationAnswer" : "We are Saburly team one" }) - obtain _id and change in web/src/App.js, and also skill_db_id in backend/config.js - enter web/ dir and run "npm run build" - - Database (tellall) with collection (token_list) - Insert tokens with : db.token_list.insert({"id" : 1, "refresh_token" : "...", "access_token" : "...", "expires_in" : 1515173601.754 }) - (Change refresh_token and access_token dots with real ones) - - Set skill_id, client_id and client_secret to appropriate values in backend/config.js - Set base_url to "tellall.saburly.com" in web/src/config. - - Start backend service from backend/ running "node express.js" - diff --git a/web/src/App.js b/web/src/App.js index 63aa044..2d405e2 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -10,7 +10,8 @@ import {getSkill, updateSkill} from './lib/api' import { NEW_INTENT_SELECTED_INDEX, LAUNCH_REQUEST_SELECTED_INDEX, - CONTACT_SELECTED_INDEX} from './config' + CONTACT_SELECTED_INDEX, + RESULT_CODES} from './config/constants' class App extends Component { @@ -180,8 +181,8 @@ class App extends Component { return new Promise((resolve,reject)=>{ updateSkill(this.createSkill(newAllIntents,newName,newAnswer,email,updateOnAmazon)).then(l=>l.text()).then(result=>{ let jResult = JSON.parse(result); - if (jResult.result !== 0){ - console.log(jResult.result); + if (jResult.result !== RESULT_CODES.OK){ + console.log(jResult.result); if (showPopUp) Popup.alert('Model was not saved. Please try again'); this.setState(rejectState); //reject('Error code : ' + jResult.result); @@ -191,7 +192,7 @@ class App extends Component { resolve(); } }).catch(e=>{ - console.log('error : ' + e); + console.log('error : ' + e); if (showPopUp) Popup.alert('Model was not saved. Please try again'); this.setState(rejectState); //reject(e); diff --git a/web/src/components/Contact.js b/web/src/components/Contact.js index 31e6b60..8a0efa8 100644 --- a/web/src/components/Contact.js +++ b/web/src/components/Contact.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import {Button, TextField} from 'react-md'; import '../css/Intent.css' -import {EMAIL_MAX_LENGTH} from '../config'; +import {EMAIL_MAX_LENGTH} from '../config/constants'; class Contact extends Component { constructor(props){ diff --git a/web/src/components/IntentDetails.js b/web/src/components/IntentDetails.js index 3919e99..de1c4a0 100644 --- a/web/src/components/IntentDetails.js +++ b/web/src/components/IntentDetails.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import {Button, SVGIcon, TextField} from 'react-md'; import '../css/Intent.css' -import {QUESTION_MAX_LENGTH, ANSWER_MAX_LENGTH, INTENT_NAME_MAX_LENGTH} from '../config'; +import {QUESTION_MAX_LENGTH, ANSWER_MAX_LENGTH, INTENT_NAME_MAX_LENGTH} from '../config/constants'; class IntentDetails extends Component { constructor(props){ diff --git a/web/src/components/IntentItem.js b/web/src/components/IntentItem.js index 0aa29a5..124d7fd 100644 --- a/web/src/components/IntentItem.js +++ b/web/src/components/IntentItem.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import {Button} from 'react-md'; import '../css/Intent.css' -import {INTENT_TITLE_MAX_LENGTH, INTENT_TITLE_TOOLTIP_DELAY} from '../config' +import {INTENT_TITLE_MAX_LENGTH, INTENT_TITLE_TOOLTIP_DELAY} from '../config/constants' class IntentItem extends Component { diff --git a/web/src/components/IntentList.js b/web/src/components/IntentList.js index e77032a..6722fc0 100644 --- a/web/src/components/IntentList.js +++ b/web/src/components/IntentList.js @@ -4,7 +4,7 @@ import IntentItem from './IntentItem'; import '../css/Intent.css' import { LAUNCH_REQUEST_SELECTED_INDEX, - CONTACT_SELECTED_INDEX} from '../config' + CONTACT_SELECTED_INDEX} from '../config/constants' class IntentList extends Component { constructor (props){ diff --git a/web/src/components/LaunchRequest.js b/web/src/components/LaunchRequest.js index c713aa3..c6c1b33 100644 --- a/web/src/components/LaunchRequest.js +++ b/web/src/components/LaunchRequest.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import {Button, TextField} from 'react-md'; import '../css/Intent.css' -import { INVOCATION_NAME_MAX_LENGTH, INVOCATION_ANSWER_MAX_LENGTH } from '../config'; +import { INVOCATION_NAME_MAX_LENGTH, INVOCATION_ANSWER_MAX_LENGTH } from '../config/constants'; class LaunchRequest extends Component { constructor(props){ diff --git a/web/src/config/config.js b/web/src/config/config.js new file mode 100644 index 0000000..ab2244e --- /dev/null +++ b/web/src/config/config.js @@ -0,0 +1,3 @@ +export const BASE_URL = 'tellall.saburly.com'; + + diff --git a/web/src/config.js b/web/src/config/constants.js similarity index 89% rename from web/src/config.js rename to web/src/config/constants.js index b7d4e2e..dc122ff 100644 --- a/web/src/config.js +++ b/web/src/config/constants.js @@ -1,5 +1,3 @@ -export const BASE_URL = 'tellall.saburly.com'; - export const INTENT_NAME_MAX_LENGTH = 30; export const QUESTION_MAX_LENGTH = 150; export const ANSWER_MAX_LENGTH = 150; @@ -15,3 +13,8 @@ export const EMAIL_MAX_LENGTH = 100; export const NEW_INTENT_SELECTED_INDEX = -1; export const LAUNCH_REQUEST_SELECTED_INDEX = -2; export const CONTACT_SELECTED_INDEX = -3; + +export const RESULT_CODES = { + OK:0, + ERROR:-1 +} \ No newline at end of file diff --git a/web/src/lib/api.js b/web/src/lib/api.js index 229cbf3..d55c12c 100644 --- a/web/src/lib/api.js +++ b/web/src/lib/api.js @@ -1,5 +1,5 @@ import fetch from 'isomorphic-fetch'; -import {BASE_URL} from '../config'; +import {BASE_URL} from '../config/config'; export const getAllIntents = (id)=>{ let url = `http://${BASE_URL}/intents/${id}` diff --git a/web/src/logo.svg b/web/src/logo.svg deleted file mode 100644 index 6b60c10..0000000 --- a/web/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - -