├── sense-go.yml ├── test ├── fixtures │ ├── error.txt │ ├── extensions │ │ └── qrs-sample │ │ │ ├── javascript.js │ │ │ ├── sub │ │ │ ├── javascript.js │ │ │ ├── json.json │ │ │ └── html.html │ │ │ ├── json.json │ │ │ ├── html.html │ │ │ └── qrs-sample.qext │ ├── foobar.txt │ └── foobarbaz.txt ├── unit │ ├── 04-ep-mime.spec.js │ ├── 00-validator.spec.js │ ├── 03-ep-extension.spec.js │ ├── 02-qrs.spec.js │ └── 01-setup.spec.js ├── test-config.yml ├── e2e │ ├── 02-qrs.e2e.spec.js │ ├── 03-ep-extension.e2e.spec.js │ └── 04-ep-mime.e2e.spec.js ├── 03-ep-extension.setup.js └── testSetup.js ├── docs ├── images │ ├── qrs.png │ └── qrs-440x220.png ├── test-basic.md ├── contributing.md ├── author.md ├── examples.md ├── tests.md ├── usage.md ├── server-setup.md └── config-options.md ├── lib ├── config-validator.js ├── logger.js ├── sugar │ ├── ep-extension.js.bak │ ├── ep-extension.js │ └── ep-mime.js └── qrs.js ├── .gitattributes ├── .travis.yml ├── index.js ├── .gitignore ├── .jshintrc ├── .editorconfig ├── CHANGELOG.yml ├── LICENSE.md ├── gulpfile.js ├── .verb.md ├── package.json └── README.md /sense-go.yml: -------------------------------------------------------------------------------- 1 | packageName: "qrs" 2 | -------------------------------------------------------------------------------- /test/fixtures/error.txt: -------------------------------------------------------------------------------- 1 | foo;;;false 2 | ;text/foobar;;false 3 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/javascript.js: -------------------------------------------------------------------------------- 1 | // This is JavaScript 2 | -------------------------------------------------------------------------------- /test/fixtures/foobar.txt: -------------------------------------------------------------------------------- 1 | foo;text/foobar;;false 2 | bar;text/foobar;;false 3 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/sub/javascript.js: -------------------------------------------------------------------------------- 1 | // This is JavaScript 2 | -------------------------------------------------------------------------------- /docs/images/qrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanwalther/qrs/HEAD/docs/images/qrs.png -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "I am a json file" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/sub/json.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "I am a json file" 3 | } 4 | -------------------------------------------------------------------------------- /docs/test-basic.md: -------------------------------------------------------------------------------- 1 | Install dev dependencies: 2 | 3 | ``` 4 | $ npm i -d && npm test 5 | ``` 6 | -------------------------------------------------------------------------------- /docs/images/qrs-440x220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanwalther/qrs/HEAD/docs/images/qrs-440x220.png -------------------------------------------------------------------------------- /test/fixtures/foobarbaz.txt: -------------------------------------------------------------------------------- 1 | foo;text/foobar;;false 2 | bar;text/foobar;;false 3 | baz;text/baz;;false 4 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | Pull requests and stars are always welcome. For bugs and feature requests, please create an issue. 2 | -------------------------------------------------------------------------------- /lib/config-validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require( 'lodash' ); 3 | 4 | var ConfigValidator = function () { 5 | 6 | 7 | 8 | }; 9 | module.exports = new ConfigValidator(); -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary 11 | -------------------------------------------------------------------------------- /docs/author.md: -------------------------------------------------------------------------------- 1 | **Stefan Walther** 2 | * [qliksite.io](http://qliksite.io) 3 | * [twitter/waltherstefan](http://twitter.com/waltherstefan) 4 | * [github.com/stefanwalther](http://github.com/stefanwalther) 5 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML file 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/sub/html.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML file 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "8.9.4" 5 | git: 6 | depth: 10 7 | install: npm install 8 | env: 9 | - TEST_DIR=test/unit 10 | script: cd $TEST_DIR && npm install && npm test 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * qrs 3 | * 4 | * Copyright (c) 2015, Stefan Walther. 5 | * Licensed under the MIT License. 6 | */ 7 | 8 | 'use strict'; 9 | module.exports = require( './lib/qrs.js' ); 10 | -------------------------------------------------------------------------------- /test/fixtures/extensions/qrs-sample/qrs-sample.qext: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "qrs-sample", 3 | "description" : "", 4 | "icon" : "extension", 5 | "type" : "visualization", 6 | "version": "0.1", 7 | "preview" : "bar", 8 | "author": "Stefan Walther" 9 | } 10 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | ### Using the generic `query` 2 | (TBD) 3 | 4 | ### Using `get` 5 | (TBD) 6 | 7 | ### Using `delete` 8 | (TBD) 9 | 10 | ### Using `put` 11 | (TBD) 12 | 13 | ### Retrieving all endpoints 14 | (TBD) 15 | 16 | ### Get a list of all extensions 17 | (TBD) 18 | 19 | ### Upload an extension 20 | (TBD) 21 | -------------------------------------------------------------------------------- /docs/tests.md: -------------------------------------------------------------------------------- 1 | Install dev dependencies first: 2 | 3 | `npm i -d` 4 | 5 | To be able to run the tests you have to set up a Qlik Sense server first. 6 | 7 | (TBD) 8 | 9 | ### Configuring your tests 10 | (TBD) 11 | 12 | ### Run the tests 13 | 14 | `npm test` 15 | 16 | This will run the [Mocha](https://mochajs.org/) based tests. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | coverage/ 4 | *.DS_Store 5 | *.sublime-* 6 | _gh_pages 7 | bower_components/ 8 | node_modules/ 9 | npm-debug.log 10 | actual/ 11 | test/actual 12 | temp/ 13 | tmp/ 14 | .tmp/ 15 | todos.md 16 | vendor 17 | scratches/ 18 | /test/mimetypes.txt 19 | /test/mimetypes_single.json 20 | /test/fixtures/extensions/*.zip 21 | /test/fixtures/extensions/*.7z 22 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | 2 | ```js 3 | var QRS = require('{%= name %}'); 4 | var config = { 5 | "host": 'qsSingle', 6 | "useSSL": false, 7 | "xrfkey": 'ABCDEFG123456789', 8 | "authentication": "header", 9 | "headerKey": 'hdr-usr', 10 | "headerValue": 'qsSingle\\swr' 11 | }; 12 | var qrs = new QRS( config ); 13 | 14 | // Now run your command like 15 | qrs.get('qrs/about', function( data ) { 16 | 17 | // do something with the result 18 | 19 | }); 20 | ``` 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "regexp": true, 15 | "undef": true, 16 | "unused": true, 17 | "strict": true, 18 | "trailing": true, 19 | "smarttabs": true, 20 | "white": true, 21 | "browser": true, 22 | "jquery": true, 23 | "devel": true 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 4 tab indentation 12 | [*.*] 13 | indent_style = tab 14 | indent_size = 4 15 | 16 | # 2 space indentation for package.json since this is npm default 17 | [package.json] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [*.yml] 22 | indent_style = space 23 | indent_size = 2 24 | -------------------------------------------------------------------------------- /test/unit/04-ep-mime.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,beforeEach,before*/ 2 | /*jshint -W030,-W117*/ 3 | 'use strict'; 4 | 5 | var chai = require( 'chai' ); 6 | var expect = chai.expect; 7 | var QRS = new require( './../../lib/qrs' ); 8 | var testSetup = require( './../testSetup' ); 9 | 10 | var qrs; 11 | var globalConfig = testSetup.globalConfig; 12 | 13 | describe( 'Unit: ep-mime', function () { 14 | 15 | beforeEach( function ( done ) { 16 | qrs = new QRS( globalConfig ); 17 | done(); 18 | } ); 19 | 20 | it( 'should be an object', function () { 21 | expect( qrs.mime ).to.exist; 22 | } ); 23 | 24 | it( 'should contain methods', function ( done ) { 25 | expect( qrs.mime.get ).to.exist; 26 | done(); 27 | } ); 28 | } ); 29 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var winston = require( 'winston' ); 3 | 4 | var logConfig = { 5 | levels: { 6 | silly: 0, 7 | verbose: 1, 8 | info: 2, 9 | data: 3, 10 | warn: 4, 11 | debug: 5, 12 | error: 6 13 | }, 14 | colors: { 15 | silly: 'magenta', 16 | verbose: 'cyan', 17 | info: 'green', 18 | data: 'grey', 19 | warn: 'yellow', 20 | debug: 'blue', 21 | error: 'red' 22 | } 23 | }; 24 | 25 | var logger = new (winston.Logger)( { 26 | transports: [ 27 | new (winston.transports.Console)( { 28 | colorize: true, 29 | level: 'silly', 30 | json: false, 31 | timestamp: false, 32 | handleExceptions: false 33 | } ) 34 | ], 35 | levels: logConfig.levels, 36 | colors: logConfig.colors 37 | } ); 38 | 39 | module.exports = logger; 40 | -------------------------------------------------------------------------------- /test/unit/00-validator.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,beforeEach,it*/ 2 | /*jshint -W030,-W117*/ 3 | 'use strict'; 4 | var chai = require( 'chai' ); 5 | var expect = chai.expect; 6 | var validator = require( './../../lib/config-validator' ); 7 | 8 | describe( 'Validator', function () { 9 | 10 | //it( 'should be an object', function ( ) { 11 | // expect( validator ).to.be.an.object; 12 | //} ); 13 | //it( 'should expose .validators', function ( ) { 14 | // expect( validator ).to.have.property( 'validators'); 15 | //} ); 16 | // 17 | //it( 'has no validators by default', function ( ) { 18 | // expect( validator.validators ).to.have.length(0); 19 | //} ); 20 | //it( 'should accept new validators', function ( done ) { 21 | // 22 | // validator.use( 'test', function ( ) { 23 | // return 'testValue'; 24 | // }); 25 | // 26 | //} ); 27 | 28 | } ); -------------------------------------------------------------------------------- /docs/server-setup.md: -------------------------------------------------------------------------------- 1 | There are several options to ensure that communication between this node.js module and Qlik Sense server is working properly: 2 | 3 | **Authenticating with HTTP headers** 4 | * [Using Header Authentication in Qlik Sense: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/header-authentication-configuration) 5 | * [Authenticating with HTTP headers](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Http-Headers.htm) in Qlik Sense Help for Developers 2.1.1 6 | 7 | 8 | **Authenticating with a server certificate** 9 | * [Authenticating with Certificates: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/authentication-certificates) 10 | * [Authenticating with the server certificate](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Certificate.htm) in Qlik Sense Help for Developer 2.1.1 11 | -------------------------------------------------------------------------------- /test/unit/03-ep-extension.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,beforeEach,it*/ 2 | /*jshint -W030,-W117*/ 3 | 4 | 'use strict'; 5 | 6 | var chai = require( 'chai' ); 7 | var chaiAsPromised = require( 'chai-as-promised' ); 8 | var expect = chai.expect; 9 | var QRS = new require( './../../lib/qrs' ); 10 | var nock = require( 'nock' ); 11 | var testSetup = require( './../testSetup' ); 12 | var extend = require('extend-shallow'); 13 | 14 | chai.use( chaiAsPromised ); 15 | 16 | describe( 'Unit: ep-extension', function () { 17 | 18 | var qrs = null; 19 | beforeEach( function () { 20 | var config = extend( testSetup.globalConfig, { 21 | 'authentication': 'header' 22 | }); 23 | qrs = new QRS( config ); 24 | } ); 25 | 26 | it.skip( 'should return only extensions of type ', function ( done ) { 27 | qrs.extension.getInstalledVis() 28 | .then( function ( data ) { 29 | expect( data ).to.exist; 30 | expect( data ).to.be.an.array; 31 | }, function ( err ) { 32 | expect( err ).to.not.exist; 33 | } ) 34 | .done( function () { 35 | done(); 36 | } ); 37 | } ); 38 | 39 | } ); -------------------------------------------------------------------------------- /CHANGELOG.yml: -------------------------------------------------------------------------------- 1 | v2.0.3: 2 | date: "2018-01-21" 3 | enhancements: 4 | - "Update dependencies" 5 | - "Remove codeship.io" 6 | v2.0.2: 7 | date: "2015-11-24" 8 | enhacements: 9 | - "Fix several smaller issues in qrs-mime" 10 | - "Add gulp-mocha + gulp-istanbul" 11 | v2.0.1: 12 | date: "2015-11-24" 13 | enhancements: 14 | - "Update all dependencies" 15 | v2.0.0: 16 | date: "2015-11-23" 17 | changes: 18 | - "Breaking Change: Every endpoint will not be automatically prefixed with 'qrs' anymore, so instead of `qrs.get('about', ...)` use `qrs.get('qrs/about' ...)` now." 19 | v1.2.1: 20 | date: "2015-11-20" 21 | enhancements: 22 | - "qrs.extension.upload" 23 | - "qrs.extension.delete" 24 | v1.1.1: 25 | date: "2015-11-20" 26 | enhancements: 27 | - "Bettor error handling for qrs.getUrl, especially urlParams" 28 | v1.1.0: 29 | date: "2015-11-03" 30 | enhancements: 31 | - "Finish documentation." 32 | v1.0.0: 33 | date: "2015-10-13" 34 | changes: 35 | - "Breaking change: Changed qrs.query to qrs.request" 36 | fixes: 37 | - "Merge PR: Adding missing require and typechecking certs (#1)" 38 | -------------------------------------------------------------------------------- /test/test-config.yml: -------------------------------------------------------------------------------- 1 | host: qssingle 2 | port: 0 ## Leave the port blank or set it to 0 if you don't want to specify a port 3 | useSSL: true 4 | xrfkey: ABCDEFG123456789 5 | fiddler: false 6 | authentication: 7 | header: 8 | enabled: true 9 | useSSL: false 10 | virtualProxy: hdr 11 | headerKey: hdr-usr 12 | headerValue: QSSINGLE\swr ## Note: use only a single backslash hare, not Javascript encoding is necessary in YML 13 | ntlm: 14 | enabled: false 15 | virtualProxy: 16 | certificates: 17 | enabled: true 18 | virtualProxy: 19 | cert: D:\CertStore\qrs-test\client.pem # Do not escape backslash in yml ! 20 | key: D:\CertStore\qrs-test\client_key.pem # Do not escape backslash in yml ! 21 | ca: D:\CertStore\qrs-test\root.pem # Do not escape backslash in yml ! 22 | passphrase: 23 | port: 4242 24 | useSSL: true 25 | headerKey: X-Qlik-User 26 | headerValue: UserDirectory=Internal;UserId=sa_repository 27 | windows: 28 | enabled: false 29 | virtualProxy: 30 | useSSL: true 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018, Stefan Walther. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/e2e/02-qrs.e2e.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe, it, beforeEach*/ 2 | /*jshint -W030,-W117*/ 3 | 4 | 'use strict'; 5 | var chai = require( 'chai' ); 6 | var expect = chai.expect; 7 | var assert = chai.assert; 8 | var QRS = new require( './../../lib/qrs' ); 9 | var extend = require( 'extend-shallow' ); 10 | var testSetup = require( './../testSetup' ); 11 | var chaiAsPromised = require('chai-as-promised'); 12 | 13 | chai.use(chaiAsPromised); 14 | 15 | var qrs; 16 | var globalConfig = testSetup.globalConfig; 17 | 18 | describe( 'qrs', function () { 19 | 20 | var testConfig = null; 21 | testSetup.testLoop.forEach( function ( testLoopConfig ) { 22 | 23 | describe( 'with ' + testLoopConfig.name, function () { 24 | 25 | /** 26 | * Reset the configuration before each test. 27 | */ 28 | beforeEach( function ( done ) { 29 | 30 | testConfig = extend( globalConfig, testLoopConfig.config ); 31 | qrs = new QRS( testConfig ); 32 | done(); 33 | 34 | } ); 35 | 36 | it( 'should return something for /about', function ( ) { 37 | 38 | return expect( qrs.get('/qrs/about') ).to.eventually.have.property('schemaPath', 'About'); 39 | 40 | } ); 41 | 42 | } ); 43 | } ); 44 | } ); 45 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require( 'gulp' ); 3 | var senseGo = require( 'sense-go' ); 4 | var path = require( 'path' ); 5 | var gulpVerb = require( 'gulp-verb' ); 6 | var template = require( 'template' )(); 7 | var mocha = require( 'gulp-mocha' ); 8 | var istanbul = require( 'gulp-istanbul' ); 9 | 10 | template.helper( 'apidocs', require( 'template-helper-apidocs' ) ); 11 | 12 | var userConfig = senseGo.loadYml( path.join( __dirname, 'sense-go.yml' ) ); 13 | senseGo.init( gulp, userConfig, function () { 14 | gulp.task( 'all', gulp.series( 15 | 'bump:patch', 16 | 'git:add', 17 | 'git:commit', 18 | 'git:push', 19 | 'npm:publish' 20 | ) ); 21 | 22 | gulp.task( 'verb', function () { 23 | gulp.src( './.verb.md' ) 24 | .pipe( gulpVerb( {dest: './README.md'} ) ) 25 | .pipe( gulp.dest( './' ) ); 26 | } ); 27 | 28 | gulp.task( 'test:unit', function ( ) { 29 | return gulp.src(['./test/unit/**/*.spec.js']) 30 | .pipe( mocha( {reporter: 'spec'} ) ); 31 | }); 32 | 33 | gulp.task( 'istanbul:pre-test', function () { 34 | return gulp.src( ['./lib/**/*.js'] ) 35 | .pipe( istanbul() ) 36 | .pipe( istanbul.hookRequire() ); 37 | } ); 38 | gulp.task( 'istanbul:unit', function () { 39 | return gulp.src( ['./test/unit/**/*.spec.js'] ) 40 | .pipe( mocha( ) ) 41 | .pipe( istanbul.writeReports() ); 42 | //.pipe( istanbul.enforceThresholds( {thresholds: {global: 90}} ) ); 43 | } ); 44 | gulp.task( 'coverage:unit', gulp.series( 'istanbul:pre-test', 'istanbul:unit' ) ); 45 | } ); 46 | -------------------------------------------------------------------------------- /docs/config-options.md: -------------------------------------------------------------------------------- 1 | The configuration passed to the constructor of *qrs* drives how authentication is handled. 2 | 3 | ### Typical configurations 4 | 5 | **Example using header authentication** 6 | 7 | ```javascript 8 | var config = { 9 | authentication: 'header', 10 | host: 'server.mydomain.com', 11 | useSSL: true, 12 | virtualProxy: 'hdr', 13 | headerKey: 'hdr-usr', 14 | headerValue: 'mydomain\\justme' 15 | }; 16 | ``` 17 | 18 | **Example using certificates** 19 | 20 | ```js 21 | var config = { 22 | authentication: 'certificates', 23 | host: 'server.mydomain.com', 24 | useSSL: true, 25 | cert: 'C:\\CertStore\\client.pem', 26 | key: 'C:\\CertStore\\client_key.pem', 27 | ca: 'C:\\CertStore\\root.pem', 28 | port: 4242, 29 | headerKey: 'X-Qlik-User', 30 | headerValue: 'UserDirectory=Internal;UserId=sa_repository' 31 | }; 32 | ``` 33 | 34 | ### All options 35 | 36 | * **`authentication`** - Authentication method, can be "`windows`", "`certificates`" or "`header`", defaults to "`windows`". 37 | * **`host`** - Qualified / fully qualified name or IP-address of the server where the Qlik Sense Repository server is running on, defaults to "`127.0.0.1`" 38 | * **`useSSL`** - Whether to use SSL or not, defaults to `false`. 39 | * **`headerKey`** - Header key. 40 | * **`headerValue`** - Header value. 41 | * **`virtualProxy`** - Name of the virtual proxy. 42 | * **`port`** - Port to be used. 43 | * **`cert`** - Path to client certificate file (client.pem). 44 | * **`key`** - Path to client key file (client_key.pem). 45 | * **`ca`** - Path to ca file (root.pem) 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | {%= badge("fury") %} {%= badge("travis") %} [![dependencies](https://david-dm.org/stefanwalther/qrs.svg)](https://david-dm.org/stefanwalther/qrs) 2 | # {%= name %} 3 | > {%= description %} 4 | 5 | NOTE: This solution is not actively maintained anymore. Contributors to take over are highly welcome. 6 | Alternatively use [qrs-interact](https://github.com/eapowertools/qrs-interact) 7 | 8 | ![]({%= verb.baseImgUrl %}docs/images/qrs.png) 9 | 10 | ## Installation 11 | {%= include("install-npm", {save: true}) %} 12 | 13 | --- 14 | ## Table of Contents 15 | 16 | 17 | --- 18 | 19 | ## Usage 20 | {%= docs('usage') %} 21 | 22 | ## Configuration Options 23 | {%= docs('config-options') %} 24 | 25 | ## Server Setup 26 | {%= docs('server-setup') %} 27 | 28 | ## API 29 | {%= apidocs("lib/qrs.js") %} 30 | 31 | ## Plugins 32 | Think of plugins as some kind of sugar layer with additional business logic on top of `qrs`. 33 | It is easy to either add new plugins to the `qrs` repository or just to load some external code/plugin. 34 | The list of built-in plugins is probably and hopefully a growing one (and hopefully not only created by the author of this document). 35 | 36 | The following plugins are available with the current version of `qrs`: 37 | 38 | 39 | 40 | 41 | 42 | ### Plugin "Mime" 43 | {%= apidocs("lib/sugar/ep-mime.js") %} 44 | --- 45 | 46 | ## Running tests 47 | {%= include("tests") %} 48 | 49 | ## Contributing 50 | {%= include("contributing") %} 51 | 52 | ## Author 53 | {%= docs("author") %} 54 | 55 | ## License 56 | {%= copyright() %} 57 | {%= license %} 58 | 59 | *** 60 | {%= include("footer") %} 61 | -------------------------------------------------------------------------------- /test/03-ep-extension.setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var zipper = require( 'zip-local' ); 4 | var fs = require( 'fs' ); 5 | var path = require( 'path' ); 6 | var rimraf = require( 'rimraf' ); 7 | var async = require( 'async' ); 8 | 9 | var ExtensionSetup = function () { 10 | 11 | var extensions = []; 12 | 13 | var init = function ( callback ) { 14 | 15 | var dirToScan = path.join( __dirname, './fixtures/extensions' ); 16 | fs.readdir( dirToScan, function ( err, files ) { 17 | 18 | var extensionDirs = files.map( function ( file ) { 19 | return path.join( dirToScan, file ); 20 | } ).filter( function ( file ) { 21 | return fs.statSync( file ).isDirectory(); 22 | } ); 23 | 24 | async.map( extensionDirs, function ( file, cb ) { 25 | var zipSource = path.normalize( file ); 26 | var fi = path.parse( file ); 27 | var zipDest = path.normalize( path.join( fi.dir, fi.base + '.zip' )); 28 | rimraf( zipDest, function ( /* err */ ) { 29 | zipper.sync.zip( zipSource ).compress().save( zipDest ); 30 | var ext = { 31 | 'name': fi.base, 32 | 'file': zipDest 33 | }; 34 | cb( null, ext ); 35 | } ); 36 | }, function ( err, results ) { 37 | extensions = results; 38 | callback( err, results ); 39 | } ); 40 | } ); 41 | 42 | }; 43 | 44 | var cleanExtZipFiles = function ( callback ) { 45 | 46 | async.map( extensions, function ( ext, cb ) { 47 | rimraf( ext.file, function ( err ) { 48 | cb( null, ext); 49 | }); 50 | }, function ( err, results ) { 51 | callback( null, results); 52 | }); 53 | 54 | }; 55 | 56 | return { 57 | init: init, 58 | extensions: extensions, 59 | cleanExtZipFiles: cleanExtZipFiles 60 | } 61 | 62 | }; 63 | module.exports = ExtensionSetup; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qrs", 3 | "version": "2.0.3", 4 | "description": "Node.js library to communicate with Qlik Sense Repository Service (QRS) API.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test/unit", 8 | "test:unit": "mocha test/unit", 9 | "coverage:unit": "gulp coverage:unit", 10 | "test:e2e": "mocha test/e2e", 11 | "preinstall": "npm install istanbul -g", 12 | "docs": "docker run --rm -v ${PWD}:/opt/verb stefanwalther/verb" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/stefanwalther/qrs.git" 17 | }, 18 | "keywords": [ 19 | "qlik", 20 | "sense", 21 | "qrs", 22 | "qlik-sense-repository", 23 | "api", 24 | "rest", 25 | "automation" 26 | ], 27 | "author": "Stefan Walther", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/stefanwalther/qrs/issues" 31 | }, 32 | "homepage": "https://github.com/stefanwalther/qrs", 33 | "dependencies": { 34 | "async": "^2.6.0", 35 | "brace-expansion": "^1.1.8", 36 | "camelcase": "^4.1.0", 37 | "extend-shallow": "^3.0.2", 38 | "fs-utils": "^0.7.0", 39 | "glob": "^7.1.2", 40 | "httpreq": "^0.4.24", 41 | "lodash": "^4.17.4", 42 | "q": "^1.5.1", 43 | "request": "^2.83.0", 44 | "string": "^3.3.3", 45 | "validate.js": "^0.12.0", 46 | "winston": "^2.4.0" 47 | }, 48 | "devDependencies": { 49 | "chai": "^4.1.2", 50 | "chai-as-promised": "^7.1.1", 51 | "gulp": "git://github.com/gulpjs/gulp#4.0", 52 | "gulp-istanbul": "^1.1.3", 53 | "gulp-mocha": "^5.0.0", 54 | "gulp-verb": "^0.3.0", 55 | "mocha": "^5.0.0", 56 | "nock": "^9.1.6", 57 | "sinon": "^4.2.0", 58 | "template": "^0.17.5" 59 | }, 60 | "files": [ 61 | "index.js", 62 | "lib/logger.js", 63 | "lib/qrs.js", 64 | "lib/sugar/ep-mime.js", 65 | "lib/sugar/ep-extension.js" 66 | ], 67 | "engines": { 68 | "node": ">=0.8.0" 69 | }, 70 | "verb": { 71 | "baseUrl": "https://github.com/stefanwalther/qrs/blob/master/", 72 | "baseImgUrl": "https://raw.githubusercontent.com/stefanwalther/qrs/master/", 73 | "run": true, 74 | "toc": { 75 | "render": true, 76 | "method": "preWrite", 77 | "maxdepth": 2, 78 | "footer": " " 79 | }, 80 | "layout": "empty", 81 | "tasks": [ 82 | "readme" 83 | ], 84 | "data": { 85 | "twitter": "waltherstefan", 86 | "travis_url": "stefanwalther" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/testSetup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var fsUtils = require( 'fs-utils' ); 3 | var path = require( 'path' ); 4 | var _ = require( 'lodash' ); 5 | 6 | var TestSetup = function () { 7 | var testConfig = fsUtils.readYAMLSync( path.join( __dirname, './test-config.yml' ) ); 8 | 9 | function getTestLoop () { 10 | 11 | var configs = []; 12 | 13 | if ( testConfig.authentication.header.enabled ) { 14 | configs.push( { 15 | 'name': 'Header authentication', 16 | 'config': { 17 | authentication: 'header', 18 | headerKey: testConfig.authentication.header.headerKey, 19 | headerValue: testConfig.authentication.header.headerValue, 20 | virtualProxy: testConfig.authentication.header.virtualProxy, 21 | port: testConfig.authentication.header.port || testConfig.port, 22 | useSSL: _.isBoolean( testConfig.authentication.header.useSSL ) ? testConfig.authentication.header.useSSL : testConfig.useSSL 23 | } 24 | }); 25 | } 26 | 27 | if (testConfig.authentication.ntlm.enabled) { 28 | 29 | configs.push({ 30 | 'name': 'Ntlm authentication', 31 | 'config': { 32 | virtualProxy: testConfig.authentication.ntlm.virtualProxy, 33 | port: testConfig.authentication.ntlm.port || testConfig.port 34 | } 35 | }); 36 | } 37 | 38 | if ( testConfig.authentication.certificates.enabled ) { 39 | configs.push({ 40 | 'name': 'Certificates based authentication', 41 | 'config': { 42 | authentication: 'certificates', 43 | headerKey: testConfig.authentication.certificates.headerKey, 44 | headerValue: testConfig.authentication.certificates.headerValue, 45 | useSSL: _.isBoolean(testConfig.authentication.certificates.useSSL) ? testConfig.authentication.certificates.useSSL : testConfig.useSSL, 46 | cert: testConfig.authentication.certificates.cert, 47 | key: testConfig.authentication.certificates.key, 48 | ca: testConfig.authentication.certificates.ca, 49 | passphrase: testConfig.authentication.certificates.passphrase, 50 | virtualProxy: testConfig.authentication.certificates.virtualProxy, 51 | port: testConfig.authentication.certificates.port || testConfig.port 52 | 53 | } 54 | }); 55 | } 56 | 57 | return configs; 58 | } 59 | 60 | var getGlobalConfig = function ( ) { 61 | 62 | return { 63 | host: testConfig.host, 64 | useSSL: testConfig.useSSL, 65 | xrfkey: testConfig.xrfkey, 66 | fiddler: testConfig.fiddler, 67 | port: 0 68 | }; 69 | }; 70 | 71 | return { 72 | globalConfig: getGlobalConfig(), 73 | testLoop: getTestLoop(), 74 | testConfig: testConfig 75 | }; 76 | 77 | }; 78 | 79 | module.exports = new TestSetup(); 80 | -------------------------------------------------------------------------------- /lib/sugar/ep-extension.js.bak: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require( 'lodash' ); 3 | var Q = require( 'q' ); 4 | 5 | /** 6 | * Extension plugin. 7 | * 8 | * @param {qrs} base - Base class, instance of `qrs`. 9 | * @api public 10 | */ 11 | function Extension ( base ) { 12 | 13 | // Borrowed from http://stackoverflow.com/questions/17251764/lodash-filter-collection-using-array-of-values 14 | _.mixin( { 15 | 'findByValues': function ( collection, property, values ) { 16 | return _.filter( collection, function ( item ) { 17 | return _.contains( values, item[property] ); 18 | } ); 19 | } 20 | } ); 21 | 22 | var defaultFilter = [ 23 | 'visualization', 24 | 'visualization-template', 25 | 'mashup', 26 | 'mashup-template' 27 | ]; 28 | 29 | /** 30 | * Return all installed extensions. Optionally pass in a filter, to get only returned those extensions matching the given filter. 31 | * 32 | * @param {String[]} [filter] - Optional. Filter installed extensions by `type`. Example: filter = `['visualization', 'visualization-type']` will only return visualization extensions and visualization extension templates. 33 | * @returns {promise} 34 | * @api public 35 | */ 36 | this.getInstalled = function ( filter ) { 37 | var defer = Q.defer(); 38 | base.get( '/qrs/extension/schema' ).then( function ( data ) { 39 | 40 | if ( filter ) { 41 | defer.resolve( _.findByValues( data, 'type', filter ) ); 42 | } else { 43 | defer.resolve( _.findByValues( data, 'type', defaultFilter ) ); 44 | } 45 | 46 | }, function ( err ) { 47 | defer.reject( err ); 48 | } ); 49 | 50 | return defer.promise; 51 | }; 52 | 53 | /** 54 | * Same as getInstalled but only returns visualization extensions (type `visualization`). 55 | * @returns {promise} 56 | * @api public 57 | */ 58 | this.getInstalledVis = function () { 59 | return this.getInstalled( ['visualization'] ); 60 | }; 61 | 62 | /** 63 | * Same as `getInstalled` but only returns extensions of type `visualization-template`, which are the templates for the Extension editor in Dev Hub. 64 | * @returns {promise} 65 | * @api public 66 | */ 67 | this.getInstalledVisTemplates = function () { 68 | return this.getInstalled( ['visualization-template'] ); 69 | }; 70 | 71 | /** 72 | * Same as `getInstalled` but only returns extensions of type `mashup`. 73 | * @returns {promise} 74 | * @api public 75 | */ 76 | this.getInstalledMashups = function () { 77 | return this.getInstalled( ['mashup'] ); 78 | }; 79 | 80 | /** 81 | * Same as `getInstalled` but only returns extensions of type `mashup`. 82 | * @returns {promise} 83 | * @api public 84 | */ 85 | this.getInstalledMashupTemplates = function () { 86 | return this.getInstalled( ['mashup-template'] ); 87 | }; 88 | 89 | /** 90 | * Deletes an extension. 91 | * @param {String} name - Name of the extension. 92 | */ 93 | this.delete = function ( name ) { 94 | var defer = Q.defer(); 95 | 96 | base.delete( 'extension/name' + name ).then( function ( data ) { 97 | console.log('data', data); 98 | defer.resolve(); 99 | }, function ( err ) { 100 | defer.reject( err ); 101 | }); 102 | 103 | return defer.promise; 104 | }; 105 | 106 | /** 107 | * Returns whether an extension is installed or not. 108 | * @param name 109 | */ 110 | var isInstalled = function ( name ) { 111 | 112 | var defer = Q.defer(); 113 | var retVal = { 114 | isInstalled: false, 115 | name: name, 116 | info: {} 117 | }; 118 | defer.resolve( retVal ); 119 | return defer.promise; 120 | }; 121 | } 122 | 123 | module.exports = Extension; 124 | -------------------------------------------------------------------------------- /lib/sugar/ep-extension.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var _ = require( 'lodash' ); 3 | var Q = require( 'q' ); 4 | var fs = require( 'fs' ); 5 | var path = require( 'path' ); 6 | 7 | /** 8 | * Extension plugin. 9 | * 10 | * @param {qrs} base - Base class, instance of `qrs`. 11 | * @api public 12 | */ 13 | function Extension ( base ) { 14 | 15 | // Borrowed from http://stackoverflow.com/questions/17251764/lodash-filter-collection-using-array-of-values 16 | _.mixin( { 17 | 'findByValues': function ( collection, property, values ) { 18 | return _.filter( collection, function ( item ) { 19 | return _.contains( values, item[property] ); 20 | } ); 21 | } 22 | } ); 23 | 24 | var defaultFilter = [ 25 | 'visualization', 26 | 'visualization-template', 27 | 'mashup', 28 | 'mashup-template' 29 | ]; 30 | 31 | /** 32 | * Return all installed extensions. Optionally pass in a filter, to get only returned those extensions matching the given filter. 33 | * 34 | * @param {String[]} [filter] - Optional. Filter installed extensions by `type`. Example: filter = `['visualization', 'visualization-type']` will only return visualization extensions and visualization extension templates. 35 | * @returns {promise} 36 | * @api public 37 | */ 38 | this.getInstalled = function ( filter ) { 39 | var defer = Q.defer(); 40 | base.get( '/qrs/extension/schema' ).then( function ( data ) { 41 | 42 | if ( filter ) { 43 | defer.resolve( _.findByValues( data, 'type', filter ) ); 44 | } else { 45 | defer.resolve( _.findByValues( data, 'type', defaultFilter ) ); 46 | } 47 | 48 | }, function ( err ) { 49 | defer.reject( err ); 50 | } ); 51 | return defer.promise; 52 | }; 53 | 54 | /** 55 | * Same as getInstalled but only returns visualization extensions (type `visualization`). 56 | * @returns {promise} 57 | * @api public 58 | */ 59 | this.getInstalledVis = function () { 60 | return this.getInstalled( ['visualization'] ); 61 | }; 62 | 63 | /** 64 | * Same as `getInstalled` but only returns extensions of type `visualization-template`, which are the templates for the Extension editor in Dev Hub. 65 | * @returns {promise} 66 | * @api public 67 | */ 68 | this.getInstalledVisTemplates = function () { 69 | return this.getInstalled( ['visualization-template'] ); 70 | }; 71 | 72 | /** 73 | * Same as `getInstalled` but only returns extensions of type `mashup`. 74 | * @returns {promise} 75 | * @api public 76 | */ 77 | this.getInstalledMashups = function () { 78 | return this.getInstalled( ['mashup'] ); 79 | }; 80 | 81 | /** 82 | * Same as `getInstalled` but only returns extensions of type `mashup`. 83 | * @returns {promise} 84 | * @api public 85 | */ 86 | this.getInstalledMashupTemplates = function () { 87 | return this.getInstalled( ['mashup-template'] ); 88 | }; 89 | 90 | /** 91 | * Deletes an extension. 92 | * @param {String} name - Name of the extension. 93 | */ 94 | this.delete = function ( name ) { 95 | var defer = Q.defer(); 96 | 97 | base.delete( 'extension/name', name ) 98 | .then( function ( data ) { 99 | defer.resolve(); 100 | }, function ( err ) { 101 | defer.reject( err ); 102 | } ); 103 | 104 | return defer.promise; 105 | }; 106 | 107 | /** 108 | * Uploads an extension. 109 | * @param {String} zipFilePath 110 | * @todo: Handle "Bad request" in case an extension is already existing. 111 | */ 112 | this.upload = function ( zipFilePath ) { 113 | var defer = Q.defer(); 114 | 115 | var skip = false; 116 | if ( !fs.existsSync( zipFilePath ) ) { 117 | defer.reject( 'File does not exist: ' + zipFilePath ); 118 | skip = true; 119 | } 120 | if ( path.extname( zipFilePath ).toLowerCase() !== '.zip' ) { 121 | defer.reject( 'Only .zip files can be uploaded.' ); 122 | skip = true; 123 | } 124 | 125 | if (!skip) { 126 | base.postFile( 'qrs/extension/upload', null, zipFilePath ) 127 | .then( function ( data ) { 128 | defer.resolve( data ); //Todo: data contains the file buffer, doesn't make a lot of sense returning this data. 129 | }, function ( err ) { 130 | defer.reject( err ); 131 | } ); 132 | } 133 | return defer.promise; 134 | }; 135 | 136 | /** 137 | * Returns whether an extension is installed or not. 138 | * @param name 139 | */ 140 | this.isInstalled = function ( name ) { 141 | 142 | var defer = Q.defer(); 143 | 144 | var retVal = { 145 | isInstalled: false, 146 | name: name, 147 | info: {} 148 | }; 149 | defer.resolve( retVal ); 150 | return defer.promise; 151 | }; 152 | } 153 | 154 | module.exports = Extension; 155 | -------------------------------------------------------------------------------- /test/unit/02-qrs.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,beforeEach,it*/ 2 | /*jshint -W030,-W117*/ 3 | 4 | 'use strict'; 5 | 6 | var chai = require( 'chai' ); 7 | var chaiAsPromised = require( 'chai-as-promised' ); 8 | var expect = chai.expect; 9 | var assert = chai.assert; 10 | var QRS = new require( './../../lib/qrs' ); 11 | var nock = require( 'nock' ); 12 | 13 | chai.use(chaiAsPromised); 14 | 15 | 16 | describe( 'Unit: qrs', function () { 17 | 18 | var qrs; 19 | 20 | /** 21 | * Reset the configuration before each test. 22 | */ 23 | beforeEach( function ( ) { 24 | 25 | var config = { 26 | useSSL: false, 27 | port: 4242, 28 | host: 'myHost', 29 | xrfkey: '123456789ABCDEFG', 30 | virtualProxy: 'sso' 31 | } ; 32 | qrs = new QRS( config ); 33 | } ); 34 | 35 | it( 'should be properly set up', function ( ) { 36 | assert( typeof(qrs) === 'object' ); 37 | assert( typeof(qrs.set) === 'function' ); 38 | expect( qrs.getConfig() ).to.not.be.empty; 39 | } ); 40 | 41 | it( 'should receive startup params', function ( ) { 42 | 43 | var qrs2 = new QRS( {host: 'testhost'} ); 44 | var cfg = qrs2.getConfig(); 45 | expect( cfg ).to.exist; 46 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost' ); 47 | } ); 48 | 49 | it( 'should allow to change params', function ( ) { 50 | var qrs2 = new QRS( {host: 'testhost'} ); 51 | qrs2.set( 'host', 'testhost2' ); 52 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost2' ); 53 | } ); 54 | 55 | it( 'should properly create URLs', function () { 56 | 57 | var qrs2 = new QRS(); // Create a new QRS to be independent from any config 58 | 59 | // Default 60 | qrs2.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 61 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 62 | 63 | // SSL 64 | qrs2.setConfig( {useSSL: true, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 65 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'https://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 66 | 67 | // Virtual Proxy 68 | qrs2.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} ); 69 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 70 | 71 | // Port 72 | qrs2.setConfig( { 73 | useSSL: false, 74 | port: 4242, 75 | host: 'myHost', 76 | xrfkey: '123456789ABCDEFG', 77 | virtualProxy: 'sso' 78 | } ); 79 | expect( qrs2.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 80 | } ); 81 | 82 | 83 | it.skip( 'should fail if METHOD is not in (GET, POST, PUT, DELETE)', function ( ) { 84 | expect( true ).to.equal( false ); 85 | done(); 86 | } ); 87 | 88 | describe( 'qrs.request', function () { 89 | it( 'should resolve properly', function () { 90 | 91 | nock( 'http://mock-host:4242/' ) 92 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' ) 93 | .reply( 200, 'Hello World' ); 94 | 95 | qrs.setConfig( { 96 | port: 4242, 97 | host: 'mock-host', 98 | xrfkey: '123456789ABCDEFG', 99 | virtualProxy: '' 100 | } ); 101 | return expect( qrs.request('GET', 'qrs/about' ) ).to.eventually.be.equal('Hello World'); 102 | 103 | } ); 104 | 105 | it.skip( 'should reject if config parameters are missing', function ( ) { 106 | 107 | nock( 'http://mock-host:4242/' ) 108 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' ) 109 | .reply( 200, 'Hello World' ); 110 | 111 | return expect( qrs.request('GET', '/qrs/about') ).to.be.rejected; 112 | } ); 113 | } ); 114 | 115 | describe( 'qrs.get', function () { 116 | it( 'should resolve for /qrs/about/', function ( ) { 117 | nock( 'http://mock-host:4242/' ) 118 | .get( '/qrs/about/?xrfkey=123456789ABCDEFG' ) 119 | .reply( 200, 'Hello World' ); 120 | 121 | qrs.setConfig( { 122 | port: 4242, 123 | host: 'mock-host', 124 | xrfkey: '123456789ABCDEFG', 125 | virtualProxy: '' 126 | } ); 127 | return expect( qrs.get('qrs/about' ) ).to.eventually.be.equal('Hello World'); 128 | } ); 129 | } ); 130 | 131 | describe( 'qrs.post', function () { 132 | 133 | it( 'should resolve for /qrs/whatever', function ( ) { 134 | 135 | nock( 'http://mock-host:4242/' ) 136 | .post( '/qrs/whatever/?xrfkey=123456789ABCDEFG' ) 137 | .reply( 200, 'Hello World' ); 138 | 139 | qrs.setConfig( { 140 | port: 4242, 141 | host: 'mock-host', 142 | xrfkey: '123456789ABCDEFG', 143 | virtualProxy: '' 144 | } ); 145 | return expect( qrs.post('qrs/whatever' ) ).to.eventually.be.equal('Hello World'); 146 | 147 | } ); 148 | 149 | } ); 150 | 151 | } ); 152 | -------------------------------------------------------------------------------- /test/unit/01-setup.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,beforeEach,it*/ 2 | /*jshint -W030,-W117*/ 3 | 'use strict'; 4 | 5 | var chai = require( 'chai' ); 6 | var assert = chai.assert; 7 | var expect = chai.expect; 8 | var QRS = new require( './../../lib/qrs' ); 9 | var testSetup = require( './../testSetup' ); 10 | 11 | var globalConfig = testSetup.globalConfig; 12 | 13 | describe( 'Unit: setup', function () { 14 | 15 | var qrs; 16 | /** 17 | * Reset the configuration before each test. 18 | */ 19 | beforeEach( function () { 20 | 21 | qrs = new QRS( globalConfig ); 22 | 23 | } ); 24 | 25 | it( 'should be properly set up', function () { 26 | assert( typeof(qrs) === 'object' ); 27 | assert( typeof(qrs.set) === 'function' ); 28 | expect( qrs.getConfig() ).to.not.be.empty; 29 | } ); 30 | 31 | it( 'should have default options', function () { 32 | var qrsDefault = new QRS( {} ); 33 | var c = qrsDefault.getConfig(); 34 | expect( c ).to.not.be.empty; 35 | expect( c ).to.have.property( 'host', '127.0.0.1' ); 36 | expect( c ).to.have.property( 'useSSL', false ); 37 | expect( c ).to.have.property( 'xrfkey', 'ABCDEFG123456789' ); 38 | expect( c ).to.have.property( 'authentication', 'windows' ); 39 | expect( c ).to.have.property( 'headerKey', '' ); 40 | expect( c ).to.have.property( 'headerValue', '' ); 41 | expect( c ).to.have.property( 'virtualProxy', '' ); 42 | } ); 43 | 44 | it( 'should receive startup params', function () { 45 | var qrs2 = new QRS( {host: 'testhost'} ); 46 | var cfg = qrs2.getConfig(); 47 | expect( cfg ).to.exist; 48 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost' ); 49 | } ); 50 | 51 | it( 'should allow to change params', function () { 52 | var qrs2 = new QRS( {host: 'testhost'} ); 53 | qrs2.set( 'host', 'testhost2' ); 54 | expect( qrs2.getConfig() ).to.have.property( 'host', 'testhost2' ); 55 | } ); 56 | 57 | describe( 'qrs.getUrl', function () { 58 | 59 | it( 'should properly return URLs: default', function () { 60 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 61 | expect( qrs.getUrl( '/qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 62 | } ); 63 | 64 | it( 'should properly return URLs: including slash', function () { 65 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 66 | expect( qrs.getUrl( '/qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 67 | } ); 68 | 69 | it( 'should properly return URLs: SSL', function () { 70 | qrs.setConfig( {useSSL: true, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 71 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'https://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 72 | } ); 73 | 74 | it( 'should properly return URLs: Virtual Proxy', function () { 75 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} ); 76 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 77 | } ); 78 | 79 | it( 'should properly return URLs: Empty Virtual Proxy', function () { 80 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: ''} ); 81 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/qrs/about/?xrfkey=123456789ABCDEFG' ); 82 | } ); 83 | 84 | it( 'should properly return URLs: Port', function () { 85 | qrs.setConfig( { 86 | useSSL: false, 87 | port: 4242, 88 | host: 'myHost', 89 | xrfkey: '123456789ABCDEFG', 90 | virtualProxy: 'sso' 91 | } ); 92 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 93 | } ); 94 | 95 | it( 'should allow to pass in port a string or as number', function () { 96 | qrs.setConfig( { 97 | useSSL: false, 98 | port: 4242, 99 | host: 'myHost', 100 | xrfkey: '123456789ABCDEFG', 101 | virtualProxy: 'sso' 102 | } ); 103 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 104 | 105 | qrs.setConfig( { 106 | useSSL: false, 107 | port: '4242', 108 | host: 'myHost', 109 | xrfkey: '123456789ABCDEFG', 110 | virtualProxy: 'sso' 111 | } ); 112 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost:4242/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 113 | } ); 114 | 115 | it( 'should revert to default values if port is not a number', function () { 116 | qrs.setConfig( { 117 | useSSL: false, 118 | port: 'abc', 119 | host: 'myHost', 120 | xrfkey: '123456789ABCDEFG', 121 | virtualProxy: 'sso' 122 | } ); 123 | expect( qrs.getUrl( 'qrs/about' ) ).to.be.equal( 'http://myHost/sso/qrs/about/?xrfkey=123456789ABCDEFG' ); 124 | } ); 125 | 126 | it( 'should handle URL params when creating the URL', function () { 127 | 128 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} ); 129 | expect( qrs.getUrl( 'qrs/about', [{'key': 'myFilter', 'value': 'filtervalue'}, { 130 | 'key': 'param', 131 | 'value': 'paramValue' 132 | }] ) ).to.be.equal( 'http://myHost/sso/qrs/about/?myFilter=filtervalue¶m=paramValue&xrfkey=123456789ABCDEFG' ); 133 | 134 | } ); 135 | 136 | it( 'should throw an exception if urlParams is not an array', function () { 137 | qrs.setConfig( {useSSL: false, host: 'myHost', xrfkey: '123456789ABCDEFG', virtualProxy: 'sso'} ); 138 | expect( qrs.getUrl.bind( null, 'qrs/about', { 139 | 'key': 'myFilter', 140 | 'value': 'filterValue' 141 | } ) ).to.throw( 'Parameter urlParams needs to be an array' ); 142 | } ); 143 | 144 | } ); 145 | } ); 146 | -------------------------------------------------------------------------------- /test/e2e/03-ep-extension.e2e.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,beforeEach,before,after*/ 2 | /* jshint -W030 */ 3 | 'use strict'; 4 | 5 | var chai = require( 'chai' ); 6 | var chaiAsPromised = require( 'chai-as-promised' ); 7 | var expect = chai.expect; 8 | var QRS = new require( './../../lib/qrs' ); 9 | var extend = require( 'extend-shallow' ); 10 | var fsUtils = require( 'fs-utils' ); 11 | var path = require( 'path' ); 12 | var testSetup = require( './../testSetup' ); 13 | var async = require( 'async' ); 14 | var extensionSetup = require( './../03-ep-extension.setup' )(); 15 | 16 | chai.use( chaiAsPromised ); 17 | 18 | var qrs; 19 | 20 | var globalConfig = testSetup.globalConfig; 21 | describe( 'qrs.extension', function () { 22 | 23 | it( 'should be an object', function () { 24 | qrs = new QRS( globalConfig ); 25 | expect( qrs.extension ).to.exist; 26 | } ); 27 | 28 | testSetup.testLoop.forEach( function ( loopConfig ) { 29 | 30 | describe( 'with ' + loopConfig.name, function () { 31 | 32 | var testConfig = extend( globalConfig, loopConfig.config ); 33 | 34 | after( function ( done ) { 35 | console.log( 'after \n' ); 36 | //extensionSetup.cleanExtZipFiles( function ( /*err, extensions*/ ) { 37 | // done(); 38 | //} ); 39 | done(); 40 | } ); 41 | 42 | before( function ( done ) { 43 | console.log( 'before\n' ); 44 | extensionSetup.init( done ); 45 | } ); 46 | 47 | beforeEach( function ( done ) { 48 | qrs = new QRS( testConfig ); 49 | async.map( extensionSetup.extensions, function ( ext, cb ) { 50 | qrs.extension.delete( ext.name ) 51 | .then( function ( /*data*/ ) { 52 | //cb( null ); 53 | }, function ( err ) { 54 | cb( err ); 55 | } ); 56 | }, function ( err /*, results */ ) { 57 | if ( err ) { 58 | throw err; 59 | } 60 | done(); 61 | } ); 62 | } ); 63 | 64 | it( 'should return all installed extensions', function () { 65 | 66 | expect( qrs.extension.getInstalled() ).to.be.fulfilled.and.to.be.an.array; 67 | 68 | } ); 69 | 70 | it( 'should allow a filter to only return specific types', function () { 71 | 72 | expect( qrs.extension.getInstalled( ['visualization-template'] ) ).to.be.fulfilled.and.to.be.an.array; 73 | 74 | } ); 75 | 76 | //Todo: Improve the test => move to unit tests 77 | it( 'should return only extensions of type ', function ( done ) { 78 | qrs.extension.getInstalledVis() 79 | .then( function ( data ) { 80 | expect( data ).to.exist; 81 | expect( data ).to.be.an.array; 82 | }, function ( err ) { 83 | expect( err ).to.not.exist; 84 | } ) 85 | .done( function () { 86 | done(); 87 | } ); 88 | } ); 89 | 90 | //Todo: Improve the test => move to unit tests 91 | it( 'should return only extensions of type ', function ( done ) { 92 | qrs.extension.getInstalledVisTemplates() 93 | .then( function ( data ) { 94 | expect( data ).to.exist; 95 | expect( data ).to.be.an.array; 96 | }, function ( err ) { 97 | expect( err ).to.not.exist; 98 | } ) 99 | .done( function () { 100 | done(); 101 | } ); 102 | } ); 103 | 104 | //Todo: Improve the test ==> move to unit tests 105 | it( 'should return only extensions of type ', function ( done ) { 106 | qrs.extension.getInstalledMashups() 107 | .then( function ( data ) { 108 | expect( data ).to.exist; 109 | expect( data ).to.be.an.array; 110 | }, function ( err ) { 111 | expect( err ).to.not.exist; 112 | } ) 113 | .done( function () { 114 | done(); 115 | } ); 116 | } ); 117 | 118 | //Todo: Improve the test ==> move to unit tests 119 | it( 'should return only extensions of type ', function ( done ) { 120 | qrs.extension.getInstalledMashupTemplates() 121 | .then( function ( data ) { 122 | expect( data ).to.exist; 123 | expect( data ).to.be.an.array; 124 | }, function ( err ) { 125 | expect( err ).to.not.exist; 126 | } ) 127 | .done( function () { 128 | done(); 129 | } ); 130 | } ); 131 | 132 | describe( 'qrs.extension.upload', function () { 133 | 134 | it( 'doesn\'t allow upload of non existing files', function () { 135 | 136 | var extPath = path.join( __dirname, './fixtures/extensions/qrs-ABCDEFGHIJKLMNOPQ.zip' ); 137 | return expect( qrs.extension.upload( extPath ) ).to.eventually.be.rejectedWith( 'File does not exist: ' + extPath ); 138 | 139 | } ); 140 | 141 | it( 'doesn\'t allow upload of an extension with a different type than .zip', function () { 142 | 143 | var extPath = path.join( __dirname, './../fixtures/extensions/qrs-sample.7z' ); 144 | return expect( qrs.extension.upload( extPath ) ).to.eventually.be.rejectedWith( 'Only .zip files can be uploaded.' ); 145 | 146 | } ); 147 | } ); 148 | 149 | /** 150 | * @todo: Returns "bad request" if extension is already existing. 151 | */ 152 | it.skip( 'should allow upload of an extension with absolute path', function ( done ) { 153 | var extPath = path.join( __dirname, './../fixtures/extensions/qrs-sample.zip' ); 154 | qrs.extension.upload( extPath ) 155 | .then( function ( data ) { 156 | expect( data ).to.exist; 157 | }, function ( err ) { 158 | expect( err ).to.not.exist; 159 | } ) 160 | .done( function () { 161 | done(); 162 | } ); 163 | } ); 164 | 165 | describe( 'should allow deletion of an extension', function () { 166 | 167 | before( '', function ( done ) { 168 | //qrs.extension.upload 169 | done(); 170 | } ); 171 | after( '', function ( done ) { 172 | 173 | done(); 174 | } ); 175 | 176 | it( '...', function ( done ) { 177 | expect( true ).to.equal( true ); 178 | done(); 179 | } ); 180 | } ); 181 | 182 | it.skip( 'should refuse to upload an extension if already existing, even with another type', function ( done ) { 183 | expect( true ).to.equal( false ); 184 | done(); 185 | } ); 186 | 187 | } ); 188 | } ); 189 | } ); 190 | -------------------------------------------------------------------------------- /lib/sugar/ep-mime.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Q = require( 'q' ); 3 | var _ = require( 'lodash' ); 4 | var fs = require( 'fs' ); 5 | 6 | /** 7 | * Mime type definition 8 | * @typedef {object} mimeTypeDef. 9 | * @property {string} mime - Mime type, e.g. "application/markdown". 10 | * @property {string} extensions - Comma delimited string of supported file extensions, e.g. "md,markdown". 11 | * @property {boolean} additionalHeaders - Additional headers, defaults to null. 12 | * @property {boolean} binary - Whether this is a binary file type or not. 13 | * @api public 14 | */ 15 | 16 | /** 17 | * Handle Mime types in QRS. 18 | * 19 | * @param {qrs} base - Base class, instance of `qrs`. 20 | * @constructor 21 | * @api public 22 | */ 23 | function Mime ( base ) { 24 | 25 | /** 26 | * Adds a mime type. 27 | * 28 | * When adding the mime type, the following validation logic will be performed: 29 | * - All existing mime types will be grouped by mime, additionalHeaders and binary. 30 | * - If there is already a mime type with the same information compared to the given one, the field extension will be updated. 31 | * 32 | * An example is listed below. 33 | * 34 | * 35 | * ```js 36 | * // ----------------------------------------------------------------- 37 | * // Inserting logic 38 | * // ----------------------------------------------------------------- * 39 | * 40 | * // Assuming that the following mime type is already stored: 41 | * { 42 | * extensions: "md" 43 | * mime: "application/markdown", 44 | * additionalHeaders: null, 45 | * binary: false 46 | * } 47 | * // If you add the following mime type 48 | * { 49 | * extensions: "markdown" 50 | * mime: "application/markdown", 51 | * additionalHeaders: null, 52 | * binary: false 53 | * } 54 | * //this would then result into: 55 | * { 56 | * extensions: "md,markdown" 57 | * mime: "application/markdown", 58 | * additionalHeaders: null, 59 | * binary: false 60 | * } 61 | * 62 | * // ----------------------------------------------------------------- 63 | * // Adding a mime type: 64 | * // ----------------------------------------------------------------- 65 | * 66 | * var mimeType = { 67 | * extensions: "md" 68 | * mime: "application/markdown", 69 | * additionalHeaders: null, 70 | * binary: false 71 | * } 72 | * 73 | * qrs.mime.add( mimeType ) 74 | * .then( function (result ) { 75 | * // work with the result 76 | * }, function (err) { 77 | * // error handling 78 | * }); 79 | * 80 | * ``` 81 | * 82 | * @param {Object} mimeTypeDef 83 | * @param {string} mimeTypeDef.mime - Mime type, e.g. "application/markdown". 84 | * @param {string} mimeTypeDef.extensions - Comma delimited string of supported file extensions, e.g. "md,markdown". 85 | * @param {boolean} mimeTypeDef.additionalHeaders - Additional headers, defaults to null. 86 | * @param {boolean} mimeTypeDef.binary - Whether this is a binary file type or not. 87 | * @return {promise} 88 | * @api public 89 | */ 90 | var add = function ( mimeTypeDef ) { 91 | 92 | var defer = Q.defer(); 93 | if ( _.isEmpty(mimeTypeDef.mime)) { 94 | defer.reject('Mime type cannot be empty'); 95 | } 96 | if ( _.isEmpty(mimeTypeDef.extensions)) { 97 | defer.reject('Extensions cannot be empty'); 98 | } 99 | get() 100 | .then( function ( mimeList ) { 101 | var o = getUpdateOrInsert( mimeTypeDef, mimeList ); 102 | if ( o.isUpdate ) { 103 | _update( o.def.id, o.def ).then( function ( data ) { 104 | defer.resolve( data ); 105 | }, function ( err ) { 106 | defer.reject( err ); 107 | } ); 108 | } else { 109 | _add( o.def ).then( function ( data ) { 110 | defer.resolve( data ); 111 | }, function ( err ) { 112 | defer.reject( err ); 113 | } ); 114 | } 115 | }, function ( err ) { 116 | defer.reject( err ); 117 | } ); 118 | return defer.promise; 119 | }; 120 | var _add = function ( mimeTypeDef ) { 121 | return base.post( '/qrs/mimetype', null, mimeTypeDef ); 122 | }; 123 | 124 | /** 125 | * Returns a list of existing mime types. 126 | * 127 | * The list can be filtered by the file-extensions as shown in the example. 128 | * 129 | * ```js 130 | * getMimeTypes( 'html') 131 | * .then( function (data) { 132 | * 133 | * // data now contains an array of mime types where the field extensions contains "html" 134 | * // Results: html, xhtml, etc. 135 | * 136 | * }) 137 | * ``` 138 | * 139 | * @param {string} filter 140 | * @returns {promise} 141 | * @api public 142 | */ 143 | var get = function ( filter ) { 144 | 145 | var queryParams = []; 146 | if ( !_.isEmpty( filter ) ) { 147 | queryParams.push( {'key': 'filter', 'value': filter} ); 148 | } 149 | return base.get( '/qrs/mimetype/full', queryParams ); 150 | }; 151 | 152 | /** 153 | * Adds an array of mime types 154 | * (See `add` for more information about `mimeTypeDef`). 155 | * 156 | * @param {mimeTypeDef[]} mimeTypeDefs - Array of mime type definitions. 157 | * @returns {promise} 158 | * @api public 159 | */ 160 | var addMultiple = function ( mimeTypeDefs ) { 161 | 162 | var defer = Q.defer(); 163 | var results = []; 164 | 165 | defer.resolve(); 166 | 167 | if ( mimeTypeDefs && _.isArray( mimeTypeDefs ) ) { 168 | 169 | var promises = []; 170 | mimeTypeDefs.forEach( function ( mimeTypeDef ) { 171 | promises.push( add.bind( null, mimeTypeDef ) ); 172 | } ); 173 | 174 | return _.reduce( mimeTypeDefs, function ( memo, value ) { 175 | return memo.then( function () { 176 | return add( value ); 177 | } ).then( function ( result ) { 178 | results.push( result ); 179 | } ); 180 | }, defer.promise ).then( function () { 181 | return results; 182 | } ); 183 | 184 | } 185 | }; 186 | 187 | /** 188 | * Add mime types defined in a file. 189 | * Every line in the file is defined by the following entries, delimited by a semi-colon (;): 190 | * - extensions - {string} file extension, multiple values separated by a comma, e.g. "md,markdown" 191 | * - mime - {string} Mime type 192 | * - additionalHeaders - {boolean} Additional defined headers, leave blank if unsure 193 | * - binary - {boolean} Whether this is a binary format or not. 194 | * 195 | * ```bash 196 | * md;application/markdown;;false 197 | * yml;text/yml;;false 198 | * woff2;application/font-woff2;;true 199 | * ``` 200 | * 201 | * @param {String} filePath - Absolute file path. 202 | * @returns {promise} 203 | * @api public 204 | */ 205 | var addFromFile = function ( filePath ) { 206 | 207 | var defer = Q.defer(); 208 | if ( !fs.existsSync( filePath ) ) { 209 | defer.reject( 'File does not exist' ); 210 | } 211 | var data = fs.readFileSync( filePath, {encoding: 'utf-8'} ); 212 | 213 | var lines = data.match( /[^\r\n]+/g ); 214 | var mimeTypeDefs = []; 215 | lines.forEach( function ( line ) { 216 | var items = line.split( ';' ); 217 | mimeTypeDefs.push( { 218 | 'extensions': items[0], 219 | 'mime': items[1], 220 | 'additionalHeaders': _.isEmpty( items[2] ) ? null : items[2], 221 | 'binary': items[4] === 'true' 222 | } ); 223 | } ); 224 | addMultiple( mimeTypeDefs ) 225 | .then( function ( data ) { 226 | defer.resolve( data ); 227 | }, function ( err ) { 228 | defer.reject( err ); 229 | } ); 230 | return defer.promise; 231 | }; 232 | 233 | /** 234 | * Delete a mime entry from the Qlik Sense Repository by its given Id. 235 | * @param {String} id 236 | * @returns {promise} 237 | * @api public 238 | */ 239 | var deleteById = function ( id ) { 240 | return base.delete( '/qrs/mimetype', id ); 241 | }; 242 | 243 | var _update = function ( id, mimeTypeDef ) { 244 | return base.put( '/qrs/mimetype', id, null, mimeTypeDef ); 245 | }; 246 | 247 | var createExport = function ( filePath ) { 248 | 249 | var defer = Q.defer(); 250 | base.get( '/qrs/mimetype/full' ) 251 | .then( function ( data ) { 252 | 253 | var s = ''; 254 | for ( var i = 0; i < data.length; i++ ) { 255 | s += data[i].extensions + ';' ; 256 | s += data[i].mime + ';'; 257 | s += ((!_.isEmpty(data[i].additionalHeaders)) ? data[i].additionalHeaders : '') + ';'; 258 | s += data[i].binary + '\n'; 259 | } 260 | fs.writeFile( filePath, s, function ( err ) { 261 | if ( err ) { 262 | defer.reject( err ); 263 | } else { 264 | defer.resolve( filePath ); 265 | } 266 | } ); 267 | 268 | }, function ( err ) { 269 | defer.reject( err ); 270 | } ); 271 | return defer.promise; 272 | }; 273 | 274 | var createExportPerFileExt = function ( filePath ) { 275 | var defer = Q.defer(); 276 | base.get( '/qrs/mimetype/full' ) 277 | .then( function ( data ) { 278 | 279 | var ext = []; 280 | for ( var i = 0; i < data.length; i++ ) { 281 | var e = data[i].extensions.split(','); 282 | ext = ext.concat(e); 283 | } 284 | 285 | fs.writeFile( filePath, JSON.stringify(ext), function ( err ) { 286 | if ( err ) { 287 | defer.reject( err ); 288 | } else { 289 | defer.resolve( filePath ); 290 | } 291 | } ); 292 | 293 | }, function ( err ) { 294 | defer.reject( err ); 295 | } ); 296 | return defer.promise; 297 | }; 298 | 299 | /** 300 | * Returns whether the mime type already exists or not. 301 | * 302 | * @param {mimeTypeDef} mimeTypeDef 303 | * @returns {object} result - Returned result. 304 | * @returns {boolean} result.isUpdate - Whether to update or add. 305 | * @api public 306 | */ 307 | var getUpdateOrInsert = function ( mimeTypeDef, listMimeTypes ) { 308 | 309 | var result = { 310 | isUpdate: false, 311 | def: {} 312 | }; 313 | 314 | var tmp = _.filter( listMimeTypes, function ( m ) { 315 | return m.mime === mimeTypeDef.mime && m.binary === mimeTypeDef.binary || false && ((m.additionalHeaders || null) === (mimeTypeDef.additionalHeaders || null)); 316 | } ); 317 | 318 | if ( tmp.length === 1 ) { 319 | result.isUpdate = true; 320 | var updatedDef = tmp[0]; 321 | updatedDef.extensions = _.uniq( updatedDef.extensions.split( ',' ).concat( mimeTypeDef.extensions.split( ',' ) ) ).join( ',' ); 322 | result.def = updatedDef; 323 | } else if ( tmp.length === 0 ) { 324 | result.def = mimeTypeDef; 325 | result.isUpdate = false; 326 | } else if ( tmp.length > 1 ) { 327 | throw new Error( 'More than on mime type found to update' ); 328 | } 329 | 330 | return result; 331 | }; 332 | 333 | return { 334 | add: add, 335 | addFromFile: addFromFile, 336 | addMultiple: addMultiple, 337 | createExport: createExport, 338 | createExportPerFileExt: createExportPerFileExt, 339 | get: get, 340 | getUpdateOrInsert: getUpdateOrInsert, 341 | deleteById: deleteById 342 | }; 343 | } 344 | 345 | module.exports = Mime; 346 | 347 | 348 | 349 | -------------------------------------------------------------------------------- /test/e2e/04-ep-mime.e2e.spec.js: -------------------------------------------------------------------------------- 1 | /*global describe,it,beforeEach,after*/ 2 | /*jshint -W030,-W117*/ 3 | 'use strict'; 4 | 5 | var chai = require( 'chai' ); 6 | var expect = chai.expect; 7 | var assert = chai.assert; 8 | var QRS = new require( './../../lib/qrs' ); 9 | var extend = require( 'extend-shallow' ); 10 | var fsUtils = require( 'fs-utils' ); 11 | var path = require( 'path' ); 12 | var testSetup = require( './../testSetup' ); 13 | var _ = require( 'lodash' ); 14 | var Q = require( 'q' ); 15 | 16 | var qrs; 17 | var globalConfig = testSetup.globalConfig; 18 | 19 | describe( 'sugar-plugin: ep-mime', function () { 20 | 21 | testSetup.testLoop.forEach( function ( testLoopConfig ) { 22 | 23 | describe( 'with ' + testLoopConfig.name, function ( ) { 24 | 25 | var testConfig = extend( globalConfig, testLoopConfig.config ); 26 | var idsToDelete = []; 27 | beforeEach( function ( done ) { 28 | qrs = new QRS( testConfig ); 29 | done(); 30 | } ); 31 | 32 | afterEach( function ( done ) { 33 | 34 | if ( idsToDelete && idsToDelete.length > 0 ) { 35 | var promises = []; 36 | _.each( idsToDelete, function ( id ) { 37 | promises.push( qrs.mime.deleteById.bind( null, id ) ); 38 | } ); 39 | Q.all( promises ) 40 | .then( function ( data ) { 41 | idsToDelete.length = 0; 42 | done(); 43 | } ); 44 | } else { 45 | done(); 46 | } 47 | } ); 48 | 49 | after( function ( done ) { 50 | fsUtils.del( ['./test/mimetypes.text', './test/mimetypes_single.json'], function () { 51 | done(); 52 | } ); 53 | } ); 54 | 55 | it( 'should return the existing mime types', function ( done ) { 56 | qrs.mime.get() 57 | .then( function ( data ) { 58 | expect( data ).to.exist; 59 | expect( data ).to.be.an.array; 60 | } ) 61 | .done( function () { 62 | done(); 63 | } ); 64 | } ); 65 | 66 | it( 'should filter for mime types', function ( done ) { 67 | /*jshint -W109 */ 68 | qrs.mime.get( "extensions so 'html'" ) 69 | /*jshint +W109 */ 70 | .then( function ( data ) { 71 | expect( data ).to.exist; 72 | expect( data ).to.not.be.empy; 73 | expect( data ).to.be.an.array; 74 | expect( data ).to.have.length.above( 1 ); 75 | } ) 76 | .done( function () { 77 | done(); 78 | } ); 79 | } ); 80 | 81 | it( 'filter for unknown should return nothing', function ( done ) { 82 | /*jshint -W109 */ 83 | qrs.mime.get( "extensions so 'abcdefg'" ) 84 | /*jshint +W109 */ 85 | .then( function ( data ) { 86 | expect( data ).to.exist; 87 | expect( data ).to.not.be.empy; 88 | expect( data ).to.be.an.array; 89 | expect( data ).to.have.length( 0 ); 90 | } ) 91 | .done( function () { 92 | done(); 93 | } ); 94 | } ); 95 | 96 | it( 'can create an export', function ( done ) { 97 | qrs.mime.createExport( path.resolve( './test/mimetypes.text' ) ) 98 | .then( function ( path ) { 99 | expect( path ).to.exist; 100 | } ) 101 | .done( function () { 102 | done(); 103 | } ); 104 | } ); 105 | 106 | it( 'can create an export (per file extension)', function ( done ) { 107 | qrs.mime.createExportPerFileExt( path.resolve( './test/mimetypes_single.json' ) ) 108 | .then( function ( path ) { 109 | expect( path ).to.exist; 110 | } ) 111 | .done( function () { 112 | done(); 113 | } ); 114 | } ); 115 | 116 | describe( 'returns either objects to be updated or added', function () { 117 | 118 | /*jshint -W109*/ 119 | var existingTypes = [ 120 | { 121 | "id": "05750907-1728-46a5-b763-14d348208bf3", 122 | "createdDate": "2015-09-02T22:09:14.104Z", 123 | "modifiedDate": "2015-09-02T22:09:14.104Z", 124 | "modifiedByUserName": "INTERNAL\\bootstrap", 125 | "mime": "application/xhtml+xml", 126 | "extensions": "xhtml,xht", 127 | "additionalHeaders": null, 128 | "binary": false, 129 | "privileges": null, 130 | "schemaPath": "MimeType" 131 | }, 132 | { 133 | "id": "49974f33-31c2-4732-b057-7acb8f5303a0", 134 | "createdDate": "2015-09-02T22:09:14.104Z", 135 | "modifiedDate": "2015-09-02T22:09:14.104Z", 136 | "modifiedByUserName": "INTERNAL\\bootstrap", 137 | "mime": "text/html;charset=utf-8", 138 | "extensions": "html,htm", 139 | "additionalHeaders": "X-UA-Compatible:IE=edge", 140 | "binary": false, 141 | "privileges": null, 142 | "schemaPath": "MimeType" 143 | } 144 | ]; 145 | /*jshint +W109*/ 146 | 147 | it( 'returns the object to be updated', function () { 148 | var r = qrs.mime.getUpdateOrInsert( { 149 | 'extensions': 'foo', 150 | 'mime': 'text/html;charset=utf-8', 151 | 'binary': false 152 | }, existingTypes ); 153 | expect( r.isUpdate ).to.equal( true ); 154 | expect( r.def ).to.be.an.object; 155 | expect( r.def ).to.not.be.an.array; 156 | expect( r.def.extensions ).to.be.equal( 'html,htm,foo' ); 157 | expect( r.def.mime ).to.be.equal( 'text/html;charset=utf-8' ); 158 | } ); 159 | 160 | it( 'adds a new entry', function ( done ) { 161 | qrs.mime.add( { 162 | 'extensions': 'foo', 163 | 'mime': 'text/foo', 164 | 'additionalHeaders': null, 165 | 'binary': false 166 | } ).then( function ( data ) { 167 | expect( data ).to.exist; 168 | expect( data ).to.have.property( 'id' ); 169 | idsToDelete.push( data.id ); 170 | }, function ( err ) { 171 | expect( err ).to.not.exist; 172 | } ) 173 | .done( function () { 174 | done(); 175 | } ); 176 | } ); 177 | 178 | it( 'should reject to add entries if mime is empty', function ( done ) { 179 | qrs.mime.add( { 180 | 'extensions': 'foo', 181 | 'mime': '', 182 | 'additionalHeaders': null, 183 | 'binary': false 184 | } ) 185 | .then( function ( /*data*/ ) { 186 | }, function ( err ) { 187 | expect( err ).to.exist; 188 | expect( err ).to.be.equal( 'Mime type cannot be empty' ); 189 | } ) 190 | .done( function () { 191 | done(); 192 | } ); 193 | } ); 194 | 195 | it( 'should reject to add entries if extensions is empty', function ( done ) { 196 | qrs.mime.add( { 197 | 'extensions': '', 198 | 'mime': 'foo/bar', 199 | 'additionalHeaders': null, 200 | 'binary': false 201 | } ) 202 | .then( function ( /*data*/ ) { 203 | }, function ( err ) { 204 | expect( err ).to.exist; 205 | expect( err ).to.be.equal( 'Extensions cannot be empty' ); 206 | } ) 207 | .done( function () { 208 | done(); 209 | } ); 210 | } ); 211 | 212 | it( 'adds multiple entries', function ( done ) { 213 | qrs.mime.addMultiple( [{ 214 | 'extensions': 'foo', 215 | 'mime': 'text/foo', 216 | 'additionalHeaders': null, 217 | 'binary': false 218 | }, { 219 | 'extensions': 'bar', 220 | 'mime': 'text/bar', 221 | 'additionalHeaders': null, 222 | 'binary': false 223 | }] ) 224 | .then( function ( data ) { 225 | expect( data ).to.exist; 226 | expect( data ).to.be.an.array; 227 | data.forEach( function ( item ) { 228 | idsToDelete.push( item.id ); 229 | } ); 230 | }, function ( err ) { 231 | assert( true, err ); 232 | } ) 233 | .done( function () { 234 | done(); 235 | } ); 236 | } ); 237 | 238 | //Todo: Test should be done once, and not for all authentication scenarios 239 | it( 'add multiple entries should throw an error if server is not available', function ( done ) { 240 | var config = JSON.parse( JSON.stringify( testConfig ) ); // clone the object 241 | config.host = 'not_a_server'; 242 | var qrs2 = new QRS( config ); 243 | qrs2.mime.addMultiple( [{ 244 | 'extensions': 'foo', 245 | 'mime': 'text/foo', 246 | 'additionalHeaders': null, 247 | 'binary': false 248 | }, { 249 | 'extensions': 'bar', 250 | 'mime': 'text/bar', 251 | 'additionalHeaders': null, 252 | 'binary': false 253 | }] ) 254 | .then( function ( data ) { 255 | expect( data ).to.not.exist; 256 | }, function ( err ) { 257 | expect( err ).to.exist; 258 | } ) 259 | .done( function () { 260 | done(); 261 | } ); 262 | } ); 263 | 264 | it( 'updates existing entries', function ( done ) { 265 | qrs.mime.addMultiple( [{ 266 | 'extensions': 'foo', 267 | 'mime': 'text/foobar', 268 | 'additionalHeaders': null, 269 | 'binary': false 270 | }, { 271 | 'extensions': 'bar', 272 | 'mime': 'text/foobar', 273 | 'additionalHeaders': null, 274 | 'binary': false 275 | }] ) 276 | .then( function ( data ) { 277 | expect( data ).to.exist; 278 | expect( data ).to.be.an.array; 279 | _.each( data, function ( item ) { 280 | expect( item ).to.have.property( 'extensions' ); 281 | expect( item ).to.have.property( 'mime' ); 282 | expect( item ).to.have.property( 'additionalHeaders' ); 283 | expect( item ).to.have.property( 'binary' ); 284 | expect( item.mime ).to.be.equal( 'text/foobar' ); 285 | expect( item.additionalHeaders ).to.be.null; 286 | expect( item.binary ).to.be.equal( false ); 287 | idsToDelete.push( data.id ); 288 | } ); 289 | //Todo: We have a cleanup error here, check this out 290 | //expect(data[0].extensions ).to.be.equal('foo'); 291 | //expect(data[1].extensions ).to.be.equal('foo,bar'); 292 | }, function ( err ) { 293 | expect( err ).to.not.exist; 294 | } ) 295 | .done( function () { 296 | done(); 297 | } ); 298 | } ); 299 | 300 | it( 'adds multiple entries from file (update)', function ( done ) { 301 | 302 | var sourceFile = path.resolve( './test/fixtures/foobar.txt' ); 303 | qrs.mime.addFromFile( sourceFile ) 304 | .then( function ( data ) { 305 | expect( data ).to.exist; 306 | expect( data ).to.be.an.array; 307 | expect( data ).to.have.length( 2 ); 308 | _.each( data, function ( item ) { 309 | expect( item ).to.have.property( 'extensions' ); 310 | expect( item ).to.have.property( 'mime' ); 311 | expect( item ).to.have.property( 'additionalHeaders' ); 312 | expect( item ).to.have.property( 'binary' ); 313 | expect( item.mime ).to.be.equal( 'text/foobar' ); 314 | expect( item.additionalHeaders ).to.be.null; 315 | expect( item.binary ).to.be.equal( false ); 316 | idsToDelete.push( data.id ); 317 | } ); 318 | //expect(data[0].extensions ).to.be.equal('foo'); 319 | //expect(data[1].extensions ).to.be.equal('foo,bar'); 320 | }, function ( err ) { 321 | expect( err ).to.not.exist; 322 | } ) 323 | .done( function () { 324 | done(); 325 | } ); 326 | } ); 327 | 328 | it( 'adds multiple entries from file (update + insert)', function ( done ) { 329 | 330 | var sourceFile = path.resolve( './test/fixtures/foobarbaz.txt' ); 331 | qrs.mime.addFromFile( sourceFile ) 332 | .then( function ( data ) { 333 | expect( data ).to.exist; 334 | expect( data ).to.be.an.array; 335 | expect( data ).to.have.length( 3 ); 336 | _.each( data, function ( item ) { 337 | expect( item ).to.have.property( 'extensions' ); 338 | expect( item ).to.have.property( 'mime' ); 339 | expect( item ).to.have.property( 'additionalHeaders' ); 340 | expect( item ).to.have.property( 'binary' ); 341 | expect( item.additionalHeaders ).to.be.null; 342 | expect( item.binary ).to.be.equal( false ); 343 | idsToDelete.push( data.id ); 344 | } ); 345 | idsToDelete.push( data.id ); 346 | }, function ( err ) { 347 | expect( err ).to.not.exist; 348 | } ) 349 | .done( function () { 350 | done(); 351 | } ); 352 | } ); 353 | 354 | it( 'adding multiple entries from file should be able to fail (wrong entries)', function ( done ) { 355 | var sourceFile = path.resolve( './test/fixtures/error.txt' ); 356 | qrs.mime.addFromFile( sourceFile ) 357 | .then( function ( /*data*/ ) { 358 | }, function ( err ) { 359 | expect( err ).to.exist; 360 | expect( err ).to.be.equal( 'Mime type cannot be empty' ); 361 | } ) 362 | .done( function () { 363 | done(); 364 | } ); 365 | } ); 366 | 367 | it( 'adding multiple entries from file should be able to fail (server unavailable)', function ( done ) { 368 | var config = JSON.parse( JSON.stringify( testConfig ) ); // clone the object 369 | config.host = 'not_a_server'; 370 | var qrs2 = new QRS( config ); 371 | var sourceFile = path.resolve( './test/fixtures/foobarbaz.txt' ); 372 | qrs2.mime.addFromFile( sourceFile ) 373 | .then( function ( /*data*/ ) { 374 | }, function ( err ) { 375 | expect( err ).to.exist; 376 | } ) 377 | .done( function () { 378 | done(); 379 | } ); 380 | } ); 381 | } ); 382 | 383 | }); 384 | 385 | }); 386 | } ); 387 | -------------------------------------------------------------------------------- /lib/qrs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var extend = require( 'extend-shallow' ); 3 | var _ = require( 'lodash' ); 4 | var Q = require( 'q' ); 5 | var request = require( 'request' ); 6 | var S = require( 'string' ); 7 | var fs = require( 'fs' ); 8 | var path = require( 'path' ); 9 | var glob = require( 'glob' ); 10 | //var configValidator = require( './config-validator' ); 11 | 12 | /** 13 | * Work with Qlik Sense's REST based Repository API (qrs) from within node.js. 14 | * 15 | * **Configuration options:** 16 | * ```js 17 | * var QRS = require('qrs'); 18 | * var config = { 19 | host: '127.0.0.1', 20 | useSSL: false, 21 | xrfkey: 'ABCDEFG123456789', 22 | authentication: 'windows', 23 | headerKey: '', 24 | headerValue: '', 25 | virtualProxy: '' 26 | }; 27 | * 28 | * var qrs = new QRS( config ); 29 | * 30 | * ``` 31 | * 32 | * @param {Object} `qrsConfig` Global configuration options 33 | * 34 | * @api public 35 | */ 36 | var qrs = function qrs ( qrsConfig ) { 37 | 38 | var that = this; 39 | this.config = null; 40 | this.defaultConfig = { 41 | host: '127.0.0.1', 42 | useSSL: false, 43 | xrfkey: 'ABCDEFG123456789', 44 | authentication: 'windows', 45 | headerKey: '', 46 | headerValue: '', 47 | virtualProxy: '' 48 | }; 49 | if ( qrsConfig && !_.isEmpty( qrsConfig ) ) { 50 | this.config = extend( that.defaultConfig, qrsConfig ); 51 | } 52 | 53 | /** 54 | * (Internal) generic method to send requests to QRS. 55 | * Typically this method is only used internally, use `get`, `post`, `put` or `delete`. 56 | * 57 | * ```js 58 | * var QRS = require('qrs'); 59 | * 60 | * var qrsConfig = ...; // Set configuration options 61 | * var qrs = new QRS( qrsConfig ); 62 | * 63 | * qrs.request( 'GET', 'qrs/about', null, null) 64 | * .then( function( data ) { 65 | * console.log( 'about', data ); 66 | * }, function ( err ) { 67 | * console.error( 'An error occurred: ', err); 68 | * }); 69 | * ``` 70 | * 71 | * @param {String} `method` Http method, can be `GET`, `POST`, `PUT` or `DELETE` (defaults to `GET`). 72 | * @param {String} `endpoint` Endpoint to be used. Check the online documentation of the Qlik Sense Repository API to get a list of all endpoints available. 73 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 74 | * @param {Object} `jsonBody` JSON object to be used as the body for the Http request. 75 | * @param {String} `body` Body, if not defined as Json object, body needs to be passed as a buffer to e.g. upload a file. 76 | * @param {Object} `additionalRequestOptions` Additional request options. 77 | * @param {Object} `additionalHeaders` Additional headers. 78 | * @returns {promise} Returns a promise. 79 | * @api public 80 | */ 81 | this.request = function ( method, endpoint, urlParams, jsonBody, body, additionalRequestOptions, additionalHeaders ) { 82 | 83 | var defer = Q.defer(); 84 | var validConfig = _validateConfig(); 85 | 86 | if ( !validConfig ) { 87 | defer.reject( {error: {errorMsg: 'Invalid configuration', errSource: 'qrs.request'}} ); 88 | } else { 89 | 90 | var url = this.getUrl( S( endpoint ).chompLeft( '/' ), urlParams ); 91 | var headers = _getHeaders( additionalHeaders ); 92 | 93 | var requestOptions = { 94 | method: method || 'GET', 95 | url: url, 96 | headers: headers, 97 | proxy: that.config.fiddler ? 'http://127.0.0.1:8888' : null, 98 | timeout: 2000 99 | }; 100 | 101 | if ( _.isObject( jsonBody ) ) { 102 | requestOptions.json = jsonBody; 103 | } 104 | if ( _.isObject( body ) ) { 105 | requestOptions.body = body; 106 | } 107 | 108 | //Todo: Encapsulate cert-file loading. 109 | //Todo: Support default local certificates. 110 | if ( that.config.authentication === 'certificates' ) { 111 | /*jshint ignore:start*/ 112 | if ( that.config['cert'] ) { 113 | requestOptions.cert = (typeof that.config['cert'] === 'object' ? that.config['cert'] : fs.readFileSync( that.config['cert'] ) ); 114 | } 115 | if ( that.config['key'] ) { 116 | requestOptions.key = (typeof that.config['key'] === 'object' ? that.config['key'] : fs.readFileSync( that.config['key'] ) ); 117 | } 118 | if ( that.config['ca'] ) { 119 | requestOptions.ca = (typeof that.config['ca'] === 'object' ? that.config['ca'] : fs.readFileSync( that.config['ca'] ) ); 120 | } 121 | /*jshint ignore:end*/ 122 | if ( that.config.passphrase && !_.isEmpty( that.config.passphrase ) ) {requestOptions.passphrase = that.config.passphrase;} 123 | } 124 | 125 | requestOptions = _.extend( requestOptions, additionalRequestOptions || {} ); 126 | 127 | request( requestOptions, function ( error, response, responseBody ) { 128 | 129 | //Todo: encapsulate content fetching 130 | if ( error || (response.statusCode < 200 || response.statusCode > 299) ) { 131 | defer.reject( { 132 | error: error, 133 | response: response 134 | } ); 135 | } else { 136 | var r = null; 137 | if ( response.statusCode !== 204 ) { 138 | if ( _.isObject( responseBody ) ) { 139 | r = responseBody; 140 | } else { 141 | try { 142 | r = JSON.parse( responseBody ); 143 | } catch ( e ) { 144 | r = responseBody; 145 | } 146 | } 147 | } 148 | defer.resolve( r ); 149 | } 150 | } 151 | ); 152 | } 153 | return defer.promise; 154 | }; 155 | 156 | /** 157 | * Same as `request()` but with `method: 'GET'`. 158 | * 159 | * ```js 160 | * qrs.get( 'qrs/about') 161 | * .then( function ( data) { 162 | * console.log('about: ', data ); 163 | * }, function ( err ) { 164 | * console.error( err ); 165 | * }); 166 | * ``` 167 | * @param {String} `endpoint` QRS endpoint to be used. 168 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 169 | * @returns {promise} Returns a promise. 170 | * @api public 171 | */ 172 | this.get = function ( endpoint, urlParams ) { 173 | return this.request( 'GET', endpoint, urlParams ); 174 | }; 175 | 176 | /** 177 | * Same as `request()` but with `method: 'POST'`. 178 | * 179 | * @param {String} `endpoint` QRS endpoint to be used. 180 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 181 | * @param {Object} `jsonBody` Body to be posted, defined as JSON object. 182 | * @returns {promise} Returns a promise. 183 | * @api public 184 | */ 185 | this.post = function ( endpoint, urlParams, jsonBody ) { 186 | return this.request( 'POST', endpoint, urlParams, jsonBody ); 187 | }; 188 | 189 | /** 190 | * Post a file, actually same as `post()`, instead of posting a JSON body, posts a file buffer. 191 | * 192 | * @param {String} `endpoint` QRS endpoint to be used. 193 | * @param {Array} `urlParams` Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]` 194 | * @param {String} filePath Absolute or relative file path. 195 | * @returns {promise} Returns a promise. 196 | * @api public 197 | */ 198 | this.postFile = function ( endpoint, urlParams, filePath ) { 199 | 200 | var fileBuffer = fs.readFileSync( filePath ); 201 | var additionalHeaders = {}; 202 | additionalHeaders['Content-Type'] = 'application/octet-stream'; 203 | var additionalRequestOptions = { 204 | 'encoding': null 205 | }; 206 | return this.request( 'POST', endpoint, urlParams, null, fileBuffer, additionalRequestOptions, additionalHeaders ); 207 | }; 208 | 209 | /** 210 | * Same as `request()` but with `method: 'PUT'`. 211 | * 212 | * @api public 213 | */ 214 | this.put = function ( endpoint, id, urlParams, jsonBody ) { 215 | var finalEndpoint = S( endpoint ).ensureRight( '/' ) + id; 216 | return this.request( 'PUT', finalEndpoint, urlParams, jsonBody ); 217 | }; 218 | 219 | /** 220 | * Same as `request()` but with `method: 'DELETE'`. 221 | * 222 | * @api public 223 | */ 224 | this.delete = function ( endpoint, id, urlParams ) { 225 | var finalEndpoint = S( endpoint ).ensureRight( '/' ) + id; 226 | return this.request( 'DELETE', finalEndpoint, urlParams ); 227 | }; 228 | 229 | /** 230 | * Return the Url for the REST call considering the given configuration options 231 | * 232 | * @param {string} `endpoint` Endpoint for the qrs call. 233 | * @param {Object[]} `urlParams` Additional URL parameters as key/value array. 234 | * @param {String} `urlParam.key` Key. 235 | * @param {Object} `urlParam.value` Value. 236 | * @return {String} The constructed Url. 237 | * @api public 238 | */ 239 | this.getUrl = function ( endpoint, urlParams ) { 240 | 241 | if ( !_.isEmpty( urlParams ) && !_.isArray( urlParams ) ) { 242 | throw new Error( 'Parameter urlParams needs to be an array' ); 243 | } 244 | var params = urlParams || []; 245 | 246 | var url = ((that.config.useSSL) ? 'https://' : 'http://'); // http:// 247 | url += that.config.host; // http://host 248 | url += (that.config.port && !isNaN( that.config.port ) && that.config.port !== 0) ? ':' + that.config.port : ''; // http[s]://host[:port] 249 | url += '/'; // http[s]://host[:port]/ 250 | url += ((that.config.virtualProxy && !_.isEmpty( that.config.virtualProxy )) ? that.config.virtualProxy + '/' : ''); // http[s]://host[:port]/[vp/] 251 | url += S( endpoint ).chompLeft( '/' ); 252 | url += '/?'; 253 | 254 | params.push( {'key': 'xrfkey', 'value': that.config.xrfkey} ); 255 | params.forEach( function ( param ) { // parameters 256 | url += param.key + '=' + param.value + '&'; 257 | } ); 258 | url = S( url ).chompRight( '&' ).s; 259 | 260 | return url; 261 | }; 262 | 263 | /** 264 | * Set global configurations options for qrs. Can be used to change the configuration options after `qrs` has been initialized. 265 | * 266 | * ```js 267 | * var QRS = require('qrs'); 268 | * var configCert = { 269 | * authentication: 'certificates', 270 | * ... 271 | * }; 272 | * var qrs = new QRS( configCert ); 273 | * 274 | * // Talking to the server using certificates 275 | * qrs.get('qrs/about', function( result ) { 276 | * // ... 277 | * }); 278 | * 279 | * // Change configuration options, e.g. 280 | * var configHeader = { 281 | * authentication: 'header', 282 | * ... 283 | * }; 284 | * 285 | * qrs.setConfig( configHeader); 286 | * 287 | * // Talking to the server, now using header authentication. 288 | * qrs.get('qrs/about', function( result ) { 289 | * // ... 290 | * }); 291 | * ``` 292 | * 293 | * @param {Object} `qrsConfig` Global configuration options 294 | * @api public 295 | */ 296 | this.setConfig = function ( qrsConfig ) { 297 | if ( typeof qrsConfig !== 'undefined' && !_.isEmpty( qrsConfig ) ) { 298 | that.config = extend( that.defaultConfig, qrsConfig ); 299 | } 300 | return that.config; 301 | }; 302 | 303 | /** 304 | * Return the current configuration options. 305 | * 306 | * ```js 307 | * var QRS = require('qrs'); 308 | * var config = { 309 | * host: 'myserver.domain.com'; 310 | * }; 311 | * var qrs = new QRS( config ); 312 | * var host = qrs.getConfig( 'host' ); 313 | * console.log(host); //<== 'myserver.domain.com' 314 | * ``` 315 | * 316 | * @returns {qrsConfig} `qrsConfig` Configuration object. 317 | * @api public 318 | */ 319 | this.getConfig = function () { 320 | return that.config || that.defaultConfig; 321 | }; 322 | 323 | /** 324 | * Change a single configuration property. 325 | * 326 | * ```js 327 | * var QRS = require('qrs'); 328 | * var config = { 329 | * host: 'myserver.domain.com'; 330 | * }; 331 | * var qrs = new QRS( config ); 332 | * 333 | * qrs.get('qrs/about', function( result ) { 334 | * // about from myserver.domain.com 335 | * }); 336 | * 337 | * qrs.set('host', 'mysecondserver.domain.com'); 338 | * qrs.get('qrs/about', function( result ) { 339 | * // about from mysecondserver.domain.com 340 | * }); 341 | * ``` 342 | * 343 | * @param {String} `key` Key of the property 344 | * @param {Object} `val` Value 345 | * @api public 346 | */ 347 | this.set = function ( key, val ) { 348 | that.config[key] = val; 349 | }; 350 | 351 | /** 352 | * Retrieve a single configuration property. 353 | * 354 | * @param {String} `key` Key of the property 355 | * @returns {Object} Value of the requested property, otherwise undefined. 356 | * @api public 357 | */ 358 | this.getConfigValue = function ( key ) { 359 | return that.config[key]; 360 | }; 361 | 362 | // **************************************************************************************** 363 | // Plugins 364 | // **************************************************************************************** 365 | 366 | /** 367 | * Returns an array of loaded plugins. Use `registerPlugin()` to load a plugin. 368 | * 369 | * @type {Array} 370 | * @api public 371 | */ 372 | this.plugins = []; 373 | 374 | /** 375 | * Register a plugin to work with the base class of `qrs`. 376 | * Have a look at some of the already existing plugins like `./lib/sugar/ep-mime.js` 377 | * 378 | * 379 | * ```js 380 | * 381 | * // ----------------------------------------------------------- 382 | * // Define the plugin. 383 | * // ~~ 384 | * // Purpose: Do something great with extensions. 385 | * // Filename: ep-extension.js 386 | * // ----------------------------------------------------------- 387 | * 388 | * function Extension ( base ) { 389 | * 390 | * function doSomething() { 391 | * base.get( 'qrs/extension/schema') 392 | * .then( function( result ) { 393 | * // result now holding the result from the server 394 | * }, function (err) { 395 | * // error handling 396 | * }); 397 | * 398 | * return { 399 | * doSomething: doSomething 400 | * }; 401 | * } 402 | * 403 | * module.exports = Extension; 404 | * 405 | * // ----------------------------------------------------------- 406 | * // Register and use it 407 | * // ----------------------------------------------------------- 408 | * 409 | * var qrs = new QRS( config ); 410 | * qrs.registerPlugin('./ep-extension.js'); 411 | * 412 | * // Use the plugin 413 | * qrs.extension.upload( myFile, function( result ) { 414 | * // The file has been uploaded 415 | * }); 416 | * 417 | * ``` 418 | * 419 | * @param {Object} plugin 420 | * @api public 421 | */ 422 | this.registerPlugin = function ( plugin ) { 423 | if ( !this[plugin.name.toLowerCase()] ) { 424 | /*jshint -W055 */ 425 | var o = new plugin( this ); 426 | /*jshint +W055 */ 427 | this.plugins[plugin.name.toLowerCase()] = o; 428 | this[plugin.name.toLowerCase()] = o; 429 | } else { 430 | throw new Error( 'Plugin cannot be registered. Namespace for qrs.' + plugin.name.toLowerCase() + ' is already reserved.' ); 431 | } 432 | }; 433 | 434 | var matches = glob.sync( path.join( __dirname, './sugar/ep-*.js' ) ); 435 | for ( var i = 0; i < matches.length; i++ ) { 436 | var m = require( matches[i] ); 437 | that.registerPlugin( m ); 438 | } 439 | 440 | // **************************************************************************************** 441 | // Internal Helper 442 | // **************************************************************************************** 443 | var _getHeaders = function ( additionalHeaders ) { 444 | 445 | var header = { 446 | 'X-Qlik-xrfkey': that.config.xrfkey 447 | }; 448 | if ( that.config.headerKey && that.config.headerValue ) { 449 | header[that.config.headerKey] = that.config.headerValue; 450 | } 451 | _.extend( header, additionalHeaders || {} ); 452 | return header; 453 | 454 | }; 455 | 456 | // **************************************************************************************** 457 | // Validation 458 | //Todo: implement a more generic validation 459 | // **************************************************************************************** 460 | var _validateConfig = function () { 461 | 462 | var required = []; 463 | switch ( that.config.authentication ) { 464 | case 'header': 465 | required = ['headerKey', 'headerValue', 'xrfkey', 'useSSL', 'virtualProxy']; 466 | return _validateConfigMissing( that.config, required ); 467 | case 'ntlm': 468 | return true; 469 | case 'certificates': 470 | required = ['cert', 'key', 'ca']; 471 | return _validateConfigMissing( that.config, required ); 472 | default: 473 | return true; 474 | } 475 | }; 476 | 477 | var _validateConfigMissing = function ( configs, required ) { 478 | 479 | var missing = []; 480 | _.each( required, function ( item ) { 481 | if ( !configs.hasOwnProperty( item ) ) { 482 | missing.push( item ); 483 | } 484 | } ); 485 | return (missing.length === 0); 486 | }; 487 | 488 | }; 489 | module.exports = qrs; 490 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](https://img.shields.io/npm/v/qrs.svg?style=flat)](https://www.npmjs.com/package/qrs) [![Build Status](https://img.shields.io/travis/stefanwalther/qrs.svg?style=flat)](https://travis-ci.org/stefanwalther/qrs) [![dependencies](https://david-dm.org/stefanwalther/qrs.svg)](https://david-dm.org/stefanwalther/qrs) 2 | # qrs 3 | > Node.js library to communicate with Qlik Sense Repository Service (QRS) API. 4 | 5 | NOTE: This solution is not actively maintained anymore. Contributors to take over are highly welcome. 6 | Alternatively use [qrs-interact](https://github.com/eapowertools/qrs-interact) 7 | 8 | ![](https://raw.githubusercontent.com/stefanwalther/qrs/master/docs/images/qrs.png) 9 | 10 | ## Installation 11 | Install with [npm](https://www.npmjs.com/): 12 | 13 | ```sh 14 | $ npm install --save qrs 15 | ``` 16 | 17 | --- 18 | ## Table of Contents 19 | - [Usage](#usage) 20 | - [Configuration Options](#configuration-options) 21 | - [Server Setup](#server-setup) 22 | - [API](#api) 23 | - [Plugins](#plugins) 24 | - [Running tests](#running-tests) 25 | - [Contributing](#contributing) 26 | - [Author](#author) 27 | - [License](#license) 28 | 29 | --- 30 | 31 | ## Usage 32 | ```js 33 | var QRS = require('qrs'); 34 | var config = { 35 | "host": 'qsSingle', 36 | "useSSL": false, 37 | "xrfkey": 'ABCDEFG123456789', 38 | "authentication": "header", 39 | "headerKey": 'hdr-usr', 40 | "headerValue": 'qsSingle\\swr' 41 | }; 42 | var qrs = new QRS( config ); 43 | 44 | // Now run your command like 45 | qrs.get('qrs/about', function( data ) { 46 | 47 | // do something with the result 48 | 49 | }); 50 | ``` 51 | 52 | ## Configuration Options 53 | The configuration passed to the constructor of *qrs* drives how authentication is handled. 54 | 55 | ### Typical configurations 56 | 57 | **Example using header authentication** 58 | 59 | ```javascript 60 | var config = { 61 | authentication: 'header', 62 | host: 'server.mydomain.com', 63 | useSSL: true, 64 | virtualProxy: 'hdr', 65 | headerKey: 'hdr-usr', 66 | headerValue: 'mydomain\\justme' 67 | }; 68 | ``` 69 | 70 | **Example using certificates** 71 | 72 | ```js 73 | var config = { 74 | authentication: 'certificates', 75 | host: 'server.mydomain.com', 76 | useSSL: true, 77 | cert: 'C:\\CertStore\\client.pem', 78 | key: 'C:\\CertStore\\client_key.pem', 79 | ca: 'C:\\CertStore\\root.pem', 80 | port: 4242, 81 | headerKey: 'X-Qlik-User', 82 | headerValue: 'UserDirectory=Internal;UserId=sa_repository' 83 | }; 84 | ``` 85 | 86 | ### All options 87 | 88 | * **`authentication`** - Authentication method, can be "`windows`", "`certificates`" or "`header`", defaults to "`windows`". 89 | * **`host`** - Qualified / fully qualified name or IP-address of the server where the Qlik Sense Repository server is running on, defaults to "`127.0.0.1`" 90 | * **`useSSL`** - Whether to use SSL or not, defaults to `false`. 91 | * **`headerKey`** - Header key. 92 | * **`headerValue`** - Header value. 93 | * **`virtualProxy`** - Name of the virtual proxy. 94 | * **`port`** - Port to be used. 95 | * **`cert`** - Path to client certificate file (client.pem). 96 | * **`key`** - Path to client key file (client_key.pem). 97 | * **`ca`** - Path to ca file (root.pem) 98 | 99 | ## Server Setup 100 | There are several options to ensure that communication between this node.js module and Qlik Sense server is working properly: 101 | 102 | **Authenticating with HTTP headers** 103 | * [Using Header Authentication in Qlik Sense: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/header-authentication-configuration) 104 | * [Authenticating with HTTP headers](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Http-Headers.htm) in Qlik Sense Help for Developers 2.1.1 105 | 106 | **Authenticating with a server certificate** 107 | * [Authenticating with Certificates: Setup and Configuration](https://github.com/stefanwalther/articles/tree/master/authentication-certificates) 108 | * [Authenticating with the server certificate](http://help.qlik.com/sense/2.1/en-us/developer/Subsystems/RepositoryServiceAPI/Content/RepositoryServiceAPI/RepositoryServiceAPI-Connect-API-Authenticate-Reqs-Certificate.htm) in Qlik Sense Help for Developer 2.1.1 109 | 110 | ## API 111 | 112 | ### [qrs](lib/qrs.js#L37) 113 | Work with Qlik Sense's REST based Repository API (qrs) from within node.js. 114 | 115 | **Configuration options:** 116 | 117 | **Params** 118 | 119 | * `qrsConfig` **{Object}**: Global configuration options 120 | 121 | **Example** 122 | 123 | ```js 124 | var QRS = require('qrs'); 125 | var config = { 126 | 127 | var qrs = new QRS( config ); 128 | 129 | ``` 130 | 131 | ### [.request](lib/qrs.js#L82) 132 | (Internal) generic method to send requests to QRS. Typically this method is only used internally, use `get`, `post`, `put` or `delete`. 133 | 134 | **Params** 135 | 136 | * `method` **{String}**: Http method, can be `GET`, `POST`, `PUT` or `DELETE` (defaults to `GET`). 137 | * `endpoint` **{String}**: Endpoint to be used. Check the online documentation of the Qlik Sense Repository API to get a list of all endpoints available. 138 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 139 | * `jsonBody` **{Object}**: JSON object to be used as the body for the Http request. 140 | * `body` **{String}**: Body, if not defined as Json object, body needs to be passed as a buffer to e.g. upload a file. 141 | * `additionalRequestOptions` **{Object}**: Additional request options. 142 | * `additionalHeaders` **{Object}**: Additional headers. 143 | * `returns` **{promise}**: Returns a promise. 144 | 145 | **Example** 146 | 147 | ```js 148 | var QRS = require('qrs'); 149 | 150 | var qrsConfig = ...; // Set configuration options 151 | var qrs = new QRS( qrsConfig ); 152 | 153 | qrs.request( 'GET', 'qrs/about', null, null) 154 | .then( function( data ) { 155 | console.log( 'about', data ); 156 | }, function ( err ) { 157 | console.error( 'An error occurred: ', err); 158 | }); 159 | ``` 160 | 161 | ### [.get](lib/qrs.js#L173) 162 | Same as `request()` but with `method: 'GET'`. 163 | 164 | **Params** 165 | 166 | * `endpoint` **{String}**: QRS endpoint to be used. 167 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 168 | * `returns` **{promise}**: Returns a promise. 169 | 170 | **Example** 171 | 172 | ```js 173 | qrs.get( 'qrs/about') 174 | .then( function ( data) { 175 | console.log('about: ', data ); 176 | }, function ( err ) { 177 | console.error( err ); 178 | }); 179 | ``` 180 | 181 | ### [.post](lib/qrs.js#L186) 182 | 183 | Same as `request()` but with `method: 'POST'`. 184 | 185 | **Params** 186 | 187 | * `endpoint` **{String}**: QRS endpoint to be used. 188 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]`. 189 | * `jsonBody` **{Object}**: Body to be posted, defined as JSON object. 190 | * `returns` **{promise}**: Returns a promise. 191 | 192 | ### [.postFile](lib/qrs.js#L199) 193 | 194 | Post a file, actually same as `post()`, instead of posting a JSON body, posts a file buffer. 195 | 196 | **Params** 197 | 198 | * `endpoint` **{String}**: QRS endpoint to be used. 199 | * `urlParams` **{Array}**: Additional URL parameters, defined as key/value array, for example `[{"key": "foo", "value": valueObj}]` 200 | * **{String}**: filePath Absolute or relative file path. 201 | * `returns` **{promise}**: Returns a promise. 202 | 203 | ### [.put](lib/qrs.js#L215) 204 | 205 | Same as `request()` but with `method: 'PUT'`. 206 | 207 | ### [.delete](lib/qrs.js#L225) 208 | 209 | Same as `request()` but with `method: 'DELETE'`. 210 | 211 | ### [.getUrl](lib/qrs.js#L240) 212 | 213 | Return the Url for the REST call considering the given configuration options 214 | 215 | **Params** 216 | 217 | * `endpoint` **{string}**: Endpoint for the qrs call. 218 | * `urlParams` **{Object[]}**: Additional URL parameters as key/value array. 219 | * `urlParam.key` **{String}**: Key. 220 | * `urlParam.value` **{Object}**: Value. 221 | * `returns` **{String}**: The constructed Url. 222 | 223 | ### [.setConfig](lib/qrs.js#L297) 224 | Set global configurations options for qrs. Can be used to change the configuration options after `qrs` has been initialized. 225 | 226 | **Params** 227 | 228 | * `qrsConfig` **{Object}**: Global configuration options 229 | 230 | **Example** 231 | 232 | ```js 233 | var QRS = require('qrs'); 234 | var configCert = { 235 | authentication: 'certificates', 236 | ... 237 | }; 238 | var qrs = new QRS( configCert ); 239 | 240 | // Talking to the server using certificates 241 | qrs.get('qrs/about', function( result ) { 242 | // ... 243 | }); 244 | 245 | // Change configuration options, e.g. 246 | var configHeader = { 247 | authentication: 'header', 248 | ... 249 | }; 250 | 251 | qrs.setConfig( configHeader); 252 | 253 | // Talking to the server, now using header authentication. 254 | qrs.get('qrs/about', function( result ) { 255 | // ... 256 | }); 257 | ``` 258 | 259 | ### [.getConfig](lib/qrs.js#L320) 260 | Return the current configuration options. 261 | 262 | * `returns` **{qrsConfig}** `qrsConfig`: Configuration object. 263 | 264 | **Example** 265 | 266 | ```js 267 | var QRS = require('qrs'); 268 | var config = { 269 | host: 'myserver.domain.com'; 270 | }; 271 | var qrs = new QRS( config ); 272 | var host = qrs.getConfig( 'host' ); 273 | console.log(host); //<== 'myserver.domain.com' 274 | ``` 275 | 276 | ### [.set](lib/qrs.js#L348) 277 | Change a single configuration property. 278 | 279 | **Params** 280 | 281 | * `key` **{String}**: Key of the property 282 | * `val` **{Object}**: Value 283 | 284 | **Example** 285 | 286 | ```js 287 | var QRS = require('qrs'); 288 | var config = { 289 | host: 'myserver.domain.com'; 290 | }; 291 | var qrs = new QRS( config ); 292 | 293 | qrs.get('qrs/about', function( result ) { 294 | // about from myserver.domain.com 295 | }); 296 | 297 | qrs.set('host', 'mysecondserver.domain.com'); 298 | qrs.get('qrs/about', function( result ) { 299 | // about from mysecondserver.domain.com 300 | }); 301 | ``` 302 | 303 | ### [.getConfigValue](lib/qrs.js#L359) 304 | 305 | Retrieve a single configuration property. 306 | 307 | **Params** 308 | 309 | * `key` **{String}**: Key of the property 310 | * `returns` **{Object}**: Value of the requested property, otherwise undefined. 311 | 312 | ### [.plugins](lib/qrs.js#L373) 313 | 314 | Returns an array of loaded plugins. Use `registerPlugin()` to load a plugin. 315 | 316 | ### [.registerPlugin](lib/qrs.js#L423) 317 | Register a plugin to work with the base class of `qrs`. Have a look at some of the already existing plugins like `./lib/sugar/ep-mime.js` 318 | 319 | **Params** 320 | 321 | * **{Object}**: plugin 322 | 323 | **Example** 324 | 325 | ```js 326 | 327 | // ----------------------------------------------------------- 328 | // Define the plugin. 329 | // ~~ 330 | // Purpose: Do something great with extensions. 331 | // Filename: ep-extension.js 332 | // ----------------------------------------------------------- 333 | 334 | function Extension ( base ) { 335 | 336 | function doSomething() { 337 | base.get( 'qrs/extension/schema') 338 | .then( function( result ) { 339 | // result now holding the result from the server 340 | }, function (err) { 341 | // error handling 342 | }); 343 | 344 | return { 345 | doSomething: doSomething 346 | }; 347 | } 348 | 349 | module.exports = Extension; 350 | 351 | // ----------------------------------------------------------- 352 | // Register and use it 353 | // ----------------------------------------------------------- 354 | 355 | var qrs = new QRS( config ); 356 | qrs.registerPlugin('./ep-extension.js'); 357 | 358 | // Use the plugin 359 | qrs.extension.upload( myFile, function( result ) { 360 | // The file has been uploaded 361 | }); 362 | 363 | ``` 364 | 365 | ## Plugins 366 | Think of plugins as some kind of sugar layer with additional business logic on top of `qrs`. 367 | It is easy to either add new plugins to the `qrs` repository or just to load some external code/plugin. 368 | The list of built-in plugins is probably and hopefully a growing one (and hopefully not only created by the author of this document). 369 | 370 | The following plugins are available with the current version of `qrs`: 371 | 372 | 373 | 374 | 375 | 376 | ### Plugin "Mime" 377 | 378 | Mime type definition 379 | 380 | ### [Mime](lib/sugar/ep-mime.js#L24) 381 | 382 | Handle Mime types in QRS. 383 | 384 | **Params** 385 | 386 | * **{qrs}**: base - Base class, instance of `qrs`. 387 | 388 | ### [add](lib/sugar/ep-mime.js#L91) 389 | Adds a mime type. 390 | 391 | When adding the mime type, the following validation logic will be performed: 392 | - All existing mime types will be grouped by mime, additionalHeaders and binary. 393 | - If there is already a mime type with the same information compared to the given one, the field extension will be updated. 394 | An example is listed below. 395 | 396 | **Params** 397 | 398 | * **{Object}**: mimeTypeDef 399 | * **{string}**: mimeTypeDef.mime - Mime type, e.g. "application/markdown". 400 | * **{string}**: mimeTypeDef.extensions - Comma delimited string of supported file extensions, e.g. "md,markdown". 401 | * **{boolean}**: mimeTypeDef.additionalHeaders - Additional headers, defaults to null. 402 | * **{boolean}**: mimeTypeDef.binary - Whether this is a binary file type or not. 403 | * `returns` **{promise}** 404 | 405 | **Example** 406 | 407 | ```js 408 | // ----------------------------------------------------------------- 409 | // Inserting logic 410 | // ----------------------------------------------------------------- * 411 | 412 | // Assuming that the following mime type is already stored: 413 | { 414 | extensions: "md" 415 | mime: "application/markdown", 416 | additionalHeaders: null, 417 | binary: false 418 | } 419 | // If you add the following mime type 420 | { 421 | extensions: "markdown" 422 | mime: "application/markdown", 423 | additionalHeaders: null, 424 | binary: false 425 | } 426 | //this would then result into: 427 | { 428 | extensions: "md,markdown" 429 | mime: "application/markdown", 430 | additionalHeaders: null, 431 | binary: false 432 | } 433 | 434 | // ----------------------------------------------------------------- 435 | // Adding a mime type: 436 | // ----------------------------------------------------------------- 437 | 438 | var mimeType = { 439 | extensions: "md" 440 | mime: "application/markdown", 441 | additionalHeaders: null, 442 | binary: false 443 | } 444 | 445 | qrs.mime.add( mimeType ) 446 | .then( function (result ) { 447 | // work with the result 448 | }, function (err) { 449 | // error handling 450 | }); 451 | 452 | ``` 453 | 454 | ### [get](lib/sugar/ep-mime.js#L144) 455 | Returns a list of existing mime types. 456 | 457 | The list can be filtered by the file-extensions as shown in the example. 458 | 459 | **Params** 460 | 461 | * **{string}**: filter 462 | * `returns` **{promise}** 463 | 464 | **Example** 465 | 466 | ```js 467 | getMimeTypes( 'html') 468 | .then( function (data) { 469 | 470 | // data now contains an array of mime types where the field extensions contains "html" 471 | // Results: html, xhtml, etc. 472 | 473 | }) 474 | ``` 475 | 476 | ### [addMultiple](lib/sugar/ep-mime.js#L161) 477 | 478 | Adds an array of mime types 479 | (See `add` for more information about `mimeTypeDef`). 480 | 481 | **Params** 482 | 483 | * **{mimeTypeDef[]}**: mimeTypeDefs - Array of mime type definitions. 484 | * `returns` **{promise}** 485 | 486 | ### [addFromFile](lib/sugar/ep-mime.js#L206) 487 | Add mime types defined in a file. Every line in the file is defined by the following entries, delimited by a semi-colon (;): - extensions - {string} file extension, multiple values separated by a comma, e.g. "md,markdown" - mime - {string} Mime type - additionalHeaders - {boolean} Additional defined headers, leave blank if unsure - binary - {boolean} Whether this is a binary format or not. 488 | 489 | **Params** 490 | 491 | * **{String}**: filePath - Absolute file path. 492 | * `returns` **{promise}** 493 | 494 | **Example** 495 | 496 | ```bash 497 | md;application/markdown;;false 498 | yml;text/yml;;false 499 | woff2;application/font-woff2;;true 500 | ``` 501 | 502 | ### [deleteById](lib/sugar/ep-mime.js#L240) 503 | 504 | Delete a mime entry from the Qlik Sense Repository by its given Id. 505 | 506 | **Params** 507 | 508 | * **{String}**: id 509 | * `returns` **{promise}** 510 | 511 | ### [getUpdateOrInsert](lib/sugar/ep-mime.js#L308) 512 | 513 | Returns whether the mime type already exists or not. 514 | 515 | **Params** 516 | 517 | * **{mimeTypeDef}**: mimeTypeDef 518 | * `returns` **{object}**: result - Returned result. 519 | * `returns` **{boolean}**: result.isUpdate - Whether to update or add. 520 | 521 | --- 522 | 523 | ## Running tests 524 | Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command: 525 | 526 | ```sh 527 | $ npm install && npm test 528 | ``` 529 | 530 | ## Contributing 531 | Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new). 532 | 533 | ## Author 534 | **Stefan Walther** 535 | * [qliksite.io](http://qliksite.io) 536 | * [twitter/waltherstefan](http://twitter.com/waltherstefan) 537 | * [github.com/stefanwalther](http://github.com/stefanwalther) 538 | 539 | ## License 540 | Copyright © 2018, Stefan Walther. 541 | MIT 542 | 543 | *** 544 | _This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on January 21, 2018._ 545 | 546 | --------------------------------------------------------------------------------