Merge 629ee1a5c8
into bcd6965b8f
This commit is contained in:
commit
e67d757fe8
32
README.md
32
README.md
|
@ -17,6 +17,38 @@ storage services including:
|
|||
|
||||
For more details on the architecture of the module, please see the introduction section of the [blog post](https://strongloop.com/strongblog/managing-nodejs-loopback-storage-service-provider/).
|
||||
|
||||
## Use
|
||||
Now you can use Container's name with slash! If you want to create a directory, like `this/isMy/newContainer`, you have to use the char `%2F` instead of `/`, so your Container's name going to be `this%2FisMy%2FnewContainer`.
|
||||
|
||||
## URL Example
|
||||
Syntax
|
||||
```
|
||||
[POST] <<YOUR_URL>>:<<YOUR_PORT>>/api/Containers/<<CONTAINER_NAME>>/
|
||||
[POST] <<YOUR_URL>>:<<YOUR_PORT>>/api/Containers/<<CONTAINER_NAME>>/upload (For upload file)
|
||||
```
|
||||
|
||||
Example
|
||||
```
|
||||
[POST] http://example.com:3000/api/Containers/images%2Fprofile%2Fpersonal/
|
||||
[POST] http://example.com:3000/api/Containers/images%2Fprofile%2Fpersonal/upload (For upload file)
|
||||
```
|
||||
|
||||
## Add option to your dataSources.json
|
||||
If you want a default name only for the upload images (not files), you have to add `defaultImageName` to your Container options.
|
||||
**datasources.json**
|
||||
```
|
||||
[...]
|
||||
"container": {
|
||||
"name": "container",
|
||||
"connector": "loopback-component-storage",
|
||||
"provider": "filesystem",
|
||||
"maxFileSize": "10485760",
|
||||
"root": "./storage",
|
||||
"defaultImageName": "photo"
|
||||
}
|
||||
[...]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See https://github.com/strongloop/loopback-example-storage.
|
||||
|
|
|
@ -16,7 +16,8 @@ var fs = require('fs'),
|
|||
stream = require('stream'),
|
||||
async = require('async'),
|
||||
File = require('./file').File,
|
||||
Container = require('./container').Container;
|
||||
Container = require('./container').Container,
|
||||
mkdirp = require('mkdirp');
|
||||
|
||||
module.exports.storage = module.exports; // To make it consistent with pkgcloud
|
||||
|
||||
|
@ -43,6 +44,7 @@ function FileSystemProvider(options) {
|
|||
var namePattern = new RegExp('[^' + path.sep + '/]+');
|
||||
// To detect any file/directory containing dotdot paths
|
||||
var containsDotDotPaths = /(^|[\\\/])\.\.([\\\/]|$)/;
|
||||
var containsDotDotPathsContainer = /(^)\.\.($)/;
|
||||
|
||||
function validateName(name, cb) {
|
||||
if (!name || containsDotDotPaths.test(name)) {
|
||||
|
@ -65,6 +67,18 @@ function validateName(name, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
function validateContainerName(name, cb) {
|
||||
if (!name || containsDotDotPathsContainer.test(name)) {
|
||||
cb && process.nextTick(cb.bind(null, new Error(g.f('Invalid name: %s', name))));
|
||||
if (!cb) {
|
||||
console.error(g.f('{{FileSystemProvider}}: Invalid name: %s', name));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function streamError(errStream, err, cb) {
|
||||
process.nextTick(function() {
|
||||
errStream.emit('error', err);
|
||||
|
@ -124,25 +138,29 @@ FileSystemProvider.prototype.getContainers = function(cb) {
|
|||
FileSystemProvider.prototype.createContainer = function(options, cb) {
|
||||
var self = this;
|
||||
var name = options.name;
|
||||
var hasSlash = name.search('%2F');
|
||||
name = (hasSlash != -1 ? name.replace(/%2F/gi, '/') : name);
|
||||
var dir = path.join(this.root, name);
|
||||
validateName(name, cb) && fs.mkdir(dir, options, function(err) {
|
||||
|
||||
mkdirp(dir, function(err) {
|
||||
if (err) {
|
||||
return cb && cb(err);
|
||||
cb(err);
|
||||
} else {
|
||||
fs.stat(dir, function(err, stat) {
|
||||
var container = null;
|
||||
if (!err) {
|
||||
var props = {name: name};
|
||||
populateMetadata(stat, props);
|
||||
container = new Container(self, props);
|
||||
}
|
||||
cb(err, container);
|
||||
});
|
||||
}
|
||||
fs.stat(dir, function(err, stat) {
|
||||
var container = null;
|
||||
if (!err) {
|
||||
var props = {name: name};
|
||||
populateMetadata(stat, props);
|
||||
container = new Container(self, props);
|
||||
}
|
||||
cb && cb(err, container);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
FileSystemProvider.prototype.destroyContainer = function(containerName, cb) {
|
||||
if (!validateName(containerName, cb)) return;
|
||||
if (!validateContainerName(containerName, cb)) return;
|
||||
|
||||
var dir = path.join(this.root, containerName);
|
||||
fs.readdir(dir, function(err, files) {
|
||||
|
@ -164,7 +182,7 @@ FileSystemProvider.prototype.destroyContainer = function(containerName, cb) {
|
|||
|
||||
FileSystemProvider.prototype.getContainer = function(containerName, cb) {
|
||||
var self = this;
|
||||
if (!validateName(containerName, cb)) return;
|
||||
if (!validateContainerName(containerName, cb)) return;
|
||||
var dir = path.join(this.root, containerName);
|
||||
fs.stat(dir, function(err, stat) {
|
||||
var container = null;
|
||||
|
@ -180,12 +198,8 @@ FileSystemProvider.prototype.getContainer = function(containerName, cb) {
|
|||
// File related functions
|
||||
FileSystemProvider.prototype.upload = function(options, cb) {
|
||||
var container = options.container;
|
||||
if (!validateName(container)) {
|
||||
return writeStreamError(
|
||||
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', container)),
|
||||
cb
|
||||
);
|
||||
}
|
||||
var hasSlash = container.search('%2F');
|
||||
container = (hasSlash != -1 ? container.replace(/%2F/gi, '/') : container);
|
||||
var file = options.remote;
|
||||
if (!validateName(file)) {
|
||||
return writeStreamError(
|
||||
|
@ -194,8 +208,8 @@ FileSystemProvider.prototype.upload = function(options, cb) {
|
|||
);
|
||||
}
|
||||
var filePath = path.join(this.root, container, file);
|
||||
|
||||
var fileOpts = {flags: options.flags || 'w+',
|
||||
var fileOpts = {
|
||||
flags: options.flags || 'w+',
|
||||
encoding: options.encoding || null,
|
||||
mode: options.mode || parseInt('0666', 8),
|
||||
};
|
||||
|
@ -216,7 +230,7 @@ FileSystemProvider.prototype.upload = function(options, cb) {
|
|||
|
||||
FileSystemProvider.prototype.download = function(options, cb) {
|
||||
var container = options.container;
|
||||
if (!validateName(container, cb)) {
|
||||
if (!validateContainerName(container, cb)) {
|
||||
return readStreamError(
|
||||
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', container)),
|
||||
cb
|
||||
|
@ -253,7 +267,7 @@ FileSystemProvider.prototype.getFiles = function(container, options, cb) {
|
|||
options = false;
|
||||
}
|
||||
var self = this;
|
||||
if (!validateName(container, cb)) return;
|
||||
if (!validateContainerName(container, cb)) return;
|
||||
var dir = path.join(this.root, container);
|
||||
fs.readdir(dir, function(err, entries) {
|
||||
entries = entries || [];
|
||||
|
@ -282,7 +296,6 @@ FileSystemProvider.prototype.getFiles = function(container, options, cb) {
|
|||
|
||||
FileSystemProvider.prototype.getFile = function(container, file, cb) {
|
||||
var self = this;
|
||||
if (!validateName(container, cb)) return;
|
||||
if (!validateName(file, cb)) return;
|
||||
var filePath = path.join(this.root, container, file);
|
||||
fs.stat(filePath, function(err, stat) {
|
||||
|
@ -303,7 +316,7 @@ FileSystemProvider.prototype.getUrl = function(options) {
|
|||
};
|
||||
|
||||
FileSystemProvider.prototype.removeFile = function(container, file, cb) {
|
||||
if (!validateName(container, cb)) return;
|
||||
if (!validateContainerName(container, cb)) return;
|
||||
if (!validateName(file, cb)) return;
|
||||
|
||||
var filePath = path.join(this.root, container, file);
|
||||
|
|
|
@ -11,9 +11,29 @@ var IncomingForm = require('formidable');
|
|||
var StringDecoder = require('string_decoder').StringDecoder;
|
||||
var path = require('path');
|
||||
var uuid = require('uuid');
|
||||
var fs = require('fs');
|
||||
var defaultOptions = function() {
|
||||
var dataSources = path.join(__dirname, '../../../server/datasources.json');
|
||||
fs.stat(dataSources, function(err, stats) {
|
||||
if (!err) {
|
||||
return require(dataSources).container;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var defaultOptions = {
|
||||
maxFileSize: 10 * 1024 * 1024, // 10 MB
|
||||
var isImage = function(ext) {
|
||||
switch (ext) {
|
||||
case '.jpg':
|
||||
case '.jpeg':
|
||||
case '.png':
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -32,7 +52,7 @@ exports.upload = function(provider, req, res, options, cb) {
|
|||
}
|
||||
|
||||
if (!options.maxFileSize) {
|
||||
options.maxFileSize = defaultOptions.maxFileSize;
|
||||
options.maxFileSize = defaultOptions.maxFileSize || 10 * 1024 * 1024;
|
||||
}
|
||||
|
||||
var form = new IncomingForm(options);
|
||||
|
@ -73,9 +93,13 @@ exports.upload = function(provider, req, res, options, cb) {
|
|||
|
||||
this._flushing++;
|
||||
|
||||
var fileName = part.filename;
|
||||
var useDefaultname = (typeof defaultOptions.defaultImageName != 'undefined');
|
||||
|
||||
var file = {
|
||||
container: container,
|
||||
name: part.filename,
|
||||
name: (isImage(path.extname(fileName)) && useDefaultname ?
|
||||
defaultOptions.defaultImageName + path.extname(fileName) : fileName),
|
||||
type: part.mime,
|
||||
field: part.name,
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"dependencies": {
|
||||
"async": "^2.1.5",
|
||||
"formidable": "^1.0.16",
|
||||
"mkdirp": "^0.5.1",
|
||||
"pkgcloud": "^1.1.0",
|
||||
"strong-globalize": "^2.6.2",
|
||||
"uuid": "^3.0.1"
|
||||
|
|
|
@ -46,6 +46,21 @@ describe('FileSystem based storage provider', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('should create a new container with slash', function(done) {
|
||||
client.createContainer({name: 'c1%2Fc2'}, function(err, container) {
|
||||
assert(!err);
|
||||
verifyMetadata(container, 'c1/c2');
|
||||
done(err, container);
|
||||
});
|
||||
});
|
||||
|
||||
it('should destroy a container c1/c2', function(done) {
|
||||
client.destroyContainer('c1/c2', function(err, container) {
|
||||
assert(!err);
|
||||
done(err, container);
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new container', function(done) {
|
||||
client.createContainer({name: 'c1'}, function(err, container) {
|
||||
assert(!err);
|
||||
|
|
|
@ -49,6 +49,41 @@ app.post('/custom/uploadWithContainer', function(req, res, next) {
|
|||
});
|
||||
});
|
||||
|
||||
// custom route with renamer
|
||||
app.post('/custom/upload', function(req, res, next) {
|
||||
var options = {
|
||||
container: 'album1',
|
||||
getFilename: function(file, req, res) {
|
||||
return file.field + '_' + file.name;
|
||||
},
|
||||
};
|
||||
ds.connector.upload(req, res, options, function(err, result) {
|
||||
if (!err) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.status(200).send({result: result});
|
||||
} else {
|
||||
res.status(500).send(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// custom route with renamer
|
||||
app.post('/custom/uploadWithContainer', function(req, res, next) {
|
||||
var options = {
|
||||
getFilename: function(file, req, res) {
|
||||
return file.field + '_' + file.name;
|
||||
},
|
||||
};
|
||||
ds.connector.upload('album1', req, res, options, function(err, result) {
|
||||
if (!err) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.status(200).send({result: result});
|
||||
} else {
|
||||
res.status(500).send(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// expose a rest api
|
||||
app.use(loopback.rest());
|
||||
|
||||
|
|
Loading…
Reference in New Issue