I have an export button on my front-end that when clicked, sends a POST on our Express server to log the button click. This uses the route app.post(usagereport) . What I want to be able to do is capture the route the user was at when they clicked export. However, since the code to send the POST request is it's own route, it only ever returns that route's name when trying something like req.route.
I'm using API Gateway + Lambda + AWS-Serverless-Express.
I was thinking I could store something like req.session.previousRoute in req.session to capture the last route the user loaded and then return this to the access log code. However, I was unsure if this approach would work on Lambda or perhaps there is just a better way to handle it.
Here is my server.js (trimmed down)
// create the server and setup routes
const app = express();
const mysql = require("mysql");
// AWS-Serverless-Express https://github.com/awslabs/aws-serverless-express
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
app.use(awsServerlessExpressMiddleware.eventContext());
//Setup paths to database connection pools
const nawfprojectsDB = require("../lib/naWfProjectsDb.js");
const queries = require("./queries.js");
const accessLog = require("../lib/accessLog.js");
//Setup a timestamp for logging
const timestamp = new Date().toString();
// S3 Data Mitigation is needed when a data set exceeds 5 MB in size.
// This is a restriction of Lambda itself (they say 6 MB but want to ensure we dont ever hit the limit)
const s3DataMitigation = require("../lib/s3DataMitigation.js");
let environment = process.env.NODE_ENV;
app.get("/wg_data", (req, res, callback) => {
const dataSet = "wg_data";
nawfprojectsDB.query(queries.wg_data, (err, result) => {
if (err) {
console.log(err);
}
s3Data(dataSet, res, callback, result);
console.log(
timestamp,
"Returned " + result.length + " rows from " + dataSet
);
});
accessLog({ dataSet, req });
});
// Usage report everytime export button is clicked
app.post("/usagereport", (req) => {
const currentPath = dataSet;
const dataSet = "Data Exported: " + currentPath;
console.log(timestamp, "Data exported");
accessLog({ dataSet, req });
});
module.exports = app;
accessLog.js
let nawfprojectsDB = require("./naWfProjectsDb.js");
let queries = require("../routes/queries.js");
let environment = process.env.NODE_ENV;
//Insert data into access_logs table when usageLog is called
const accessLog = ({ dataSet, req }) => {
// We only want to log access when in beta, gamma, or prod. Not in development.
if (environment === "development") {
console.log("No access log as we are in dev");
} else {
// req.apiGateway comes from AWS-Serverless-Express - https://github.com/awslabs/aws-serverless-express
const user = req.apiGateway.event.requestContext.authorizer.principalId;
let sqlData = [dataSet, user, environment];
// Run the log_access query using the sqlData above
nawfprojectsDB.query(queries.log_access, sqlData, (err) => {
if (err) {
console.error("MySQL query error: " + err);
}
console.log("Access log added for: ", user, " at data set: ", dataSet);
});
}
};
module.exports = accessLog;
Solved my own question.
The way solved this was using express-session. I set req.session.previousRoute within each of my routes. I can then access this in my usagereport route.
const { v4: uuidv4 } = require("uuid");
const express = require("express");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const randomString = uuidv4();
let sessionOptions = {
cookie: {
secret: randomString,
maxAge: 269999999999,
},
saveUninitialized: true,
resave: true,
};
// create the server and setup routes
const app = express();
// Add express-session Middleware - https://www.npmjs.com/package/express-session
app.use(cookieParser(randomString)); // Need cookieParser to properly parse our random string into the type of value expected by session
app.use(session(sessionOptions));
// AWS-Serverless-Express - https://github.com/awslabs/aws-serverless-express
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
app.use(awsServerlessExpressMiddleware.eventContext());
app.get("/wg_data", (req, res, callback) => {
const dataSet = "wg_data";
const action = "Accessed";
req.session.previousRoute = dataSet; // This is where we set the previousRoute in session
nawfprojectsDB.query(queries.wg_data, (err, result) => {
if (err) {
console.log(err);
}
s3Data(dataSet, res, callback, result);
console.log(
timestamp,
"Returned " + result.length + " rows from " + dataSet
);
});
accessLog({ dataSet, action, req });
});
// Usage report everytime export button is clicked
app.post("/usagereport", (req) => {
// Here we grab the previousRoute set in session to see the true place the data was exported from
const action = "Exported";
const previousRoute = req.session.previousRoute; // Now when usagereport is triggered, it knows the previous route from the session and uses that here.
const dataSet = previousRoute;
console.log(timestamp, "Data exported from ", previousRoute);
accessLog({ dataSet, action, req });
});
Related
I have a problem with my nodejs code and the connection to the official whatsapp business api.
The bot connects the webhook correctly, the messages arrive to the server correctly but the code I have implemented to make it respond is not being effective, I checked the code from top to bottom but I can't find the fault.
I leave you the codes so you have more context:
whatsappController.js:
const fs = require("fs");
const myConsole = new console.Console(fs.createWriteStream("./logs.txt"));
const whatsappService = require("../services/whatsappService")
const VerifyToken = (req, res) => {
try {
var accessToken = "456E7GR****************************";
var token = req.query["hub.verify_token"];
var challenge = req.query["hub.challenge"];
if(challenge != null && token != null && token == accessToken){
res.send(challenge);
}
else{
res.status(400).send();
}
} catch(e) {
res.status(400).send();
}
}
const ReceivedMessage = (req, res) => {
try {
var entry = (req.body["entry"])[0];
var changes = (entry["changes"])[0];
var value = changes["value"];
var messageObject = value["messages"];
if(typeof messageObject != "undefined"){
var messages = messageObject[0];
var text = GetTextUser(messages);
var number = messages["from"];
myConsole.log("Message: " + text + " from: " + number);
whatsappService.SendMessageWhatsApp("The user say: " + text, number);
myConsole.log(messages);
myConsole.log(messageObject);
}
res.send("EVENT_RECEIVED");
}catch(e) {
myConsole.log(e);
res.send("EVENT_RECEIVED");
}
}
function GetTextUser(messages){
var text = "";
var typeMessage = messages["type"];
if(typeMessage == "text"){
text = (messages["text"])["body"];
}
else if(typeMessage == "interactive"){
var interactiveObject = messages["interactive"];
var typeInteractive = interactiveObject["type"];
if(typeInteractive == "button_reply"){
text = (interactiveObject["button_reply"])["title"];
}
else if(typeInteractive == "list_reply"){
text = (interactiveObject["list_reply"])["title"];
}else{
myConsole.log("sin mensaje");
}
}else{
myConsole.log("sin mensaje");
}
return text;
}
module.exports = {
VerifyToken,
ReceivedMessage
}
The second file is whatsappService which I make the connection with the api using the token and I also send the format of the message I want to send when I receive a hello for example...
const https = require("https");
function SendMessageWhatsApp(textResponse, number){
const data = JSON.stringify({
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": number,
"type": "text",
"text": {
"preview_url": false,
"body": textResponse
}
});
const options = {
host:"graph.facebook.com",
path:"/v15.0/1119744*************/messages",
method:"POST",
body:data,
headers: {
"Content-Type":"application/json",
Authorization:"Bearer EAAWNbICfuWEBAK5ObPbD******************************************************"
}
};
const req = https.request(options, res => {
res.on("data", d=> {
process.stdout.write(d);
});
});
req.on("error", error => {
console.error(error);
});
req.write(data);
req.end();
}
module.exports = {
SendMessageWhatsApp
};
Then I declare the routes for the get (to check token) and post (to receive and reply to messages) methods:
const expres = require("express");
const router = expres.Router();
const whatsappController = require("../controllers/whatsappControllers");
router
.get("/", whatsappController.VerifyToken)
.post("/", whatsappController.ReceivedMessage)
module.exports = router;
Last but not least the index file for the code to run correctly:
const express = require("express");
const apiRoute = require("./routes/routes");
const app = express();
const PORT = process.env.PORT || 3000
app.use(express.json());
app.use("/whatsapp", apiRoute);
app.listen(PORT, () => (console.log("El puerto es: " + PORT)));
I should clarify that I did the tests with Postman and they were all successful, it responds and receives messages correctly, finally I did the tests by uploading the bot to the Azure service and it works without problem until it has to answer/replicate the user's message.
The bot is not responding to the user when he talks to it but everything arrives correctly to the server and it processes it with a 200 response. I attach the evidence that there is no problem in the reception.
Finally I must say that in the meta platform I have everything configured as specified by the same platform, I have already configured the api to answer the messages through the webhooks and everything is correct, I just can't get the bot to answer correctly.
The bot is hosted in the Azure service.
Solved: some numbers have a problema with the api of WAB in my country (Argentina) the phone numbers start in +54 9 11. The problem is the 9 in the phone number, and this have a conflict in the servers of meta, Solution quit number 9 to numbers of this country and the message will send to user.
Can someone please tell me what is wrong in my code before I go back to MongoDB?
Project is in Node.js (Next.js)
This is how I set firebase (it works for authentication with Google Login for instance):
import { initializeApp } from 'firebase/app';
const credentials = {
...
}
const firebase = initializeApp(credentials);
export default firebase;
then this is my api js file where it throws error "db.ref" is not a function:
import firebase from '#/firebase/firebase'
import { getDatabase, ref, onValue, update, child, orderByChild, equalTo, once } from "firebase/database"
export default async (req, res) => {
const db = getDatabase(firebase);
if (req.method === 'POST') {
const body = req.body
const playlistTracks = body.playlist
const playlistName = body.name
const uid = body.uid
const data = ...
console.log(data)
var ref = db.ref().child('users');
ref.child(uid).orderByChild('n').equalTo(playlistName).once("child_added", function(snapshot) {
let listId = snapshot.key;
db.ref("users/" + uid + "/" + listId).update(data);
res.send({ risp : 'ok' })
});
}
}
realtime database structure is:
- users
- <user uid>
- <playlist uid>
c: []
n: "playlist name"
so I'm trying to first retrieve the correct playlist by it's name ("n" value) comparing all "n" with the name of the given playlist, then I'd need to update (overwrite) it with my object (data)
UPDATE:
So I found the other methods Web version 9 (modular) in the documentation, as suggested by Frank van Puffelen below, but it now thorws a error
#firebase/database: FIREBASE WARNING: Exception was thrown by user
callback. Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they
are sent to the client
My code now is like this:
try {
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
const updates = {};
updates["users/" + uid + "/" + listId] = dataToUpdate;
update(ref(db), updates);
}, {
onlyOnce: true
});
res.send({ risp : 'ok' })
} catch (e) {
res.status(400).end();
}
also tried like this, but it's the same error:
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
update(ref(db, "users/" + uid + "/" + listId), dataToUpdate)
.then(() => {
res.send({ risp: 'ok' })
})
.catch((error) => {
res.status(400).end();
});
}, {
onlyOnce: true
});
You're using the new modular API, so can't use namespaced accessors like db.ref() anymore. Instead use ref(db, 'users').
I highly recommend keeping the documentation handy while upgrading this code to the new API version, as it has example of the modular and namespaced API side by side. The upgrade guide is probably also a worthwhile read).
I'm trying to implement a reminders dialogflow agent in node js that reminds the user on his google calendar upcoming events. however I'm getting an No responses defined for platform: null error when calling the intent for listing the upcoming events.
This is my code:
const express = require('express');
const google = require('googleapis').google;
const jwt = require('jsonwebtoken');
const dfff = require('dialogflow-fulfillment')
const {googlec} = require('googleapis');
// Google's OAuth2 client
const OAuth2 = google.auth.OAuth2;
// Including our config file
const CONFIG = require('./config');
// Creating our express application
const app = express();
// Allowing ourselves to use cookies
const cookieParser = require('cookie-parser');
app.use(cookieParser());
// Setting up EJS Views
app.set('view engine', 'ejs');
app.set('views', __dirname);
console.log(dfff)
app.get('/', function (req, res) {
// Create an OAuth2 client object from the credentials in our config file
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
// Obtain the google login link to which we'll send our users to give us access
const loginLink = oauth2Client.generateAuthUrl({
access_type: 'offline', // Indicates that we need to be able to access data continously without the user constantly giving us consent
scope: CONFIG.oauth2Credentials.scopes // Using the access scopes from our config file
});
return res.render("index", { loginLink: loginLink });
});
app.get('/auth_callback', function (req, res) {
// Create an OAuth2 client object from the credentials in our config file
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
if (req.query.error) {
// The user did not give us permission.
return res.redirect('/error');
} else {
oauth2Client.getToken(req.query.code, function(err, token) {
if (err)
return res.redirect('/');
// Store the credentials given by google into a jsonwebtoken in a cookie called 'jwt'
res.cookie('jwt', jwt.sign(token, CONFIG.JWTsecret));
return res.redirect('/');
});
}
});
app.post('/', express.json(),(req,res)=>{
//if (!req.cookies.jwt) {
// We haven't logged in
//return res.redirect('/');
//}
const oauth2Client = new OAuth2(CONFIG.oauth2Credentials.client_id, CONFIG.oauth2Credentials.client_secret, CONFIG.oauth2Credentials.redirect_uris[0]);
const calendar = google.calendar({version: 'v3' , auth:oauth2Client});
const agent = new dfff.WebhookClient({
request : req,
response : res
})
function welcome(agent){
agent.add("Hi")
}
function listEvents(agent){
calendar.events.list({
'calendarId': 'primary',
'auth':oauth2Client,
'timeMin': (new Date()).toISOString(),
'showDeleted': false,
'singleEvents': true,
'maxResults': 10,
'singleEvents': true,
'orderBy': 'startTime'
}).then((err,response)=> {
let events = response.result.items;
if (events.length > 0) {
for (let i = 0; i < events.length; i++) {
var event = events[i];
var when = event.start.dateTime;
if (!when) {
when = event.start.date;
}
return agent.add('Be ready for '+ event.summary + ' event '+ 'at ' + when )
}}
else {
return agent.add('You dont have any upcoming events.');
}
});
}
let intenMap = new Map()
intenMap.set('Default_Welcome_Intent',welcome)
intenMap.set('Remind_me',listEvents)
agent.handleRequest(intenMap)
});
// Listen on the port defined in the config file
app.listen(CONFIG.port, function () {
console.log(`Listening on port ${CONFIG.port}`);
});
Whenever listEvents function is processed I'm getting (Error: No responses defined for platform null) any idea why?
The issue is that listEvents() does an asynchronous operation that returns a Promise (the call to calendar.events.list()), which you handle through the .then() block, but you don't have a way for the agent.handleRequest() function to know this and to wait for the Promise to complete. To do this, you need to return a Promise.
Fortunately, in your case, the solution is straightforward, since the call and then() chain return a Promise, you can just return it. This would be done by adding the return keyword before calendar.events.list(). It might look something like this:
function listEvents(agent){
return calendar.events.list({
// Parameters go here
}).then((err,response)=> {
// Code to handle response and return a message go here
});
}
I am trying to get users data from firebase but I keep receive 'CANNOT GET /' error.
//firebase init
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
//express and cors init
const express = require('express');
const cors = require('cors');
//middleware init
const app = express();
app.use(cors());
const database = admin.database();
app.get("/users", function (request, response) {
return database.ref('/users').on("value", snapshot => {
return response.status(200).send(snapshot.val());
}, error => {
console.error(database);
return response.status(500).send(err);
})
});
exports.users = functions.https.onRequest(app);
I also try to use another function from another website I refer to get at least the username but it returns error.
exports.users = functions.https.onRequest((req, res) => {
database().ref('/merchants').once('value').then(function(snapshot) {
var username = snapshot.val().username;
res.status(200).send(username);
});
});
Problem 1: Exporting express applications
When you export an express app through a Cloud Function, the paths become relative to the exported function name. You exported your app as exports.users which sets the root path of your function to /users and to call it, you would visit https://us-central1-<project-id>.cloudfunctions.net/users.
However, because you defined a route handler for /users (using app.get("/users", ...)) as well, you added a handler for https://us-central1-<project-id>.cloudfunctions.net/users/users instead.
The error Cannot GET / is thrown because when you call the function at https://us-central1-<project-id>.cloudfunctions.net/users, the relative URL is set as "/", which you haven't configured a handler for (using app.get("/", ...)).
So to fix your code above, change
app.get("/users", function (request, response) {
to
app.get("/", function (request, response) {
Problem 2: Username queries
The issue could be as simple as that you call database() instead of admin.database().
However, the purpose of this query is unclear, so I will assume that you have a merchant's ID and you are trying to get the username of the owner of that particular merchant.
This would mean a data structure similar to:
{
"users": {
"somePerson": { ... },
"otherPerson: { ... }
},
"merchants": {
"reallyGreatGuyInc": {
"username": "somePerson",
...
},
"realShadyPeopleInc": {
"username": "otherPerson",
...
}
}
}
If you exported a function called getMerchantUsername where you pass in the merchant ID via a GET parameter and call it at the URL https://us-central1-<project-id>.cloudfunctions.net/getMerchantUsername?id=reallyGreatGuyInc, you would define it using the following code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.getMerchantUsername = functions.https.onRequest((req, res) => {
if (!req.query.id) {
// '?id=' is required
return res.status(400).send("Missing 'id' parameter");
} else if (/[\u0000-\u001F\u007F\.\$#/\[\]\\]/.test(req.query.id)) {
// check id for invalid characters
return res.status(400).send("Invalid 'id' parameter");
}
// run query
admin.database().ref(`/merchants/${req.query.id}/username`).once('value')
.then((snapshot) => {
var username = snapshot.val();
res.status(200).send(username);
})
.catch((error) => {
console.error(error);
res.status(500).send('Request failed.');
});
});
Notes:
The test for invalid characters is based on the list in the RTDB: Structure data documentation. This somewhat prevents looking at other database values.
If you only need the value of username, rather than request the entire merchant object and parse it, request only the username as in the example above where I used `/merchants/${req.query.id}/username`.
I have a post interface that does not submit data correctly.
test shows: req.body undefined
const express = require('express');
const router = express.Router();
const passport = require("passport");
const passportInfo = passport.authenticate('jwt',{ session: false });
const HomeSchema = require('../../../models/AnvizHome');
const homeBannerValidator = require('../../../validation/anviz/homeBanner');
router.post("/banner",passportInfo,(req,res) => {
const {msg,isValid} = homeBannerValidator(req.body);
if(!isValid){
return res.status(400).json(msg);
}
HomeSchema.findOne({handle:req.body.handle}).then(banner => {
console.log('current: ' + req.body);
const newBanner = {
bannerBg:req.body.bannerBg,
bannerName:req.body.bannerName,
bannerSubName:req.body.bannerSubName,
bannerFeather:req.body.bannerFeather,
bannerLink:req.body.bannerLink
};
banner.prodcutBanner = newBanner;
banner.then(home => res.json(home));
})
.catch((err) => res.json(err));
});
module.exports = router;
postman test:
In fact, the terminal can see the returned data.
[Object: null prototype] {
bannerBg: '5555555555555555555555555555555555',
bannerName: 'THE GLOBAL LEADING',
bannerSubName: 'PROVIDER OF INTELLIGENT SECURITY',
bannerLink: 'www.anviz.com',
handle: 'true' }
Seeking one or two!Thank you!
you forgot to import and use body parser
https://www.npmjs.com/package/body-parser
var bodyParser = require('body-parser');
var express = require('express');
express.use(bodyParser.json());
express.use(bodyParser.urlencoded({ extended: false }));
I think I know the reason, because this is based on the user's token to submit data, so must be in the post according to the user id to determine whether the data is successful?
Here is the code that has been successfully saved, but I don't know right and wrong:
router.post("/banner",passportInfo,(req,res) => {
const {msg,isValid} = homeBannerValidator(req.body);
if(!isValid){
return res.status(400).json(msg);
}
const profileFields = {};
profileFields.prodcutBanner = {};
if(req.body.handle) profileFields.handle = req.body.handle;
if(req.body.bannerBg) profileFields.prodcutBanner.bannerBg = req.body.bannerBg;
if(req.body.bannerName) profileFields.prodcutBanner.bannerName = req.body.bannerName;
if(req.body.bannerSubName) profileFields.prodcutBanner.bannerSubName = req.body.bannerSubName;
if(req.body.bannerFeather) profileFields.prodcutBanner.bannerFeather = req.body.bannerFeather;
if(req.body.bannerLink) profileFields.prodcutBanner.bannerLink = req.body.bannerLink;
HomeSchema.findOne({user: req.user.id}).then(profile => {
if(profile){
HomeSchema.findByIdAndUpdate({user: req.user.id},{$set:profileFields},{new:true}).then(profile => res.json(profile));
}else{
HomeSchema.findOne({handle:profileFields.handle}).then(profile => {
if(profile){
msg.handle = "The user's handle personal information already exists, please do not re-create it!";
res.status(400).json(msg);
}
new HomeSchema(profileFields).save().then(profile => res.json(profile));
})
}
})
.catch((err) => res.json(err));});
module.exports = router;