hapi-darwin
Advanced tools
Comparing version
263
index.js
@@ -1,251 +0,36 @@ | ||
/* Hapi-darwin 1.0.2 */ | ||
/* Multiple mode have lots of problems */ | ||
/* | ||
darwin dont need eventEmitter because we using async...await | ||
additional darwin handling errors with try...catch | ||
*/ | ||
'use strict'; | ||
import os from 'os'; | ||
import * as fs from 'fs'; | ||
import mkdirp from 'mkdirp'; | ||
import { promisify } from 'util'; | ||
import { basename, extname, join } from 'path'; | ||
import fileType from 'file-type'; | ||
import readChunk from 'read-chunk'; | ||
import imageMagick from 'imagemagick'; | ||
const uploader = require('./lib/uploader'); | ||
const internals = {}; | ||
const register = (server, options, next) => { | ||
const sizeOf = promisify(require('image-size')); | ||
internals.uploader = (file, options = {}) => { | ||
if (!file) throw new Error('No file(s) recived'); | ||
internals.options = Object.assign({ | ||
tmp: os.tmpdir(), | ||
name: undefined, | ||
filter: /\.(jpg|jpeg|png|gif)$/, | ||
multiple: false, | ||
options = { | ||
dest: `./images`, | ||
names: undefined, | ||
safeName: true, | ||
delOrginal: false, // when using `imageVersions` | ||
formats: ['jpeg', 'png', 'gif', 'webp'], | ||
maxFiles: 1, | ||
minFileSize: 0.001 * 1024 * 1024, // 10 KB | ||
maxFileSize: 1 * 1024 * 1024, // 1 MB | ||
minHeight: 10, // in pixels | ||
maxHeight: 10000, // in pixels | ||
minWidth: 10, // in pixels | ||
maxWidth: 10000, // in pixels | ||
destination: './public/storage/null', | ||
urlDestination: 'https://hapi-darwin/public/storage/null', | ||
// imageVersions: { | ||
// ['thumbnail-720']: { | ||
// width: 2 * 720, | ||
// height: 720, | ||
// imageArgs: ['-auto-orient'] | ||
// }, | ||
// ['thumbnail-360']: { | ||
// width: 2 * 360, | ||
// height: 360, | ||
// imageArgs: ['-auto-orient'] | ||
// } | ||
// }, | ||
imageVersions: undefined | ||
}, internals.options, options); | ||
minPixels: 1e2, | ||
maxPixels: 1e6, | ||
versions: [], | ||
addOriginal: false, | ||
...options | ||
}; | ||
if (Array.isArray(file) && internals.options.multiple) { | ||
server.expose({ | ||
uploader: (files, opts = {}) => uploader(files, { ...options, ...opts }) | ||
}); | ||
if (file.length > internals.options.maxFiles) throw new Error('Maximum files recived'); | ||
const promises = file.map(each => internals._fileHandler(each)); | ||
return Promise.all(promises); | ||
} else { | ||
if (file.length > 1) throw new Error('Maximum files recived'); | ||
return internals._fileHandler(file); | ||
} | ||
} | ||
internals.setOptions = (options) => { | ||
internals.options = Object.assign({}, options); | ||
next && next(); | ||
}; | ||
internals.validateBeforReciving = (fileName) => { | ||
if (!fileName.match(internals.options.filter)) return false; | ||
return true; | ||
register.attributes = { | ||
once: true, | ||
pkg: require('./package.json'), | ||
name: '@ecmacommunity/hapi-darwin' | ||
}; | ||
internals.validateAfterRecived = async (fileDetails) => { | ||
const dimensions = await sizeOf(fileDetails.path); | ||
const buffer = readChunk.sync(fileDetails.path, 0, 4100); | ||
const file = fileType(buffer); | ||
let error; | ||
if (internals.options.minFileSize && internals.options.minFileSize > fileDetails.size) { | ||
error = 'File is too small'; | ||
} else if (internals.options.maxFileSize && internals.options.maxFileSize < fileDetails.size) { | ||
error = 'File is too big'; | ||
} else if (!internals.options.filter.test(`x.${file.ext}`)) { | ||
error = 'Filetype not allowed'; | ||
} else if (internals.options.minHeight && internals.options.minHeight > dimensions.height) { | ||
error = `Minimum height should be ${internals.options.minHeight}px`; | ||
} else if (internals.options.maxHeight && internals.options.maxHeight < dimensions.height) { | ||
error = `Maximum height should be ${internals.options.maxHeight}px`; | ||
} else if (internals.options.minWidth && internals.options.minWidth > dimensions.width) { | ||
error = `Minimum width should be ${internals.options.minWidth}px`; | ||
} else if (internals.options.maxWidth && internals.options.maxWidth < dimensions.width) { | ||
error = `Maximum width should be ${internals.options.maxWidth}px`; | ||
} | ||
return error; | ||
module.exports = { | ||
register, | ||
...register.attributes | ||
}; | ||
internals.safeNameFs = async (filename) => { | ||
// Prevent directory traversal and creating hidden system files: | ||
let name = basename(filename.toString()).replace(/^\.+/, ''); | ||
// Prevent overwriting existing files: | ||
while (fs.existsSync(join(internals.options.destination, name))) { | ||
name = name.replace(/(?:(?:_\(([\d]+)\))?(\.[^.]+))?$/, (s, index, ext) => `_(${(parseInt(index, 10) || 0) + 1})${ext || ''}`); | ||
} | ||
return name; | ||
}; | ||
internals._fileHandler = async (file) => { | ||
const orignalname = file.hapi.filename; | ||
let filename = internals.options.name ? `${internals.options.name}${extname(orignalname)}` : orignalname; | ||
if (internals.options.safeName) { | ||
filename = await internals.safeNameFs(filename); | ||
} | ||
const path = join(internals.options.destination, filename); | ||
const tmpName = `_darwin_${Date.now()}_${filename}`; | ||
const tmpPath = join(internals.options.tmp, tmpName); | ||
const fileStream = fs.createWriteStream(tmpPath); | ||
const imageVersions = internals.options.imageVersions ? Object.keys(internals.options.imageVersions) : []; | ||
if (!internals.validateBeforReciving(orignalname)) { | ||
throw new Error('Filetype not allowed'); | ||
} | ||
return new Promise((resolve, reject) => { | ||
file.on('error', err => { | ||
reject(err); | ||
}); | ||
file.pipe(fileStream); | ||
file.on('end', async err => { | ||
const fileDetails = { | ||
fieldname: file.hapi.name, | ||
originalname: file.hapi.filename, | ||
filename, | ||
mimetype: file.hapi.headers['content-type'], | ||
destination: internals.options.destination, | ||
urlDestination: internals.options.urlDestination, | ||
path, | ||
url: join(internals.options.urlDestination, filename), | ||
size: fs.statSync(tmpPath).size, | ||
delOrginal: internals.options.delOrginal ? true : false, | ||
versions: imageVersions.length > 0 ? {} : undefined | ||
} | ||
const finish = () => { resolve(fileDetails) } | ||
const validate = await internals.validateAfterRecived({ | ||
path: tmpPath, | ||
size: fileDetails.size | ||
}); | ||
if (validate) { | ||
await internals.deleteFileFs(tmpPath); | ||
return reject(new Error(validate)); | ||
} | ||
mkdirp(internals.options.destination, (err, made) => { | ||
fs.rename(tmpPath, path, err => { | ||
if (!err) { | ||
generateVersions(); | ||
} else { | ||
const is = fs.createReadStream(tmpPath); | ||
const os = fs.createWriteStream(path); | ||
is.on('end', async err => { | ||
if (!err) { | ||
await internals.deleteFileFs(path); | ||
generateVersions(); | ||
} | ||
}); | ||
is.pipe(os); | ||
} | ||
}); | ||
}); | ||
const generateVersions = () => { | ||
if (internals.options.imageVersions === undefined) finish(); | ||
else { | ||
let counter = 0; | ||
imageVersions.forEach(version => { | ||
mkdirp(join(internals.options.destination, version), (err, made) => { | ||
const opts = internals.options.imageVersions[version]; | ||
imageMagick.resize({ | ||
width: opts.width, | ||
height: opts.height, | ||
srcPath: join(internals.options.destination, filename), | ||
dstPath: join(internals.options.destination, version, filename), | ||
customArgs: opts.imageArgs || ['-auto-orient'] | ||
}, async (err, stdout, stderr) => { | ||
if (err) throw err; | ||
counter++; | ||
fileDetails.versions[version] = { | ||
destination: join(internals.options.destination, version), | ||
urlDestination: join(internals.options.urlDestination, version), | ||
path: join(internals.options.destination, version, filename), | ||
url: join(internals.options.urlDestination, version, filename) | ||
} | ||
if (imageVersions.length === counter) { | ||
if (internals.options.delOrginal) await internals.deleteFileFs(path); | ||
finish(); | ||
} | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
internals.deleteFileFs = async (filePath) => { | ||
fs.unlinkSync(filePath); | ||
}; | ||
export function register(server, options, next) { | ||
internals.setOptions(options); | ||
server.expose('uploader', internals.uploader); | ||
server.expose('deleteFileFs', internals.deleteFileFs); | ||
next(); | ||
} | ||
exports.register.attributes = { | ||
name: 'hapi-darwin' | ||
}; |
{ | ||
"name": "hapi-darwin", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "simple image storage", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "lab -c -L", | ||
"test-cover": "lab -c -r html -o ./test/artifacts/coverage.html && open ./test/artifacts/coverage.html" | ||
"test": "lab -c -L -a code" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tavousi/hapi-darwin" | ||
"url": "https://github.com/escommunity/hapi-darwin.git" | ||
}, | ||
"author": "Seyed Amirhossein Tavousi", | ||
"license": "MIT", | ||
"keywords": [ | ||
"hapi", | ||
"upload" | ||
], | ||
"bugs": { | ||
"url": "https://github.com/tavousi/hapi-darwin/issues" | ||
"url": "https://github.com/escommunity/hapi-darwin/issues" | ||
}, | ||
"homepage": "https://github.com/tavousi/hapi-darwin", | ||
"homepage": "https://github.com/escommunity/hapi-darwin", | ||
"license": "MIT", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=8.0.0" | ||
}, | ||
"dependencies": { | ||
"globby": "^8.0.0", | ||
"mkdirp": "^0.5.0", | ||
"sharp": "^0.20.0" | ||
}, | ||
"devDependencies": { | ||
"hapi": "15.x.x", | ||
"code": "3.x.x", | ||
"lab": "11.x.x" | ||
}, | ||
"dependencies": { | ||
"file-type": "^7.4.0", | ||
"image-size": "^0.6.1", | ||
"imagemagick": "^0.1.3", | ||
"mkdirp": "^0.5.1", | ||
"read-chunk": "^2.1.0" | ||
"code": "^5.0.0", | ||
"del": "^3.0.0", | ||
"eslint-plugin-hapi": "^4.1.0", | ||
"hapi": "^17.0.0", | ||
"lab": "^15.0.0" | ||
} | ||
} |
200
README.md
# hapi-darwin | ||
image storage | ||
A Hapi plugin for image storage | ||
[](https://david-dm.org/tavousi/hapi-darwin) | ||
[](https://david-dm.org/tavousi/hapi-darwin?type=dev) | ||
[](https://travis-ci.org/tavousi/hapi-darwin) | ||
[](https://david-dm.org/ecmascriptforever/hapi-darwin) | ||
[](https://david-dm.org/ecmascriptforever/hapi-darwin?type=dev) | ||
[](https://travis-ci.org/ecmascriptforever/hapi-darwin) | ||
## Install | ||
```bash | ||
$ npm install hapi-darwin | ||
$ npm install @ecmacommunity/hapi-darwin | ||
``` | ||
## Register | ||
```js | ||
await server.register({ | ||
plugin: require('@ecmacommunity/hapi-darwin'), | ||
options: { | ||
// Any uploader method options | ||
dest: './path/to/destination', | ||
formats: ['jpeg', 'png'] | ||
} | ||
}); | ||
``` | ||
## Usage | ||
```js | ||
server.route({ | ||
method: 'POST', | ||
path: '/upload', | ||
config: { | ||
payload: { | ||
maxBytes: 1110.5 * 1024 * 1024, // 500KB | ||
output: 'stream', | ||
allow: 'multipart/form-data' // important | ||
}, | ||
cors: { | ||
origin: ['*'], | ||
credentials: true, | ||
additionalHeaders: ['content-type'] | ||
} | ||
}, | ||
handler: async function (request, reply) { | ||
'use strict'; | ||
try { | ||
const uploader = request.server.plugins['hapi-darwin'].uploader; | ||
server.route({ | ||
method: 'POST', | ||
path: '/path/to/endpoint', | ||
options: { | ||
payload: { | ||
allow: 'multipart/form-data', | ||
output: 'stream' | ||
} | ||
}, | ||
handler: async ({ payload }, h) => { | ||
const data = request.payload; | ||
const file = data['avatar']; | ||
const { uploader } = server.plugins['@ecmacommunity/hapi-darwin']; | ||
const fileName = `${Date.now()}-drwin`; | ||
try { | ||
return await uploader(payload.avatar, { names: 'avatar' }); | ||
} catch (err) { | ||
// ... | ||
} | ||
} | ||
}) | ||
``` | ||
const fileOptions = { | ||
name: fileName, | ||
destination: './public/storage/course' | ||
} | ||
## API | ||
const result = await uploader(file, fileOptions); | ||
### uploader(files, [options]) | ||
reply(result); | ||
Returns a Promise for `object` or `object[]` or `object[][]` with: | ||
} catch (err) { | ||
reply(Boom.badRequest(err.message, err)); | ||
} | ||
- `filename` (string) - corrected filename | ||
- `path` (string) - absolute path of uploaded version | ||
} | ||
}); | ||
``` | ||
#### files | ||
Type: `Readable` `Readable[]` | ||
## Config | ||
#### options | ||
Type: `object` | ||
##### dest | ||
Type: `string` | ||
Default: `./images` | ||
Destination directory. | ||
##### names | ||
Type: `string` `string[]` | ||
Default: `files.hapi.filename`. | ||
Name(s) of input files to be used. | ||
##### safeName | ||
Type: `boolean` | ||
Default: `true` | ||
Whether replace new file with older exist or generate a unique filename. | ||
##### formats | ||
Type: `string[]` | ||
Default: `['jpeg', 'png', 'gif', 'webp']` | ||
File types that are allowed (it don't check extension). | ||
##### maxFiles | ||
Type: `number` | ||
Default: `1` | ||
Maximum input files can be uploaded. | ||
##### minPixels | ||
Type: `number` | ||
Default: `1e2` | ||
Minimum pixels can be manipulated. | ||
##### maxPixels | ||
Type: `number` | ||
Default: `1e6` | ||
Maximum pixels can be manipulated. | ||
##### versions | ||
Type: `object[]` | ||
Default: `[]` | ||
Define `array` of version `object` with: | ||
- `width` (number) - pixels width the version image should be. Use `null` or `undefined` to auto-scale the width to match the height. | ||
- `height` (number) - pixels height the version image should be. Use `null` or `undefined` to auto-scale the height to match the width. | ||
- `enlargement` (boolean) - enlarge the output image if the input image width or height are already less than the required dimensions; default is false. | ||
- `suffix` (string) - suffix of version filename. | ||
Example: | ||
```js | ||
internals.options = Object.assign({ | ||
tmp: os.tmpdir(), | ||
name: undefined, | ||
filter: /\.(jpg|jpeg|png|gif)$/, | ||
multiple: false, | ||
safeName: true, | ||
delOrginal: false, // when using `imageVersions` | ||
maxFiles: 1, | ||
minFileSize: 0.001 * 1024 * 1024, // 10 KB | ||
maxFileSize: 1 * 1024 * 1024, // 1 MB | ||
minHeight: 10, // in pixels | ||
maxHeight: 10000, // in pixels | ||
minWidth: 10, // in pixels | ||
maxWidth: 10000, // in pixels | ||
destination: './public/storage/null', | ||
urlDestination: 'https://hapi-darwin/public/storage/null', | ||
imageVersions: { | ||
['thumbnail-720']: { | ||
width: 2 * 720, | ||
height: 720, | ||
imageArgs: ['-auto-orient'] | ||
}, | ||
['thumbnail-360']: { | ||
width: 2 * 360, | ||
height: 360, | ||
imageArgs: ['-auto-orient'] | ||
} | ||
}, | ||
// imageVersions: undefined | ||
}, internals.options, options); | ||
versions: [ | ||
{ width: 128, height: 128 }, | ||
{ width: 64, suffix: 'thumbnail' } | ||
] | ||
``` | ||
##### addOriginal | ||
Type: `boolean` | ||
Default: `false` | ||
Whether adding the original input file to the destination directory or not. | ||
## License | ||
MIT |
'use strict'; | ||
const Lab = require('lab'); | ||
const Code = require('code'); | ||
const Hapi = require('hapi'); | ||
const Plugin = require('..'); | ||
const { expect } = require('code'); | ||
const { describe, it } = exports.lab = require('lab').script(); | ||
const Plugin = require('../'); | ||
const lab = exports.lab = Lab.script(); | ||
describe('Plugin Registration', () => { | ||
it('should registers successfully and expose upload method', () => { | ||
lab.experiment('Plugin Registration', () => { | ||
const server = new Hapi.Server(); | ||
lab.test('it registers successfully', (done) => { | ||
expect(async () => await server.register(Plugin)).to.not.throws(); | ||
const server = new Hapi.Server(); | ||
server.register(Plugin, (err) => { | ||
const { uploader } = server.plugins[Plugin.name]; | ||
Code.expect(err).to.not.exist(); | ||
done(); | ||
}); | ||
expect(uploader).to.be.function(); | ||
expect(uploader()).to.rejects(); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
720593
4973.88%3
-40%14
133.33%319
49.07%161
56.31%5
66.67%3
50%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated