Google Actions Smart Home on Lambda not working - actions-on-google

I've been trying to get my Google Actions Smart Home (nodejs) working in AWS lambda. However it isn't working. Whenever I connect it on the Google Home app, I just get a message of "Couldn't update the setting...". I've already configured the API gateway correctly and set the Handler to "index.smarthome" as shown in the below image link. Why isn't it working, and how can I get my lambda google action smart home working?
Image Link
My firebase version is working though (modified from the washing machine example at https://codelabs.developers.google.com/codelabs/smarthome-washer/#2).
const functions = require('firebase-functions');
const {smarthome} = require('actions-on-google');
const app = smarthome();
app.onSync(body => {
return {
requestId: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
payload: {
agentUserId: '123',
devices: [{
id: 'washer',
type: 'action.devices.types.WASHER',
traits: [
'action.devices.traits.OnOff',
'action.devices.traits.StartStop',
'action.devices.traits.RunCycle',
'action.devices.traits.Modes',
'action.devices.traits.Toggles',
],
name: {
defaultNames: ['My Washer'],
name: 'Washer',
nicknames: ['Washer']
},
deviceInfo: {
manufacturer: 'Acme Co',
model: 'acme-washer',
hwVersion: '1.0',
swVersion: '1.0.1'
},
attributes: {
pausable: true,
availableModes: [{
name: 'load',
name_values: [{
name_synonym: ['load'],
lang: 'en'
}],
settings: [{
setting_name: 'small',
setting_values: [{
setting_synonym: ['small'],
lang: 'en'
}]
}, {
setting_name: 'large',
setting_values: [{
setting_synonym: ['large'],
lang: 'en'
}]
}],
ordered: true
}],
availableToggles: [{
name: 'Turbo',
name_values: [{
name_synonym: ['turbo'],
lang: 'en'
}]
}]
}
}]
}
};
});
app.onExecute((body) => {
const {requestId} = body;
const payload = {
commands: [{
ids: [],
status: 'SUCCESS',
states: {
online: true,
},
}],
};
for (const input of body.inputs) {
for (const command of input.payload.commands) {
for (const device of command.devices) {
const deviceId = device.id;
payload.commands[0].ids.push(deviceId);
for (const execution of command.execution) {
const execCommand = execution.command;
const {params} = execution;
switch (execCommand) {
case 'action.devices.commands.OnOff':
payload.commands[0].states.on = params.on;
break;
case 'action.devices.commands.StartStop':
payload.commands[0].states.isRunning = params.start;
break;
case 'action.devices.commands.PauseUnpause':
payload.commands[0].states.isPaused = params.pause;
break;
case 'action.devices.commands.SetModes':
break;
case 'action.devices.commands.SetToggles':
break;
}
}
}
}
}
return {
requestId: requestId,
payload: payload,
};
});
exports.smarthome = functions.https.onRequest(app);
And here is the code that I used in my AWS lambda function. I referenced https://github.com/actions-on-google/actions-on-google-nodejs & creating dialogflow v2 project with serverless to make it lambda compatible. The main difference between the lambda and firebase versions is the "exports.smarthome" code.
const {smarthome} = require('actions-on-google');
const app = smarthome();
app.onSync(body => {
return {
requestId: 'ff36a3cc-ec34-11e6-b1a0-64510650abcf',
payload: {
agentUserId: '123',
devices: [{
id: 'washer',
type: 'action.devices.types.WASHER',
traits: [
'action.devices.traits.OnOff',
'action.devices.traits.StartStop',
'action.devices.traits.RunCycle',
'action.devices.traits.Modes',
'action.devices.traits.Toggles',
],
name: {
defaultNames: ['My Washer'],
name: 'Washer',
nicknames: ['Washer']
},
deviceInfo: {
manufacturer: 'Acme Co',
model: 'acme-washer',
hwVersion: '1.0',
swVersion: '1.0.1'
},
attributes: {
pausable: true,
availableModes: [{
name: 'load',
name_values: [{
name_synonym: ['load'],
lang: 'en'
}],
settings: [{
setting_name: 'small',
setting_values: [{
setting_synonym: ['small'],
lang: 'en'
}]
}, {
setting_name: 'large',
setting_values: [{
setting_synonym: ['large'],
lang: 'en'
}]
}],
ordered: true
}],
availableToggles: [{
name: 'Turbo',
name_values: [{
name_synonym: ['turbo'],
lang: 'en'
}]
}]
}
}]
}
};
});
app.onExecute((body) => {
const {requestId} = body;
const payload = {
commands: [{
ids: [],
status: 'SUCCESS',
states: {
online: true,
},
}],
};
for (const input of body.inputs) {
for (const command of input.payload.commands) {
for (const device of command.devices) {
const deviceId = device.id;
payload.commands[0].ids.push(deviceId);
for (const execution of command.execution) {
const execCommand = execution.command;
const {params} = execution;
switch (execCommand) {
case 'action.devices.commands.OnOff':
payload.commands[0].states.on = params.on;
break;
case 'action.devices.commands.StartStop':
payload.commands[0].states.isRunning = params.start;
break;
case 'action.devices.commands.PauseUnpause':
payload.commands[0].states.isPaused = params.pause;
break;
case 'action.devices.commands.SetModes':
break;
case 'action.devices.commands.SetToggles':
break;
}
}
}
}
}
return {
requestId: requestId,
payload: payload,
};
});
exports.smarthome = function(event, context, callback) {
app.handler(event, {})
.then((res) => {
if (res.status != 200) {
callback(null, {
"fulfillmentText": `I got status code: ${res.status}`
});
} else {
callback(null, res.body);
}
}).catch((e) => {
callback(null, {
"fulfillmentText": `There was an error\n${e}`
});
});
};

Check your AWS CloudWatch logs and see what happens when the lambda is called. You can print to stdout in your lambda and have it show up in these logs.

Along with your Cloudwatch logs, you could also have a look at your Stackdriver logs.

Related

PrismaClientValidationError: Missing required argument in connectOrCreate

Problem:When I try and send/store data in my database I get this error. Specifically, I am trying to create/save a classroom with student names.
Tech Used:
Prisma/Postgres connected to AWS RDS and Next.js, deployed on Vercel, etc.
Error Message
PrismaClientValidationError: Argument data.classrooms.upsert.0.create.students.connectOrCreate.0.create.school.connect of type schoolWhereUniqueInput needs at least one argument.
Argument data.classrooms.upsert.0.update.students.upsert.0.create.school.connect of type schoolWhereUniqueInput needs at least one argument.
at Document.validate (/var/task/node_modules/#prisma/client/runtime/index.js:29501:20)
at serializationFn (/var/task/node_modules/#prisma/client/runtime/index.js:33060:19)
at runInChildSpan (/var/task/node_modules/#prisma/client/runtime/index.js:22550:12)
at PrismaClient._executeRequest (/var/task/node_modules/#prisma/client/runtime/index.js:33067:31)
at async PrismaClient._request (/var/task/node_modules/#prisma/client/runtime/index.js:32994:16)
at async profile (/var/task/.next/server/pages/api/user/profile.js:175:27)
at async Object.apiResolver (/var/task/node_modules/next/dist/server/api-utils/node.js:366:9)
at async NextNodeServer.runApi (/var/task/node_modules/next/dist/server/next-server.js:481:9)
at async Object.fn (/var/task/node_modules/next/dist/server/next-server.js:735:37)
at async Router.execute (/var/task/node_modules/next/dist/server/router.js:247:36) {
clientVersion: '4.9.0'
}
DB Models with relationships: school (1 to many w/students); students (many to many with classrooms); teachers (one to many with students, many to many with classrooms)
Code/Prisma Query
export default async (req, res) => {
...
classroom.students.forEach((student) => {
const totalStudentPoints = student.rewardsRecieved.reduce(
(totalPoints, reward) => {
return totalPoints + reward.pointValue;
},
0
);
groups[student.group.name] += totalStudentPoints;
});
return { ...classroom, groupsTotalPoints: groups };
});
user.classrooms = newClassrooms;
res.json(user);
} else {
console.log("Could Not Find User");
res.status(401).json({
error: "Not authorized",
});
}
}
if (req.method === "PUT") {
const connectStudents = (shouldUpsert) => {
const students = req.body.students;
return students.map((student) => {
const UNSAFEHASH = md5(student.id);
const studentQuery: any = {
where: {
id: student.id,
},
create: {
id: student.id,
firstName: student.firstName,
lastName: student.lastName,
profilePicture: student.profilePicture,
userKey: UNSAFEHASH,
school: {
connect: {
id: req.body.schoolId,
},
},
group: {
connect: {
id: student.group.id,
},
},
},
};
if (shouldUpsert) {
studentQuery.update = {
firstName: student.firstName,
lastName: student.lastName,
profilePicture: student.profilePicture,
userKey: UNSAFEHASH,
group: {
connect: {
id: student.group.id,
},
},
};
}
return studentQuery;
});
};
try {
const user = await prisma.staff.update({
where: {
id: session.id,
},
data: {
firstName: req.body.firstName,
lastName: req.body.lastName,
classrooms: {
upsert: [
{
where: {
id: req.body.classId || "-1",
},
create: {
// id: req.body.classId,
name: req.body.className,
subject: req.body.classSubject,
students: {
connectOrCreate: connectStudents(false),
},
},
update: {
name: req.body.className,
subject: req.body.classSubject,
students: {
upsert: connectStudents(true),
},
},
},
],
},
},
});
Take a look at the PUT request and the prima.staff.update method more specifically. I was looking at the UPSERT I have there, but I can't figure out what's wrong.

Mongoose: update one document out of a bunch

I have the following code which nearly works. It will check if a status for a domain is already set, if not, it will push a new status. If there already is one, it will update it, unless there's multiple statuses for different domains stored, in which case it will replace all of the other documents for the other domains and not just the one entry for the specific domain.
if (Boolean(checkStat) === true) {
await userModel.findByIdAndUpdate(
{
_id: ctx.userId,
domain: args.domain,
},
{
["preferences.domain.status"]: {
domain: args.domain,
status: args.status,
},
},
);
return {
message:
"sucessfully updated status of " +
args.domain +
" to " +
args.status,
};
} else {
await userModel.findByIdAndUpdate(
ctx.userId,
{
$push: {
["preferences.domain.status"]: {
domain: args.domain,
status: args.status,
},
},
},
{
upsert: true,
new: true,
}
);
return {
message:
"sucessfully set status of " + args.domain + " to " + args.status,
};
}
preferences:
domain:
labels:
status:
0:
domain: "x.wtf"
status: "online"
_id: "Jz-ttsjEXKBSVxAN91CzD"
1:
domain: "a.lol"
status: "online"
_id: "Jz-ttsjEXKBSVxAN91CzD"
const userSchema = new mongoose.Schema({
preferences: {
domain: {
status: [
{
_id: {
type: String,
default: () => nanoid(),
},
domain: String,
status: String,
},
],
labels: [
{
_id: {
type: String,
default: () => nanoid(),
},
domain: String,
label: String,
},
],
},
},
});
You can use $ operator and findOneAndUpdate instead of findByIdAndUpdate to do that:
await userModel.findOneAndUpdate(
{
_id: ctx.userId,
"preferences.domain.status.domain": args.domain,
},
{
"preferences.domain.status.$.status": args.status,
}
);

How to update any amount of fields in a nested documen in Mongoose?

I need to update different fields of a nested array in Mongoose. Sometimes I will send runId and runStatus, some other times siteFetched and some other times siteInfo.
I have tried with the following code but the $set operator replaces the old fields.
The model:
campaignId: { type: String },
keywords: [{
keyword: { type: String },
serp: {
runId: { type: String },
runStatus: { type: String },
siteFetched: { type: Boolean },
sitesInfo: [{
title: { type: String },
url: { type: String },
description: { type: String },
}],
},
},
],
Here is the code to update
const campaign = await Campaign.findOneAndUpdate(
{ _id: campaignId, "keywords.keyword": keyword },
{
$set: { "keywords.$.apifySerp": {...serp }},
}
);
the value for serp varies like
const serp = {
runId: '1kLgbnvpADsDJyP1x',
runStatus: 'READY'
}
and
const serp = {
siteFetched: true
}
Here is the code that solved my problem.
const serp = {
siteFetched: true,
};
let update = Object.keys(serp).reduce((acc, cur) => {
acc[`keywords.$.apifySerp.${cur}`] = serp[cur];
return acc;
}, {});

Mongoose.populate() producing no change in the model

Listing Schema:
const mongoose = require('mongoose');
const listingSchema = new mongoose.Schema({
title: String,
name: String,
tel: String,
service: String,
description: String,
location: Object,
isAvailible: Boolean,
canTravel: Boolean,
distance: Number,
isPublic: { type: Boolean, default: true},
pro: { type: mongoose.Types.ObjectId, ref: 'User' }
}, { timestamps: true });
const Listing = mongoose.model('Listing', listingSchema);
module.exports = Listing;
Request to DB:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
await listings[0].populate('pro');
console.log(listings[0].pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});
Screenshot of console
I'm also curious what is the best way to populate an array of models, however, I can't even get it to populate one. Any thoughts?
can you please tye execPopulate() method
try below code
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listing=await listings[0].populate('pro').execPopulate();
console.log(listing.pro);
res.render('search', {
title: 'Search',
listings: listing,
search: {
service: service,
zip: zip
}
});
});
assignment to constant variable before the populate may work like so:
Listing.find({ 'title': { '$regex' : service, '$options' : 'i' } , isPublic: { $gte: true }}, async (err, listings) => {
if (err) { return next(err); }
const listings = await listings[0].populate('pro');
console.log(listings.pro);
res.render('search', {
title: 'Search',
listings: listings,
search: {
service: service,
zip: zip
}
});
});

PayPal express checkout: Can't process payment with sandbox test accounts

I tried to integrate PayPal Express Checkout to my webapp (server-side REST) and I managed to:
get create-payment working
However, when I tried to login with a sandbox test buyer account, it failed to process the payment.
There's a 400 Bad request error for the POST request:
POST https://www.sandbox.paypal.com/webapps/hermes/api/batch/setbuyer 400 (Bad Request)
with the following response:
{
"ack": "success",
"data": {
"buyerEligibility": {
"ack": "success",
"data": {
"eligible": true,
"clearedRT": false
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "xG7Ol-1A5r4xEqHEubzBtkj6LgLo88Z7UFOPmqsoK957Q11gkENbvzjGa02RjhyhvYG_ff2SZRFgHYp0Nq5rCCIdwQAwbwL-RkZ0piofvsP6-i9NmpkouuuH47EBynDbMencyfNKhT-cIewGtGK2jKUX_q0FaWq9Gx0MaRB6QBwINBmRQB5tAuklfWE8ooIwOO7szgPXVg9pOXWI2ukxup08j93HODToZ4DnSLuqCK6XM2M49-_DDSyS6GviI3gWrBy7BLOsHky"
},
"eConsent": {
"ack": "success",
"data": {
"required": false
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "-bDk6FVAJFycsTL-R5q_CGdrJwDz9XbAF9KqMHpr6QIMACZ6IA5zQ_BVyqi3jy6w9pKC5SS4TBrpDB_OJC0rU1W5wz5XBgo3ze_iOG0gDEwxuzu7WtAT1Nv5_VmLhmUWIdMm7qtgfy1y11v18zXSxhATUDaRI8hNdlnArSlBtKVNGWkhCD4OTp4KvSBXQ3lLHm-wCSrJzhpEmBoNZmDQMrd4wv1YEYA0VFPG1cPHapq9t4xJMLfiZOad10irqxJP"
},
"createCheckoutSession": {
"ack": "contingency",
"contingency": "PAYER_CANNOT_PAY",
"errorData": {
"cause": "",
"step_up_context": {}
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "-eKOVjOLP5i0k_9Et8_N5HyfLSVBzycsA2AE8UY8RD88MnM3729QBQoHY2eD3sMhSThBqdYmvFoARIAbkHNoOT9jsHzAUCk1CtbA717xHK5gSuYujf5mvuDJQFXWlPEDBk7XFlZSyhUWy8VGKvYWwWhuTSzcjMdKIzRI_XTjfA2hQpzIvkbRQ5jLMDIIKeNm1XrF3mEMN3gkHzZIc2OBiRaVEA2Q0se_uVgEEGIbSgN2aeSOeh4WiMC08zUCvmdCLyCP0ZyE24fzDvL4ZMUurG"
}
},
"meta": {
"calc": "8f7cc8e266c07",
"rlog": "PVs6nBwuIQ9X2gSdIEqzR%2BxkmohYC3WlOiW4HauQ%2FY%2Fh%2BkWFfmr2pOeyxVs3sSiqXDydWuJ%2B6QWLAsZZVtRfIA_15d276b99bf"
},
"server": "JwJRYq2SF3kUujC16-VsiMQu8IDN_RxPNOz8wY8m8YD4P3PzhHZB73hNd_IM9PktfJcPPHx2RyVUk1PV8bC2lLtejwTFKzq-7QDM9nLmxJLw7os2tgLnGYAebFJAkmIt2fFvlncVMrAg9bAsMF9INhPqixaCEWn7ug9OcPCci_3autJi3cvmTLb_8XvTaGBpPxI0ASQnkXTSVJa2GPIptYhGVHFN5N92hFdxzwp2uYQhHeJrePmExV4NlLd0s_wa"
}
My client side implementation for PayPal Express Checkout:
class PayPalButton extends Component {
render() {
const Btn = paypal.Button.driver('react', {React, ReactDOM});
const CREATE_PAYMENT_URL = `${ROOT_URL}/paypal/create-payment/${this.props.regId}`;
const EXECUTE_PAYMENT_URL = `${ROOT_URL}/paypal/execute-payment/${this.props.regId}`;
const token = localStorage.getItem('deotoken');
let client = {
sandbox: 'TO_BE_REPLACED_WITH_SANDBOX_CLIENT_ID'
};
let payment = () => {
return paypal.request({
method: 'post',
url: CREATE_PAYMENT_URL,
headers: {
authorization: token
}
})
.then(function(data) {
return data.id;
});
};
let onAuthorize = (data) => {
return paypal.request({
method: 'post',
url: EXECUTE_PAYMENT_URL,
headers: {
authorization: token
},
json: {
paymentID: data.paymentID,
payerID: data.payerID
}
}).then(function() {
// The payment is complete!
// You can now show a confirmation message to the customer
console.log('done');
});
};
return (
<div>
<Btn env={'sandbox'}
client={client}
payment={payment}
commit={true}
onAuthorize={onAuthorize}
/>
</div>
);
}
My server side implementation):
module.exports = {
createPayment(req, res, next) {
const { registration_id } = req.params;
const { user } = req;
Registration.findById(registration_id)
.populate({ path: 'category', model: 'category' })
.populate({ path: 'orders.meal', model: 'meal' })
.then(reg => {
if (reg) {
const categoryItem = [{
name: reg.category.name,
sku: reg.category.name,
price: reg.category.price,
currency: 'MYR',
quantity: 1
}];
const ordersItems = reg.orders.map(order => {
return {
name: order.meal.name,
sku: order.meal.name,
price: order.meal.price,
currency: 'MYR',
quantity: order.quantity
}
});
const create_payment_json = {
intent: 'sale',
payer: {
payment_method: 'paypal'
},
redirect_urls: {
return_url: 'http://localhost:8080/',
cancel_url: 'http://localhost:8080/'
},
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'MYR',
total: reg.totalBill
}
}]
};
paypal.payment.create(create_payment_json, function(err, payment) {
if (err) {
next(err);
} else {
res.send(payment);
}
})
} else {
return res.status(422).send({error: 'Registration not found'});
}
})
.catch(next);
},
executePayment(req, res, next) {
const { registration_id } = req.params;
const { paymentID, payerID } = req.body;
const execute_payment_json = {
payer_id: payerID
}
paypal.payment.execute(paymentID, execute_payment_json, function(err, paypalResponse) {
if (err) {
next(err);
} else {
if (paypalResponse.state === 'approved') {
const payment = new Payment({
user: req.user._id,
registration: registration_id,
amount: paypalResponse.transactions[0].amount.total,
currency: paypalResponse.transactions[0].amount.currency,
paypalPaymentId: paypalResponse.id
});
payment.save()
.then(p => res.send(p))
.catch(next);
} else {
res.status(422).send({ error: 'Payment not approved' });
}
}
});
}
};
What is the issue? How can I make this work?
I have faced same issue.
I have set INR as my currency in my paypal sandbox, but sending amount as USD from API call.
So, I think we have to make transaction in currency that we set in sandbox.
Previous Payload:
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'USD',
total: reg.totalBill
}
}]
After Change Payload:
transactions: [{
item_list: {
items: [...categoryItem, ...ordersItems]
},
amount: {
currency: 'INR',
total: reg.totalBill
}
}]
After changing currency, my code works fine !!