├── .travis.yml ├── .npmignore ├── .editorconfig ├── lib └── Request.js ├── providers └── SpreadSheetProvider.js ├── LICENSE ├── .gitignore ├── bin └── index.js ├── package.json ├── README.md ├── src └── SpreadSheet │ └── index.js └── test └── spread-sheet.spec.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | sudo: false 5 | install: 6 | - npm install 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | .DS_Store 4 | npm-debug.log 5 | test 6 | .travis.yml 7 | .editorconfig 8 | benchmarks 9 | .idea 10 | bin 11 | out 12 | .nyc_output 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 2 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /lib/Request.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-spread-sheet 5 | * 6 | * (c) Artem Kolesnik 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | class Request { 13 | constructor () { 14 | this.buffer = null 15 | this.headers = {} 16 | } 17 | 18 | /** 19 | * @param {String} key 20 | * @param {String} value 21 | */ 22 | header (key, value) { 23 | this.headers[key] = value 24 | } 25 | 26 | /** 27 | * @param {Buffer} buffer 28 | */ 29 | send (buffer) { 30 | this.buffer = buffer 31 | } 32 | } 33 | 34 | module.exports = Request 35 | -------------------------------------------------------------------------------- /providers/SpreadSheetProvider.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-spread-sheet 5 | * 6 | * (c) Artem Kolesnik 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | const { ServiceProvider } = require('@adonisjs/fold') 13 | 14 | class SpreadSheetProvider extends ServiceProvider { 15 | /** 16 | * Register spreadsheet provider under `Adonis/Addons/SpreadSheet` namespace 17 | * 18 | * @method register 19 | * 20 | * @return {void} 21 | */ 22 | register () { 23 | this.app.bind('Adonis/Addons/SpreadSheet', () => require('../src/SpreadSheet')) 24 | this.app.alias('Adonis/Addons/SpreadSheet', 'SpreadSheet') 25 | } 26 | } 27 | 28 | module.exports = SpreadSheetProvider 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Artem Kolesnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | 63 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-search 5 | * 6 | * (c) Harminder Virk 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | const semver = require('semver') 13 | const { spawn } = require('child_process') 14 | const spawnArgs = [] 15 | 16 | if (semver.lt(process.version, '8.0.0')) { 17 | spawnArgs.push('--harmony-async-await') 18 | } 19 | 20 | function local () { 21 | spawnArgs.push('./node_modules/.bin/japa') 22 | const tests = spawn('node', spawnArgs) 23 | tests.stdout.on('data', (data) => process.stdout.write(data)) 24 | tests.stderr.on('data', (data) => process.stderr.write(data)) 25 | tests.on('close', (code) => process.exit(code)) 26 | } 27 | 28 | function win () { 29 | spawnArgs.push('./node_modules/japa-cli/index.js') 30 | const tests = spawn('node', spawnArgs) 31 | tests.stdout.on('data', (data) => process.stdout.write(data)) 32 | tests.stderr.on('data', (data) => process.stderr.write(data)) 33 | tests.on('close', (code) => process.exit(code)) 34 | } 35 | 36 | if (process.argv.indexOf('--local') > -1) { 37 | local() 38 | } else if (process.argv.indexOf('--win') > -1) { 39 | win() 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adonis-spreadsheet", 3 | "version": "1.0.1", 4 | "description": "Spread sheet provider for adonis framework and has support for csv, xls, xlsx, ods", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "lint": "standard", 11 | "pretest": "npm run lint", 12 | "posttest": "npm run coverage", 13 | "test:local": "FORCE_COLOR=true node bin/index.js --local", 14 | "test": "nyc npm run test:local", 15 | "test:win": "set FORCE_COLOR=true && node bin/index.js --win", 16 | "coverage": "nyc report --reporter=text-lcov | coveralls" 17 | }, 18 | "keywords": [ 19 | "adonis", 20 | "adonis-framework", 21 | "spread", 22 | "sheet", 23 | "csv", 24 | "xls", 25 | "xlsx", 26 | "ods" 27 | ], 28 | "author": "Artem Kolesnik ", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@adonisjs/sink": "^1.0.13", 32 | "coveralls": "^2.13.1", 33 | "cz-conventional-changelog": "^2.0.0", 34 | "japa": "^1.0.4", 35 | "japa-cli": "^1.0.1", 36 | "nyc": "^11.1.0", 37 | "semver": "^5.4.1", 38 | "standard": "^10.0.3" 39 | }, 40 | "config": { 41 | "commitizen": { 42 | "path": "./node_modules/cz-conventional-changelog" 43 | } 44 | }, 45 | "nyc": { 46 | "exclude": [ 47 | "bin", 48 | "lib" 49 | ] 50 | }, 51 | "dependencies": { 52 | "@adonisjs/validator": "^4.0.3", 53 | "xlsx": "^0.17.0" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/ntvsx193/adonis-spreadsheet.git" 58 | }, 59 | "bugs": { 60 | "url": "https://github.com/ntvsx193/adonis-spreadsheet/issues" 61 | }, 62 | "homepage": "https://github.com/ntvsx193/adonis-spreadsheet#readme" 63 | } 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Adonis Spread Sheet 2 | 3 | This is repo is a AdonisJs provider for simplicity work with sheets docs. 4 | It's base on powerful [SheetJS](https://github.com/SheetJS/js-xlsx) 5 | 6 | [![npm version](https://badge.fury.io/js/adonis-spreadsheet.svg)](https://badge.fury.io/js/adonis-spreadsheet) 7 | [![Build Status](https://travis-ci.org/ntvsx193/adonis-spreadsheet.svg?branch=master)](https://travis-ci.org/ntvsx193/adonis-spreadsheet) 8 | [![Coverage Status](https://coveralls.io/repos/github/ntvsx193/adonis-spreadsheet/badge.svg?branch=master)](https://coveralls.io/github/ntvsx193/adonis-spreadsheet?branch=master) 9 | 10 | ## Install 11 | 12 | ``` 13 | npm i --save adonis-spreadsheet 14 | ``` 15 | 16 | ## Registering provider 17 | 18 | The provider is registered inside `start/app.js` file under `providers` array. 19 | 20 | ```js 21 | const providers = [ 22 | 'adonis-spreadsheet/providers/SpreadSheetProvider' 23 | ] 24 | ``` 25 | 26 | That's all! Now you can use the mail provider as follows. 27 | 28 | ## Getting started 29 | 30 | ```js 31 | const Route = use('Route') 32 | const SpreadSheet = use('SpreadSheet') 33 | const User = use('App/Models/User') 34 | 35 | Route.get('/users/export/:format', async ({ response, params }) => { 36 | const ss = new SpreadSheet(response, params.format) 37 | 38 | const users = await User.all() 39 | const data = [] 40 | 41 | data.push([ 42 | 'id', 43 | 'First Name', 44 | 'Last Name', 45 | 'Email', 46 | 'Phone' 47 | ]) 48 | 49 | users.toJSON().forEach(user => { 50 | data.push([ 51 | user.id, 52 | user.first_name, 53 | user.last_name, 54 | user.phone 55 | ]) 56 | }) 57 | 58 | ss.addSheet('Users', data) 59 | ss.download('users-export') 60 | }) 61 | ``` 62 | 63 | Then you can fire url `/rest/users/export/csv` and received csv file sheet. 64 | 65 | ## Supported formats 66 | 67 | - xls 68 | - xlsx 69 | - csv 70 | - ods 71 | -------------------------------------------------------------------------------- /src/SpreadSheet/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-spread-sheet 5 | * 6 | * (c) Artem Kolesnik 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | const xlsx = require('xlsx') 13 | const VE = require('@adonisjs/validator/src/Exceptions') 14 | 15 | class SpreadSheet { 16 | /** 17 | * @param {Response} response 18 | * @param {string} format 19 | */ 20 | constructor (response, format) { 21 | this.response = response 22 | this.format = format 23 | this._validateFormat() 24 | this.wb = xlsx.utils.book_new() 25 | } 26 | 27 | /** 28 | * @private 29 | */ 30 | _validateFormat () { 31 | const valid = this.format in this.constructor.formats 32 | if (!valid) { 33 | throw VE.ValidationException.validationFailed([{ 34 | field: 'format', 35 | value: this.format, 36 | formats: Object.keys(this.constructor.formats).join(','), 37 | message: 'validation failed on format' 38 | }]) 39 | } 40 | } 41 | 42 | /** 43 | * @param {String} name 44 | * @param {Array} data 45 | */ 46 | addSheet (name, data) { 47 | const ws = xlsx.utils.aoa_to_sheet(data) 48 | xlsx.utils.book_append_sheet(this.wb, ws, name) 49 | } 50 | 51 | /** 52 | * @return {Array} 53 | */ 54 | static get formats () { 55 | return { 56 | ods: { 57 | bookType: 'ods', 58 | header: 'application/vnd.oasis.opendocument.spreadsheet' 59 | }, 60 | xls: { 61 | bookType: 'xlml', 62 | header: 'application/vnd.ms-excel' 63 | }, 64 | xlsx: { 65 | bookType: 'xlsx', 66 | header: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' 67 | }, 68 | csv: { 69 | bookType: 'csv', 70 | header: 'application/csv' 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * @param {String} filename 77 | */ 78 | download (filename) { 79 | const buffer = xlsx.write(this.wb, { 80 | bookType: this.constructor.formats[this.format].bookType, 81 | type: 'buffer' 82 | }) 83 | 84 | this.response.header('Content-Type', `${this.constructor.formats[this.format].header}; charset=UTF-8`) 85 | this.response.header('Content-Disposition', `attachment; filename="${filename}.${this.format}"`) 86 | this.response.send(buffer) 87 | } 88 | } 89 | 90 | module.exports = SpreadSheet 91 | -------------------------------------------------------------------------------- /test/spread-sheet.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* 4 | * adonis-spread-sheet 5 | * 6 | * (c) Artem Kolesnik 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | const test = require('japa') 13 | const SpreadSheet = require('../src/SpreadSheet') 14 | const Request = require('../lib/Request') 15 | 16 | test.group('SpreadSheet', (group) => { 17 | let request 18 | 19 | group.beforeEach(() => { 20 | request = new Request() 21 | }) 22 | 23 | test('throw exception when unable to find format', (assert) => { 24 | const fn = () => new SpreadSheet(request, '') 25 | assert.throws(fn, 'E_VALIDATION_FAILED: Validation failed') 26 | }) 27 | 28 | test('should not be throw with correct format', (assert) => { 29 | const fn = () => new SpreadSheet(request, 'csv') 30 | assert.doesNotThrow(fn, 'E_VALIDATION_FAILED: Validation failed') 31 | }) 32 | 33 | test('should not be throw with correct format', (assert) => { 34 | const fn = () => new SpreadSheet(request, 'csv') 35 | assert.doesNotThrow(fn, 'E_VALIDATION_FAILED: Validation failed') 36 | }) 37 | 38 | test('should be success add sheet', (assert) => { 39 | const ss = new SpreadSheet(request, 'csv') 40 | const sheetName = 'MyPersonal' 41 | const data = [['id', 'first_name']] 42 | ss.addSheet(sheetName, data) 43 | assert.property(ss.wb, 'SheetNames') 44 | assert.property(ss.wb, 'Sheets') 45 | assert.deepEqual(ss.wb.SheetNames, [sheetName]) 46 | assert.isObject(ss.wb.Sheets[sheetName]) 47 | }) 48 | 49 | test('should be downloaded csv', (assert) => { 50 | const ss = new SpreadSheet(request, 'csv') 51 | const sheetName = 'MyPersonal' 52 | const data = [['id', 'first_name']] 53 | const filename = 'my-personal-filename' 54 | ss.addSheet(sheetName, data) 55 | ss.download(filename) 56 | 57 | assert.property(request.headers, 'Content-Type', 'application/csv; charset=UTF-8') 58 | assert.property(request.headers, 'Content-Disposition', `${filename}.csv`) 59 | assert.isNotNull(request.buffer) 60 | assert.instanceOf(request.buffer, Buffer) 61 | assert.isAbove(request.buffer.length, 0) 62 | }) 63 | 64 | test('should be downloaded xls', (assert) => { 65 | const ss = new SpreadSheet(request, 'xls') 66 | const sheetName = 'MyPersonal' 67 | const data = [['id', 'first_name']] 68 | const filename = 'my-personal-filename' 69 | ss.addSheet(sheetName, data) 70 | ss.download(filename) 71 | 72 | assert.property(request.headers, 'Content-Type', 'application/vnd.ms-excel; charset=UTF-8') 73 | assert.property(request.headers, 'Content-Disposition', `${filename}.xls`) 74 | assert.isNotNull(request.buffer) 75 | assert.instanceOf(request.buffer, Buffer) 76 | assert.isAbove(request.buffer.length, 0) 77 | }) 78 | 79 | test('should be downloaded xlsx', (assert) => { 80 | const ss = new SpreadSheet(request, 'xlsx') 81 | const sheetName = 'MyPersonal' 82 | const data = [['id', 'first_name']] 83 | const filename = 'my-personal-filename' 84 | ss.addSheet(sheetName, data) 85 | ss.download(filename) 86 | 87 | assert.property(request.headers, 'Content-Type', 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8') 88 | assert.property(request.headers, 'Content-Disposition', `${filename}.xlsx`) 89 | assert.isNotNull(request.buffer) 90 | assert.instanceOf(request.buffer, Buffer) 91 | assert.isAbove(request.buffer.length, 0) 92 | }) 93 | 94 | test('should be downloaded ods', (assert) => { 95 | const ss = new SpreadSheet(request, 'ods') 96 | const sheetName = 'MyPersonal' 97 | const data = [['id', 'first_name']] 98 | const filename = 'my-personal-filename' 99 | ss.addSheet(sheetName, data) 100 | ss.download(filename) 101 | 102 | assert.property(request.headers, 'Content-Type', 'application/vnd.oasis.opendocument.spreadsheet; charset=UTF-8') 103 | assert.property(request.headers, 'Content-Disposition', `${filename}.ods`) 104 | assert.isNotNull(request.buffer) 105 | assert.instanceOf(request.buffer, Buffer) 106 | assert.isAbove(request.buffer.length, 0) 107 | }) 108 | }) 109 | --------------------------------------------------------------------------------