Concatenate express route parameter and file extension - node.js

In my Express app I create snapshots whose details I store in MongoDB. The actual snapshot files are stored in the snapshots folder under their _id, eg /snapshots/575fe038a84ca8e42f2372da.png.
These snapshots can currently be loaded by a user by navigating to the folder and id, in their browser, i.e. /snapshots/575fe038a84ca8e42f2372da, which returns the image file. However I think to be more intuitive the url path should include the file extension; i.e. the user should have to put in /snapshots/575fe038a84ca8e42f2372da .PNG to get the file.
This is what I have currently:
router.get('/:shotID', function(req, res, next) {
// Checks if shot exists in DB
Shots.findOne({
_id: req.params.shotID // More conditions might get put here, e.g. user restrictions
}, (err, result) => {
if (err) {
res.status(404).res.end();
return;
}
var file = fs.createReadStream(`./snapshots/${req.params.shotID}.png`);
file.pipe(res);
});
});
How can I incorporate the user putting in the file extension in this path?

You can provide a custom regular expression to match a named parameter, which can also contain a file extension:
router.get('/:shotID(?:([a-fA-F0-9]{24})\.png$)', ...);
For the URL path /snapshots/575fe038a84ca8e42f2372da.png, req.params.shotID will be 575fe038a84ca8e42f2372da.
If you want to match both with and without .png, you can use this:
router.get('/:shotID(?:([a-f0-9]{24})(?:\.png)?$)', ...);

Related

How to dynamically create routes in Node.js express. ex-- my.app.com/ghi5938

In my webapp, I give users the ability to upload a photo. When that photo is uploaded to my server, I would like to dynamically create a route that they can click on and go view that image. Im not sure where to start looking into this and the few articles I found didnt seem to answer my question. How do I dynamically create new routes in node.js?
Typically, you don't create new routes in Express once you've already initialized the server. Instead, you create a single route at the beginning with some sort of parameter to it. The parameter can either be part of the URL or part of the query string. Then, you have a single, pre-defined route that handle the URL with the parameter in it. The parameter varies per each request and the code in the single route uses the parameter to decide which content to send.
So, for your photo upload, you would accept the upload and then allocate that image some sort of unique imageID. That imageID will then put put in the URL to serve that specific image. If the imageID for a just-uploaded image was 123456789, then it could look like this:
// incoming URL path /image/xxxxxx
// where xxxxxx is the imageID
app.get("/image/:imageID", (req, res) => {
let imageID = req.params.imageID;
// in your server file system, find the image for imageID and build a path to it
let imagePath = findImageID(imageID);
if (imagePath) {
res.sendFile(imagePath);
} else {
res.sendStatus(404);
}
});
An imageID just needs to be something that you can lookup and turn into an actual image filepath. It's really up to you how you do it. It can be the actual filename of the image. It can be an ID you look up in your database where you store the path to the image. Or, you can invent some other scheme. There just needs to be a way to create the imageID when your image is uploaded and a way to implement findImageID(imageID) that gives you a path to the image from an imageID.
For fixed resources like images, I would prefer the scheme above where the imageID is part of the path since it really is part of the URL resource specification. But, technically, it could also be done with a query string:
// image?id=xxxxxxx
// where xxxxxx is the imageID
app.get("/image", (req, res) => {
let imageID = req.query.id;
// in your server file system, find the image for imageID and build a path to it
let imagePath = findImageID(imageID);
if (imagePath) {
res.sendFile(imagePath);
} else {
res.sendStatus(404);
}
});

Dynamically created s3 folders are not showing in listObjects

