From 13e9097ff0deb9870e7a101285fd62e9c43ef1bf Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 10 Jan 2014 11:34:37 -0800 Subject: [PATCH] Refactor the storage service --- example/app-loopback.js | 25 +- index.js | 6 + lib/index.js | 150 ------------ lib/models/container.js | 8 + lib/models/file.js | 9 + lib/providers/filesystem/container.js | 16 +- lib/providers/filesystem/file.js | 16 +- lib/providers/filesystem/index.js | 335 +++++++++++++------------- lib/storage-connector.js | 4 +- lib/storage-handler.js | 25 +- lib/storage-service.js | 214 ++++++++++++++++ package.json | 4 +- test/images/album1/.gitignore | 1 + test/storage-service.test.js | 2 +- test/upload.test.js | 39 +++ 15 files changed, 487 insertions(+), 367 deletions(-) create mode 100644 index.js delete mode 100644 lib/index.js create mode 100644 lib/models/container.js create mode 100644 lib/models/file.js create mode 100644 lib/storage-service.js create mode 100644 test/images/album1/.gitignore create mode 100644 test/upload.test.js diff --git a/example/app-loopback.js b/example/app-loopback.js index 57386b5..ff30c6c 100644 --- a/example/app-loopback.js +++ b/example/app-loopback.js @@ -1,7 +1,7 @@ var loopback = require('loopback') , app = module.exports = loopback(); -// var StorageService = require('../'); +var path = require('path'); // expose a rest api app.use(loopback.rest()); @@ -11,31 +11,21 @@ app.configure(function () { }); var ds = loopback.createDataSource({ - connector: require('../lib/storage-connector'), + connector: require('../index'), provider: 'filesystem', - root: '/tmp/storage' + root: path.join(__dirname, 'storage') }); -var Container = ds.createModel('container', {name: String}); - -console.log(Container); -Container.getContainers(console.log); - -console.log('shared', Container.getContainers.shared); +var Container = ds.createModel('container'); app.model(Container); -/* -var handler = new StorageService({provider: 'filesystem', root: '/tmp/storage'}); - -app.service('storage', handler); - app.get('/', function (req, res, next) { res.setHeader('Content-Type', 'text/html'); var form = "

Storage Service Demo

" + - "List all containers

" + + "List all containers

" + "Upload to container c1:

" + - "

