I have an application with a form that collects data from users. I want to store this data in MongoDB. However at the moment only the Id is being shown in the database.
I am sending the data in the following format:
let userData = [{
Name: customerName,
address: address,
workType: work,
time: date,
imageOne: image1,
imageTwo: image2,
imageThree: image3,
}];
/* submitting the data */
const scheduleEvent = (e) => {
e.preventDefault();
axios.post(
"My url",
userData
)
};
/* end of submission*/
I have read a few questions here on Stack Overflow that the issue is the object in application and MongoDB are different. Here is my webhook code
exports = async function(payload, response) {
if (payload.body) {
const body = EJSON.parse(payload.body.text());
const customerEvents= context.services.get("mongodb-atlas").db("Curb").collection("Customer-event");
const event = {
Name: body.Name,
address: body.address,
workType: body.workType,
time: body.time,
imageOne: body.image1,
imageTwo: body.image2,
imageThree: body.image3,
};
return await customerEvents.insertOne(event);
}
return {}
I have done some tinkering with the names to see if anything will be sent, but so far nothing. I figured that if the issue were the names of the properties that at least one would go through, like the Name property.
My full page is viewable here.
If you say only the Id is created on MongoDB, maybe there is a problem with the way the data is dealt with.
One possible cause is that userData is of type array. And you're never accessing it. Maybe try:
axios.post(
"My url",
userData[0]
)
Or just keep it as an object:
let userData = {
Name: customerName,
address: address,
workType: work,
time: date,
imageOne: image1,
imageTwo: image2,
imageThree: image3,
};
Another suggestion is to try payload.body.text (instead of .text()). Or to check if it has other properties, like .json or .data.
Related
I have a set of automated tests that is working fine for my "base case". I have some different defaults (e.g. escalation contacts) that are displayed for users accessing the bot via certain URLs. This works by setting values in user and/or conversation state when a user connects to the bot. I can spoof these values in Emulator to test different cases. This all works fine for manual tests.
However, when I try to test via Mocha using TestAdapter, it seems that user state isn't being retained. I can see in my first step that I am getting the spoofed state values based on my value for channelId in the activity. However, when I send a second activity, the state values have reverted to null.
I think this is due to the processActivity function always creating a new TurnContext, though I'm a little unclear why that doesn't retain at least the user state.
So the question is, how can I modify this test, or is there a different test approach I can take, so that I can keep the values I set in user state across all activities? I can provide the escalationActivity file if needed, but it's just an object with the Test Cases and Test Steps so that I don't have to define them in my test.js file (and so that I can potentially reuse the same framework for other tests).
Here is my test.js file in its entirety.
const { TestAdapter, ActivityTypes, TurnContext, ConversationState, MemoryStorage, UserState } = require('botbuilder');
const { DispatchBot } = require('../../bots/dispatchBot');
const assert = require('assert');
const nock = require('nock');
require('dotenv').config({ path: './.env' });
const { escalationActivity } = require('../testData/escalationDialogTestDataPC');
const memoryStorage = new MemoryStorage();
const userState = new UserState(memoryStorage);
var userDialogStateAccessor = userState.createProperty('userDialogStateProperty');
const conversationState = new ConversationState(memoryStorage);
var dialogState = conversationState.createProperty('dialogState');
const appInsights = require('applicationinsights');
appInsights.setup('dummyKey').start();
const appInsightsClient = appInsights.defaultClient;
// Static texts
const myEatonMessage = `For more information on products and orders, please go to [My.Eaton.com](https://my.eaton.com). If you do not have a My.Eaton account, you can click "Request Access" at the top right.`;
describe('Project Center', async () => {
describe('Escalation', async () => {
const testAdapter = new TestAdapter();
async function processActivity(activity, bot) {
const context = new TurnContext(testAdapter, activity);
await bot.run(context);
}
let bot = new DispatchBot(new ConversationState(memoryStorage), new UserState(memoryStorage), appInsightsClient);
it('Welcome message', async () => {
const conversationUpdateActivity = {
type: ActivityTypes.ConversationUpdate,
channelId: 'projectCenter',
conversation: {
id: 'someId'
},
membersAdded: [
{ id: 'theUser' }
],
from: { id: 'theBot' },
recipient: { id: 'theUser' }
};
await processActivity(conversationUpdateActivity, bot);
let reply = testAdapter.activityBuffer.shift();
assert.strictEqual(reply.attachments[0].content.id, 'menuCard', 'Reply does not match.')
});
// BOT IS LOSING USER STATE ON SUBSEQUENT STEPS
Object.keys(escalationActivity).map((key) => {
describe(key, async () => {
let conversationData = escalationActivity[key].conversation;
//let channel = escalationActivity[key].channel;
//let intent = escalationActivity[key].intialOptions.topIntent;
conversationData.map((interaction, idx) => {
it(idx + '. ' + interaction.type, async () => {
// Create message activity
const messageActivity = {
type: ActivityTypes.Message,
channelId: 'projectCenter',
conversation: {
id: 'someId'
},
from: { id: 'theUser' },
recipient: { id: 'theBot' },
text: interaction.input
};
// Send the conversation update activity to the bot.
await processActivity(messageActivity, bot);
let reply = testAdapter.activityBuffer.shift();
if (idx == 0) { // First index has two extra activities that need to be skipped
reply = testAdapter.activityBuffer.shift();
reply = testAdapter.activityBuffer.shift();
}
assert.strictEqual(reply.text, interaction.reply, 'Reply does not match.');
//assertStrictEqual(1,1);
});
});
});
});
});
});
This isn't really an answer per say, but I was able to get the tests working by "re-spoofing" my projectCenter channel on every turn. For the actual bot function, this information is set in either onEvent via webchat/join event in directline or in onMembersAdded for all other channels. As mentioned in my question, user/conversation state is not being retained after my first Mocha test. I have not fixed that, thus I am not considering this issue completely resolved.
However, from the standpoint of at least being able to automate tests for this functionality, I accomplished that via onMessage handler. I simply look for my test channel, projectCenter, on every message, and reset all of the user and conversation state attributes.
I don't like this because it is a piece of code solely for testing and it is overwriting state every turn which is a bad practice, but as of yet I can't find any better way to get the information which I need to have in user state to persist through all of my tests. I am still hoping that someone can come up with a better solution to this issue... That said, this is at least working for my purposes so I wanted to present it as an answer here.
I'm having some trouble passing into a variable that holds a json object into sendgrid's dynamic_template_data. My setup looks like this:
const send = async (address, mentions) => {
console.log('mentions json obj', mentions)
let name = "john"
try {
let config = {
headers: {
Authorization: `Bearer ${process.env.sendgridKey}`,
}
}
let data = {
personalizations: [
{
to: [
{
email: `${address}`,
},
],
dynamic_template_data: {
name: name,
allMentions: mentions
}
}
],
from: {
email: "email#email.com",
name: "Mentionscrawler Team"
},
template_id: process.env.template_id,
}
await axios.post("https://api.sendgrid.com/v3/mail/send", data, config)
} catch (error) {
console.error(error, 'failing here>>>>>>>')
}
}
when I console.log mentions, which is json, and paste the code I get from the terminal directly into the allMentions key, it works. but when I just pass in mentions itself, nothing shows up on the sent email. I've been very confused the last few hours why this is happening. Any advice appreciated.
edit: i should also note that allmentions is an object with keys that hold arrays. So I'm looking to iterate over those arrays. Again, this totally all works if I just paste in directly what mentions is, but passing in mentions is giving me an issue.
Thank you very much,
just realized what was wrong. sendgrid's template requires a json object, so I assumed that I needed to use json.stringify on my mentions obj. Turns out I didn't need to do that, as long as all values are in string format.
I am trying to make a messages schema + routes for my backend. I want that two users can write a message to each other and the message has to be stored for both of them.
I made the the user-model-schema and the user-routes, they are working but I'm stuck with the messaging.
mongoDB should contain the message
how can I manage sending messages?
Here is what I tried so far
messages-route:
var express = require("express");
var User = require("../models/users.js");
var router = express.Router();
const message = require("../models/messages");
router.post("/:recipient", (request, response) => {
User.find({
username: [request.body.sender, request.params.recipient],
// }, {
// message: request.body.message
// }, {
// upsert: false,
// new: true,
})
.then((users) => {
users.forEach((user) => {
user.updateMany({
message: request.body.message
})
})
response.status(200).json(users);
})
.catch((error) => {
response.status(500).json(error);
});
});
module.exports = router;
and my messages-schema:
var mongoose = require("mongoose");
var UserMessage = new mongoose.Schema({
user: { "type": mongoose.Schema.ObjectId, "ref": "User" },
username: String,
view: {
inbox: Boolean,
outbox: Boolean,
archive: Boolean
},
content: {type: String},
read: {
marked: { "type": Boolean, default: false },
date: Date
}
});
var schemaMessage = new mongoose.Schema.ObjectId({
from: String,
to: [UserMessage],
message: String,
created: Date
});
module.exports = mongoose.model("Messages", UserMessage);
I'm very unsure with the schema, I put in some suggestions I found here on stackoverflow.
Thanks!
I'm now making a messages Schema, and i thought of something like this:
const ChatSchema = new mongoose.Schema({
participants: [{type: mongoose.Schema.objectId, ref: "users"}],
last_message: Number,
_id: {type: mongoose.Schema.objectId}
//...
})
Here you could use the same schema to make chat groups, and it will work having a separate document for each message with a field with the chatroom _id, so you could query them
And you might be thinking, why not put all the messages in an array on ChatSchema?
Well, because we don't want the users to recieve all their messages each time they enter a chat.
I know, you might be thinking about something like this to prevent that over-load of data to the client
MessageSchema.find(({ ... })
.sort({ updatedAt: -1 })
.limit(20)
But here you're still recieving all the messages from the database to the server, and users could have even 1000 messages per chat, so an idea that came to my mind was making MessageSchema like this:
const MessageSchema = new mongoose.Schema({
message: String,
chatRoom_id: {type: mongoose.Schema.objectId},
sentBy: {type: mongoose.Schema.objectId, ref: "users"},
seenBy: [{ user: {type: mongoose.Schema.objectId, ref: "users"}, seen: Boolean }],
numberOfMessage: Number
//...
})
And the magic is with numberOfMessage, because you could query the MessageSchema with comparison operators like this:
const currentMessage = 151 //this is the numberOfMessage number of the last message the user saw, and when the user scrolls up the chat you could send the numberOfMessage of the last loaded message.
//To know from wich numberOfMessage start, just use the ``last_message`` field from the chatroom's ``ChatSchema``
const quantityNewMessages = 10 //this is the quantity of new messages you want the user to recieve
MessageSchema.find({
chatRoom_id,
numberOfMessage:
{ $lt: currentMessage , $gte: currentMessage + quantityNewMessages } //we use less than operator because the user will always recieve the last message first, so the previous messages are going to have a numberOfMessage less than this first message
})
And it will return us an array of 10 messages, wich you can concat to the messages array you already have in your frontend.
take note:
With this MessageSchema, all the users will share the same document for each message, you could make a separate document of a message for each user in a chatroom, so each user can delete a message without affecting the others.
I don't recommend you saving a username field, because the user could change it's username and if he does that, you would have to update ALL message documents with that username to the new username, so just leave the ref and populate it
You shouldn't put the document object in the to field, make it a separate document and only save it's ref. But I don't recommend doing it that way either
Don't use users.forEach because it modifies the array, use users.map because it returns a new array and doesn't mutates the original array
I see you have a bug, you are asking the data with the returned variable of the forEach( user => user.update(...) ), it should be map( user => UserSchema.update({ _id: user._id }) )
But still, looping an array and making a call to the DB each time is very expensive and will lag the server, so use something like the ChatSchema I showed you, because you could get the chatroom information, and with the _id of that chatroom, query the newest messages
I've not tested this code, i'm about to do it. If you run into any problem feel free to comment
When i update this data, the deslon and deslat part is not inserted in the document.
var locationData = { update_time: new Date() ,
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]};
the update
userLocationModel.update({uid: req.params.accesskey}, locationData, { upsert: true }, function (err, numberAffected, raw) {
//DO SOMETHING
});
I cannot understand why this is happining.
Here is the mongo document that gets inserted. The deslon and deslat are missing even if a new document is created.
{
_id: ObjectId("52f876d7dbe6f9ea80344fd4"),
location: [
{
curlon: 160,
curlat: 160,
_id: ObjectId("52f8788578aa340000e51673")
},
{
_id: ObjectId("52f8788578aa340000e51672")
}
],
uid: "testuser6",
update_time: ISODate("2014-02-10T06:58:13.790Z")
}
Also : Should I be using a structure like this if the document is updated frequently.
This is the mongoose model:
var userLocationSchema = mongoose.Schema({
uid: String, //same as the user access key
update_time: Date, //time stamp to validate, insert when updating. created by server.
location:[
{
curlon: Number, //current location in latitude and longitude <INDEX>
curlat: Number
},
{
deslon: Number, //destination in latitude and longitude <INDEX>
deslat: Number
}
]
});
I wish to update both of the elemets. I don't wan't to insert a new one. But even when I update a non existent document(ie- which results in the creation of a new one), the deslon and deslat are missing.
I have a real problem with this structure but, oh well.
Your Schema is wrong for doing this. Hence also the superfluous _id entries. To do what you want you need something like this:
var currentSchema = mongoose.Schema({
curlon: Number,
curlat: Number
});
var destSchema = mongoose.Schema({
destlon: Number,
destlat: Number
});
var userLocationSchema = mongoose.Schema({
uid: String,
update_time: Date,
location: [ ]
});
This is how mongoose expects you to do embedded documents. That will allow the update in your form you are using to work.
Also your logic on upsert is wrong as you have not included the new uid that is not found in the updated document part. You should take a look at $setOnInsert in the MongoDB documentation, or just live with updating it every time.
Actually, I'm just pointing you to how to separate the schema. As your usage in code stands location will accept anything by the above definition. See the mongoose docs on Embedded Documents for a more detailed usage.
This will work with your update statement as stands. However I would strongly urge you to re-think this schema structure, especially if you intend to do Geo-spatial work with the data. That's out of the scope of this question. Happy googling.
You have to tell mongo how to update your data. So add a simple $set to your update data:
var locationData = {
$set: {
update_time: new Date(),
location: [
{curlon: req.payload.loclon , curlat: req.payload.loclat},
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
EDIT:
If you do not want to exchange the location property as a whole, but insert a new item into the array, use:
var locationData = {
$set: {
update_time: new Date()
},
$push: {
location: [
{deslon: req.payload.deslon , deslat: req.payload.deslat}
]
};
What you should consider is, if it is a good idea to put the current location and the destinations in one array, just because they have the same properties (lon/lat). If for example, there is always one current location and zero to many destinations, you could put the current location into a separate property.
To modify a specific location within an array, you can address it via.
var index = 2, // this is an example
arrayElement = 'location.' + n,
locationData = { $set: {} };
locationData.$set[arrayElement] = {deslon: req.payload.deslon , deslat: req.payload.deslat};
userLocationModel.update({uid: req.params.accesskey}, locationData );
could it be that the intial collection was built with an other version of the schema? i.e. one that had only curlon and curlat? you may have to update the documents then to reflect the amended schema with the deslon and deslat properties.
I read with interest the blog post here, which describes how to make a query equivalent to sql WHERE email = x
new Firebase("https://examples-sql-queries.firebaseio.com/user")
.startAt('kato#firebase.com')
.endAt('kato#firebase.com')
.once('value', function(snap) {
console.log('accounts matching email address', snap.val())
});
I've tried to replicate this as follows:
root
|-rewards
|--JAJoFho0MYBMGNGrCdc
|-name: "apple"
|--JAJoFj7KsLSXMdGZ77V
|-name: "orange"
|--JAJoFp7HP6Ajq-VuMMx
|-name: "banana"
There are many other fields in each rewards object... but I want to index the object by name and to be able to query all these objects to find the one matching a given name. The blog post instructs us to use setPriority() to achieve this.
I have tried the following:
var ref = new Firebase('https://<example>.firebaseio.com/rewards').push({
name: 'apple',
otherKey: 'somevalue',
...
});
ref.setPriority('apple');
If I then query firebase, it returns null:
new Firebase('https://<example>.firebaseio.com/rewards')
.startAt('apple')
.endAt('apple')
.once('value', function(snap) {
console.log('found:', snap.val()); // logs: "found null"
});
What am I doing wrong?
Looks like you're attempting to run these commands synchronously. At the time that you request rewards, there may not be any data yet (your push ops may not have finished).
Next, you should use setWithPriority, which will allow you to push the data and priority at the same time.
Last but not least, you haven't mentioned errors. I'll assume you checked those like any diligent dev would. In addition to the JS console, you can log the results of the callback functions (there's one for each of the methods you called, which could return an error if something went wrong).
So all together, it should look more like this:
var ref = new Firebase('https://<example>.firebaseio.com/rewards').push();
ref.setWithPriority({
name: 'apple',
otherKey: 'somevalue',
...
}, 'apple', function(err) {
if( error ) { console.error(err); }
else {
fetchValue();
}
});
function fetchValue() {
// wait for the set to complete before fetching data
new Firebase('https://<example>.firebaseio.com/rewards')
.startAt('apple')
.endAt('apple')
.once('value', function(snap) {
console.log('found:', snap.val()); // logs: "found null"
});
}