I'm using signed url to upload a file from my react application to a s3 bucket. I specify the path as part of my Key and the folders are getting created properly:
let params = {
Bucket: vars.aws.bucket,
Key: `${req.body.path}/${req.body.fileName}`,
Expires: 5000,
ACL: 'public-read-write',
ContentType: req.body.fileType,
};
s3.getSignedUrl('putObject', params, (err, data)=>{...
However, when I use s3.listObject, the folders that are created this way are not getting returned. Here is my node api code:
const getFiles = (req, res) => {
let params = {
s3Params:{
Bucket: vars.aws.bucket,
Delimiter: '',
Prefix: req.body.path
}
}
s3.listObjects(params.s3Params, function (err, data) {
if (err) {
res.status(401).json(err);
} else {
res.status(200).json(data);
}
});
}
The folders that are getting created through the portal are showing in the returned object properly. Is there any attribute I need to set as part of generating the signed URL to make the folder recognized as an object?
I specify the path as part of my Key and the folders are getting created properly
Actually, they aren't.
Rule 1: The console displays a folder icon for the folder foo because one or more objects exists in the bucket with the prefix foo/.
The console appears to allow you to create "folders," but that isn't what's happening when you do that. If you create a folder named foo in the console, what actually happens is that an ordinary object, zero bytes in length, with the name foo/ is created. Because this now means there is at least one object that exists in the bucket with the prefix foo/, a folder is displayed in the console (see Rule 1).
But that folder is not really a folder. It's just a feature of the console interacting with another feature of the console. You can actually delete the foo/ object using the API/SDK and nothing happens, because the console till shows that folder as long as there remains at least one object in the bucket with the prefix foo/. (Deleting the folder in the console sends delete requests for all objects with that prefix. Deleting the dummy object via the API does not.)
In short, the behavior you are observing is normal.
If you set the delimiter to /, then the listObjects response will include CommonPrefixes -- and this is where you should be looking if you want to see "folders." Objects ending with / are just the dummy objects the console creates. CommonPrefixes does not depend on these.

create stackoverflow like url for search in node.js and javascript

I want to change my page's URL with search item like stackoverflow and also display the page with the newly created URL,every time I hit the URL in new tab.
Like if I search using 'natures photography' which retrieves a photo named 'sunset in a village' then the URL should be like this: http://localhost:8080/sunset-in-a-village
How to do it with Node.js / Javascript?
if I search using 'natures photography' which retrieves a photo named 'sunset in a village'
What do you mean exactly? Search often return not one item.
I suggest that result is set and have url like /seach/natures-photography and one of finded item have url /photo/sunset-in-a-village
app.get('/search/:query', function(req, res, next) {
// client send already dashed and lowercase query
var query = req.params.query; // natures-photography
// render result page. Each image have url like /photo/:dashed-lowercase-name
})
...
app.get('/photo/:name', function(req, res, next) {
var name = req.params.name; // sunset-in-a-village
name = name.replace('-', ' '); // remove dash from name and find image by original lowercase-name
// render chosen photo-page
})

Access uploaded image in Sails.js backend project

I am trying to do an upload and then accessing the image. The upload is going well, uploading the image to assets/images, but when I try to access the image from the browser like http://localhost:1337/images/image-name.jpg it gives me 404. I use Sails.js only for backend purposes - for API and the project is created with --no-front-end option. My front end is on AngularJS.
My upload function:
avatarUpload: function(req, res) {
req.file('avatar').upload({
// don't allow the total upload size to exceed ~10MB
maxBytes: 10000000,
dirname: '../../assets/images'
}, function whenDone(err, uploadedFiles) {
console.log(uploadedFiles);
if (err) {
return res.negotiate(err);
}
// If no files were uploaded, respond with an error.
if (uploadedFiles.length === 0) {
return res.badRequest('No file was uploaded');
}
// Save the "fd" and the url where the avatar for a user can be accessed
User
.update(req.userId, {
// Generate a unique URL where the avatar can be downloaded.
avatarUrl: require('util').format('%s/user/avatar/%s', sails.getBaseUrl(), req.userId),
// Grab the first file and use it's `fd` (file descriptor)
avatarFd: uploadedFiles[0].fd
})
.exec(function (err){
if (err) return res.negotiate(err);
return res.ok();
});
});
}
I see the image in the assets/images folder - something like this - 54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg
http://localhost:1337/assets/images/54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg - gives 404
http://localhost:1337/images/54cd1fc5-89e8-477d-84e4-dd5fd048abc0.jpg - gives 404
This happens because the resources your application accesses are not accessed directly from the assets directory but the .tmp directory in the project root.
The assets are copied to the .tmp directory when sails is lifted, so anything added after the lift isn't present in .tmp.
What I usually do is upload to .tmp and copy the file to assets on completion. This way assets isn't polluted in case the upload fails for any reason.
Let us know if this works. Good luck!
Update
Found a relevant link for this.

Using express to send a modified file

I'd like to deliver a modified version of a file based on the URL route.
app.get('/file/:name/file.cfg', function (req, res) {
res.send(<the file file.cfg piped through some sed command involving req.params.name>)
});
The point being, the response should not be of type text/html, it should be the same MIME type as normal (which may still be wrong, but at least it works).
I am aware of security issues with this approach. The question is about how to do this with express and node.js, I'll be sure to put in lots of code to sanitize input. Better yet, never hit the shell (easy enough to use JS rather than e.g. sed to do the transformation)
I believe the answer is something along these lines:
app.get('/file/:name/file.cfg', function (req, res) {
fs.readFile('../dir/file.cfg', function(err, data) {
if (err) {
res.send(404);
} else {
res.contentType('text/cfg'); // Or some other more appropriate value
transform(data); // use imagination please, replace with custom code
res.send(data)
}
});
});
The cfg file I happen to be working with is (this is dump of node repl):
> express.static.mime.lookup("../kickstart/ks.cfg")
'application/octet-stream'
>
Quite the generic option, I'll say. Anaconda would probably appreciate it.
What's a normal filetype for you?
Set the mimetype using (docs):
app.get('/file/:name/file.cfg', function (req, res) {
res.set('content-type', 'text/plain');
res.send(<the file file.cfg piped through some sed command involving req.params.name>)
});
If you want to detect the file's mime type use node-mime
To send a file from disk use res.sendfile which sets the mimetype based on the extension
res.sendfile(path, [options], [fn]])
Transfer the file at the given path.
Automatically defaults the Content-Type response header field based on the filename's extension. The callback fn(err) is invoked when the transfer is complete or when an error occurs.
app.get('/file/:name/file.cfg', function (req, res) {
var path = './storage/' + req.params.name + '.cfg';
if (!fs.existsSync(path)) res.status(404).send('Not found');
else res.sendfile(path);
});
You can also force the browser to download the file with res.download. express has much more to offer, have a look at the docs.

Resources