I'm having some issues referencing some dynamic variables which equate to some mongoose model based on user session location value. I have two scripts.
location.js & reporting.js
location.js
module.exports = function(req) {
// Setting some variables to do the following.
// Assign var to value of the client remote address. This should return the ip address.
// The remoteAddress object returns an IPV6 format. Because of this, I setup a new var to the value of the client IP stripped of the ":::ffff" portion.
// I then split the string by the period character to get an array of the sections of string.
var clientIP = req.connection.remoteAddress;
var strippedIP = clientIP.replace(/^.*:/, '');
var splitIP = clientIP.split('.')
// Determining if the site session value based on second octet of ip response.
if (splitIP[1] == '28') {
req.session.site = 'shk';
}
else if (splitIP[1] == '29') {
req.session.site = 'ftm';
}
else if (splitIP[1] == '31') {
req.session.site = 'tpe';
}
else {
req.session.site = 'ftm';
}
// Using case statement to determine the machinery model to use as well as passdowns.
switch(req.session.site) {
// Shakopee Variables
case 'shk':
console.log("You're located in Shakopee.");
var Machinery = require('../models/machinery_shk');
var Loggings = require('../models/passdowns_shk');
break;
// Fort Mill Variables
case 'ftm':
console.log("You're located in Fort Mill.");
var Machinery = require('../models/machinery_ftm');
var Loggings = require('../models/passdowns_ftm');
break;
// Tempe Variables
case 'tpe':
console.log("You're located in Tempe.");
var Machinery = require('../models/machinery_tpe');
var Loggings = require('../models/passdowns_tpe');
break;
// Default values to use if no case is matched.
default:
console.log("You're located in Default");
var Machinery = require('../models/machinery_ftm');
var Loggings = require('../models/passdowns_ftm');
break;
}
};
reporting.js - route
reportingRouter.route('/')
.get((req, res, next) => {
Location(req);
if (req.session.loggedIn === false || req.session.loggedIn === undefined || !req.session.loggedIn) {
res.redirect('/reporting/login')
}
else if (req.session.loggedIn === true) {
Loggings.find({}, (err, loggings) => {
if (err) {
throw err;
}
else {
Machinery.find({}, (err, machinery) => {
if (err) {
throw err;
}
else {
// console.log(machinery)
Shifts.find({}, (err, shifts) => {
if (err) {
throw err;
}
else {
res.render('reporting', { pageTitle: 'Reporting', loggings: loggings, machinery: machinery, shifts: shifts, ldapFullName: req.session.fullName })
// console.log(loggings)
}
})
}
})
}
})
}
})
location.js is set up to expose a function taking one parameter. That parameter is to be the express "req" object. This script also takes the client IP Address. I take the client IP and get the second octet of the string.
Based on that value, I assign a property in the session object of the req object in express session.
req.session.site = <some-value>
Once that is set, I perform a switch case on that value assigned. If some value, assign more variables to certain mongoose models. For example, I set up a "Loggings" variable to a certain mongoose model.
var Loggings = require('../models/passdown_<site-id>')
Assuming those variables are assigned, I should be able to "require" this script inside of my reporting.js script.
Inside reporting.js, I assign a variable to that module.
var Location = require('location')
Then, I call that variable and pass in the req parameter when on some route. For example, when a "GET" is performed on some route, I call this Location module "function" passing in the req object.
Location(req)
Now, assuming all of this works, shouldn't I be able to perform a mongoose query referencing the "Loggings" variable I set up in location.js? I'm getting some undefined and I believe it's due to variable scope issues. In this instance, should I "export" those model requires? For example,
exports.Loggings = require('../models/passdown_<site-id>')
Apologies for my ignorance here.
Nevermind. I decided to throw location check logic into function under app.js. Added next() method to the end of function after location variables are determined. This way, after function runs, it will proceed to any awaiting routes.
Then I tell app to use this middleware function.
app.use(checkLocation);
This works for me and the app needs. Probably not the best solution, but I got it working using this. If anyone has any other input, I'm open to it.
Related
In a DocDb stored procedure, as the first step in a process retrieving data that I'm mutating, I read and then use the data iff it matches the etag like so:
collection.readDocument(reqSelf, function(err, doc) {
if (doc._etag == requestEtag) {
// Success - want to update
} else {
// CURRENTLY: Discard the read result I just paid lots of RUs to read
// IDEALLY: check whether response `options` or similar indicates retrieval
was skipped due to doc not being present with that etag anymore
...
// ... Continue with an alternate strategy
}
});
Is there a way to pass an options to the readDocument call such that the callback will be informed "It's changed so we didn't get it, as you requested" ?
(My real problem here is that I can't find any documentation other than the readDocument undocumentation in the js-server docs)
Technically you can do that by creating a responseOptions object and passing it to the call.
function sample(selfLink, requestEtag) {
var collection = getContext().getCollection();
var responseOptions = { accessCondition: { type: "IfMatch", condition: requestEtag } };
var isAccepted = collection.readDocument(selfLink, responseOptions, function(err, doc, options) {
if(err){
throw new Error('Error thrown. Check the status code for PreconditionFailed errors');
}
var response = getContext().getResponse();
response.setBody(doc);
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
However, even if the etag you provide is not the one that the document has, you won't get an error and you will properly get the document itself back. It's just not supposed to work with that using the readDocument function in a stored procedure.
Thanks to some pushing from #Nick Chapsas, and this self-answer from #Redman I worked out that in my case I can achieve my goal (either read the current document via the self-link, or the newer one that has replaced it bearing the same id) by instead generating an Alt link within the stored procedure like so:
var docId = collection.getAltLink() + "/docs/"+req.id;
var isAccepted = collection.readDocument(docId, {}, function (err, doc, options) {
if (err) throw err;
// Will be null or not depending on whether it exists
executeUpsert(doc);
});
if (!isAccepted) throw new Error("readDocument not Accepted");
So here's the problem. I have a REST API that handles a booking creation, however, before saving the booking inside mongo it validates if there is a clash with another booking.
exports.create = function(req, res) {
var new_type = new Model(req.body);
var newBooking = new_type._doc;
//check if the new booking clashes with existing bookings
validateBooking.bookingClash(newBooking, function(clash){
if(clash == null) // no clashes, therefore save new booking
{
new_type.save(function(err, type) {
if (err)
{
res.send(err); //error saving
}
else{
res.json(type); //return saved new booking
}
});
}
else //clash with booking
{
//respond with "clashDate"
}
});
};
Here you have the validation function to check if there is a clash with bookings on the same day:
exports.bookingClash = function (booking, clash) {
//find the bookings for the same court on the same day
var courtId = (booking.courtId).toString();
Model.find({courtId: courtId, date: booking.date}, function(err, bookings) {
if(err == null && bookings == null)
{
//no bookings found so no clashes
clash(null);
}
else //bookings found
{
//for each booking found, check if the booking start hour falls between other booking hours
for(var i = 0; i<bookings.length ; i++)
{
//here is where I check if the new booking clashes with bookings that are already in the DB
{
//the new booking clashes
//return booking date of the clash
clash(clashDate); //return the clashDate in order to tell the front-end
return;
}
}
//if no clashes with bookings, return null
clash(null);
}
});
};
So, ALL of this works with one single new booking. However, now I want to be able to handle a recursive booking (booking that is made weekly). I have recreated the "create" function and call the validateBooking.bookingClash function inside a for loop.
Unfortunately, when I run this, it calls the bookingClash function perfectly, but when it reaches the line making the search in the database:
Model.find({courtId: courtId, date: booking.date}, function(err, bookings)
It does not wait for the callback and before handling the response "clash", makes i++ and continues.
How can I make it work and wait for the callback?
var array = req.body;
var clashes = [];
for(var i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}
Seems like a basic async call problem, for loops do not wait for callbacks to be called.
You could use async 'series' function for exmaple instead of the for loop. This way each find will get called after the previous one.
Mongoose also has a promise based syntax which can help you : http://mongoosejs.com/docs/promises.html
You Can use async eachSeries
async.eachSeries(users, function iterator(user, callback) {
if(something) {
//thing you want to do
callback();
} else {
callback();
}
}
Since you are using callback functions there are two ways you could try to solve this:
1) use some external library that allows you to perform an asynchronous map operation and run all the checks for each clash. Once they are done check the combined results for a clash and proceed accordingly
I would suggest using the async library
your code would look something like:
async.map(array,(entry,callback) => validateBooking.bookingClash(entry,callback),(error,mappingResults)=>{...})
2) you could try to change this function to a recursive one
`function recursiveValidation(arrayToCheck,mainCallback){
if(arrayToCheck.length === 0) {
return cb(null} // end of array without errors
}
validateBooking.bookingClash(_.head(arrayToCheck), function(clash)
{
if(clash)
{
return mainCallback(clash);
}
return recursiveValidation(_.tail(arrayToCheck),mainCallback);
}
}`
The above code is just a mockup but it should show the point.
The _ is lodash
No need to changing anything in your code except the declaration use let instead of var and your loop should work.
var array = req.body;
var clashes = [];
`
for(**let** i = 0; i<array.length;i++)
{
validateBooking.bookingClash(array[i], function(clash)
{
if(clash)
{
clashes.push(clash);
}
else{
console.log("no clash");
}
}
}`
You have to understand the difference between let and var. Also why var cannot be used for running async code inside a loop.
Learn about let: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let
I found the way to get this done after trying all of your answers.
What I had to do was this:
validateBooking.singleBooking(new_type._doc, newBookingClubId, function (clash) {
if (clash == null) // no clash
{
validatorArray.push(0);
if(validatorArray.length == array.length) //has received everything from mongo
{
console.log("Clashes: " + clashes.toString());
if(validatorArray.indexOf(1) > -1) //contains a clash
{
var error = {
code: 409,
message: "409 Conflict",
clashes: clashes
};
errorsHandler.handleError(error, res);
}
This way, I created an array called "validatorArray" that was called every time I received something back from Mongo.
This way I could easily compare the length of the array of bookings and the validatorArray length. When they were equal, it meant that it had received everything back from mongo and could send back the response.
Thanks for the help!
I am coding a simple registration form using mongoose.
I have use a javascript file to process the values of the registration form.
Here is my registrationButtonAction.js
window.onload = function() {
var User = require('/models/Mongoose Database/user_database');
// this line is causing the problem
var registerButton = document.getElementById("registerMe");
var firstName = document.getElementById("firstName");
var lastName = document.getElementById("lastName");
var usernameRegister = document.getElementById("usernameRegister");
var passwordRegister = document.getElementById("passwordRegister");
var repasswordRegister = document.getElementById("repasswordRegister");
registerButton.onclick = function () {
if(!firstName.value || !passwordRegister.value || !repasswordRegister.value || !usernameRegister.value){
alert("Enter all required fields");
}else if (passwordRegister.value != repasswordRegister.value){
alert("Passwords must match");
}else {
var newUser = new User({
username : usernameRegister.value,
password : passwordRegister.value
});
User.find({username:usernameRegister.value}, function (error, user) {
if (error) throw error;
if(user){
window.location("/register");
}else {
newUser.save(function (error) {
if(error) throw error;
});
window.location("/login");
}
// user.comparePassword(passwordRegister.value, function (error, isMatch) {
// if (error) throw error;
//
// return 1;
//})
});
}
}
}
When I comment the var User = require('/models/Mongoose Database/user_database');, all the checks are working fine inside the onclick function. But when I uncomment it, it is not recognizing the button click.
I want to know whether this is a correct way of taking values from the registration page and storing them in a mongoose database.
You are mixing server and client code. Mongoose models and Node.js functions are not accessible inside window.onload on your client.
To put it simply, you need to create a REST API to perform database operations on the server. You have all the right tools aready, just need to reorder them.
The flow would be as such :
get the values entered in the browser
call an endpoint on your server (for example /api/createUser)
in the express router, have a route called /api/createUser in which you can access your User model and perform creation/deletion/update, etc.
My suggestion would be for you to go through this tutorial which should remove your confusion and bring you up to speed in no time. Good Luck!
Also, Passport can help you with authentication, but I believe you should first learn how to build a basic API. Authentication is a tricky beast ;)
I have some code that exercises the “invalid values” setting on an element range index. In this case, I have configured a dateTime element range index on the onDate element in my database (which will apply to both XML elements and JSON properties). I’ve set that index to reject invalid values. This setting means if I try to set the value of an onDate element and it is not castable to a dateTime or is null (literal null in JSON or xsi:nil="true" in XML), my update will fail. (The opposite behavior is to completely ignore invalid values.)
I tried the following code in Server-Side JavaScript in MarkLogic 8.0-4:
'use strict';
declareUpdate();
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf', // Should throw an error
'/37107-null.json': null
};
for(var uri in inputs) {
try {
xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107'] // Collections
);
} catch(err) {
errors.push(err);
}
}
errors.length;
I would have expected my request to succeed and to end up with 1 === errors.length, because only the second insert would have failed because 'asdf' is not castable as a dateTime and it is not null. However, instead I get an XDMP-RANGEINDEX error and my transaction fails. Why doesn’t my try/catch work here?
The issue is how MarkLogic processes update transactions. Rather than actually changing the data with each xdmp.docuentInsert(…) call, MarkLogic queues up all of the updates and applies them atomically at the end of the request. (This is also why you can’t see database updates within the same transaction.) Thus, the error isn’t being thrown until after the loop has executed and the database tries to commit the queued transactions. This behavior is the same in XQuery (slightly simplified):
let $uris := (
'/37107-valid.xml',
'/37107-invalid.xml',
'/37107-null.xml'
)
let $docs := (
<onDate>{fn:current-dateTime()}</onDate>,
<onDate>asdf</onDate>,
<onDate xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
)
return
for $uri at $i in $uris
return
try {
xdmp:document-insert($uri, $docs[$i], (), ('37107'))
} catch($err) {
xdmp:log($err)
}
In order to catch the errors synchronously, you’d need to put each update into its own transaction. In general, this approach will be much slower and resource intensive than MarkLogic’s default transaction handling. However, it’s illustrative here to demonstrate what’s happening under the covers and can come in handy for specific use cases, like this one.
In the example below, I use xdmp.invokeFunction() to “call” a function in a separate transaction from the parent request. (First-class functions for the win!) This allows the updates to be fully applied (or rolled back with an error) and the calling module to see the updates (or errors). I’ve wrapped the low-level xdmp.invokeFunction() in my own applyAs() function to provide some niceties, like correctly passing function arguments to the curried function.
'use strict';
var errors = [];
var inputs = {
'/37107-valid.json': (new Date()).toISOString(),
'/37107-invalid.json': 'asdf',
'/37107-null.json': null
};
var insert = applyAs(
function(uri, value) {
return xdmp.documentInsert(
uri,
{ 'onDate': inputs[uri] },
xdmp.defaultPermissions(),
['37107']
);
},
{ isolation: 'different-transaction', transactionMode: 'update' },
'one'
);
for(var uri in inputs) {
try {
insert(uri, inputs[uri]);
} catch(err) {
errors.push(err);
}
}
errors.length; // Correctly returns 1
// <https://gist.github.com/jmakeig/0a331823ad9a458167f6>
function applyAs(fct, options, returnType /* 'many', 'one', 'iterable' (default) */) {
options = options || {};
return function() {
var params = Array.prototype.slice.call(arguments);
// Curry the function to include the params by closure.
// xdmp.invokeFunction requires that invoked functions have
// an arity of zero.
var f = (function() {
return fct.apply(null, params);
}).bind(this);
// Allow passing in user name, rather than id
if(options.user) { options.userId = xdmp.user(options.user); delete options.user; }
// Allow the functions themselves to declare their transaction mode
if(fct.transactionMode && !(options.transactionMode)) { options.transactionMode = fct.transactionMode; }
var result = xdmp.invokeFunction(f, options); // xdmp.invokeFunction returns a ValueIterator
switch(returnType) {
case 'one':
// return fn.head(result); // 8.0-5
return result.next().value;
case 'many':
return result.toArray();
case 'iterable':
default:
return result;
}
}
}
Scroll down to the bottom of this post to see a work around / possible solution.
This is probably easier just to explain in the source code with comments. The issue at hand is I cannot figure out how pseudo classes work together to perform the task I'm trying to do (explained in the code below).
The code is broken down into 3 files: lead.js, router.js, and db.js.
There are a decent amount of lines of code but most of it is comments.
[lead.js]
var bcrypt = require('bcrypt'),
validators = require('../lib/validators'),
utility = require('../lib/utility'),
document = {};
var Lead = module.exports = function (db) {
// Save a reference to the database.
this.db = db;
// Reference initial document.
// This is totally wrong, not sure how to 'send' a variable to the constructor of a class
// when I cannot add another param. Due to how I'm importing the db model, I won't know what
// the document is until I fill out the form. I've also tried 'document' instead of 'Lead.document'.
this.document = Lead.document;
// Setup the document if it exists.
// This also doesn't work.
// Basically I want to be able to set up a document variable outside of this module (line #100),
// Then pass it to this module after filling it up with values from a form.
// Then based on what's been filled in, it would fix up (trim, convert to lower case)
// some of the values automatically and default a few values that I'm not always going to pass.
if (!document) {
var salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(utility.generatePassword(), salt);
// Default values.
if (!document.meta.createdAt) { this.document.meta.createdAt = Date.now(); }
if (!document.login.password) { this.document.login.password = hash; }
if (!document.login.role) { this.document.login.role = 'User'; }
// Normalize a few values.
this.document.login.email = document.login.email.toLowerCase().trim();
this.document.contact.name.first = document.contact.name.first.trim();
this.document.contact.name.last = document.contact.name.last.trim();
this.document.contact.address.street = document.contact.address.street.trim();
this.document.contact.address.city = document.contact.address.city.trim();
this.document.contact.address.state = document.contact.address.state.trim();
this.document.contact.address.zip = document.contact.address.zip.trim();
this.document.contact.phone.home = document.contact.phone.home.trim();
}
// So in regards to the above code, the end result I'm looking for is...
// I want to append some properties to the this.document reference when the document is empty (when I'm updating it, I won't set the document),
// and on new documents it will append a few default values/normalize all the fields.
};
Lead.prototype.validate = function(fn) {
var errors = [];
// Some validation rules I cut out to make this shorter.
if (errors.length) return fn(errors);
fn();
};
Lead.prototype.save = function(fn) {
this.db.collection('leads', function(err, collection) {
if (err) { fn(new Error({message: err})); }
collection.insert(this.document, function(err, result) {
return fn(err, result);
});
});
};
---
[route.js file]
var db = require('../models/db');
app.post('/register', function(req, res) {
var data = req.body.lead || {};
// Fill the document.
var document = {
meta: {
host: req.headers.host,
referer: req.headers.referer,
createdIPAddress: req.connection.remoteAddress
},
login: {
email: data.email
},
contact: {
name: {
first: data.first,
last: data.last
},
address: {
street: data.street,
city: data.city,
state: data.state,
zip: data.zip
},
phone: {
home: data.phone
}
}
};
// Write the document.
db.lead.document = document;
db.lead.validate(function(err) {
if (err) {
req.session.error = err;
return res.redirect('back');
}
db.lead.save(function(err) {
res.redirect('/register/success');
});
});
});
---
[db.js]
var mongodb = require('mongodb'),
server = new mongodb.Server('localhost', 27017),
connection = new mongodb.Db('test', server);
connection.open(function(err, db) {});
module.exports = {
lead: new (require('./lead'))(connection)
};
When I run this, my validator always reports that the password is empty which makes sense. I'm sending the document initially to the class with an empty password (the password is randomly generated, not a form field) -- the problem is I have no idea what to do with the if (!document) ... code block to actually set the this.document properly.
I hope between the comments and code you can get an idea of what I'm trying to do. I've been stuck on this for a while.
EDIT
I changed the flow of it a bit to get a solution.
In the db.js, I exported the connection rather than instantiating the lead (and future models) directly.
In the router.js file, I require the db and lead file, then pass both the db connection and the document in the constructor of the Lead. Ex.
var lead = new Lead(db, document);
In the lead.js file, it becomes as simple as doing this.document = document (same as the db). When I submit a new lead, the values I don't send over from router.js get appended to the document (the created date, a random password, etc.) and everything is good.
Is this a decent way of handling this, or is there a better way to refactor this?
This is completely wrong way even if make this code work as you want. In this example you have singleton lead. By requesting /register url you want to set 'document' field to this singleton . (IMPORTANT) But requests work asynchronously. Absolutely no guarantee that you save the document, which has just validate. Because new request may overwrite it in lead object. You need to do this logic in request scope. One scope for one request. Not one for all.
You need to read up on object-oriented programming in Javascript.
The anonymous function you're defining near the top of your code is the constructor function, so with respect to the document property you want that is currently uninitialized, just type something like:
this.document = null;
Then some time later when you create a new object using this constructor, like so:
var myLead = new Lead(dbConnection);
You'll have the myLead.document property.
There are many other things wrong with your code, though. Why are you assuming that there is a global document variable with relevant data visible in your library when it's defined as {}? The code in that if statement at the end of your constructor should be run when the document property is set in your other file below, and should only expect this.document to exist.
You set var document = {} initially, and {} is not falsy. Better would be to set as a starting value document = null and then after checking for !document set document = {} before assigning whatever properties you need.