diff --git a/README.md b/README.md index cd8f2c4..87ee728 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,18 @@ > This is the backend API for the Kapture server -# Getting started + +# Quickstart with Docker + +To get the entire system up and running, the quickest way is to use the docker build: + +```bash +docker-compose up +``` + +This will get you a fully working kapture server installation (with dependent services) in docker that you can access via [localhost:9000](localhost:9000). See the [API documentation](http://kapture.docs.stoplight.io) on how to properly use it. + +# Local Development Easiest way to get up and running here is to start up a local server: @@ -19,7 +30,7 @@ Then connect to http://localhost:9000 -# API Spec +# API [The entire API spec is well documented here](https://kapture.docs.stoplight.io) diff --git a/app/components/dest.js b/app/components/dest.js new file mode 100644 index 0000000..bcf7fef --- /dev/null +++ b/app/components/dest.js @@ -0,0 +1,28 @@ +const _ = require('lodash'); +const config = require('../config'); +const path = require('path'); + +const WHERES = { + music: 'downloadPaths.music', + photo: 'downloadPaths.photos', + photos: 'downloadPaths.photos', + tvshows: 'downloadPaths.shows', + tvshow: 'downloadPaths.shows', + series: 'downloadPaths.shows', + movie: 'downloadPaths.movies', + movies: 'downloadPaths.movies', + other: 'downloadPaths.default', + default: 'downloadPaths.default', + auto: 'downloadPaths.default' +}; + +exports.determineDest = function(where) { + if(!_.includes(Object.keys(WHERES), where)) { + throw new Error(`key ${where} not valid target location`); + } + + let subpath = config.get(WHERES[where]); + let fullbase = path.join(config.get('downloadPaths.root'), subpath); + + return path.resolve(fullbase); +} diff --git a/app/components/plugin_handler/plugins.js b/app/components/plugin_handler/plugins.js index 629dc99..cbe9174 100644 --- a/app/components/plugin_handler/plugins.js +++ b/app/components/plugin_handler/plugins.js @@ -21,6 +21,9 @@ const PLUGIN_REQUIREMENTS = { }, series: { // ie showrss functions: ['enableId', 'disableId', 'getEnabledSeries'] + }, + uploader: { + functions: ['uploadFile'] } } diff --git a/app/components/plugins/upload/index.js b/app/components/plugins/upload/index.js new file mode 100644 index 0000000..8371a16 --- /dev/null +++ b/app/components/plugins/upload/index.js @@ -0,0 +1,109 @@ +const crypto = require('crypto'); +const path = require('path'); +const prettyBytes = require('pretty-bytes'); + +const Dest = require('../../dest'); +const Plugin = require('../../plugin_handler/base'); + +// standard plugin metadata, and some additional flexget properties +class UploadHandler extends Plugin { + constructor() { + const metadata = { + pluginId: 'com_kapturebox_uploader', // Unique ID of plugin + pluginName: 'Uploader', // Display name of plugin + pluginTypes: ['downloader', 'uploader'], // 'source', 'downloader', 'player' + link: 'http://kapturebox.com', // Link to provider site + description: 'Uploader plugin' // Description of plugin provider + }; + + const defaultSettings = { + enabled: true, + }; + + super(metadata, defaultSettings); + } + + // we keep this around so that we can add the uploads to the + // 'download' holistic list of all files in system + status() { + // handle ID if passed in optionally + if(arguments[0]) { + return this.getState(arguments[0]); + } else { + return this.getState(); + } + } + + uploadFile(file, where) { + const self = this; + + return new Promise((resolve, reject) => { + const fname = file.name; + const basepath = Dest.determineDest(where); + const fullpath = path.join(basepath, fname); + const id = crypto.createHash('sha1') + .update(`${fullpath}`) + .digest('hex'); + + const saveObj = { + id: id, + fullPath: fullpath, + title: fname, + where: where, + contentType: file.mimetype, + size: prettyBytes(file.data.byteLength), + sourceId: self.metadata.sourceId, + sourceName: self.metadata.sourceName + }; + + file.mv(path.resolve(fullpath), function(err) { + if (err) + return reject(err); + + self.setState(id, saveObj); + + return resolve(saveObj); + }); + }); + } + + removeDownloadId(id, fromDisk) { + const self = this; + + return new Promise((resolve,reject) => { + let canonical = self.getState(id); + + if(!canonical) { + let err = new Error(`not found ${id}`); + err.statusCode(404); + throw err; + } + + self.removeState(id); + + if(fromDisk) { + fs.unlink(canonical.fullPath, (err) => { + if(err) { + return reject(err); + } + resolve(canonical); + }); + } else { + resolve(canonical); + } + }); + } + + + + downloadSlug(slug, where) { + return Promise.reject(new Error('KaptureUploadPlugin.downloadSlug not yet implemented')); + } + + + removeSlug(slug) { + return Promise.reject(new Error('KaptureUploadPlugin.removeSlug not yet implemented')); + } +} + +module.exports = UploadHandler; diff --git a/app/components/plugins/url/index.js b/app/components/plugins/url/index.js index a9fdfac..586c2af 100644 --- a/app/components/plugins/url/index.js +++ b/app/components/plugins/url/index.js @@ -6,15 +6,17 @@ const path = require('path'); const sanitize = require('sanitize-filename'); const Promise = require('bluebird'); const Url = require('url'); + const Plugin = require('../../plugin_handler/base'); +const Dest = require('../../dest'); class KaptureURLHandler extends Plugin { constructor() { const metadata = { - pluginId: 'com_kapture_url', // Unique ID of plugin + pluginId: 'com_kapturebox_url', // Unique ID of plugin pluginName: 'Kapture URL Handler', // Display name of plugin - pluginTypes: ['downloader'], // 'source', 'downloader', 'player' + pluginTypes: ['downloader'], // 'source', 'downloader', 'player' sourceTypes: 'adhoc', // 'adhoc', 'continuous' link: 'http://kapturebox.com', // Link to provider site downloadProviders: 'url', // if plugin can also download, what @@ -223,13 +225,9 @@ class KaptureURLHandler extends Plugin { getDestPath(url, dest) { - return path.resolve( - path.join( - this.config.getUserSetting('downloadPaths.root'), - this.config.getUserSetting('downloadPaths.' + (dest || 'default')), - this.getFilename(url) - ) - ); + let cleanDest = Dest.determineDest(dest); + + return path.join(cleanDest, this.getFilename(url)); } getFilename(url) { diff --git a/app/endpoints/getuploads.js b/app/endpoints/getuploads.js index 7f91641..f441edf 100644 --- a/app/endpoints/getuploads.js +++ b/app/endpoints/getuploads.js @@ -4,7 +4,13 @@ * GET: /api/v1/uploads * */ +const plugins = require('../components/plugin_handler'); + exports.handler = function getuploads(req, res, next) { - res.send('getuploads') - next() + const uploader = plugins.getPlugin('com_kapturebox_uploader'); + + uploader + .status() + .then((results) => res.status(200).json(results)) + .catch(next); } diff --git a/app/endpoints/newupload.js b/app/endpoints/newupload.js index ec2ef44..1dd9a8a 100644 --- a/app/endpoints/newupload.js +++ b/app/endpoints/newupload.js @@ -1,14 +1,33 @@ /** * Uploads a new file * - * POST: /api/v1/uploads + * POST: /api/v1/uploads?where={where} * * formData: * file {file} Binary upload data. - * type {string} Type of the media being uploaded. + * where {string} destination where file should be placed * */ +const plugins = require('../components/plugin_handler'); + +// TODO: This upload module needs tests + exports.handler = function newupload(req, res, next) { - res.send('newupload') - next() + const where = req.query.where || 'default'; + + if (!req.files) { + let err = new Error('no files were uploaded'); + err.statusCode = 400; + return next(err); + } + + const uploader = plugins.getPlugin('com_kapturebox_uploader'); + + const uplPromises = Object.keys(req.files) + .map((k) => uploader.uploadFile(req.files[k], where)); + + Promise + .all(uplPromises) + .then((results) => res.status(202).send(results)) + .catch(next) } diff --git a/app/express.js b/app/express.js index bcffc80..eab58fe 100644 --- a/app/express.js +++ b/app/express.js @@ -4,13 +4,11 @@ -const express = require('express'); const compression = require('compression'); const bodyParser = require('body-parser'); const methodOverride = require('method-override'); -const errorHandler = require('errorhandler'); -const path = require('path'); const winstonExpress = require('express-winston'); +const uploader = require('express-fileupload'); const cors = require('cors') const config = require('./config'); @@ -22,6 +20,7 @@ module.exports = function( app ) { app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.use(methodOverride()); + app.use(uploader()); app.set('x-powered-by', false); app.set('json spaces', 2); app.use( diff --git a/app/oas/main.oas2.yml b/app/oas/main.oas2.yml index 1019e29..5e0ac15 100644 --- a/app/oas/main.oas2.yml +++ b/app/oas/main.oas2.yml @@ -1,949 +1,946 @@ -swagger: "2.0" -info: - version: "1.0" - title: "Kapture API" - description: "This is the API spec for kapture to perform all the functions that are needed to \"curate your media\"." - contact: - name: "Evin Callahan" - url: "http://kapturebox.com" - license: - name: "" -host: "kapture.local" -schemes: - - "http" -consumes: - - "application/json" -produces: - - "application/json" -securityDefinitions: {} -paths: - /search: - get: - responses: - 200: - $ref: "#/responses/searchResponse" - description: "Searches all 'sources' to return results that match the `q` argument in the query string" - operationId: "search" - parameters: - - - in: "query" - name: "q" - type: "string" - description: "String to query sources for" +--- + swagger: "2.0" + info: + version: "1.0" + title: "Kapture API" + description: "This is the API spec for kapture to perform all the functions that are needed to \"curate your media\"." + contact: + name: "Evin Callahan" + url: "http://kapturebox.com" + license: + name: "" + host: "kapture.local" + schemes: + - "http" + consumes: + - "application/json" + produces: + - "application/json" + securityDefinitions: {} + paths: + /search: + get: + responses: + 200: + $ref: "#/responses/searchResponse" + description: "Searches all 'sources' to return results that match the `q` argument in the query string" + operationId: "search" + parameters: + - + in: "query" + name: "q" + type: "string" + description: "String to query sources for" + required: true + - + $ref: "#/parameters/filterQueryString" + summary: "Search all available sources" + tags: + - "Searching" + /downloads: + get: + responses: + 200: + description: "Successfully got download results" + schema: {} + summary: "Get all downloads, active and inactive" + operationId: "getalldownloads" + parameters: + - + $ref: "#/parameters/filterQueryString" + tags: + - "Downloading" + description: "Returns a list of all of the downloads that this instance of kapture has been responsible for. Both active and inactive results will be returned" + /series/{sourceId}/{entryId}: + post: + responses: + 202: + description: "Successfully added series" + schema: + type: "object" + properties: {} + 400: + description: "Invalid request (see error)" + parameters: + - + in: "path" + name: "sourceId" + type: "string" + required: true + description: "ID of source plugin to request entryId from" + - + in: "path" + name: "entryId" + type: "string" + required: true + description: "ID of series in question that you want added to autokapture" + summary: "Start a new series to be autokaptured" + operationId: "enableseries" + tags: + - "Series" + description: "Enables a new series to be autokaptured and downloaded whenever a new episode of that series is discovered." + parameters: + - + name: "sourceId" + in: "path" + type: "string" required: true - - - $ref: "#/parameters/filterQueryString" - summary: "Search all available sources" - tags: - - "Searching" - /downloads: - get: - responses: - 200: - description: "Successfully got download results" - schema: {} - summary: "Get all downloads, active and inactive" - operationId: "getalldownloads" - parameters: - - - $ref: "#/parameters/filterQueryString" - tags: - - "Downloading" - description: "Returns a list of all of the downloads that this instance of kapture has been responsible for. Both active and inactive results will be returned" - /series/{sourceId}/{entryId}: - post: - responses: - 202: - description: "Successfully added series" - schema: - type: "object" - properties: {} - 400: - description: "Invalid request (see error)" - parameters: - - + - + name: "entryId" in: "path" - name: "sourceId" type: "string" required: true - description: "ID of source plugin to request entryId from" - - + delete: + responses: + 202: + description: "Successfully deleted autokaptured series" + schema: + type: "object" + properties: {} + 400: + description: "Invalid request (see error)" + summary: "Delete a series from being autokaptured" + parameters: + - + in: "path" + name: "sourceId" + type: "string" + required: true + - + in: "path" + name: "entryId" + type: "string" + required: true + operationId: "deleteseries" + tags: + - "Series" + description: "Stops a specific `seriesId` from being autokaptured" + get: + responses: + 200: + $ref: "#/responses/seriesInfoResponse" + 404: + description: "Series doesn't exist" + summary: "Get details about a specific series" + description: "Will return an object with as much data as the source can provide, however it will namely return info about \"seen\" episodes, as well as \"upcoming\" ones." + operationId: "getseriesinfo" + tags: + - "Series" + /series: + get: + responses: + 200: + description: "Provides info on all currently enabled series" + schema: + type: "array" + items: + $ref: "#/definitions/seriesEnabledEntry" + examples: {} + summary: "Get all currently configured autokapture series" + operationId: "getallseries" + tags: + - "Series" + description: "Returns an array of `seriesEntry` objects that are currently configured to be autokaptured" + parameters: + - + $ref: "#/parameters/filterQueryString" + /settings: + get: + responses: + 200: + description: "" + schema: + type: "object" + properties: {} + summary: "Get all current settings" + operationId: "getallsettings" + tags: + - "Settings" + description: "Gets a list of all settings currently active" + put: + responses: + 202: + description: "Updated successfully" + schema: + type: "object" + properties: {} + summary: "Change ALL settings via body object" + description: "This will allow you to change all of the settings in the system at once.. if parts of the object are missing they will be nullified. If you don't want this behavior, use the `PATCH` method." + operationId: "putallsettings" + tags: + - "Settings" + parameters: + - + in: "body" + name: "body" + schema: + $ref: "#/definitions/settingsModel" + description: "Settings to change" + x-examples: + application/json: + downloadPaths: + default: "in commodo esse" + movies: "dolor proident" + music: "sit labore" + photos: "amet velit" + root: "aliqua adipisicing" + series: "qui et adipisicing aute labor" + plugins: + com_flexget: + apiToken: null + enabled: true + flexgetCheckFrequency: -73103553 + flexgetHost: "commodo aliquip minim" + flexgetPass: "fugiat" + flexgetPort: 41967102 + flexgetUser: "esse quis" + com_kapture_url: + enabled: true + com_piratebay: + enabled: true + com_transmissionbt: + enabled: true + transmissionHost: "ipsum" + transmissionPass: "labore laborum sit" + transmissionPort: -45449836 + transmissionUser: "eu ut ea magna" + com_youtube: + enabled: false + info_showrss: + enabled: false + system: + name: "dolor mollit proident amet" + userInfo: + email: "ipsum eiusmod velit labore" + patch: + responses: + 202: + description: "Successfully changed settings" + schema: + type: "object" + properties: {} + summary: "Update only the settings presented in body" + description: "This will update only the supplied settings" + operationId: "patchsettings" + tags: + - "Settings" + parameters: + - + in: "body" + name: "body" + schema: + $ref: "#/definitions/settingsModel" + description: "All of these options are optional, only supply the ones that need to be updated" + x-examples: {} + /settings/{key}: + get: + responses: + 200: + description: "Value of requested setting" + schema: + type: "object" + required: + - "value" + properties: + value: + type: "string" + 404: + description: "Setting not found" + summary: "Get a setting" + operationId: "getsetting" + tags: + - "Settings" + description: "Returns the value of a specific setting supplied in the `key` argument" + parameters: + - + name: "key" + in: "path" + type: "string" + required: true + description: "Key of setting you would like to change (nested keys are separated by .)" + /downloads/source:{sourceId}/{entryId}: + post: + responses: + 200: + description: "" + schema: + type: "object" + properties: {} + 400: + description: "Invalid request (see error)" + summary: "Start download based on source configuration of entryId" + description: "Allows `sourceId` plugin to determine how to download the given `entryId` denoted by the `id` field of search source objects. If this is a series, it largely does the same thing that a `POST /series/{sourceId}/{entryId}` does." + operationId: "startdownloadfromsource" + tags: + - "Downloading" + parameters: + - + name: "sourceId" in: "path" + type: "string" + required: true + - name: "entryId" + in: "path" type: "string" required: true - description: "ID of series in question that you want added to autokapture" - summary: "Start a new series to be autokaptured" - operationId: "enableseries" - tags: - - "Series" - description: "Enables a new series to be autokaptured and downloaded whenever a new episode of that series is discovered." - parameters: - - - name: "sourceId" - in: "path" - type: "string" - required: true - - - name: "entryId" - in: "path" - type: "string" - required: true - delete: - responses: - 202: - description: "Successfully deleted autokaptured series" - schema: - type: "object" - properties: {} - 400: - description: "Invalid request (see error)" - summary: "Delete a series from being autokaptured" - parameters: - - + /downloads/method:{methodId}/{slug}: + post: + responses: + 200: + description: "Successfully started new download. Returns information about newly started download." + schema: + type: "object" + properties: {} + 400: + description: "Invalid `methodId` provided" + summary: "Start download based on download method" + description: "Starts a new download based on `methodId` download provider of `slug` value provided by source.\n\nIn order to allow for various content in the `slug` field, **this field must be base64 encoded**. Generally source plugins will return the slug in this format." + operationId: "startdownloadfrommethod" + tags: + - "Downloading" + parameters: + - + $ref: "#/parameters/whereQueryString" + parameters: + - + name: "methodId" + in: "path" + type: "string" + required: true + description: "`methodId` that determines how the `slug` should be downloaded via" + - + name: "slug" in: "path" + type: "string" + required: true + description: "slug provided by source that represents what entry is desired to be initiatated" + /trending: + get: + responses: + 200: + $ref: "#/responses/trendingMediaAggregateResponse" + 409: + description: "Unable to perform request due to unexpected issue (see error)" + summary: "Get currently trending media" + parameters: + - + $ref: "#/parameters/filterQueryString" + operationId: "gettrending" + tags: + - "Trending" + description: "Gets a list of all currently trending media from all enabled `trending` sources. The `filter` querystring argument can be used to filter on entry object key/values, including the `sourceId` of the trending media object" + /downloads/{id}: + delete: + responses: + 200: + description: "" + schema: + type: "object" + properties: {} + summary: "Stops the given download ID" + operationId: "stopdownload" + tags: + - "Downloading" + parameters: + - + in: "query" + name: "fromDisk" + type: "boolean" + description: "If set to true, will delete the file from disk as well" + default: false + description: "This will stop an active download, and/or delete the file from disk as needed. If you want to just delete files, you can use this on a completed download as well." + parameters: + - + name: "id" + in: "path" + type: "string" + required: true + get: + responses: + 200: + description: "" + schema: + type: "object" + properties: {} + summary: "Gets the given download ID" + operationId: "getdownload" + tags: + - "Downloading" + description: "Gets details about a specific download `id`" + /uploads: + get: + responses: + 200: + description: "List of uploads" + schema: + type: "object" + properties: {} + summary: "Get a list of all of the uploads that have been performed" + operationId: "getuploads" + tags: + - "Uploading" + description: "Returns only a list of uploads that have been **successfully** sent to this kapture instance" + post: + responses: + 202: + description: "Successfully uploaded file" + schema: + type: "object" + properties: {} + summary: "Uploads a new file" + parameters: + - + in: "formData" + name: "file" + type: "file" + required: true + description: "Binary upload data" + - + $ref: "#/parameters/whereQueryString" + operationId: "newupload" + tags: + - "Uploading" + description: "Upload a new file to kapture instance. This should be uploaded via the multipart upload method, and will accept any files that are sent to it, regardless if the media player enabled can handle it." + consumes: + - "multipart/form-data" + /trending/{sourceId}/info/{id}: + get: + responses: + 200: + description: "" + schema: + type: "object" + properties: {} + 400: + description: "Invalid request (see error)" + summary: "Get more details about trending entry" + description: "Will provide as much info as possible to the user about a specific trending `id` from a given `sourceId`." + operationId: "trendinginfo" + tags: + - "Trending" + parameters: + - name: "sourceId" + in: "path" type: "string" required: true - - + - + name: "id" in: "path" - name: "entryId" type: "string" required: true - operationId: "deleteseries" - tags: - - "Series" - description: "Stops a specific `seriesId` from being autokaptured" - get: - responses: - 200: - $ref: "#/responses/seriesInfoResponse" - 404: - description: "Series doesn't exist" - summary: "Get details about a specific series" - description: "Will return an object with as much data as the source can provide, however it will namely return info about \"seen\" episodes, as well as \"upcoming\" ones." - operationId: "getseriesinfo" - tags: - - "Series" - /series: - get: - responses: - 200: - description: "Provides info on all currently enabled series" - schema: - type: "array" - items: - $ref: "#/definitions/seriesEnabledEntry" - examples: {} - summary: "Get all currently configured autokapture series" - operationId: "getallseries" - tags: - - "Series" - description: "Returns an array of `seriesEntry` objects that are currently configured to be autokaptured" - parameters: - - - $ref: "#/parameters/filterQueryString" - /settings: - get: - responses: - 200: - description: "" - schema: - type: "object" - properties: {} - summary: "Get all current settings" - operationId: "getallsettings" - tags: - - "Settings" - description: "Gets a list of all settings currently active" - put: - responses: - 202: - description: "Updated successfully" - schema: - type: "object" - properties: {} - summary: "Change ALL settings via body object" - description: "This will allow you to change all of the settings in the system at once.. if parts of the object are missing they will be nullified. If you don't want this behavior, use the `PATCH` method." - operationId: "putallsettings" - tags: - - "Settings" - parameters: - - - in: "body" - name: "body" - schema: - $ref: "#/definitions/settingsModel" - description: "Settings to change" - x-examples: - application/json: - downloadPaths: - default: "in commodo esse" - movies: "dolor proident" - music: "sit labore" - photos: "amet velit" - root: "aliqua adipisicing" - series: "qui et adipisicing aute labor" - plugins: - com_flexget: - apiToken: null - enabled: true - flexgetCheckFrequency: -73103553 - flexgetHost: "commodo aliquip minim" - flexgetPass: "fugiat" - flexgetPort: 41967102 - flexgetUser: "esse quis" - com_kapture_url: - enabled: true - com_piratebay: - enabled: true - com_transmissionbt: - enabled: true - transmissionHost: "ipsum" - transmissionPass: "labore laborum sit" - transmissionPort: -45449836 - transmissionUser: "eu ut ea magna" - com_youtube: - enabled: false - info_showrss: - enabled: false - system: - name: "dolor mollit proident amet" - userInfo: - email: "ipsum eiusmod velit labore" - patch: - responses: - 202: - description: "Successfully changed settings" - schema: - type: "object" - properties: {} - summary: "Update only the settings presented in body" - description: "This will update only the supplied settings" - operationId: "patchsettings" - tags: - - "Settings" - parameters: - - - in: "body" - name: "body" - schema: - $ref: "#/definitions/settingsModel" - description: "All of these options are optional, only supply the ones that need to be updated" - x-examples: {} - /settings/{key}: - get: - responses: - 200: - description: "Value of requested setting" - schema: - type: "object" - required: - - "value" - properties: - value: - type: "string" - 404: - description: "Setting not found" - summary: "Get a setting" - operationId: "getsetting" - tags: - - "Settings" - description: "Returns the value of a specific setting supplied in the `key` argument" - parameters: - - - name: "key" - in: "path" - type: "string" - required: true - description: "Key of setting you would like to change (nested keys are separated by .)" - /downloads/source:{sourceId}/{entryId}: - post: - responses: - 200: - description: "" - schema: - type: "object" - properties: {} - 400: - description: "Invalid request (see error)" - summary: "Start download based on source configuration of entryId" - description: "Allows `sourceId` plugin to determine how to download the given `entryId` denoted by the `id` field of search source objects. If this is a series, it largely does the same thing that a `POST /series/{sourceId}/{entryId}` does." - operationId: "startdownloadfromsource" - tags: - - "Downloading" - parameters: - - - name: "sourceId" - in: "path" - type: "string" - required: true - - - name: "entryId" - in: "path" - type: "string" - required: true - /downloads/method:{methodId}/{slug}: - post: - responses: - 200: - description: "Successfully started new download. Returns information about newly started download." - schema: - type: "object" - properties: {} - 400: - description: "Invalid `methodId` provided" - summary: "Start download based on download method" - description: "Starts a new download based on `methodId` download provider of `slug` value provided by source.\n\nIn order to allow for various content in the `slug` field, **this field must be base64 encoded**. Generally source plugins will return the slug in this format." - operationId: "startdownloadfrommethod" - tags: - - "Downloading" - parameters: - - - in: "query" - name: "where" - type: "string" - description: "String of download path where the downloaded file should be placed after completion. If not set, defaults to `default` download path setting" - enum: - - "default" - - "movies" + definitions: + downloadEntry: + type: "object" + description: "A typical download entry will look something like this" + properties: + type: + type: "string" + enum: + - "other" + - "movie" - "music" - - "photos" - - "shows" - allowEmptyValue: true - parameters: - - - name: "methodId" - in: "path" - type: "string" - required: true - description: "`methodId` that determines how the `slug` should be downloaded via" - - - name: "slug" - in: "path" - type: "string" - required: true - description: "slug provided by source that represents what entry is desired to be initiatated" - /trending: - get: - responses: - 200: - $ref: "#/responses/trendingMediaAggregateResponse" - 409: - description: "Unable to perform request due to unexpected issue (see error)" - summary: "Get currently trending media" - parameters: - - - $ref: "#/parameters/filterQueryString" - operationId: "gettrending" - tags: - - "Trending" - description: "Gets a list of all currently trending media from all enabled `trending` sources. The `filter` querystring argument can be used to filter on entry object key/values, including the `sourceId` of the trending media object" - /downloads/{id}: - delete: - responses: - 200: - description: "" - schema: - type: "object" - properties: {} - summary: "Stops the given download ID" - operationId: "stopdownload" - tags: - - "Downloading" - parameters: - - - in: "query" - name: "fromDisk" + - "photo" + - "series" + sourceId: + type: "string" + size: + type: "integer" + startDate: + type: "string" + format: "date-time" + title: + type: "string" + downloadMechanism: + type: "string" + id: + type: "string" + percentDone: + type: "integer" + rateDownload: + type: "integer" + eta: + type: "integer" + isFinished: type: "boolean" - description: "If set to true, will delete the file from disk as well" - default: false - description: "This will stop an active download, and/or delete the file from disk as needed. If you want to just delete files, you can use this on a completed download as well." - parameters: - - - name: "id" - in: "path" - type: "string" - required: true - get: - responses: - 200: - description: "" - schema: - type: "object" - properties: {} - summary: "Gets the given download ID" - operationId: "getdownload" - tags: - - "Downloading" - description: "Gets details about a specific download `id`" - /uploads: - get: - responses: - 200: - description: "List of uploads" - schema: - type: "object" - properties: {} - summary: "Get a list of all of the uploads that have been performed" - operationId: "getuploads" - tags: - - "Uploading" - description: "Returns only a list of uploads that have been **successfully** sent to this kapture instance" - post: - responses: - 202: - description: "Successfully uploaded file" - schema: - type: "object" - properties: {} - summary: "Uploads a new file" - parameters: - - - in: "formData" - name: "file" - type: "file" - required: true - description: "Binary upload data" - - - in: "formData" - name: "type" + isStalled: + type: "boolean" + sourceData: + type: "object" + required: + - "id" + - "percentDone" + - "eta" + - "isFinished" + - "isStalled" + searchResultEntry: + type: "object" + description: "A single entry for what you'll normally get back from the search results endpoint" + title: "searchResultEntry" + properties: + sourceId: + type: "string" + sourceName: + type: "string" + score: + type: "number" + downloadMechanism: + type: "string" + flexgetModel: + type: "string" + type: + type: "string" + enum: + - "series" + - "movie" + - "other" + - "music" + - "photo" + id: + type: "string" + category: + type: "string" + size: + type: "string" + title: + type: "string" + required: + - "sourceId" + - "downloadMechanism" + - "id" + - "title" + seriesEntry: + type: "object" + description: "A (usually) tvshow series entry provided by a plugin" + properties: + sourceId: + type: "string" + sourceName: + type: "string" + flexgetModel: type: "string" - description: "Type of the media being uploaded" - enum: + type: + type: "string" + enum: + - "series" - "movie" - - "tvshow" - "music" + - "photo" - "other" + id: + type: "string" + category: + type: "string" + size: + type: "string" + title: + type: "string" + required: + - "sourceId" + - "type" + - "id" + - "title" + trendingMediaEntry: + type: "object" + description: "How a trending-media object is presented" + properties: + type: + type: "string" + enum: + - "other" + - "music" + - "series" + - "movie" - "photo" - - "auto" - required: true - default: "other" - operationId: "newupload" - tags: - - "Uploading" - description: "Upload a new file to kapture instance. This should be uploaded via the multipart upload method, and will accept any files that are sent to it, regardless if the media player enabled can handle it." - /trending/{sourceId}/info/{id}: - get: - responses: - 200: - description: "" - schema: - type: "object" - properties: {} - 400: - description: "Invalid request (see error)" - summary: "Get more details about trending entry" - description: "Will provide as much info as possible to the user about a specific trending `id` from a given `sourceId`." - operationId: "trendinginfo" - tags: - - "Trending" - parameters: - - - name: "sourceId" - in: "path" - type: "string" - required: true - - - name: "id" - in: "path" - type: "string" - required: true -definitions: - downloadEntry: - type: "object" - description: "A typical download entry will look something like this" - properties: - type: - type: "string" - enum: - - "other" - - "movie" - - "music" - - "photo" - - "series" - sourceId: - type: "string" - size: - type: "integer" - startDate: - type: "string" - format: "date-time" - title: - type: "string" - downloadMechanism: - type: "string" - id: - type: "string" - percentDone: - type: "integer" - rateDownload: - type: "integer" - eta: - type: "integer" - isFinished: - type: "boolean" - isStalled: - type: "boolean" - sourceData: - type: "object" - required: - - "id" - - "percentDone" - - "eta" - - "isFinished" - - "isStalled" - searchResultEntry: - type: "object" - description: "A single entry for what you'll normally get back from the search results endpoint" - title: "searchResultEntry" - properties: - sourceId: - type: "string" - sourceName: - type: "string" - score: - type: "number" - downloadMechanism: - type: "string" - flexgetModel: - type: "string" - type: - type: "string" - enum: - - "series" - - "movie" - - "other" - - "music" - - "photo" - id: - type: "string" - category: - type: "string" - size: - type: "string" - title: - type: "string" - required: - - "sourceId" - - "downloadMechanism" - - "id" - - "title" - seriesEntry: - type: "object" - description: "A (usually) tvshow series entry provided by a plugin" - properties: - sourceId: - type: "string" - sourceName: - type: "string" - flexgetModel: - type: "string" - type: - type: "string" - enum: - - "series" - - "movie" - - "music" - - "photo" - - "other" - id: - type: "string" - category: - type: "string" - size: - type: "string" - title: - type: "string" - required: - - "sourceId" - - "type" - - "id" - - "title" - trendingMediaEntry: - type: "object" - description: "How a trending-media object is presented" - properties: - type: - type: "string" - enum: - - "other" - - "music" - - "series" - - "movie" - - "photo" - title: - type: "string" - additionalInfoUrl: - type: "string" - sourceId: - type: "string" - id: - type: "string" - score: - type: "number" - settingsModel: - type: "object" - description: "Represents the settings of the currently running kapture system" - properties: - downloadPaths: - type: "object" - properties: - default: - type: "string" - movies: - type: "string" - music: - type: "string" - photos: - type: "string" - root: - type: "string" - series: - type: "string" - plugins: - type: "object" - properties: - com_flexget: + title: + type: "string" + additionalInfoUrl: + type: "string" + sourceId: + type: "string" + id: + type: "string" + score: + type: "number" + settingsModel: + type: "object" + description: "Represents the settings of the currently running kapture system" + properties: + downloadPaths: + type: "object" + properties: + default: + type: "string" + movies: + type: "string" + music: + type: "string" + photos: + type: "string" + root: + type: "string" + series: + type: "string" + plugins: + type: "object" + properties: + com_flexget: + type: "object" + properties: + apiToken: + type: "null" + enabled: + type: "boolean" + flexgetCheckFrequency: + type: "integer" + flexgetHost: + type: "string" + flexgetPass: + type: "string" + flexgetPort: + type: "integer" + flexgetUser: + type: "string" + com_kapture_url: + type: "object" + properties: + enabled: + type: "boolean" + com_piratebay: + type: "object" + properties: + enabled: + type: "boolean" + com_transmissionbt: + type: "object" + properties: + enabled: + type: "boolean" + transmissionHost: + type: "string" + transmissionPass: + type: "string" + transmissionPort: + type: "integer" + transmissionUser: + type: "string" + com_youtube: + type: "object" + properties: + enabled: + type: "boolean" + info_showrss: + type: "object" + properties: + enabled: + type: "boolean" + system: + type: "object" + properties: + name: + type: "string" + userInfo: + type: "object" + properties: + email: + type: + - "null" + - "string" + trendingMediaAggregateModel: + type: "object" + description: "Captures a the response from an aggregation of trending media sources" + properties: + series: + type: + - "array" + - "null" + items: + $ref: "#/definitions/trendingMediaEntry" + photos: + type: + - "array" + - "null" + items: + $ref: "#/definitions/trendingMediaEntry" + music: + type: + - "array" + - "null" + items: + $ref: "#/definitions/trendingMediaEntry" + movies: + type: + - "array" + - "null" + items: + $ref: "#/definitions/trendingMediaEntry" + other: + type: + - "array" + - "null" + items: + $ref: "#/definitions/trendingMediaEntry" + seriesInfoEntry: + type: "object" + properties: + seen: + type: "array" + items: type: "object" - properties: - apiToken: - type: "null" - enabled: - type: "boolean" - flexgetCheckFrequency: - type: "integer" - flexgetHost: + properties: + title: type: "string" - flexgetPass: + uploaded: type: "string" - flexgetPort: - type: "integer" - flexgetUser: + format: "date-time" + slug: type: "string" - com_kapture_url: - type: "object" - properties: - enabled: - type: "boolean" - com_piratebay: - type: "object" - properties: - enabled: - type: "boolean" - com_transmissionbt: - type: "object" - properties: - enabled: - type: "boolean" - transmissionHost: + sourceData: type: "string" - transmissionPass: + downloadMechanism: type: "string" - transmissionPort: - type: "integer" - transmissionUser: + showName: type: "string" - com_youtube: - type: "object" - properties: - enabled: - type: "boolean" - info_showrss: + upcoming: + type: "array" + items: type: "object" - properties: - enabled: - type: "boolean" - system: - type: "object" - properties: - name: - type: "string" - userInfo: - type: "object" - properties: - email: - type: - - "null" - - "string" - trendingMediaAggregateModel: - type: "object" - description: "Captures a the response from an aggregation of trending media sources" - properties: - series: - type: - - "array" - - "null" - items: - $ref: "#/definitions/trendingMediaEntry" - photos: - type: - - "array" - - "null" - items: - $ref: "#/definitions/trendingMediaEntry" - music: - type: - - "array" - - "null" - items: - $ref: "#/definitions/trendingMediaEntry" - movies: - type: - - "array" - - "null" - items: - $ref: "#/definitions/trendingMediaEntry" - other: - type: - - "array" - - "null" - items: - $ref: "#/definitions/trendingMediaEntry" - seriesInfoEntry: - type: "object" - properties: - seen: + properties: + title: + type: "string" + date: + type: "string" + description: + type: "string" + detailLink: + type: "string" + title: + type: "string" + id: + type: "string" + seriesEnabledEntry: + type: "object" + properties: + id: + type: "string" + sourceId: + type: "string" + title: + type: "string" + basePath: "/api/v1" + responses: + downloadResponse: + description: "Typical response from a call to /downloads" + schema: type: "array" - items: - type: "object" - properties: - title: - type: "string" - uploaded: - type: "string" - format: "date-time" - slug: - type: "string" - sourceData: - type: "string" - downloadMechanism: - type: "string" - showName: - type: "string" - upcoming: + items: + $ref: "#/definitions/downloadEntry" + examples: + application/json: + - + hashString: "nisi" + percentDone: -92746936 + eta: 85060245 + isFinished: false + isStalled: true + mediaType: "photo" + sourceId: "cillum laboris labo" + size: 76965655 + startDate: "1949-09-06T11:27:55.902Z" + title: "Duis lab" + downloadMechanism: "laboris ex consequat reprehenderit" + rateDownload: 38597331 + sourceData: {} + - + hashString: "enim dolor" + percentDone: -30360695 + eta: 88104805 + isFinished: true + isStalled: false + mediaType: "photo" + sourceId: "Lorem amet ut" + size: 2952358 + startDate: "2016-10-17T08:15:18.136Z" + title: "incididunt id ea" + downloadMechanism: "aute" + rateDownload: -34736926 + sourceData: {} + - + hashString: "ipsum elit quis amet" + percentDone: 76582371 + eta: 30156531 + isFinished: false + isStalled: true + mediaType: "photo" + sourceId: "voluptate incididunt pariatur ad" + size: 68510321 + startDate: "1941-02-08T21:40:19.552Z" + title: "Ut culpa do" + downloadMechanism: "elit Excepteur dolo" + rateDownload: 52744531 + sourceData: {} + searchResponse: + description: "" + schema: type: "array" - items: - type: "object" - properties: - title: - type: "string" - date: - type: "string" - description: - type: "string" - detailLink: - type: "string" - title: - type: "string" - id: - type: "string" - seriesEnabledEntry: - type: "object" - properties: - id: - type: "string" - sourceId: - type: "string" - title: - type: "string" -basePath: "/api/v1" -responses: - downloadResponse: - description: "Typical response from a call to /downloads" - schema: - type: "array" - items: - $ref: "#/definitions/downloadEntry" - examples: - application/json: - - - hashString: "nisi" - percentDone: -92746936 - eta: 85060245 - isFinished: false - isStalled: true - mediaType: "photo" - sourceId: "cillum laboris labo" - size: 76965655 - startDate: "1949-09-06T11:27:55.902Z" - title: "Duis lab" - downloadMechanism: "laboris ex consequat reprehenderit" - rateDownload: 38597331 - sourceData: {} - - - hashString: "enim dolor" - percentDone: -30360695 - eta: 88104805 - isFinished: true - isStalled: false - mediaType: "photo" - sourceId: "Lorem amet ut" - size: 2952358 - startDate: "2016-10-17T08:15:18.136Z" - title: "incididunt id ea" - downloadMechanism: "aute" - rateDownload: -34736926 - sourceData: {} - - - hashString: "ipsum elit quis amet" - percentDone: 76582371 - eta: 30156531 - isFinished: false - isStalled: true - mediaType: "photo" - sourceId: "voluptate incididunt pariatur ad" - size: 68510321 - startDate: "1941-02-08T21:40:19.552Z" - title: "Ut culpa do" - downloadMechanism: "elit Excepteur dolo" - rateDownload: 52744531 - sourceData: {} - searchResponse: - description: "" - schema: - type: "array" - items: - $ref: "#/definitions/searchResultEntry" - examples: - application/json: - - - sourceId: "cillum aliqua" - downloadMechanism: "sed anim reprehenderit" - id: "Ut nostrud consequat dolore" - title: "nostrud pariatur dolor et" - sourceName: "ut ea ullamco sed labore" - score: 98500140.3094976 - flexgetModel: "exercitation proident" - mediaType: "ea consectetur aute deserunt" - category: "nostrud enim adipisicing culpa" - size: "sed id" - - - sourceId: "ad Lorem proident dolor" - downloadMechanism: "anim sunt officia amet Duis" - id: "pariatur proident minim" - title: "ipsum ea Lorem et" - sourceName: "pariatur irure id sit" - score: 44001117.74601233 - flexgetModel: "nisi enim consequat Ut" - mediaType: "in ex" - category: "elit cupidatat" - size: "voluptate anim irure" - - - sourceId: "culpa id" - downloadMechanism: "dolor magna" - id: "dolore" - title: "non" - sourceName: "voluptate velit" - score: 41821842.43931264 - flexgetModel: "magna nulla" - mediaType: "reprehenderit anim" - category: "in commod" - size: "in voluptate" - seriesEntriesResponse: - description: "" - schema: - type: "array" - items: - $ref: "#/definitions/seriesEntry" - examples: - application/json: - - - sourceId: "dolore deserunt aute aliquip" - mediaType: "aliqua cillum veniam" - id: "et ullamco" - title: "anim ad" - sourceName: "Duis eu occaecat" - flexgetModel: "commodo eu do" - category: "qui dolor eu fugiat" - size: "amet dolor et tempor nisi" - - - sourceId: "culpa Ut Duis irure" - mediaType: "irure Lorem" - id: "aliqua aliquip sit ea" - title: "mollit" - sourceName: "lab" - flexgetModel: "incididunt deserunt cupidatat" - category: "esse pariatur dolor" - size: "pariatur minim" - - - sourceId: "reprehenderit elit adipisicing sint" - mediaType: "quis commod" - id: "incididunt ut" - title: "veniam sint ut sunt" - sourceName: "ex fugiat" - flexgetModel: "elit" - category: "est ad" - size: "dolor Lorem mollit" - trendingMediaResponse: - description: "" - schema: - type: "array" - items: - $ref: "#/definitions/trendingMediaEntry" - examples: - application/json: - - - type: "photo" - title: "cupidatat dolor" - additionalInfoUrl: "cillum ex reprehenderit" - sourceId: "Excepteur in occaecat irure" - id: "cillum dolor ipsum pariatur" - score: -93292389.50193739 - - - type: "music" - title: "deserunt si" - additionalInfoUrl: "labore qui fugiat non" - sourceId: "est proident fugiat pariatur labore" - id: "elit dolor Ut fugiat" - score: -64599013.23188566 - - - type: "movie" - title: "nulla dolore" - additionalInfoUrl: "cillum sed aliquip" - sourceId: "laboris ex Duis laborum minim" - id: "officia" - score: 81205392.92131376 - - - type: "movie" - title: "et voluptate" - additionalInfoUrl: "ut in" - sourceId: "esse id qui amet" - id: "sint et nisi nulla est" - score: 30192020.44975567 - - - type: "music" - title: "id nisi" - additionalInfoUrl: "reprehenderit ut in deserunt" - sourceId: "aute dolor" - id: "ullamco ad" - score: -58345879.4525455 - trendingMediaAggregateResponse: - description: "Response of a trending media aggregate query" - schema: - $ref: "#/definitions/trendingMediaAggregateModel" - seriesInfoResponse: - description: "Response that contains info about a particular series info" - schema: - $ref: "#/definitions/seriesInfoEntry" -parameters: - filterQueryString: - name: "filter" - in: "query" - type: "string" - description: "This field allows for users to filter on specific fields that come back in objects from search results. The format is simply: `key1:value;key2:value2`." - pattern: "((?:\\w+:\\w+));?" -tags: - - - name: "Searching" - - - name: "Downloading" - - - name: "Uploading" - - - name: "Trending" - - - name: "Series" - - - name: "Settings" + items: + $ref: "#/definitions/searchResultEntry" + examples: + application/json: + - + sourceId: "cillum aliqua" + downloadMechanism: "sed anim reprehenderit" + id: "Ut nostrud consequat dolore" + title: "nostrud pariatur dolor et" + sourceName: "ut ea ullamco sed labore" + score: 98500140.3094976 + flexgetModel: "exercitation proident" + mediaType: "ea consectetur aute deserunt" + category: "nostrud enim adipisicing culpa" + size: "sed id" + - + sourceId: "ad Lorem proident dolor" + downloadMechanism: "anim sunt officia amet Duis" + id: "pariatur proident minim" + title: "ipsum ea Lorem et" + sourceName: "pariatur irure id sit" + score: 44001117.74601233 + flexgetModel: "nisi enim consequat Ut" + mediaType: "in ex" + category: "elit cupidatat" + size: "voluptate anim irure" + - + sourceId: "culpa id" + downloadMechanism: "dolor magna" + id: "dolore" + title: "non" + sourceName: "voluptate velit" + score: 41821842.43931264 + flexgetModel: "magna nulla" + mediaType: "reprehenderit anim" + category: "in commod" + size: "in voluptate" + seriesEntriesResponse: + description: "" + schema: + type: "array" + items: + $ref: "#/definitions/seriesEntry" + examples: + application/json: + - + sourceId: "dolore deserunt aute aliquip" + mediaType: "aliqua cillum veniam" + id: "et ullamco" + title: "anim ad" + sourceName: "Duis eu occaecat" + flexgetModel: "commodo eu do" + category: "qui dolor eu fugiat" + size: "amet dolor et tempor nisi" + - + sourceId: "culpa Ut Duis irure" + mediaType: "irure Lorem" + id: "aliqua aliquip sit ea" + title: "mollit" + sourceName: "lab" + flexgetModel: "incididunt deserunt cupidatat" + category: "esse pariatur dolor" + size: "pariatur minim" + - + sourceId: "reprehenderit elit adipisicing sint" + mediaType: "quis commod" + id: "incididunt ut" + title: "veniam sint ut sunt" + sourceName: "ex fugiat" + flexgetModel: "elit" + category: "est ad" + size: "dolor Lorem mollit" + trendingMediaResponse: + description: "" + schema: + type: "array" + items: + $ref: "#/definitions/trendingMediaEntry" + examples: + application/json: + - + type: "photo" + title: "cupidatat dolor" + additionalInfoUrl: "cillum ex reprehenderit" + sourceId: "Excepteur in occaecat irure" + id: "cillum dolor ipsum pariatur" + score: -93292389.50193739 + - + type: "music" + title: "deserunt si" + additionalInfoUrl: "labore qui fugiat non" + sourceId: "est proident fugiat pariatur labore" + id: "elit dolor Ut fugiat" + score: -64599013.23188566 + - + type: "movie" + title: "nulla dolore" + additionalInfoUrl: "cillum sed aliquip" + sourceId: "laboris ex Duis laborum minim" + id: "officia" + score: 81205392.92131376 + - + type: "movie" + title: "et voluptate" + additionalInfoUrl: "ut in" + sourceId: "esse id qui amet" + id: "sint et nisi nulla est" + score: 30192020.44975567 + - + type: "music" + title: "id nisi" + additionalInfoUrl: "reprehenderit ut in deserunt" + sourceId: "aute dolor" + id: "ullamco ad" + score: -58345879.4525455 + trendingMediaAggregateResponse: + description: "Response of a trending media aggregate query" + schema: + $ref: "#/definitions/trendingMediaAggregateModel" + seriesInfoResponse: + description: "Response that contains info about a particular series info" + schema: + $ref: "#/definitions/seriesInfoEntry" + parameters: + filterQueryString: + name: "filter" + in: "query" + type: "string" + description: "This field allows for users to filter on specific fields that come back in objects from search results. The format is simply: `key1:value;key2:value2`." + pattern: "((?:\\w+:\\w+));?" + whereQueryString: + name: "where" + in: "query" + type: "string" + description: "Where the file should be placed. This should be one of:\n\n- `series`\n- `tvshow`\n- `movie[s]`\n- `music`\n- `photo[s]`\n- `other`\n- `auto` - will auto detect possible locations based on file type" + enum: + - "movie" + - "tvshow" + - "music" + - "other" + - "photo" + - "auto" + - "series" + - "photos" + - "movies" + - "tvshows" + tags: + - + name: "Searching" + - + name: "Downloading" + - + name: "Uploading" + - + name: "Trending" + - + name: "Series" + - + name: "Settings" diff --git a/app/tests/scenarios.json b/app/tests/scenarios.json index dde7952..80cf2f0 100644 --- a/app/tests/scenarios.json +++ b/app/tests/scenarios.json @@ -342,6 +342,9 @@ } ], "script": "tests['verify list has 2 download entries'] = output.body.get().length === 2;\n\n$.ctx.set('todelete', output.body.get().map(function(e) {\n return e.id;\n}));" + }, + "before": { + "script": "SL.sleep(5000)" } }, { @@ -400,6 +403,9 @@ } ], "script": "tests['verify list has 2 download entries'] = output.body.get().length === 2;\n\n$.ctx.set('todelete', output.body.get().map(function(e) {\n return e.id;\n}));" + }, + "before": { + "script": "SL.sleep(5000)" } }, { @@ -628,5 +634,203 @@ ] } }, - "utilities": {} + "utilities": {}, + "after": { + "url": { + "name": "Verify URL functionality", + "steps": [ + { + "type": "http", + "input": { + "method": "post", + "url": "{$.env.host}/api/v1/downloads/method:url/{$.ctx.videoUrl}" + }, + "name": "Try video file", + "before": { + "script": "const videoUrl = \"http://kapturebox.com/video/explainer.webm\"; // video/webm\nconst imageUrl = \"http://kapturebox.com/img/logo2.png\"; // image/png\nconst musicUrl = \"https://freemusicarchive.org/music/download/62ae36da26642672bdc9f2e822a09cf3050cd4ac\"; // audio/mpeg\n\nconst videoUrlEnc = Buffer.from(videoUrl).toString('base64');\nconst imageUrlEnc = Buffer.from(imageUrl).toString('base64');\nconst musicUrlEnc = Buffer.from(musicUrl).toString('base64');\n\n$.ctx.set('videoUrl', videoUrlEnc);\n$.ctx.set('imageUrl', imageUrlEnc);\n$.ctx.set('musicUrl', musicUrlEnc);" + }, + "after": { + "assertions": [ + { + "target": "output.status", + "op": "eq", + "expected": 200 + }, + { + "target": "output.body.destName", + "op": "eq", + "expected": "movies" + }, + { + "target": "output.body.contentType", + "op": "eq", + "expected": "video/webm" + } + ] + } + }, + { + "type": "http", + "input": { + "method": "post", + "url": "{$.env.host}/api/v1/downloads/method:url/{$.ctx.videoUrl}", + "query": { + "where": "tvshows" + } + }, + "name": "Try video file with dest override", + "before": {}, + "after": { + "assertions": [ + { + "target": "output.status", + "op": "eq", + "expected": 200 + }, + { + "target": "output.body.destName", + "op": "eq", + "expected": "tvshows" + }, + { + "target": "output.body.contentType", + "op": "eq", + "expected": "video/webm" + }, + { + "target": "output.body.fullDestPath", + "op": "contains", + "expected": "/tvshows/" + } + ] + } + }, + { + "type": "http", + "input": { + "method": "post", + "url": "{$.env.host}/api/v1/downloads/method:url/{$.ctx.imageUrl}" + }, + "name": "Try image file", + "before": {}, + "after": { + "assertions": [ + { + "target": "output.status", + "op": "eq", + "expected": 200 + }, + { + "target": "output.body.destName", + "op": "eq", + "expected": "photos" + }, + { + "target": "output.body.fullDestPath", + "op": "contains", + "expected": "/photos/" + }, + { + "target": "output.body.contentType", + "op": "eq", + "expected": "image/png" + } + ] + } + }, + { + "type": "http", + "input": { + "method": "post", + "url": "{$.env.host}/api/v1/downloads/method:url/{$.ctx.musicUrl}" + }, + "name": "Try music file", + "before": {}, + "after": { + "assertions": [ + { + "target": "output.status", + "op": "eq", + "expected": 200 + }, + { + "target": "output.body.destName", + "op": "eq", + "expected": "music" + }, + { + "target": "output.body.contentType", + "op": "eq", + "expected": "audio/mpeg" + }, + { + "target": "output.body.fullDestPath", + "op": "contains", + "expected": "/music/" + } + ] + } + }, + { + "type": "http", + "name": "Make sure downloads exist", + "input": { + "method": "get", + "url": "{$.env.host}/api/v1/downloads" + }, + "after": { + "assertions": [ + { + "target": "output.status", + "op": "eq", + "expected": 200 + }, + { + "target": "output.body", + "op": "length", + "expected": 4 + } + ], + "script": "const bdy = output.body.get();\nconst ids = bdy.map(function(e) {return e.id});\n\n$.ctx.set('todelete', ids);" + }, + "before": { + "script": "SL.sleep(10000);" + } + }, + { + "type": "http", + "name": "Remove download 1", + "input": { + "method": "delete", + "url": "{$.env.host}/api/v1/downloads/{$.ctx.todelete[0]}" + } + }, + { + "type": "http", + "name": "Remove download 2", + "input": { + "method": "delete", + "url": "{$.env.host}/api/v1/downloads/{$.ctx.todelete[1]}" + } + }, + { + "type": "http", + "name": "Remove download 3", + "input": { + "method": "delete", + "url": "{$.env.host}/api/v1/downloads/{$.ctx.todelete[2]}" + } + }, + { + "type": "http", + "name": "Remove download 4", + "input": { + "method": "delete", + "url": "{$.env.host}/api/v1/downloads/{$.ctx.todelete[3]}" + } + } + ], + "description": "Checks that the url plugin works" + } + } } diff --git a/app/tests/uploadtest.html b/app/tests/uploadtest.html new file mode 100644 index 0000000..eb19178 --- /dev/null +++ b/app/tests/uploadtest.html @@ -0,0 +1,12 @@ + +
+ + + diff --git a/package-lock.json b/package-lock.json index ba8df06..5b268c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,13 @@ "dotenv": "^16.0.3", "errorhandler": "^1.5.0", "express": "^4.16.4", + "express-fileupload": "^0.4.0", "express-session": "^1.15.6", "express-winston": "^4.2.0", "lodash": "^4.17.13", "method-override": "^3.0.0", "node-persist": "^2.1.0", + "pretty-bytes": "^5.6.0", "request": "^2.88.0", "request-promise": "^4.2.4", "sanitize-filename": "^1.6.1", @@ -969,6 +971,34 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/busboy/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/busboy/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -1050,6 +1080,14 @@ "node": ">=0.10.0" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -1688,6 +1726,14 @@ "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", @@ -2000,6 +2046,34 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==" }, + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/dicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2577,6 +2651,31 @@ "node": ">= 0.10.0" } }, + "node_modules/express-fileupload": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-0.4.0.tgz", + "integrity": "sha512-jPv3aCdTIdQrGAUXQ1e1hU0Vnl+0jE9IbzEsI7VRIevQybrUrIMUgvwNwBThnsetandW8+9ICgflAkhKwLUuLw==", + "deprecated": "Please upgrade express-fileupload to version 1.1.8+ due to a security vulnerability with the parseNested option", + "dependencies": { + "busboy": "^0.2.14", + "fs-extra": "^4.0.1", + "md5": "^2.2.1", + "streamifier": "^0.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/express-fileupload/node_modules/fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "node_modules/express-session": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", @@ -3314,6 +3413,7 @@ }, "node_modules/fsevents/node_modules/abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3321,6 +3421,7 @@ }, "node_modules/fsevents/node_modules/ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3331,6 +3432,7 @@ }, "node_modules/fsevents/node_modules/aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3349,6 +3451,7 @@ }, "node_modules/fsevents/node_modules/balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3356,6 +3459,7 @@ }, "node_modules/fsevents/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3374,6 +3478,7 @@ }, "node_modules/fsevents/node_modules/code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3384,6 +3489,7 @@ }, "node_modules/fsevents/node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3391,6 +3497,7 @@ }, "node_modules/fsevents/node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3398,6 +3505,7 @@ }, "node_modules/fsevents/node_modules/core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3405,6 +3513,7 @@ }, "node_modules/fsevents/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3425,6 +3534,7 @@ }, "node_modules/fsevents/node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3432,6 +3542,7 @@ }, "node_modules/fsevents/node_modules/detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -3445,6 +3556,7 @@ }, "node_modules/fsevents/node_modules/fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3455,6 +3567,7 @@ }, "node_modules/fsevents/node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3462,6 +3575,7 @@ }, "node_modules/fsevents/node_modules/gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3497,6 +3611,7 @@ }, "node_modules/fsevents/node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3517,6 +3632,7 @@ }, "node_modules/fsevents/node_modules/ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3527,6 +3643,7 @@ }, "node_modules/fsevents/node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3538,6 +3655,7 @@ }, "node_modules/fsevents/node_modules/inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3545,6 +3663,7 @@ }, "node_modules/fsevents/node_modules/ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3555,6 +3674,7 @@ }, "node_modules/fsevents/node_modules/is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3568,6 +3688,7 @@ }, "node_modules/fsevents/node_modules/isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3575,6 +3696,7 @@ }, "node_modules/fsevents/node_modules/minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3588,6 +3710,7 @@ }, "node_modules/fsevents/node_modules/minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3616,6 +3739,7 @@ }, "node_modules/fsevents/node_modules/mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3629,6 +3753,7 @@ }, "node_modules/fsevents/node_modules/ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3676,6 +3801,7 @@ }, "node_modules/fsevents/node_modules/nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3708,6 +3834,7 @@ }, "node_modules/fsevents/node_modules/npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3721,6 +3848,7 @@ }, "node_modules/fsevents/node_modules/number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3731,6 +3859,7 @@ }, "node_modules/fsevents/node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3741,6 +3870,7 @@ }, "node_modules/fsevents/node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3751,6 +3881,7 @@ }, "node_modules/fsevents/node_modules/os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3761,6 +3892,7 @@ }, "node_modules/fsevents/node_modules/os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3771,6 +3903,7 @@ }, "node_modules/fsevents/node_modules/osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3782,6 +3915,7 @@ }, "node_modules/fsevents/node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3792,6 +3926,7 @@ }, "node_modules/fsevents/node_modules/process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3822,6 +3957,7 @@ }, "node_modules/fsevents/node_modules/readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3851,6 +3987,7 @@ }, "node_modules/fsevents/node_modules/safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3858,6 +3995,7 @@ }, "node_modules/fsevents/node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3865,6 +4003,7 @@ }, "node_modules/fsevents/node_modules/sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3882,6 +4021,7 @@ }, "node_modules/fsevents/node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3889,6 +4029,7 @@ }, "node_modules/fsevents/node_modules/signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -3896,6 +4037,7 @@ }, "node_modules/fsevents/node_modules/string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3906,6 +4048,7 @@ }, "node_modules/fsevents/node_modules/string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3921,6 +4064,7 @@ }, "node_modules/fsevents/node_modules/strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3934,6 +4078,7 @@ }, "node_modules/fsevents/node_modules/strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3963,6 +4108,7 @@ }, "node_modules/fsevents/node_modules/util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "dev": true, "inBundle": true, "license": "MIT", @@ -3980,6 +4126,7 @@ }, "node_modules/fsevents/node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "dev": true, "inBundle": true, "license": "ISC", @@ -6631,6 +6778,16 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7981,6 +8138,17 @@ "node": ">=0.10.0" } }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -9306,6 +9474,14 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "node_modules/streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/streamify": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/streamify/-/streamify-0.2.9.tgz", @@ -9317,6 +9493,14 @@ "node": ">=0.12" } }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -11909,6 +12093,33 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg==", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -11972,6 +12183,11 @@ "supports-color": "^2.0.0" } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==" + }, "cheerio": { "version": "0.22.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", @@ -12485,6 +12701,11 @@ "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz", "integrity": "sha1-naHpgOO9RPxck79as9ozeNheRms=" }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==" + }, "cryptiles": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", @@ -12727,6 +12948,33 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz", "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==" }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg==", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -13271,6 +13519,29 @@ } } }, + "express-fileupload": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-0.4.0.tgz", + "integrity": "sha512-jPv3aCdTIdQrGAUXQ1e1hU0Vnl+0jE9IbzEsI7VRIevQybrUrIMUgvwNwBThnsetandW8+9ICgflAkhKwLUuLw==", + "requires": { + "busboy": "^0.2.14", + "fs-extra": "^4.0.1", + "md5": "^2.2.1", + "streamifier": "^0.1.1" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "express-session": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.15.6.tgz", @@ -13802,18 +14073,21 @@ "dependencies": { "abbrev": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "bundled": true, "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "bundled": true, "dev": true, "optional": true }, "aproba": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "bundled": true, "dev": true, "optional": true @@ -13830,12 +14104,14 @@ }, "balanced-match": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "bundled": true, "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "bundled": true, "dev": true, "optional": true, @@ -13852,30 +14128,35 @@ }, "code-point-at": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "bundled": true, "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "bundled": true, "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "bundled": true, "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "bundled": true, "dev": true, "optional": true }, "debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "bundled": true, "dev": true, "optional": true, @@ -13891,18 +14172,21 @@ }, "delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "bundled": true, "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "bundled": true, "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "bundled": true, "dev": true, "optional": true, @@ -13912,12 +14196,14 @@ }, "fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "bundled": true, "dev": true, "optional": true }, "gauge": { "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "bundled": true, "dev": true, "optional": true, @@ -13948,6 +14234,7 @@ }, "has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "bundled": true, "dev": true, "optional": true @@ -13963,6 +14250,7 @@ }, "ignore-walk": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "bundled": true, "dev": true, "optional": true, @@ -13972,6 +14260,7 @@ }, "inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "bundled": true, "dev": true, "optional": true, @@ -13982,18 +14271,21 @@ }, "inherits": { "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "bundled": true, "dev": true, "optional": true }, "ini": { "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "bundled": true, "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "bundled": true, "dev": true, "optional": true, @@ -14003,12 +14295,14 @@ }, "isarray": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "bundled": true, "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "bundled": true, "dev": true, "optional": true, @@ -14018,6 +14312,7 @@ }, "minimist": { "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "bundled": true, "dev": true, "optional": true @@ -14043,6 +14338,7 @@ }, "mkdirp": { "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "bundled": true, "dev": true, "optional": true, @@ -14052,6 +14348,7 @@ }, "ms": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "bundled": true, "dev": true, "optional": true @@ -14087,6 +14384,7 @@ }, "nopt": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "bundled": true, "dev": true, "optional": true, @@ -14113,6 +14411,7 @@ }, "npmlog": { "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "bundled": true, "dev": true, "optional": true, @@ -14125,18 +14424,21 @@ }, "number-is-nan": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "bundled": true, "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "bundled": true, "dev": true, "optional": true }, "once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "bundled": true, "dev": true, "optional": true, @@ -14146,18 +14448,21 @@ }, "os-homedir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "bundled": true, "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "bundled": true, "dev": true, "optional": true }, "osenv": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "bundled": true, "dev": true, "optional": true, @@ -14168,12 +14473,14 @@ }, "path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "bundled": true, "dev": true, "optional": true @@ -14200,6 +14507,7 @@ }, "readable-stream": { "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "bundled": true, "dev": true, "optional": true, @@ -14224,18 +14532,21 @@ }, "safe-buffer": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "bundled": true, "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "bundled": true, "dev": true, "optional": true }, "sax": { "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "bundled": true, "dev": true, "optional": true @@ -14248,18 +14559,21 @@ }, "set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "bundled": true, "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "bundled": true, "dev": true, "optional": true }, "string_decoder": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "bundled": true, "dev": true, "optional": true, @@ -14269,6 +14583,7 @@ }, "string-width": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "bundled": true, "dev": true, "optional": true, @@ -14280,6 +14595,7 @@ }, "strip-ansi": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "bundled": true, "dev": true, "optional": true, @@ -14289,6 +14605,7 @@ }, "strip-json-comments": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "bundled": true, "dev": true, "optional": true @@ -14310,6 +14627,7 @@ }, "util-deprecate": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "bundled": true, "dev": true, "optional": true @@ -14325,6 +14643,7 @@ }, "wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "bundled": true, "dev": true, "optional": true @@ -16500,6 +16819,16 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", "dev": true }, + "md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "requires": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -17523,6 +17852,11 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -18588,6 +18922,11 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==" + }, "streamify": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/streamify/-/streamify-0.2.9.tgz", @@ -18596,6 +18935,11 @@ "hashish": "~0.0.4" } }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA==" + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", diff --git a/package.json b/package.json index 47f4022..7f154bd 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,13 @@ "dotenv": "^16.0.3", "errorhandler": "^1.5.0", "express": "^4.16.4", + "express-fileupload": "^0.4.0", "express-session": "^1.15.6", "express-winston": "^4.2.0", "lodash": "^4.17.13", "method-override": "^3.0.0", "node-persist": "^2.1.0", + "pretty-bytes": "^5.6.0", "request": "^2.88.0", "request-promise": "^4.2.4", "sanitize-filename": "^1.6.1",