" + "" + "File to upload:
" + "Notes about the file:
" + "
" + @@ -44,8 +34,5 @@ app.get('/', function (req, res, next) { res.end(); }); -*/ - - app.listen(app.get('port')); console.log('http://127.0.0.1:' + app.get('port')); diff --git a/index.js b/index.js new file mode 100644 index 0000000..2e97726 --- /dev/null +++ b/index.js @@ -0,0 +1,6 @@ +var StorageConnector = require('./lib/storage-connector'); +StorageConnector.Container = require('./lib/models/container'); +StorageConnector.File = require('./lib/models/file'); + +module.exports = StorageConnector; + diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 6f106a1..0000000 --- a/lib/index.js +++ /dev/null @@ -1,150 +0,0 @@ -var factory = require('./factory'); -var handler = require('./storage-handler'); - -var storage = require('pkgcloud').storage; - -module.exports = StorageService; - -/** - * @param options The options to create a provider - * @returns {StorageService} - * @constructor - */ -function StorageService(options) { - if (!(this instanceof StorageService)) { - return new StorageService(options); - } - this.provider = options.provider; - this.client = factory.createClient(options); -} - -StorageService.prototype.getContainers = function (cb) { - return this.client.getContainers(cb); -} - -StorageService.prototype.createContainer = function (options, cb) { - options = options || {}; - if('object' === typeof options && !(options instanceof storage.Container)) { - var Container = factory.getProvider(this.provider).Container; - options = new Container(this.client, options); - } - return this.client.createContainer(options, cb); -} - -StorageService.prototype.destroyContainer = function (container, cb) { - return this.client.destroyContainer(container, cb); -} - -StorageService.prototype.getContainer = function (container, cb) { - return this.client.getContainer(container, cb); -} - -// File related functions -StorageService.prototype.uploadStream = function (container, file, options, cb) { - if(!cb && typeof options === 'function') { - cb = options; - options = {}; - } - options = options || {}; - if(container) options.container = container; - if(file) options.remote = file; - - return this.client.upload(options, cb); -} - -StorageService.prototype.downloadStream = function (container, file, options, cb) { - if(!cb && typeof options === 'function') { - cb = options; - options = {}; - } - options = options || {}; - if(container) options.container = container; - if(file) options.remote = file; - - return this.client.download(options, cb); -} - -StorageService.prototype.getFiles = function (container, download, cb) { - return this.client.getFiles(container, download, cb); -} - -StorageService.prototype.getFile = function (container, file, cb) { - return this.client.getFile(container, file, cb); -} - -StorageService.prototype.removeFile = function (container, file, cb) { - return this.client.removeFile(container, file, cb); -} - -StorageService.prototype.upload = function (req, res, cb) { - return handler.upload(this.client, req, res, cb); -} - -StorageService.prototype.download = function (req, res, cb) { - return handler.download(this.client, req, res, cb); -} - -StorageService.modelName = 'storage'; - -StorageService.prototype.getContainers.shared = true; -StorageService.prototype.getContainers.accepts = []; -StorageService.prototype.getContainers.returns = {arg: 'containers', type: 'array'}; -StorageService.prototype.getContainers.http = [ - {verb: 'get', path: '/'} -]; - -StorageService.prototype.getContainer.shared = true; -StorageService.prototype.getContainer.accepts = [{arg: 'container', type: 'string'}]; -StorageService.prototype.getContainer.returns = {arg: 'container', type: 'object'}; -StorageService.prototype.getContainer.http = [ - {verb: 'get', path: '/:container'} -]; - -StorageService.prototype.createContainer.shared = true; -StorageService.prototype.createContainer.accepts = [{arg: 'options', type: 'object'}]; -StorageService.prototype.createContainer.returns = {arg: 'container', type: 'object'}; -StorageService.prototype.createContainer.http = [ - {verb: 'post', path: '/'} -]; - -StorageService.prototype.destroyContainer.shared = true; -StorageService.prototype.destroyContainer.accepts = [{arg: 'container', type: 'string'}]; -StorageService.prototype.destroyContainer.returns = {}; -StorageService.prototype.destroyContainer.http = [ - {verb: 'delete', path: '/:container'} -]; - -StorageService.prototype.getFiles.shared = true; -StorageService.prototype.getFiles.accepts = [{arg: 'container', type: 'string'}]; -StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array'}; -StorageService.prototype.getFiles.http = [ - {verb: 'get', path: '/:container/files'} -]; - -StorageService.prototype.getFile.shared = true; -StorageService.prototype.getFile.accepts = [{arg: 'container', type: 'string'}, {arg: 'file', type: 'string'}]; -StorageService.prototype.getFile.returns = {arg: 'file', type: 'object'}; -StorageService.prototype.getFile.http = [ - {verb: 'get', path: '/:container/files/:file'} -]; - -StorageService.prototype.removeFile.shared = true; -StorageService.prototype.removeFile.accepts = [{arg: 'container', type: 'string'}, {arg: 'file', type: 'string'}]; -StorageService.prototype.removeFile.returns = {}; -StorageService.prototype.removeFile.http = [ - {verb: 'delete', path: '/:container/files/:file'} -]; - -StorageService.prototype.upload.shared = true; -StorageService.prototype.upload.accepts = [{arg: 'req', type: 'undefined', 'http': {source: 'req'}}]; -StorageService.prototype.upload.returns = {arg: 'result', type: 'object'}; -StorageService.prototype.upload.http = [ - {verb: 'post', path: '/:container/upload/:file'} -]; - -StorageService.prototype.download.shared = true; -StorageService.prototype.download.accepts = [{arg: 'req', type: 'undefined', 'http': {source: 'req'}}]; -StorageService.prototype.download.returns = {arg: 'res', type: 'stream'}; -StorageService.prototype.download.http = [ - {verb: 'get', path: '/:container/download/:file'} -]; \ No newline at end of file diff --git a/lib/models/container.js b/lib/models/container.js new file mode 100644 index 0000000..dc81254 --- /dev/null +++ b/lib/models/container.js @@ -0,0 +1,8 @@ +var loopback = require('loopback'); + +var Container = loopback.createModel('container', { + name: String, + url: String +}, {idInjection: false, strict: false}); + +module.exports = Container; \ No newline at end of file diff --git a/lib/models/file.js b/lib/models/file.js new file mode 100644 index 0000000..fb98ccc --- /dev/null +++ b/lib/models/file.js @@ -0,0 +1,9 @@ +var loopback = require('loopback'); + +var File = loopback.createModel('file', { + name: String, + url: String, + container: String +}, {idInjection: false, strict: false}); + +module.exports = File; diff --git a/lib/providers/filesystem/container.js b/lib/providers/filesystem/container.js index 43a4b05..9329c8b 100644 --- a/lib/providers/filesystem/container.js +++ b/lib/providers/filesystem/container.js @@ -4,15 +4,15 @@ var util = require('util'); exports.Container = Container; function Container(client, details) { - base.Container.call(this, client, details); -}; + base.Container.call(this, client, details); +} util.inherits(Container, base.Container); -Container.prototype._setProperties = function(details) { - for(var k in details) { - if(typeof details[k] !== 'function') { - this[k] = details[k]; - } +Container.prototype._setProperties = function (details) { + for (var k in details) { + if (typeof details[k] !== 'function') { + this[k] = details[k]; } -} + } +}; diff --git a/lib/providers/filesystem/file.js b/lib/providers/filesystem/file.js index c280c68..09bc990 100644 --- a/lib/providers/filesystem/file.js +++ b/lib/providers/filesystem/file.js @@ -4,15 +4,15 @@ var util = require('util'); exports.File = File; function File(client, details) { - base.File.call(this, client, details); -}; + base.File.call(this, client, details); +} util.inherits(File, base.File); -File.prototype._setProperties = function(details) { - for(var k in details) { - if(typeof details[k] !== 'function') { - this[k] = details[k]; - } +File.prototype._setProperties = function (details) { + for (var k in details) { + if (typeof details[k] !== 'function') { + this[k] = details[k]; } -} \ No newline at end of file + } +}; \ No newline at end of file diff --git a/lib/providers/filesystem/index.js b/lib/providers/filesystem/index.js index e56106c..3854afa 100644 --- a/lib/providers/filesystem/index.js +++ b/lib/providers/filesystem/index.js @@ -3,215 +3,218 @@ */ var fs = require('fs'), - path = require('path'), - async = require('async'), - File = require('./file').File, - Container = require('./container').Container; + path = require('path'), + async = require('async'), + File = require('./file').File, + Container = require('./container').Container; module.exports.File = File; module.exports.Container = Container; module.exports.Client = FileSystemProvider; module.exports.createClient = function (options) { - return new FileSystemProvider(options); + return new FileSystemProvider(options); }; function FileSystemProvider(options) { - options = options || {}; - this.root = options.root; - var exists = fs.existsSync(this.root); - if (!exists) { - throw new Error('Path does not exist: ' + this.root); - } - var stat = fs.statSync(this.root); - if (!stat.isDirectory()) { - throw new Error('Invalid directory: ' + this.root); - } + options = options || {}; + this.root = options.root; + var exists = fs.existsSync(this.root); + if (!exists) { + throw new Error('Path does not exist: ' + this.root); + } + var stat = fs.statSync(this.root); + if (!stat.isDirectory()) { + throw new Error('Invalid directory: ' + this.root); + } } var namePattern = new RegExp('[^' + path.sep + '/]+'); function validateName(name, cb) { - if (!name) { - cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name))); - if(!cb) { - console.error('Invalid name: ', name); - } - return false; + if (!name) { + cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name))); + if (!cb) { + console.error('Invalid name: ', name); } - var match = namePattern.exec(name); - if (match && match.index === 0 && match[0].length === name.length) { - return true; - } else { - cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name))); - if(!cb) { - console.error('Invalid name: ', name); - } - return false; + return false; + } + var match = namePattern.exec(name); + if (match && match.index === 0 && match[0].length === name.length) { + return true; + } else { + cb && process.nextTick(cb.bind(null, new Error('Invalid name: ' + name))); + if (!cb) { + console.error('Invalid name: ', name); } + return false; + } } // Container related functions FileSystemProvider.prototype.getContainers = function (cb) { - var self = this; - fs.readdir(self.root, function (err, files) { - var containers = []; - var tasks = []; - files.forEach(function (f) { - tasks.push(fs.stat.bind(null, path.join(self.root, f))); - }); - async.parallel(tasks, function (err, stats) { - if (err) { - cb && cb(err); - } else { - stats.forEach(function (stat, index) { - if (stat.isDirectory()) { - var name = files[index]; - var props = {name: name}; - for (var p in stat) { - props[p] = stat[p]; - } - var container = new Container(self, props); - containers.push(container); - } - }); - cb && cb(err, containers); - } - }); + var self = this; + fs.readdir(self.root, function (err, files) { + var containers = []; + var tasks = []; + files.forEach(function (f) { + tasks.push(fs.stat.bind(null, path.join(self.root, f))); }); -} + async.parallel(tasks, function (err, stats) { + if (err) { + cb && cb(err); + } else { + stats.forEach(function (stat, index) { + if (stat.isDirectory()) { + var name = files[index]; + var props = {name: name}; + for (var p in stat) { + props[p] = stat[p]; + } + var container = new Container(self, props); + containers.push(container); + } + }); + cb && cb(err, containers); + } + }); + }); +}; FileSystemProvider.prototype.createContainer = function (options, cb) { - var self = this; - var name = options.name; - validateName(name, cb) && fs.mkdir(path.join(this.root, name), options, function (err) { - cb && cb(err, new Container(self, {name: name})); - }); -} + var self = this; + var name = options.name; + validateName(name, cb) && fs.mkdir(path.join(this.root, name), options, function (err) { + cb && cb(err, new Container(self, {name: name})); + }); +}; FileSystemProvider.prototype.destroyContainer = function (containerName, cb) { - if (!validateName(containerName, cb)) return; + if (!validateName(containerName, cb)) return; - var dir = path.join(this.root, containerName); - fs.readdir(dir, function (err, files) { - var tasks = []; - files.forEach(function (f) { - tasks.push(fs.unlink.bind(null, path.join(dir, f))); - }); - async.parallel(tasks, function (err) { - if (err) { - cb && cb(err); - } else { - fs.rmdir(dir, cb); - } - }); + var dir = path.join(this.root, containerName); + fs.readdir(dir, function (err, files) { + var tasks = []; + files.forEach(function (f) { + tasks.push(fs.unlink.bind(null, path.join(dir, f))); }); -} + async.parallel(tasks, function (err) { + if (err) { + cb && cb(err); + } else { + fs.rmdir(dir, cb); + } + }); + }); +}; FileSystemProvider.prototype.getContainer = function (containerName, cb) { - var self = this; - if (!validateName(containerName, cb)) return; - var dir = path.join(this.root, containerName); - fs.stat(dir, function (err, stat) { - var container = null; - if (!err) { - var props = {name: containerName}; - for (var p in stat) { - props[p] = stat[p]; - } - container = new Container(self, props); - } - cb && cb(err, container); - }); -} - + var self = this; + if (!validateName(containerName, cb)) return; + var dir = path.join(this.root, containerName); + fs.stat(dir, function (err, stat) { + var container = null; + if (!err) { + var props = {name: containerName}; + for (var p in stat) { + props[p] = stat[p]; + } + container = new Container(self, props); + } + cb && cb(err, container); + }); +}; // File related functions FileSystemProvider.prototype.upload = function (options, cb) { - var container = options.container; - if (!validateName(container, cb)) return; - var file = options.remote; - if (!validateName(file, cb)) return; - var filePath = path.join(this.root, container, file); + var container = options.container; + if (!validateName(container, cb)) return; + var file = options.remote; + if (!validateName(file, cb)) return; + var filePath = path.join(this.root, container, file); - var fileOpts = {flags: 'w+', - encoding: null, - mode: 0666 }; + var fileOpts = {flags: 'w+', + encoding: null, + mode: 0666 }; - return fs.createWriteStream(filePath, fileOpts); -} + return fs.createWriteStream(filePath, fileOpts); +}; FileSystemProvider.prototype.download = function (options, cb) { - var container = options.container; - if (!validateName(container, cb)) return; - var file = options.remote; - if (!validateName(file, cb)) return; + var container = options.container; + if (!validateName(container, cb)) return; + var file = options.remote; + if (!validateName(file, cb)) return; - var filePath = path.join(this.root, container, file); + var filePath = path.join(this.root, container, file); - var fileOpts = {flags: 'r', - autoClose: true }; + var fileOpts = {flags: 'r', + autoClose: true }; - return fs.createReadStream(filePath, fileOpts); - -} + return fs.createReadStream(filePath, fileOpts); +}; FileSystemProvider.prototype.getFiles = function (container, download, cb) { - if (typeof download === 'function' && !(download instanceof RegExp)) { - cb = download; - download = false; - } - var self = this; - if (!validateName(container, cb)) return; - var dir = path.join(this.root, container); - fs.readdir(dir, function (err, entries) { - var files = []; - var tasks = []; - entries.forEach(function (f) { - tasks.push(fs.stat.bind(null, path.join(dir, f))); - }); - async.parallel(tasks, function (err, stats) { - if (err) { - cb && cb(err); - } else { - stats.forEach(function (stat, index) { - if (stat.isFile()) { - var props = {container: container, name: entries[index]}; - for (var p in stat) { - props[p] = stat[p]; - } - var file = new File(self, props); - files.push(file); - } - }); - cb && cb(err, files); - } - }); + if (typeof download === 'function' && !(download instanceof RegExp)) { + cb = download; + download = false; + } + var self = this; + if (!validateName(container, cb)) return; + var dir = path.join(this.root, container); + fs.readdir(dir, function (err, entries) { + var files = []; + var tasks = []; + entries.forEach(function (f) { + tasks.push(fs.stat.bind(null, path.join(dir, f))); }); - -} + async.parallel(tasks, function (err, stats) { + if (err) { + cb && cb(err); + } else { + stats.forEach(function (stat, index) { + if (stat.isFile()) { + var props = {container: container, name: entries[index]}; + for (var p in stat) { + props[p] = stat[p]; + } + var file = new File(self, props); + files.push(file); + } + }); + cb && cb(err, files); + } + }); + }); +}; 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) { - var f = null; - if (!err) { - var props = {container: container, name: file}; - for (var p in stat) { - props[p] = stat[p]; - } - f = new File(self, props); - } - cb && cb(err, f); - }); -} + 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) { + var f = null; + if (!err) { + var props = {container: container, name: file}; + for (var p in stat) { + props[p] = stat[p]; + } + f = new File(self, props); + } + cb && cb(err, f); + }); +}; + +FileSystemProvider.prototype.getUrl = function (options) { + options = options || {}; + var filePath = path.join(this.root, options.container, options.path); + return filePath; +}; FileSystemProvider.prototype.removeFile = function (container, file, cb) { - if (!validateName(container, cb)) return; - if (!validateName(file, cb)) return; + if (!validateName(container, cb)) return; + if (!validateName(file, cb)) return; - var filePath = path.join(this.root, container, file); - fs.unlink(filePath, cb); -} + var filePath = path.join(this.root, container, file); + fs.unlink(filePath, cb); +}; diff --git a/lib/storage-connector.js b/lib/storage-connector.js index 24ed13c..f72dc65 100644 --- a/lib/storage-connector.js +++ b/lib/storage-connector.js @@ -1,4 +1,4 @@ -var StorageService = require('./index'); +var StorageService = require('./storage-service'); /** * Export the initialize method to Loopback data * @param dataSource @@ -23,4 +23,4 @@ exports.initialize = function (dataSource, callback) { } connector.define = function(model, properties, settings) {}; -} +}; diff --git a/lib/storage-handler.js b/lib/storage-handler.js index 2e91eac..e7b6d01 100644 --- a/lib/storage-handler.js +++ b/lib/storage-handler.js @@ -4,13 +4,14 @@ var StringDecoder = require('string_decoder').StringDecoder; /** * Handle multipart/form-data upload to the storage service * @param provider The storage service provider - * @param req The HTTP request - * @param res The HTTP response - * @param cb The callback + * @param {Request} req The HTTP request + * @param {Response} res The HTTP response + * @param {String} container The container name + * @param {Function} cb The callback */ -exports.upload = function (provider, req, res, cb) { +exports.upload = function (provider, req, res, container, cb) { var form = new IncomingForm(this.options); - var container = req.params.container; + container = container || req.params.container; var fields = {}, files = {}; form.handlePart = function (part) { var self = this; @@ -104,14 +105,16 @@ exports.upload = function (provider, req, res, cb) { /** * Handle download from a container/file * @param provider The storage service provider - * @param req The HTTP request - * @param res The HTTP response - * @param cb The callback + * @param {Request} req The HTTP request + * @param {Response} res The HTTP response + * @param {String} container The container name + * @param {String} file The file name + * @param {Function} cb The callback */ -exports.download = function(provider, req, res, cb) { +exports.download = function(provider, req, res, container, file, cb) { var reader = provider.download({ - container: req.params.container, - remote: req.params.file + container: container || req.params.container, + remote: file || req.params.file }); reader.pipe(res); reader.on('error', function(err) { diff --git a/lib/storage-service.js b/lib/storage-service.js new file mode 100644 index 0000000..0f0ee15 --- /dev/null +++ b/lib/storage-service.js @@ -0,0 +1,214 @@ +var factory = require('./factory'); +var handler = require('./storage-handler'); + +var storage = require('pkgcloud').storage; + +var Container = require('./models/container'); +var File = require('./models/file'); + +module.exports = StorageService; + +/** + * @param options The options to create a provider + * @returns {StorageService} + * @constructor + */ +function StorageService(options) { + if (!(this instanceof StorageService)) { + return new StorageService(options); + } + this.provider = options.provider; + this.client = factory.createClient(options); +} + +function map(obj) { + if(!obj || typeof obj !== 'object') { + return obj; + } + var data = {}; + for (var i in obj) { + if (obj.hasOwnProperty(i) && typeof obj[i] !== 'function' + && typeof obj[i] !== 'object') { + if (i === 'newListener' || i === 'delimiter' || i === 'wildcard') { + // Skip properties from the base class + continue; + } + data[i] = obj[i]; + } + } + return data; +} + +StorageService.prototype.getContainers = function (cb) { + this.client.getContainers(function(err, containers) { + if(err) { + cb(err, containers); + } else { + cb(err, containers.map(function(c) { + return new Container(map(c)); + })); + } + }); +}; + +StorageService.prototype.createContainer = function (options, cb) { + options = options || {}; + if ('object' === typeof options && !(options instanceof storage.Container)) { + var Container = factory.getProvider(this.provider).Container; + options = new Container(this.client, options); + } + return this.client.createContainer(options, function(err, container) { + return cb(err, map(container)); + }); +}; + +StorageService.prototype.destroyContainer = function (container, cb) { + return this.client.destroyContainer(container, cb); +}; + +StorageService.prototype.getContainer = function (container, cb) { + return this.client.getContainer(container, function(err, container) { + return cb(err, map(container)); + }); +}; + +// File related functions +StorageService.prototype.uploadStream = function (container, file, options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + if (container) options.container = container; + if (file) options.remote = file; + + return this.client.upload(options, cb); +}; + +StorageService.prototype.downloadStream = function (container, file, options, cb) { + if (!cb && typeof options === 'function') { + cb = options; + options = {}; + } + options = options || {}; + if (container) options.container = container; + if (file) options.remote = file; + + return this.client.download(options, cb); +}; + +StorageService.prototype.getFiles = function (container, download, cb) { + return this.client.getFiles(container, download, function(err, files) { + if(err) { + cb(err, files); + } else { + cb(err, files.map(function(f) { + return new File(map(f)); + })); + } + }); +}; + +StorageService.prototype.getFile = function (container, file, cb) { + return this.client.getFile(container, file, function(err, f) { + return cb(err, map(f)); + }); +}; + +StorageService.prototype.removeFile = function (container, file, cb) { + return this.client.removeFile(container, file, cb); +}; + +StorageService.prototype.upload = function (req, res, cb) { + return handler.upload(this.client, req, res, req.params.container, cb); +}; + +StorageService.prototype.download = function (req, res, cb) { + return handler.download(this.client, req, res, + req.params.container, req.params.file, cb); +}; + +StorageService.modelName = 'storage'; + +StorageService.prototype.getContainers.shared = true; +StorageService.prototype.getContainers.accepts = []; +StorageService.prototype.getContainers.returns = {arg: 'containers', type: 'array', root: true}; +StorageService.prototype.getContainers.http = [ + {verb: 'get', path: '/'} +]; + +StorageService.prototype.getContainer.shared = true; +StorageService.prototype.getContainer.accepts = [ + {arg: 'container', type: 'string'} +]; +StorageService.prototype.getContainer.returns = {arg: 'container', type: 'object', root: true}; +StorageService.prototype.getContainer.http = [ + {verb: 'get', path: '/:container'} +]; + +StorageService.prototype.createContainer.shared = true; +StorageService.prototype.createContainer.accepts = [ + {arg: 'options', type: 'object'} +]; +StorageService.prototype.createContainer.returns = {arg: 'container', type: 'object', root: true}; +StorageService.prototype.createContainer.http = [ + {verb: 'post', path: '/'} +]; + +StorageService.prototype.destroyContainer.shared = true; +StorageService.prototype.destroyContainer.accepts = [ + {arg: 'container', type: 'string'} +]; +StorageService.prototype.destroyContainer.returns = {}; +StorageService.prototype.destroyContainer.http = [ + {verb: 'delete', path: '/:container'} +]; + +StorageService.prototype.getFiles.shared = true; +StorageService.prototype.getFiles.accepts = [ + {arg: 'container', type: 'string'} +]; +StorageService.prototype.getFiles.returns = {arg: 'files', type: 'array', root: true}; +StorageService.prototype.getFiles.http = [ + {verb: 'get', path: '/:container/files'} +]; + +StorageService.prototype.getFile.shared = true; +StorageService.prototype.getFile.accepts = [ + {arg: 'container', type: 'string'}, + {arg: 'file', type: 'string'} +]; +StorageService.prototype.getFile.returns = {arg: 'file', type: 'object', root: true}; +StorageService.prototype.getFile.http = [ + {verb: 'get', path: '/:container/files/:file'} +]; + +StorageService.prototype.removeFile.shared = true; +StorageService.prototype.removeFile.accepts = [ + {arg: 'container', type: 'string'}, + {arg: 'file', type: 'string'} +]; +StorageService.prototype.removeFile.returns = {}; +StorageService.prototype.removeFile.http = [ + {verb: 'delete', path: '/:container/files/:file'} +]; + +StorageService.prototype.upload.shared = true; +StorageService.prototype.upload.accepts = [ + {arg: 'req', type: 'undefined', 'http': {source: 'req'}}, + {arg: 'res', type: 'undefined', 'http': {source: 'res'}} +]; +StorageService.prototype.upload.returns = {arg: 'result', type: 'object'}; +StorageService.prototype.upload.http = [ + {verb: 'post', path: '/:container/upload'} +]; + +StorageService.prototype.download.shared = true; +StorageService.prototype.download.accepts = [ + {arg: 'req', type: 'undefined', 'http': {source: 'req'}}, + {arg: 'res', type: 'undefined', 'http': {source: 'res'}} +]; +StorageService.prototype.download.returns = {arg: 'res', type: 'stream'}; +StorageService.prototype.download.http = [ + {verb: 'get', path: '/:container/download/:file'} +]; \ No newline at end of file diff --git a/package.json b/package.json index 1300406..ab8f91e 100644 --- a/package.json +++ b/package.json @@ -2,12 +2,12 @@ "name": "loopback-storage-service", "description": "Loopback Storage Service", "version": "1.0.0", - "main": "lib/index.js", + "main": "index.js", "scripts": { "test": "./node_modules/.bin/mocha --timeout 30000 test/*test.js" }, "dependencies": { - "pkgcloud": "~0.8.14", + "pkgcloud": "~0.8.17", "async": "~0.2.9" }, "devDependencies": { diff --git a/test/images/album1/.gitignore b/test/images/album1/.gitignore new file mode 100644 index 0000000..a25287a --- /dev/null +++ b/test/images/album1/.gitignore @@ -0,0 +1 @@ +test.jpg diff --git a/test/storage-service.test.js b/test/storage-service.test.js index 6af0c5d..1d756de 100644 --- a/test/storage-service.test.js +++ b/test/storage-service.test.js @@ -1,4 +1,4 @@ -var StorageService = require('../lib/index.js'); +var StorageService = require('../lib/storage-service.js'); var assert = require('assert'); var path = require('path'); diff --git a/test/upload.test.js b/test/upload.test.js new file mode 100644 index 0000000..f96b2af --- /dev/null +++ b/test/upload.test.js @@ -0,0 +1,39 @@ +var request = require('supertest'); +var loopback = require('loopback'); +var assert = require('assert'); + +var app = loopback(); +var path = require('path'); + +// expose a rest api +app.use(loopback.rest()); + +var ds = loopback.createDataSource({ + connector: require('../lib/storage-connector'), + provider: 'filesystem', + root: path.join(__dirname, 'images') +}); + +var Container = ds.createModel('container'); +app.model(Container); + +describe('storage service', function () { + it('uploads files', function (done) { + + var server = app.listen(3000, function () { + + request('http://localhost:3000') + .post('/containers/album1/upload') + .attach('image', path.join(__dirname, '../example/test.jpg')) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(200, function (err, res) { + assert.deepEqual(res.body, {"result": {"files": {"image": [ + {"container": "album1", "name": "test.jpg", "type": "image/jpeg"} + ]}, "fields": {}}}); + server.close(); + done(); + }); + }); + }); +});