I am currently routing every page to my pagesController that cannot be found in previous routes by including this line in routes.js:
this.match('/:page', { controller: 'pages', action: 'show' });
I had the idea to let my PagesController handle serving a 404 if not found:
PagesController.show = function() {
var page = this.param('page');
if(page != undefined){
page = page.replace(".html","");
try {
this.render("./"+page);
} catch(error){ //Failed to look up view -- not working at the moment =(
this.redirect({action : "404"});
};
}
return;
};
But my idea is failing. The error cannot be caught, so the fatal still gets served. Should I append a fn to the render call? With what arguments? How does it work? (/simple questions).
It might look something like this:
PagesController.show = function() {
var self = this;
var page = this.param('page');
if (page !== undefined) {
page = page.replace('.html', '');
this.render("./" + page, function(err, html) {
if (! err)
return self.res.send(html);
self.redirect({ action : '404' });
});
} else {
// this will probably never be called, because `page`
// won't be undefined, but still...
this.redirect({ action : '404' });
}
};
Related
I'm trying to prevent the user to save a piece if it doesn't achieve some requirements.
Currently I'm doing it like this:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
let error = "";
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
self.apos.notify(req, "Check the compatibility between parent event and subevents", { type: "error" });
error = "Subevents are not compatible with parent event";
}
callback(error);
};
This works but the problem is it shows 2 errors notifications (the default and my custom), 1 because of callback(error) and 1 because of apos.notify.
Any idea how to stop the item of being saved and only show my notification?
Thanks in advance.
UPDATE 1:
As Tom pointed out, my code looks like this now:
// lib/modules/events/public/js/editor-modal.js
apos.define('events-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
apos.notify('A message suitable for this case.', { type: 'error' });
} else {
apos.notify('A generic error message.', { type: 'error' });
}
};
}
});
// lib/modules/events/index.js
var superPushAssets = self.pushAssets;
self.pushAssets = function() {
superPushAssets();
self.pushAsset("script", "editor-modal", { when: "user" });
};
self.beforeSave = async function(req, piece, options, callback) {
return callback("incompatible")
};
For testing purposes I'm just returning the error in beforeSave. The problem is that an exception is being thrown in the browser console and the modal is not properly rendered again. Here's a screenshot about what I'm talking:
I'm trying to debug it and understand what's happening but no clue yet.
In your server-side code:
self.beforeSave = function(req, piece, options, callback) {
let success = true;
if (Array.isArray(piece._subevents) && piece._subevents.length) {
success = self.checkDateAndTimeCompabilitiyWithChildren(piece);
}
if (!success) {
return callback('incompatible');
}
return callback(null);
};
And on the browser side:
// in lib/modules/my-pieces-module/public/js/editor-modal.js
apos.define('my-pieces-module-editor-modal', {
extend: 'apostrophe-pieces-editor-modal',
construct: function(self, options) {
self.getErrorMessage = function(err) {
if (err === 'incompatible') {
return 'A message suitable for this case.';
} else {
return 'A generic error message.';
}
};
}
});
If the error reported by the callback is a string, it is passed to the browser. The browser can then recognize that case and handle it specially. 'my-pieces-module-editor-modal' should be substituted with the name of your pieces module followed by -editor-modal.
I am new to Apostrophe and trying to create a contact us form with file attachment in Apostrophe by following the tutorial.
https://apostrophecms.org/docs/tutorials/intermediate/forms.html
I have also created the attachment field in my index.js and it works fine from the admin panel.
Now, I am trying to create my own html for the form with file submission.
// in lib/modules/contact-form-widgets/public/js/always.js
apos.define('contact-form-widgets', {
extend: 'apostrophe-widgets',
construct: function(self, options) {
self.play = function($widget, data, options) {
var $form = $widget.find('[data-contact-form]');
var schema = self.options.submitSchema;
var piece = _.cloneDeep(self.options.piece);
return apos.schemas.populate($form, self.schema, self.piece, function(err) {
if (err) {
alert('A problem occurred setting up the contact form.');
return;
}
enableSubmit();
});
function enableSubmit() {
$form.on('submit', function() {
submit();
//I can access file here
// console.log($form.find('file'))
return false;
});
}
function submit() {
return async.series([
convert,
submitToServer
], function(err) {
if (err) {
alert('Something was not right. Please review your submission.');
} else {
// Replace the form with its formerly hidden thank you message
$form.replaceWith($form.find('[data-thank-you]'));
}
});
function convert(callback) {
return apos.schemas.convert($form, schema, piece, callback);
}
function submitToServer(callback) {
return self.api('submit', piece, function(data) {
alert("I AM AT SUBMIT API ")
if (data.status === 'ok') {
// All is well
return callback(null);
}
// API-level error
return callback('error');
}, function(err) {
// Transport-level error
alert("I AM HERE AT API ERROR")
return callback(err);
});
}
}
};
}
});
//and my widget.html is
<div class="form-group">
<input name="custom-file" type="file">
</div>
When I run this I get following errors
user.js:310 Uncaught TypeError: Cannot read property 'serialize' of undefined
at Object.self.getArea (user.js:310)
at Object.self.getSingleton (user.js:303)
at Object.convert (user.js:686)
at user.js:164
at async.js:181
at iterate (async.js:262)
at async.js:274
at async.js:44
at setImmediate.js:27
at runIfPresent (setImmediate.js:46)
My question is, how do I handle file submission? Is there any better approach for this?
This is much easier to do using the apostrophe-pieces-submit-widgets module, which allows you to define a schema for what the user can submit. You can include a field of type attachment in that, and this is demonstrated in the README.
I'm newish to NodeJS and ExpressJS and am trying to get the session management flow in main.js worked out.
What I currently have that's working:
app.get('*', function(req, res){
var page = getPage();
session_.initSession( req, res, function( ){
loggedIn = false;
if( req.session && typeof req.session.username !== "undefined" ){
loggedIn = true;
userFName = req.session.first_name;
userLName = req.session.last_name;
}
if( !loggedIn ){
res.render('pages/login', { message: "<div class='notice centered' style='width: 40%;'>Please login</div>" });
returnFlag = true;
return;
} else {
if (page.length < 1){
// render index page here ...
returnFlag = true;
return;
}
// render 'test' page
if( page == 'test' ){
// do test functions here...
returnFlag = true;
return;
}
}
});
if( returnFlag == true ){
return;
}
res.render('partials/home', { message: "404 not found (unknown page GET request)" });
return;
});
app.post('*', files, function(req, res){
var page = getPage();
if( page == 'test' ){
// do test functions here...
returnFlag = true;
return;
}
if( returnFlag == true ){
return;
}
res.render('partials/home', { message: "404 not found (unknown page POST request)" });
return;
});
The problem with this is that POST requests are being processed even when no session is in place. I've tried adding app.all/use blocks above the app.get and app.post code blocks to set up a session, but then the app.get/post blocks were not getting processed. What is the optimal way to architect this so all requests get filtered through session management and then on to page request blocks if a proper session is in place?
A Middleware function in Express is what you're looking for.
Middleware functions are functions that have access to the request object (req), the response object (res), and the next middleware function in the application’s request-response cycle. The next middleware function is commonly denoted by a variable named next.
Learn more from Express's Documentation - here
A Middleware can be connected like a chain and is processed in the sequence you write it in. As long as you're executing the 'Next' function, you can link as many as you would like.
So before the POST requests are actually processed, you can exit out and redirect the user, otherwise execute the next function to continue processing.
Here is an example of how you could simplify your code.
function _sessionAuthorization(req, res, next) {
if(typeof req.session.username == "undefined") {
return res.redirect("/login");
} else {
next();
}
}
app.get('*', function(req, res){
res.render('partials/home', { message: "404 not found (unknown page GET request)" });
});
app.post('*', files, _sessionAuthorization, function(req, res){
res.render('partials/home', { message: "404 not found (unknown page POST request)" });
});
I have a code in controller like below:
BASE.APP.post('/uploadFile/:request/:file', function(req, res, next) {
var url = req.usersession.webipAddress;
var path = 'uploads/' + req.params.file;
var formData = new BASE.FormData();
formData.append('fileNameUnique', req.params.file);
formData.append('file', BASE.FS.createReadStream(path));
// console.log(formData);
formData.submit(url + '/service/uploadFile/', function(err, response) {
// console.log(response.statusCode);
res.send(response.statusCode);
});
});
I want to interrupt file upload if status == "cancel", is that
possible?
If status == "cancel" try this:
req.pause()
res.status = 400;
res.end('Upload cancelled');
I don't know much about the way your code works or your workflow. This is a generic soln that most likely will work. Add more code in the question if you want a more specific soln.
try {
if (status === 'cancel') {
throw new Error("Stopping file upload...");
}
} catch (e) {
res.end("the upload was cancelled because of error: " + e.toString());
}
Save the value returned from formData.submit and use that as a handle to call request.abort on.
E.g.
BASE.APP.post('/uploadFile/:request/:file', function(req, res, next) {
var formData = new BASE.FormData();
// ...
var formSubmitRequest = formData.submit(url + '/service/uploadFile/', function(err, response) {
res.send(response.statusCode);
});
statusChanger.on('status-change', function(status) {
if (status === "cancel" && formSubmitRequest) {
formSubmitRequest.abort();
res.send(524);
}
});
}
From https://github.com/form-data/form-data:
For more advanced request manipulations submit() method returns http.ClientRequest object
From https://nodejs.org/api/http.html#http_request_abort:
request.abort()#
Added in: v0.3.8
Marks the request as aborting. Calling this will cause remaining data in the response to be dropped and the socket to be destroyed.
I am making an express app with ejs and mongoose.
I am getting this error:
Error: Failed to lookup view "error" in views directory "/Users/ben/Documents/csMSc/web/site/app/views"
at EventEmitter.app.render (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/application.js:555:17)
at ServerResponse.res.render (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:938:7)
at module.exports (/Users/ben/Documents/csMSc/web/site/app/app.js:94:7)
at Layer.handle_error (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/layer.js:58:5)
at trim_prefix (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:300:13)
at /Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:270:7
at Function.proto.process_params (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:321:12)
at IncomingMessage.next (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:261:10)
at fn (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:933:25)
at EventEmitter.app.render (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/application.js:557:14)
at ServerResponse.res.render (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:938:7)
at app.use.res.render.message (/Users/ben/Documents/csMSc/web/site/app/app.js:83:9)
at Layer.handle_error (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/layer.js:58:5)
at trim_prefix (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:300:13)
at /Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:270:7
at Function.proto.process_params (/Users/ben/Documents/csMSsc/web/site/app/node_modules/express/lib/router/index.js:321:12)
from two calls to res.render() where the data that is passed in comes back from a mongoose query e.g.:
if(req.query.author !== undefined) {
var author = req.query.author;
Post.find().where('author').equals(author).sort({ created: -1 }).limit(10).exec(function(err, authorsPosts) {
if (err) return res.send("error");
if(authorsPosts.length==0) {
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: authorsPosts,
error: "Sorry there are no posts with that tag."
});
} else {
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: authorsPosts
});
}
});
}
and the other one is the same but with a query of
Post.find( { tags : { $elemMatch: { $in : tagList } } } ).limit(10).exec(function(err, taggedPosts) {
However all my other render calls on this view are working just fine including one later in the same function:
//or just latest
Post.find().sort({ created: 1 }).limit(10).exec(function(err, latestPosts) {
if (err) return res.send(err);
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: latestPosts
});
});
where Im pretty sure latestPosts is in exactly the same format as authorsPosts in the one above.
There are no calls to render a view called error.
The error data passed to some of the calls to res.render('pages/index') above is passed using a custom filter
//custom ejs filter, sets to default value if data not supplied
ejs.filters.get = function(obj, prop, def) {
return obj[prop] === undefined ? def : obj[prop];
};
which appears in the file app/views/pages/index.ejs as
<p><%=: locals | get:'error','' %> </p>
My ejs setup looks like:
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.engine('ejs', require('ejs').renderFile);
heres the whole offending route function in all its horrible glory
router.get('/', function(req, res, next) {
var accountController = new AccountController(User, req.session);
console.log("here1");
var signedIn = accountController.session.userProfileModel !== undefined ? 1 : 0;
console.log("here2");
//Author search
if(req.query.author !== undefined) {
var author = req.query.author;
Post.find().where('author').equals(author).sort({ created: -1 }).limit(10).exec(function(err, authorsPosts) {
if (err) return res.send("error");
console.log("\n\nAuthorsPosts:" +authorsPosts);
console.log("\n\authorsPosts.length: " +authorsPosts.length);
console.log("authors post.constructor = " +authorsPosts.constructor);
if(authorsPosts.length==0) {
console.log("length=0");
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: authorsPosts,
error: "Sorry there are no posts with that tag."
});
} else {
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: authorsPosts
});
}
});
}
//Tag search
if(req.query.filter !== undefined) {
var tagList = req.query.filter.constructor == Array ? req.query.filter : req.query.filter.split(",");
Post.find( { tags : { $elemMatch: { $in : tagList } } } ).limit(10).exec(function(err, taggedPosts) {
if (err) return res.send("error");
console.log("\n\taggedPosts.length: " +taggedPosts.length);
if(taggedPosts.length==0) {
console.log("length=0");
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: taggedPosts,
error: "Sorry there are no posts with that tag."
});
} else {
console.log("\n\ntaggedPosts:\n\n" +taggedPosts);
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: taggedPosts
});
}
});
}
//or just latest
Post.find().sort({ created: 1 }).limit(10).exec(function(err, latestPosts) {
if (err) return res.send(err);
res.render('pages/index', {
viewDataSignStatus: viewDataSignedIn[signedIn],
previews: latestPosts
});
});
});
Whats more, it isn't totally not working. When the code gets to those render calls it throws the error and the page usually freezes, you cant click any links, then if I reload once or twice it will work, and render the template with the correct data.
Also when I travel to '/' with on of these query strings e.g. GET /?filter=Marc%20Behrens it gets as far printing all the returned posts and then throws the error.
Thank you!
Edit: thanks Alex Ford.
new error is:
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (http.js:690:11)
at ServerResponse.header (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:700:10)
at ServerResponse.send (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:154:12)
at ServerResponse.json (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:240:15)
at ServerResponse.send (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/response.js:142:21)
at module.exports (/Users/ben/Documents/csMSc/web/site/app/app.js:100:9)
at Layer.handle_error (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/layer.js:58:5)
at trim_prefix (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:300:13)
at /Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:270:7
at Function.proto.process_params (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:321:12)
at next (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:261:10)
at Layer.handle_error (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/layer.js:60:5)
at trim_prefix (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:300:13)
at /Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:270:7
at Function.proto.process_params (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:321:12)
at next (/Users/ben/Documents/csMSc/web/site/app/node_modules/express/lib/router/index.js:261:10)
My guess for the "headers already sent" error is that your // or just latest code is running even when one of the above if statements runs. If that's the case then you'll surely be making multiple calls to res.render or res.send. Try this:
router.get('/', function(req, res, next) {
/* ... */
//Author search
if(req.query.author !== undefined) {
/* ... */
if(authorsPosts.length==0) {
res.render(/*...*/);
} else {
res.render(/*...*/);
}
/* ... */
}
//Tag search
else if(req.query.filter !== undefined) {
/* ... */
if(taggedPosts.length==0) {
res.render(/*...*/);
} else {
res.render(/*...*/);
}
/* ... */
}
//or just latest
else {
res.render(/*...*/);
}
});
You're having an error and the default express error handler is trying to display the error to the user by rendering the error view. Did you use a generator to generate your initial app? If so, did you delete the error view from the views directory?
Either change the default express error handler (likely in your app.js) so that it just spits out the raw error instead of trying to render it into a nice little view, or add the error view that it's looking for.
The error handler generated by express-cli usually looks something like this:
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
Notice it's trying to render('error' and failing to find a view named "error". What makes it an error handler is the simple fact that it accepts 4 arguments, the first being the error. Express knows that if an error is caught then it should jump down to that handler.
As far as your actual error that is causing the error handler to run, I'm not sure. You'll need to fix that so that the error shows properly and then you'll be able to debug from there.
I had this error, using the jsx engine though. I fixed it by making sure the file I had in my view folder was the right extension *.jsx in my case. I did have it as just index.js.
what i didn't do right is omitting the extension .html in the routes directory.
router.get('/', function(req, res, next) {
res.render('index'); //change from index to index.html
});
router.get('/', function(req, res, next) {
res.render('index.html');
});