thumbnails
config/production.yaml
ffmpeg
+<<<<<<< HEAD
torrents
+=======
+.tags
+*.sublime-project
+*.sublime-workspace
+torrents/
+>>>>>>> master
language: node_js
node_js:
- - "4.4"
- - "6.2"
+ - "4.5"
+ - "6.6"
env:
- CXX=g++-4.8
services:
- mongodb
+before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
+
before_script:
- - npm install electron-prebuilt -g
+ - npm install electron -g
- npm run build
- wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-3.0.2-64bit-static.tar.xz"
- tar xf ffmpeg-release-3.0.2-64bit-static.tar.xz
* You can directly test in your browser with this [demo server](http://peertube.cpy.re). Don't forget to use the latest version of Firefox/Chromium/(Opera?) and check your firewall configuration (for WebRTC)
* You can find [a video](https://vimeo.com/164881662 "Yes Vimeo, please don't judge me") to see how the "decentralization feature" looks like
+ * Experimental demo servers that share videos (they are in the same network): [peertube2](http://peertube2.cpy.re), [peertube3](http://peertube3.cpy.re). Since I do experiments with them, sometimes they might not work correctly.
## Why
- [ ] Validate the prototype (test PeerTube in a real world with many pods and videos)
- [ ] Manage API breaks
- [ ] Add "DDOS" security (check if a pod don't send too many requests for example)
-- [ ] Admin panel
- - [ ] Stats about the network (how many friends, how many requests per hour...)
- - [ ] Stats about videos
- - [ ] Manage users (create/remove)
+- [X] Admin panel
+ - [X] Stats
+ - [X] Friends list
+ - [X] Manage users (create/remove)
+- [ ] User playlists
+- [ ] User subscriptions (by tags, author...)
## Installation
### Dependencies
* **NodeJS >= 4.2**
+ * **npm >= 3.0**
* OpenSSL (cli)
* MongoDB
* ffmpeg xvfb-run libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin (for electron)
# apt-get update
# apt-get install ffmpeg mongodb openssl xvfb curl sudo git build-essential libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin
- # npm install -g electron-prebuilt
+ # npm install -g npm@3
+ # npm install -g electron
#### Other distribution... (PR welcome)
$ NODE_ENV=production npm start
+**Nginx template** (reverse proxy): https://github.com/Chocobozzz/PeerTube/tree/master/support/nginx
+
+**Systemd template**: https://github.com/Chocobozzz/PeerTube/tree/master/support/systemd
+
### Other commands
To print all available command run:
return process.argv.join('').indexOf(flag) > -1
}
+function isWebpackDevServer () {
+ return process.argv[1] && !!(/webpack-dev-server$/.exec(process.argv[1]))
+}
+
function root (args) {
args = Array.prototype.slice.call(arguments, 0)
return path.join.apply(path, [ROOT].concat(args))
}
exports.hasProcessFlag = hasProcessFlag
+exports.isWebpackDevServer = isWebpackDevServer
exports.root = root
* Webpack Plugins
*/
-var CopyWebpackPlugin = (CopyWebpackPlugin = require('copy-webpack-plugin'), CopyWebpackPlugin.default || CopyWebpackPlugin)
+const CopyWebpackPlugin = require('copy-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin
+const AssetsPlugin = require('assets-webpack-plugin')
+const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin')
const WebpackNotifierPlugin = require('webpack-notifier')
/*
*/
const METADATA = {
title: 'PeerTube',
- baseUrl: '/'
+ baseUrl: '/',
+ isDevServer: helpers.isWebpackDevServer()
}
/*
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
-module.exports = {
- /*
- * Static metadata for index.html
- *
- * See: (custom attribute)
- */
- metadata: METADATA,
+module.exports = function (options) {
+ var isProd = options.env === 'production'
- /*
- * Cache generated modules and chunks to improve performance for multiple incremental builds.
- * This is enabled by default in watch mode.
- * You can pass false to disable it.
- *
- * See: http://webpack.github.io/docs/configuration.html#cache
- */
- // cache: false,
-
- /*
- * The entry point for the bundle
- * Our Angular.js app
- *
- * See: http://webpack.github.io/docs/configuration.html#entry
- */
- entry: {
- 'polyfills': './src/polyfills.ts',
- 'vendor': './src/vendor.ts',
- 'main': './src/main.ts'
- },
+ return {
+ /*
+ * Static metadata for index.html
+ *
+ * See: (custom attribute)
+ */
+ metadata: METADATA,
- /*
- * Options affecting the resolving of modules.
- *
- * See: http://webpack.github.io/docs/configuration.html#resolve
- */
- resolve: {
/*
- * An array of extensions that should be used to resolve modules.
+ * Cache generated modules and chunks to improve performance for multiple incremental builds.
+ * This is enabled by default in watch mode.
+ * You can pass false to disable it.
*
- * See: http://webpack.github.io/docs/configuration.html#resolve-extensions
+ * See: http://webpack.github.io/docs/configuration.html#cache
*/
- extensions: [ '', '.ts', '.js', '.scss' ],
+ // cache: false,
- // Make sure root is src
- root: helpers.root('src'),
+ /*
+ * The entry point for the bundle
+ * Our Angular.js app
+ *
+ * See: http://webpack.github.io/docs/configuration.html#entry
+ */
+ entry: {
+ 'polyfills': './src/polyfills.ts',
+ 'vendor': './src/vendor.ts',
+ 'main': './src/main.ts'
+ },
- // remove other default values
- modulesDirectories: [ 'node_modules' ],
+ /*
+ * Options affecting the resolving of modules.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#resolve
+ */
+ resolve: {
+ /*
+ * An array of extensions that should be used to resolve modules.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#resolve-extensions
+ */
+ extensions: [ '', '.ts', '.js', '.scss' ],
- packageAlias: 'browser'
+ // Make sure root is src
+ root: helpers.root('src'),
- },
+ // remove other default values
+ modulesDirectories: [ 'node_modules' ]
+ },
- output: {
- publicPath: '/client/'
- },
+ output: {
+ publicPath: '/client/'
+ },
- /*
- * Options affecting the normal modules.
- *
- * See: http://webpack.github.io/docs/configuration.html#module
- */
- module: {
/*
- * An array of applied pre and post loaders.
+ * Options affecting the normal modules.
*
- * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders
+ * See: http://webpack.github.io/docs/configuration.html#module
*/
- preLoaders: [
-
+ module: {
/*
- * Tslint loader support for *.ts files
+ * An array of applied pre and post loaders.
*
- * See: https://github.com/wbuchwalter/tslint-loader
+ * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders
*/
- // { test: /\.ts$/, loader: 'tslint-loader', exclude: [ helpers.root('node_modules') ] },
+ preLoaders: [
+ {
+ test: /\.ts$/,
+ loader: 'string-replace-loader',
+ query: {
+ search: '(System|SystemJS)(.*[\\n\\r]\\s*\\.|\\.)import\\((.+)\\)',
+ replace: '$1.import($3).then(mod => (mod.__esModule && mod.default) ? mod.default : mod)',
+ flags: 'g'
+ },
+ include: [helpers.root('src')]
+ }
+ ],
/*
- * Source map loader support for *.js files
- * Extracts SourceMaps for source files that as added as sourceMappingURL comment.
+ * An array of automatically applied loaders.
+ *
+ * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
+ * This means they are not resolved relative to the configuration file.
*
- * See: https://github.com/webpack/source-map-loader
+ * See: http://webpack.github.io/docs/configuration.html#module-loaders
*/
- {
- test: /\.js$/,
- loader: 'source-map-loader',
- exclude: [
- // these packages have problems with their sourcemaps
- helpers.root('node_modules/rxjs'),
- helpers.root('node_modules/@angular')
- ]
- }
-
- ],
+ loaders: [
+
+ /*
+ * Typescript loader support for .ts and Angular 2 async routes via .async.ts
+ *
+ * See: https://github.com/s-panferov/awesome-typescript-loader
+ */
+ {
+ test: /\.ts$/,
+ loaders: [
+ '@angularclass/hmr-loader?pretty=' + !isProd + '&prod=' + isProd,
+ 'awesome-typescript-loader',
+ 'angular2-template-loader'
+ ],
+ exclude: [/\.(spec|e2e)\.ts$/]
+ },
+
+ /*
+ * Json loader support for *.json files.
+ *
+ * See: https://github.com/webpack/json-loader
+ */
+ {
+ test: /\.json$/,
+ loader: 'json-loader'
+ },
+
+ {
+ test: /\.(sass|scss)$/,
+ loaders: ['css-to-string-loader', 'css-loader?sourceMap', 'resolve-url', 'sass-loader?sourceMap']
+ },
+ { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url?limit=10000&minetype=application/font-woff' },
+ { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file' },
+
+ /* Raw loader support for *.html
+ * Returns file content as string
+ *
+ * See: https://github.com/webpack/raw-loader
+ */
+ {
+ test: /\.html$/,
+ loader: 'raw-loader',
+ exclude: [ helpers.root('src/index.html') ]
+ }
+
+ ]
+
+ },
+
+ sassLoader: {
+ precision: 10
+ },
/*
- * An array of automatically applied loaders.
- *
- * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to.
- * This means they are not resolved relative to the configuration file.
+ * Add additional plugins to the compiler.
*
- * See: http://webpack.github.io/docs/configuration.html#module-loaders
+ * See: http://webpack.github.io/docs/configuration.html#plugins
*/
- loaders: [
+ plugins: [
+ new AssetsPlugin({
+ path: helpers.root('dist'),
+ filename: 'webpack-assets.json',
+ prettyPrint: true
+ }),
/*
- * Typescript loader support for .ts and Angular 2 async routes via .async.ts
+ * Plugin: ForkCheckerPlugin
+ * Description: Do type checking in a separate process, so webpack don't need to wait.
*
- * See: https://github.com/s-panferov/awesome-typescript-loader
+ * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse
*/
- {
- test: /\.ts$/,
- loader: 'awesome-typescript-loader',
- exclude: [/\.(spec|e2e)\.ts$/]
- },
+ new ForkCheckerPlugin(),
/*
- * Json loader support for *.json files.
+ * Plugin: CommonsChunkPlugin
+ * Description: Shares common code between the pages.
+ * It identifies common modules and put them into a commons chunk.
*
- * See: https://github.com/webpack/json-loader
+ * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
+ * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app
*/
- {
- test: /\.json$/,
- loader: 'json-loader'
- },
+ new webpack.optimize.CommonsChunkPlugin({
+ name: [ 'polyfills', 'vendor' ].reverse()
+ }),
- {
- test: /\.scss$/,
- exclude: /node_modules/,
- loaders: [ 'raw-loader', 'sass-loader' ]
- },
-
- {
- test: /\.(woff2?|ttf|eot|svg)$/,
- loader: 'url?limit=10000&name=assets/fonts/[hash].[ext]'
- },
+ /**
+ * Plugin: ContextReplacementPlugin
+ * Description: Provides context to Angular's use of System.import
+ *
+ * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin
+ * See: https://github.com/angular/angular/issues/11580
+ */
+ new ContextReplacementPlugin(
+ // The (\\|\/) piece accounts for path separators in *nix and Windows
+ /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
+ helpers.root('src') // location of your src
+ ),
- /* Raw loader support for *.html
- * Returns file content as string
+ /*
+ * Plugin: CopyWebpackPlugin
+ * Description: Copy files and directories in webpack.
*
- * See: https://github.com/webpack/raw-loader
+ * Copies project static assets.
+ *
+ * See: https://www.npmjs.com/package/copy-webpack-plugin
*/
- {
- test: /\.html$/,
- loader: 'raw-loader',
- exclude: [ helpers.root('src/index.html') ]
- }
-
- ]
-
- },
+ new CopyWebpackPlugin([
+ {
+ from: 'src/assets',
+ to: 'assets'
+ },
+ {
+ from: 'node_modules/webtorrent/webtorrent.min.js',
+ to: 'assets/webtorrent'
+ }
+ ]),
- sassLoader: {
- precision: 10
- },
-
- /*
- * Add additional plugins to the compiler.
- *
- * See: http://webpack.github.io/docs/configuration.html#plugins
- */
- plugins: [
-
- /*
- * Plugin: ForkCheckerPlugin
- * Description: Do type checking in a separate process, so webpack don't need to wait.
- *
- * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse
- */
- new ForkCheckerPlugin(),
-
- /*
- * Plugin: OccurenceOrderPlugin
- * Description: Varies the distribution of the ids to get the smallest id length
- * for often used ids.
- *
- * See: https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin
- * See: https://github.com/webpack/docs/wiki/optimization#minimize
- */
- new webpack.optimize.OccurenceOrderPlugin(true),
-
- /*
- * Plugin: CommonsChunkPlugin
- * Description: Shares common code between the pages.
- * It identifies common modules and put them into a commons chunk.
- *
- * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin
- * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app
- */
- new webpack.optimize.CommonsChunkPlugin({
- name: [ 'polyfills', 'vendor' ].reverse()
- }),
+ /*
+ * Plugin: HtmlWebpackPlugin
+ * Description: Simplifies creation of HTML files to serve your webpack bundles.
+ * This is especially useful for webpack bundles that include a hash in the filename
+ * which changes every compilation.
+ *
+ * See: https://github.com/ampedandwired/html-webpack-plugin
+ */
+ new HtmlWebpackPlugin({
+ template: 'src/index.html',
+ chunksSortMode: 'dependency'
+ }),
- /*
- * Plugin: CopyWebpackPlugin
- * Description: Copy files and directories in webpack.
- *
- * Copies project static assets.
- *
- * See: https://www.npmjs.com/package/copy-webpack-plugin
- */
- new CopyWebpackPlugin([
- {
- from: 'src/assets',
- to: 'assets'
- },
- {
- from: 'node_modules/webtorrent/webtorrent.min.js',
- to: 'assets/webtorrent'
- }
- ]),
+ new WebpackNotifierPlugin({ alwaysNotify: true })
+ ],
/*
- * Plugin: HtmlWebpackPlugin
- * Description: Simplifies creation of HTML files to serve your webpack bundles.
- * This is especially useful for webpack bundles that include a hash in the filename
- * which changes every compilation.
+ * Include polyfills or mocks for various node stuff
+ * Description: Node configuration
*
- * See: https://github.com/ampedandwired/html-webpack-plugin
+ * See: https://webpack.github.io/docs/configuration.html#node
*/
- new HtmlWebpackPlugin({
- template: 'src/index.html',
- chunksSortMode: 'dependency'
- }),
-
- new WebpackNotifierPlugin({ alwaysNotify: true })
- ],
-
- /*
- * Include polyfills or mocks for various node stuff
- * Description: Node configuration
- *
- * See: https://webpack.github.io/docs/configuration.html#node
- */
- node: {
- global: 'window',
- crypto: 'empty',
- fs: 'empty',
- events: true,
- module: false,
- clearImmediate: false,
- setImmediate: false
+ node: {
+ global: 'window',
+ crypto: 'empty',
+ fs: 'empty',
+ events: true,
+ module: false,
+ clearImmediate: false,
+ setImmediate: false
+ }
}
-
}
* Webpack Plugins
*/
const DefinePlugin = require('webpack/lib/DefinePlugin')
+const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin')
/**
* Webpack Constants
*/
const ENV = process.env.ENV = process.env.NODE_ENV = 'development'
+const HOST = process.env.HOST || 'localhost'
+const PORT = process.env.PORT || 3000
const HMR = helpers.hasProcessFlag('hot')
-const METADATA = webpackMerge(commonConfig.metadata, {
- host: 'localhost',
- port: 3000,
+const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
+ host: HOST,
+ port: PORT,
ENV: ENV,
HMR: HMR
})
*
* See: http://webpack.github.io/docs/configuration.html#cli
*/
-module.exports = webpackMerge(commonConfig, {
- /**
- * Merged metadata from webpack.common.js for index.html
- *
- * See: (custom attribute)
- */
- metadata: METADATA,
-
- /**
- * Switch loaders to debug mode.
- *
- * See: http://webpack.github.io/docs/configuration.html#debug
- */
- debug: true,
-
- /**
- * Developer tool to enhance debugging
- *
- * See: http://webpack.github.io/docs/configuration.html#devtool
- * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
- */
- devtool: 'cheap-module-source-map',
-
- /**
- * Options affecting the output of the compilation.
- *
- * See: http://webpack.github.io/docs/configuration.html#output
- */
- output: {
+module.exports = function (env) {
+ return webpackMerge(commonConfig({env: ENV}), {
/**
- * The output directory as absolute path (required).
+ * Merged metadata from webpack.common.js for index.html
*
- * See: http://webpack.github.io/docs/configuration.html#output-path
+ * See: (custom attribute)
*/
- path: helpers.root('dist'),
+ metadata: METADATA,
/**
- * Specifies the name of each output file on disk.
- * IMPORTANT: You must not specify an absolute path here!
+ * Switch loaders to debug mode.
*
- * See: http://webpack.github.io/docs/configuration.html#output-filename
+ * See: http://webpack.github.io/docs/configuration.html#debug
*/
- filename: '[name].bundle.js',
+ debug: true,
/**
- * The filename of the SourceMaps for the JavaScript files.
- * They are inside the output.path directory.
+ * Developer tool to enhance debugging
*
- * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
+ * See: http://webpack.github.io/docs/configuration.html#devtool
+ * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
*/
- sourceMapFilename: '[name].map',
+ devtool: 'cheap-module-source-map',
- /** The filename of non-entry chunks as relative path
- * inside the output.path directory.
+ /**
+ * Options affecting the output of the compilation.
*
- * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
+ * See: http://webpack.github.io/docs/configuration.html#output
*/
- chunkFilename: '[id].chunk.js'
-
- },
-
- externals: {
- webtorrent: 'WebTorrent'
- },
+ output: {
+ /**
+ * The output directory as absolute path (required).
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-path
+ */
+ path: helpers.root('dist'),
+
+ /**
+ * Specifies the name of each output file on disk.
+ * IMPORTANT: You must not specify an absolute path here!
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-filename
+ */
+ filename: '[name].bundle.js',
+
+ /**
+ * The filename of the SourceMaps for the JavaScript files.
+ * They are inside the output.path directory.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
+ */
+ sourceMapFilename: '[name].map',
+
+ /** The filename of non-entry chunks as relative path
+ * inside the output.path directory.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
+ */
+ chunkFilename: '[id].chunk.js',
+
+ library: 'ac_[name]',
+ libraryTarget: 'var'
+
+ },
+
+ externals: {
+ webtorrent: 'WebTorrent'
+ },
+
+ plugins: [
+
+ /**
+ * Plugin: DefinePlugin
+ * Description: Define free variables.
+ * Useful for having development builds with debug logging or adding global constants.
+ *
+ * Environment helpers
+ *
+ * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+ */
+ // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
+ new DefinePlugin({
+ 'ENV': JSON.stringify(METADATA.ENV),
+ 'HMR': METADATA.HMR,
+ 'process.env': {
+ 'ENV': JSON.stringify(METADATA.ENV),
+ 'NODE_ENV': JSON.stringify(METADATA.ENV),
+ 'HMR': METADATA.HMR
+ }
+ }),
- plugins: [
+ new NamedModulesPlugin()
+ ],
/**
- * Plugin: DefinePlugin
- * Description: Define free variables.
- * Useful for having development builds with debug logging or adding global constants.
+ * Static analysis linter for TypeScript advanced options configuration
+ * Description: An extensible linter for the TypeScript language.
*
- * Environment helpers
+ * See: https://github.com/wbuchwalter/tslint-loader
+ */
+ tslint: {
+ emitErrors: false,
+ failOnHint: false,
+ resourcePath: 'src'
+ },
+
+ devServer: {
+ port: METADATA.port,
+ host: METADATA.host,
+ historyApiFallback: true,
+ watchOptions: {
+ aggregateTimeout: 300,
+ poll: 1000
+ },
+ outputPath: helpers.root('dist')
+ },
+
+ /*
+ * Include polyfills or mocks for various node stuff
+ * Description: Node configuration
*
- * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+ * See: https://webpack.github.io/docs/configuration.html#node
*/
- // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts
- new DefinePlugin({
- 'ENV': JSON.stringify(METADATA.ENV),
- 'HMR': METADATA.HMR,
- 'process.env': {
- 'ENV': JSON.stringify(METADATA.ENV),
- 'NODE_ENV': JSON.stringify(METADATA.ENV),
- 'HMR': METADATA.HMR
- }
- })
- ],
-
- /**
- * Static analysis linter for TypeScript advanced options configuration
- * Description: An extensible linter for the TypeScript language.
- *
- * See: https://github.com/wbuchwalter/tslint-loader
- */
- tslint: {
- emitErrors: false,
- failOnHint: false,
- resourcePath: 'src'
- },
-
- /*
- * Include polyfills or mocks for various node stuff
- * Description: Node configuration
- *
- * See: https://webpack.github.io/docs/configuration.html#node
- */
- node: {
- global: 'window',
- crypto: 'empty',
- process: true,
- module: false,
- clearImmediate: false,
- setImmediate: false
- }
-
-})
+ node: {
+ global: 'window',
+ crypto: 'empty',
+ process: true,
+ module: false,
+ clearImmediate: false,
+ setImmediate: false
+ }
+ })
+}
/**
* Webpack Plugins
*/
+// const ProvidePlugin = require('webpack/lib/ProvidePlugin')
const DefinePlugin = require('webpack/lib/DefinePlugin')
-const DedupePlugin = require('webpack/lib/optimize/DedupePlugin')
+const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplacementPlugin')
+// const IgnorePlugin = require('webpack/lib/IgnorePlugin')
+// const DedupePlugin = require('webpack/lib/optimize/DedupePlugin')
const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin')
-const CompressionPlugin = require('compression-webpack-plugin')
const WebpackMd5Hash = require('webpack-md5-hash')
/**
const ENV = process.env.NODE_ENV = process.env.ENV = 'production'
const HOST = process.env.HOST || 'localhost'
const PORT = process.env.PORT || 8080
-const METADATA = webpackMerge(commonConfig.metadata, {
+const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
host: HOST,
port: PORT,
ENV: ENV,
HMR: false
})
-module.exports = webpackMerge(commonConfig, {
- /**
- * Switch loaders to debug mode.
- *
- * See: http://webpack.github.io/docs/configuration.html#debug
- */
- debug: false,
-
- /**
- * Developer tool to enhance debugging
- *
- * See: http://webpack.github.io/docs/configuration.html#devtool
- * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
- */
- devtool: 'source-map',
-
- /**
- * Options affecting the output of the compilation.
- *
- * See: http://webpack.github.io/docs/configuration.html#output
- */
- output: {
+module.exports = function (env) {
+ return webpackMerge(commonConfig({env: ENV}), {
/**
- * The output directory as absolute path (required).
+ * Switch loaders to debug mode.
*
- * See: http://webpack.github.io/docs/configuration.html#output-path
+ * See: http://webpack.github.io/docs/configuration.html#debug
*/
- path: helpers.root('dist'),
+ debug: false,
/**
- * Specifies the name of each output file on disk.
- * IMPORTANT: You must not specify an absolute path here!
+ * Developer tool to enhance debugging
*
- * See: http://webpack.github.io/docs/configuration.html#output-filename
+ * See: http://webpack.github.io/docs/configuration.html#devtool
+ * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps
*/
- filename: '[name].[chunkhash].bundle.js',
+ devtool: 'source-map',
/**
- * The filename of the SourceMaps for the JavaScript files.
- * They are inside the output.path directory.
+ * Options affecting the output of the compilation.
*
- * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
+ * See: http://webpack.github.io/docs/configuration.html#output
*/
- sourceMapFilename: '[name].[chunkhash].bundle.map',
+ output: {
+ /**
+ * The output directory as absolute path (required).
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-path
+ */
+ path: helpers.root('dist'),
+
+ /**
+ * Specifies the name of each output file on disk.
+ * IMPORTANT: You must not specify an absolute path here!
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-filename
+ */
+ filename: '[name].[chunkhash].bundle.js',
+
+ /**
+ * The filename of the SourceMaps for the JavaScript files.
+ * They are inside the output.path directory.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename
+ */
+ sourceMapFilename: '[name].[chunkhash].bundle.map',
+
+ /**
+ * The filename of non-entry chunks as relative path
+ * inside the output.path directory.
+ *
+ * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
+ */
+ chunkFilename: '[id].[chunkhash].chunk.js'
+
+ },
+
+ externals: {
+ webtorrent: 'WebTorrent'
+ },
/**
- * The filename of non-entry chunks as relative path
- * inside the output.path directory.
+ * Add additional plugins to the compiler.
*
- * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename
+ * See: http://webpack.github.io/docs/configuration.html#plugins
*/
- chunkFilename: '[id].[chunkhash].chunk.js'
-
- },
-
- externals: {
- webtorrent: 'WebTorrent'
- },
-
- /**
- * Add additional plugins to the compiler.
- *
- * See: http://webpack.github.io/docs/configuration.html#plugins
- */
- plugins: [
-
- /**
- * Plugin: WebpackMd5Hash
- * Description: Plugin to replace a standard webpack chunkhash with md5.
- *
- * See: https://www.npmjs.com/package/webpack-md5-hash
- */
- new WebpackMd5Hash(),
+ plugins: [
+
+ /**
+ * Plugin: WebpackMd5Hash
+ * Description: Plugin to replace a standard webpack chunkhash with md5.
+ *
+ * See: https://www.npmjs.com/package/webpack-md5-hash
+ */
+ new WebpackMd5Hash(),
+
+ /**
+ * Plugin: DedupePlugin
+ * Description: Prevents the inclusion of duplicate code into your bundle
+ * and instead applies a copy of the function at runtime.
+ *
+ * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+ * See: https://github.com/webpack/docs/wiki/optimization#deduplication
+ */
+ // new DedupePlugin(),
+
+ /**
+ * Plugin: DefinePlugin
+ * Description: Define free variables.
+ * Useful for having development builds with debug logging or adding global constants.
+ *
+ * Environment helpers
+ *
+ * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+ */
+ // NOTE: when adding more properties make sure you include them in custom-typings.d.ts
+ new DefinePlugin({
+ 'ENV': JSON.stringify(METADATA.ENV),
+ 'HMR': METADATA.HMR,
+ 'process.env': {
+ 'ENV': JSON.stringify(METADATA.ENV),
+ 'NODE_ENV': JSON.stringify(METADATA.ENV),
+ 'HMR': METADATA.HMR
+ }
+ }),
+
+ /**
+ * Plugin: UglifyJsPlugin
+ * Description: Minimize all JavaScript output of chunks.
+ * Loaders are switched into minimizing mode.
+ *
+ * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
+ */
+ // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines
+ new UglifyJsPlugin({
+ // beautify: true, //debug
+ // mangle: false, //debug
+ // dead_code: false, //debug
+ // unused: false, //debug
+ // deadCode: false, //debug
+ // compress: {
+ // screw_ie8: true,
+ // keep_fnames: true,
+ // drop_debugger: false,
+ // dead_code: false,
+ // unused: false
+ // }, // debug
+ // comments: true, //debug
+
+ beautify: false, // prod
+ mangle: { screw_ie8: true, keep_fnames: true }, // prod
+ compress: { screw_ie8: true }, // prod
+ comments: false // prod
+ }),
+
+ new NormalModuleReplacementPlugin(
+ /angular2-hmr/,
+ helpers.root('config/modules/angular2-hmr-prod.js')
+ )
+
+ /**
+ * Plugin: CompressionPlugin
+ * Description: Prepares compressed versions of assets to serve
+ * them with Content-Encoding
+ *
+ * See: https://github.com/webpack/compression-webpack-plugin
+ */
+ // new CompressionPlugin({
+ // regExp: /\.css$|\.html$|\.js$|\.map$/,
+ // threshold: 2 * 1024
+ // })
- /**
- * Plugin: DedupePlugin
- * Description: Prevents the inclusion of duplicate code into your bundle
- * and instead applies a copy of the function at runtime.
- *
- * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
- * See: https://github.com/webpack/docs/wiki/optimization#deduplication
- */
- new DedupePlugin(),
+ ],
/**
- * Plugin: DefinePlugin
- * Description: Define free variables.
- * Useful for having development builds with debug logging or adding global constants.
- *
- * Environment helpers
+ * Static analysis linter for TypeScript advanced options configuration
+ * Description: An extensible linter for the TypeScript language.
*
- * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin
+ * See: https://github.com/wbuchwalter/tslint-loader
*/
- // NOTE: when adding more properties make sure you include them in custom-typings.d.ts
- new DefinePlugin({
- 'ENV': JSON.stringify(METADATA.ENV),
- 'HMR': METADATA.HMR,
- 'process.env': {
- 'ENV': JSON.stringify(METADATA.ENV),
- 'NODE_ENV': JSON.stringify(METADATA.ENV),
- 'HMR': METADATA.HMR
- }
- }),
+ tslint: {
+ emitErrors: true,
+ failOnHint: true,
+ resourcePath: 'src'
+ },
/**
- * Plugin: UglifyJsPlugin
- * Description: Minimize all JavaScript output of chunks.
- * Loaders are switched into minimizing mode.
+ * Html loader advanced options
*
- * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
+ * See: https://github.com/webpack/html-loader#advanced-options
*/
- // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines
- new UglifyJsPlugin({
- // beautify: true, //debug
- // mangle: false, //debug
- // dead_code: false, //debug
- // unused: false, //debug
- // deadCode: false, //debug
- // compress: {
- // screw_ie8: true,
- // keep_fnames: true,
- // drop_debugger: false,
- // dead_code: false,
- // unused: false
- // }, // debug
- // comments: true, //debug
-
- beautify: false, // prod
-
- mangle: {
- screw_ie8: true,
- keep_fnames: true
- }, // prod
-
- compress: {
- screw_ie8: true
- }, // prod
-
- comments: false // prod
- }),
-
- /**
- * Plugin: CompressionPlugin
- * Description: Prepares compressed versions of assets to serve
- * them with Content-Encoding
+ // TODO: Need to workaround Angular 2's html syntax => #id [bind] (event) *ngFor
+ htmlLoader: {
+ minimize: true,
+ removeAttributeQuotes: false,
+ caseSensitive: true,
+ customAttrSurround: [
+ [/#/, /(?:)/],
+ [/\*/, /(?:)/],
+ [/\[?\(?/, /(?:)/]
+ ],
+ customAttrAssign: [/\)?\]?=/]
+ },
+
+ /*
+ * Include polyfills or mocks for various node stuff
+ * Description: Node configuration
*
- * See: https://github.com/webpack/compression-webpack-plugin
+ * See: https://webpack.github.io/docs/configuration.html#node
*/
- new CompressionPlugin({
- regExp: /\.css$|\.html$|\.js$|\.map$/,
- threshold: 2 * 1024
- })
-
- ],
-
- /**
- * Static analysis linter for TypeScript advanced options configuration
- * Description: An extensible linter for the TypeScript language.
- *
- * See: https://github.com/wbuchwalter/tslint-loader
- */
- tslint: {
- emitErrors: true,
- failOnHint: true,
- resourcePath: 'src'
- },
-
- /**
- * Html loader advanced options
- *
- * See: https://github.com/webpack/html-loader#advanced-options
- */
- // TODO: Need to workaround Angular 2's html syntax => #id [bind] (event) *ngFor
- htmlLoader: {
- minimize: true,
- removeAttributeQuotes: false,
- caseSensitive: true,
- customAttrSurround: [
- [/#/, /(?:)/],
- [/\*/, /(?:)/],
- [/\[?\(?/, /(?:)/]
- ],
- customAttrAssign: [/\)?\]?=/]
- },
-
- /*
- * Include polyfills or mocks for various node stuff
- * Description: Node configuration
- *
- * See: https://webpack.github.io/docs/configuration.html#node
- */
- node: {
- global: 'window',
- crypto: 'empty',
- process: false,
- module: false,
- clearImmediate: false,
- setImmediate: false
- }
-
-})
+ node: {
+ global: 'window',
+ crypto: 'empty',
+ process: false,
+ module: false,
+ clearImmediate: false,
+ setImmediate: false
+ }
+
+ })
+}
"url": "git://github.com/Chocobozzz/PeerTube.git"
},
"scripts": {
- "postinstall": "typings install",
"test": "standard && tslint -c ./tslint.json src/**/*.ts",
"webpack": "webpack"
},
"license": "GPLv3",
"dependencies": {
- "@angular/common": "2.0.0-rc.4",
- "@angular/compiler": "2.0.0-rc.4",
- "@angular/core": "2.0.0-rc.4",
- "@angular/http": "2.0.0-rc.4",
- "@angular/platform-browser": "2.0.0-rc.4",
- "@angular/platform-browser-dynamic": "2.0.0-rc.4",
- "@angular/router": "3.0.0-beta.2",
- "angular-pipes": "^2.0.0",
- "awesome-typescript-loader": "^0.17.0",
- "bootstrap-loader": "^1.0.8",
+ "@angular/common": "^2.0.0",
+ "@angular/compiler": "^2.0.0",
+ "@angular/core": "^2.0.0",
+ "@angular/forms": "^2.0.0",
+ "@angular/http": "^2.0.0",
+ "@angular/platform-browser": "^2.0.0",
+ "@angular/platform-browser-dynamic": "^2.0.0",
+ "@angular/router": "^3.0.0",
+ "@angularclass/hmr": "^1.2.0",
+ "@angularclass/hmr-loader": "^3.0.2",
+ "@types/core-js": "^0.9.28",
+ "@types/node": "^6.0.38",
+ "@types/source-map": "^0.1.26",
+ "@types/uglify-js": "^2.0.27",
+ "@types/webpack": "^1.12.29",
+ "angular-pipes": "^3.0.0",
+ "angular2-template-loader": "^0.5.0",
+ "assets-webpack-plugin": "^3.4.0",
+ "awesome-typescript-loader": "^2.2.1",
+ "bootstrap-loader": "^2.0.0-beta.11",
"bootstrap-sass": "^3.3.6",
"compression-webpack-plugin": "^0.3.1",
"copy-webpack-plugin": "^3.0.1",
- "core-js": "^2.4.0",
- "css-loader": "^0.23.1",
+ "core-js": "^2.4.1",
+ "css-loader": "^0.25.0",
+ "css-to-string-loader": "https://github.com/Chocobozzz/css-to-string-loader#patch-1",
"es6-promise": "^3.0.2",
"es6-promise-loader": "^1.0.1",
"es6-shim": "^0.35.0",
- "file-loader": "^0.8.5",
+ "extract-text-webpack-plugin": "^2.0.0-beta.4",
+ "file-loader": "^0.9.0",
"html-webpack-plugin": "^2.19.0",
"ie-shim": "^0.1.0",
"intl": "^1.2.4",
"json-loader": "^0.5.4",
- "ng2-bootstrap": "1.0.16",
+ "ng2-bootstrap": "^1.1.5",
"ng2-file-upload": "^1.0.3",
- "node-sass": "^3.7.0",
+ "node-sass": "^3.10.0",
"normalize.css": "^4.1.1",
"raw-loader": "^0.5.1",
"reflect-metadata": "0.1.3",
- "resolve-url-loader": "^1.4.3",
- "rxjs": "5.0.0-beta.6",
- "sass-loader": "^3.2.0",
+ "resolve-url-loader": "^1.6.0",
+ "rxjs": "5.0.0-beta.12",
+ "sass-loader": "^4.0.2",
"source-map-loader": "^0.1.5",
+ "string-replace-loader": "^1.0.3",
"style-loader": "^0.13.1",
"ts-helpers": "^1.1.1",
- "tslint": "^3.7.4",
+ "tslint": "3.15.1",
"tslint-loader": "^2.1.4",
- "typescript": "^1.8.10",
- "typings": "^1.0.4",
+ "typescript": "^2.0.0",
"url-loader": "^0.5.7",
- "webpack": "^1.13.1",
+ "webpack": "2.1.0-beta.22",
"webpack-md5-hash": "0.0.5",
- "webpack-merge": "^0.13.0",
+ "webpack-merge": "^0.14.1",
"webpack-notifier": "^1.3.0",
- "webtorrent": "^0.95.2",
- "zone.js": "0.6.12"
+ "webtorrent": "^0.96.0",
+ "zone.js": "0.6.23"
},
"devDependencies": {
- "codelyzer": "0.0.19",
- "standard": "^7.0.1"
+ "codelyzer": "0.0.28",
+ "standard": "^8.0.0"
}
}
--- /dev/null
+<h3>Account</h3>
+
+<div *ngIf="information" class="alert alert-success">{{ information }}</div>
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
+ <div class="form-group">
+ <label for="new-password">New password</label>
+ <input
+ type="password" class="form-control" id="new-password"
+ formControlName="new-password"
+ >
+ <div *ngIf="formErrors['new-password']" class="alert alert-danger">
+ {{ formErrors['new-password'] }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="name">Confirm new password</label>
+ <input
+ type="password" class="form-control" id="new-confirmed-password"
+ formControlName="new-confirmed-password"
+ >
+ </div>
+
+ <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid">
+</form>
--- /dev/null
+import { } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { Router } from '@angular/router';
+
+import { AccountService } from './account.service';
+import { FormReactive, USER_PASSWORD } from '../shared';
+
+@Component({
+ selector: 'my-account',
+ templateUrl: './account.component.html'
+})
+
+export class AccountComponent extends FormReactive implements OnInit {
+ information: string = null;
+ error: string = null;
+
+ form: FormGroup;
+ formErrors = {
+ 'new-password': '',
+ 'new-confirmed-password': ''
+ };
+ validationMessages = {
+ 'new-password': USER_PASSWORD.MESSAGES,
+ 'new-confirmed-password': USER_PASSWORD.MESSAGES
+ };
+
+ constructor(
+ private accountService: AccountService,
+ private formBuilder: FormBuilder,
+ private router: Router
+ ) {
+ super();
+ }
+
+ buildForm() {
+ this.form = this.formBuilder.group({
+ 'new-password': [ '', USER_PASSWORD.VALIDATORS ],
+ 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ],
+ });
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+ }
+
+ ngOnInit() {
+ this.buildForm();
+ }
+
+ changePassword() {
+ const newPassword = this.form.value['new-password'];
+ const newConfirmedPassword = this.form.value['new-confirmed-password'];
+
+ this.information = null;
+ this.error = null;
+
+ if (newPassword !== newConfirmedPassword) {
+ this.error = 'The new password and the confirmed password do not correspond.';
+ return;
+ }
+
+ this.accountService.changePassword(newPassword).subscribe(
+ ok => this.information = 'Password updated.',
+
+ err => this.error = err
+ );
+ }
+}
--- /dev/null
+import { AccountComponent } from './account.component';
+
+export const AccountRoutes = [
+ { path: 'account', component: AccountComponent }
+];
--- /dev/null
+import { Injectable } from '@angular/core';
+
+import { AuthHttp, AuthService, RestExtractor } from '../shared';
+
+@Injectable()
+export class AccountService {
+ private static BASE_USERS_URL = '/api/v1/users/';
+
+ constructor(
+ private authHttp: AuthHttp,
+ private authService: AuthService,
+ private restExtractor: RestExtractor
+ ) {}
+
+ changePassword(newPassword: string) {
+ const url = AccountService.BASE_USERS_URL + this.authService.getUser().id;
+ const body = {
+ password: newPassword
+ };
+
+ return this.authHttp.put(url, body)
+ .map(this.restExtractor.extractDataBool)
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+}
--- /dev/null
+export * from './account.component';
+export * from './account.routes';
+export * from './account.service';
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ template: '<router-outlet></router-outlet>'
+})
+
+export class AdminComponent {
+}
--- /dev/null
+import { Routes } from '@angular/router';
+
+import { AdminComponent } from './admin.component';
+import { FriendsRoutes } from './friends';
+import { RequestsRoutes } from './requests';
+import { UsersRoutes } from './users';
+
+export const AdminRoutes: Routes = [
+ {
+ path: 'admin',
+ component: AdminComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'users',
+ pathMatch: 'full'
+ },
+ ...FriendsRoutes,
+ ...RequestsRoutes,
+ ...UsersRoutes
+ ]
+ }
+];
--- /dev/null
+<h3>Make friends</h3>
+
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form (ngSubmit)="makeFriends()" [formGroup]="form">
+ <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy">
+ <label for="username">Url</label>
+
+ <div class="input-group">
+ <input
+ type="text" class="form-control" placeholder="http://domain.com"
+ [id]="'url-' + id" [formControlName]="'url-' + id"
+ />
+ <span class="input-group-btn">
+ <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button>
+ <button *ngIf="displayRemoveField(id)" (click)="removeField(id)" class="btn btn-default" type="button">-</button>
+ </span>
+ </div>
+
+ <div [hidden]="form.controls['url-' + id].valid || form.controls['url-' + id].pristine" class="alert alert-warning">
+ It should be a valid url.
+ </div>
+ </div>
+
+ <input type="submit" value="Make friends" class="btn btn-default" [disabled]="!isFormValid()">
+</form>
--- /dev/null
+table {
+ margin-bottom: 40px;
+}
+
+.input-group-btn button {
+ width: 35px;
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+import { FormControl, FormGroup } from '@angular/forms';
+import { Router } from '@angular/router';
+
+import { validateUrl } from '../../../shared';
+import { FriendService } from '../shared';
+
+@Component({
+ selector: 'my-friend-add',
+ templateUrl: './friend-add.component.html',
+ styleUrls: [ './friend-add.component.scss' ]
+})
+export class FriendAddComponent implements OnInit {
+ form: FormGroup;
+ urls = [ ];
+ error: string = null;
+
+ constructor(private router: Router, private friendService: FriendService) {}
+
+ ngOnInit() {
+ this.form = new FormGroup({});
+ this.addField();
+ }
+
+ addField() {
+ this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ]));
+ this.urls.push('');
+ }
+
+ customTrackBy(index: number, obj: any): any {
+ return index;
+ }
+
+ displayAddField(index: number) {
+ return index === (this.urls.length - 1);
+ }
+
+ displayRemoveField(index: number) {
+ return (index !== 0 || this.urls.length > 1) && index !== (this.urls.length - 1);
+ }
+
+ isFormValid() {
+ // Do not check the last input
+ for (let i = 0; i < this.urls.length - 1; i++) {
+ if (!this.form.controls[`url-${i}`].valid) return false;
+ }
+
+ const lastIndex = this.urls.length - 1;
+ // If the last input (which is not the first) is empty, it's ok
+ if (this.urls[lastIndex] === '' && lastIndex !== 0) {
+ return true;
+ } else {
+ return this.form.controls[`url-${lastIndex}`].valid;
+ }
+ }
+
+ removeField(index: number) {
+ // Remove the last control
+ this.form.removeControl(`url-${this.urls.length - 1}`);
+ this.urls.splice(index, 1);
+ }
+
+ makeFriends() {
+ this.error = '';
+
+ const notEmptyUrls = this.getNotEmptyUrls();
+ if (notEmptyUrls.length === 0) {
+ this.error = 'You need to specify at less 1 url.';
+ return;
+ }
+
+ if (!this.isUrlsUnique(notEmptyUrls)) {
+ this.error = 'Urls need to be unique.';
+ return;
+ }
+
+ const confirmMessage = 'Are you sure to make friends with:\n - ' + notEmptyUrls.join('\n - ');
+ if (!confirm(confirmMessage)) return;
+
+ this.friendService.makeFriends(notEmptyUrls).subscribe(
+ status => {
+ // TODO: extractdatastatus
+ // if (status === 409) {
+ // alert('Already made friends!');
+ // } else {
+ alert('Make friends request sent!');
+ this.router.navigate([ '/admin/friends/list' ]);
+ // }
+ },
+ error => alert(error.text)
+ );
+ }
+
+ private getNotEmptyUrls() {
+ const notEmptyUrls = [];
+
+ Object.keys(this.form.value).forEach((urlKey) => {
+ const url = this.form.value[urlKey];
+ if (url !== '') notEmptyUrls.push(url);
+ });
+
+ return notEmptyUrls;
+ }
+
+ private isUrlsUnique(urls: string[]) {
+ return urls.every(url => urls.indexOf(url) === urls.lastIndexOf(url));
+ }
+}
--- /dev/null
+export * from './friend-add.component';
--- /dev/null
+<h3>Friends list</h3>
+
+<table class="table table-hover">
+ <thead>
+ <tr>
+ <th class="table-column-id">ID</th>
+ <th>Url</th>
+ <th>Score</th>
+ <th>Created Date</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr *ngFor="let friend of friends">
+ <td>{{ friend.id }}</td>
+ <td>{{ friend.url }}</td>
+ <td>{{ friend.score }}</td>
+ <td>{{ friend.createdDate | date: 'medium' }}</td>
+ </tr>
+ </tbody>
+</table>
+
+<a *ngIf="friends?.length !== 0" class="add-user btn btn-danger pull-left" (click)="quitFriends()">
+ Quit friends
+</a>
+
+<a *ngIf="friends?.length === 0" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']">
+ Make friends
+</a>
--- /dev/null
+table {
+ margin-bottom: 40px;
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+
+import { Friend, FriendService } from '../shared';
+
+@Component({
+ selector: 'my-friend-list',
+ templateUrl: './friend-list.component.html',
+ styleUrls: [ './friend-list.component.scss' ]
+})
+export class FriendListComponent implements OnInit {
+ friends: Friend[];
+
+ constructor(private friendService: FriendService) { }
+
+ ngOnInit() {
+ this.getFriends();
+ }
+
+ quitFriends() {
+ if (!confirm('Are you sure?')) return;
+
+ this.friendService.quitFriends().subscribe(
+ status => {
+ alert('Quit friends!');
+ this.getFriends();
+ },
+ error => alert(error.text)
+ );
+ }
+
+ private getFriends() {
+ this.friendService.getFriends().subscribe(
+ friends => this.friends = friends,
+
+ err => alert(err.text)
+ );
+ }
+}
--- /dev/null
+export * from './friend-list.component';
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ template: '<router-outlet></router-outlet>'
+})
+
+export class FriendsComponent {
+}
--- /dev/null
+import { Routes } from '@angular/router';
+
+import { FriendsComponent } from './friends.component';
+import { FriendAddComponent } from './friend-add';
+import { FriendListComponent } from './friend-list';
+
+export const FriendsRoutes: Routes = [
+ {
+ path: 'friends',
+ component: FriendsComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'list',
+ pathMatch: 'full'
+ },
+ {
+ path: 'list',
+ component: FriendListComponent
+ },
+ {
+ path: 'add',
+ component: FriendAddComponent
+ }
+ ]
+ }
+];
--- /dev/null
+export * from './friend-add';
+export * from './friend-list';
+export * from './shared';
+export * from './friends.component';
+export * from './friends.routes';
--- /dev/null
+export interface Friend {
+ id: string;
+ url: string;
+ score: number;
+ createdDate: Date;
+}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { Friend } from './friend.model';
+import { AuthHttp, RestExtractor } from '../../../shared';
+
+@Injectable()
+export class FriendService {
+ private static BASE_FRIEND_URL: string = '/api/v1/pods/';
+
+ constructor (
+ private authHttp: AuthHttp,
+ private restExtractor: RestExtractor
+ ) {}
+
+ getFriends(): Observable<Friend[]> {
+ return this.authHttp.get(FriendService.BASE_FRIEND_URL)
+ // Not implemented as a data list by the server yet
+ // .map(this.restExtractor.extractDataList)
+ .map((res) => res.json())
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+
+ makeFriends(notEmptyUrls) {
+ const body = {
+ urls: notEmptyUrls
+ };
+
+ return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body)
+ .map(this.restExtractor.extractDataBool)
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+
+ quitFriends() {
+ return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends')
+ .map(res => res.status)
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+}
+export * from './friend.model';
export * from './friend.service';
--- /dev/null
+export * from './friends';
+export * from './requests';
+export * from './users';
+export * from './admin.component';
+export * from './admin.routes';
+export * from './menu-admin.component';
--- /dev/null
+<menu class="col-md-2 col-sm-3 col-xs-3">
+
+ <div class="panel-block">
+ <div id="panel-users" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-user"></span>
+ <a [routerLink]="['/admin/users/list']">List users</a>
+ </div>
+
+ <div id="panel-friends" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-cloud"></span>
+ <a [routerLink]="['/admin/friends/list']">List friends</a>
+ </div>
+
+ <div id="panel-request-stats" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-stats"></span>
+ <a [routerLink]="['/admin/requests/stats']">Request stats</a>
+ </div>
+ </div>
+
+ <div class="panel-block">
+ <div id="panel-quit-administration" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-cog"></span>
+ <a [routerLink]="['/videos/list']">Quit admin.</a>
+ </div>
+ </div>
+</menu>
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'my-menu-admin',
+ templateUrl: './menu-admin.component.html'
+})
+export class MenuAdminComponent { }
--- /dev/null
+export * from './request-stats';
+export * from './shared';
+export * from './requests.component';
+export * from './requests.routes';
--- /dev/null
+export * from './request-stats.component';
--- /dev/null
+<h3>Requests stats</h3>
+
+<div *ngIf="stats !== null">
+ <div>
+ <span class="label-description">Interval seconds between requests:</span>
+ {{ stats.secondsInterval }}
+ </div>
+
+ <div>
+ <span class="label-description">Remaining time before the scheduled request:</span>
+ {{ stats.remainingSeconds }}
+ </div>
+
+ <div>
+ <span class="label-description">Maximum number of requests per interval:</span>
+ {{ stats.maxRequestsInParallel }}
+ </div>
+
+ <div>
+ <span class="label-description">Remaining requests:</span>
+ {{ stats.requests.length }}
+ </div>
+</div>
--- /dev/null
+.label-description {
+ display: inline-block;
+ width: 350px;
+ font-weight: bold;
+ color: black;
+}
--- /dev/null
+import { Component, OnInit, OnDestroy } from '@angular/core';
+
+import { RequestService, RequestStats } from '../shared';
+
+@Component({
+ selector: 'my-request-stats',
+ templateUrl: './request-stats.component.html',
+ styleUrls: [ './request-stats.component.scss' ]
+})
+export class RequestStatsComponent implements OnInit, OnDestroy {
+ stats: RequestStats = null;
+
+ private interval: NodeJS.Timer = null;
+
+ constructor(private requestService: RequestService) { }
+
+ ngOnInit() {
+ this.getStats();
+ }
+
+ ngOnDestroy() {
+ if (this.stats.secondsInterval !== null) {
+ clearInterval(this.interval);
+ }
+ }
+
+ getStats() {
+ this.requestService.getStats().subscribe(
+ stats => {
+ console.log(stats);
+ this.stats = stats;
+ this.runInterval();
+ },
+
+ err => alert(err.text)
+ );
+ }
+
+ private runInterval() {
+ this.interval = setInterval(() => {
+ this.stats.remainingMilliSeconds -= 1000;
+
+ if (this.stats.remainingMilliSeconds <= 0) {
+ setTimeout(() => this.getStats(), this.stats.remainingMilliSeconds + 100);
+ clearInterval(this.interval);
+ }
+ }, 1000);
+ }
+
+
+}
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ template: '<router-outlet></router-outlet>'
+})
+
+export class RequestsComponent {
+}
--- /dev/null
+import { Routes } from '@angular/router';
+
+import { RequestsComponent } from './requests.component';
+import { RequestStatsComponent } from './request-stats';
+
+export const RequestsRoutes: Routes = [
+ {
+ path: 'requests',
+ component: RequestsComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'stats',
+ pathMatch: 'full'
+ },
+ {
+ path: 'stats',
+ component: RequestStatsComponent
+ }
+ ]
+ }
+];
--- /dev/null
+export * from './request-stats.model';
+export * from './request.service';
--- /dev/null
+export interface Request {
+ request: any;
+ to: any;
+}
+
+export class RequestStats {
+ maxRequestsInParallel: number;
+ milliSecondsInterval: number;
+ remainingMilliSeconds: number;
+ requests: Request[];
+
+ constructor(hash: {
+ maxRequestsInParallel: number,
+ milliSecondsInterval: number,
+ remainingMilliSeconds: number,
+ requests: Request[];
+ }) {
+ this.maxRequestsInParallel = hash.maxRequestsInParallel;
+ this.milliSecondsInterval = hash.milliSecondsInterval;
+ this.remainingMilliSeconds = hash.remainingMilliSeconds;
+ this.requests = hash.requests;
+ }
+
+ get remainingSeconds() {
+ return Math.floor(this.remainingMilliSeconds / 1000);
+ }
+
+ get secondsInterval() {
+ return Math.floor(this.milliSecondsInterval / 1000);
+ }
+
+}
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
+
+import { RequestStats } from './request-stats.model';
+import { AuthHttp, RestExtractor } from '../../../shared';
+
+@Injectable()
+export class RequestService {
+ private static BASE_REQUEST_URL: string = '/api/v1/requests/';
+
+ constructor (
+ private authHttp: AuthHttp,
+ private restExtractor: RestExtractor
+ ) {}
+
+ getStats(): Observable<RequestStats> {
+ return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats')
+ .map(this.restExtractor.extractDataGet)
+ .map((data) => new RequestStats(data))
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+}
--- /dev/null
+export * from './shared';
+export * from './user-add';
+export * from './user-list';
+export * from './users.component';
+export * from './users.routes';
--- /dev/null
+export * from './user.service';
--- /dev/null
+import { Injectable } from '@angular/core';
+
+import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared';
+
+@Injectable()
+export class UserService {
+ // TODO: merge this constant with account
+ private static BASE_USERS_URL = '/api/v1/users/';
+
+ constructor(
+ private authHttp: AuthHttp,
+ private restExtractor: RestExtractor
+ ) {}
+
+ addUser(username: string, password: string) {
+ const body = {
+ username,
+ password
+ };
+
+ return this.authHttp.post(UserService.BASE_USERS_URL, body)
+ .map(this.restExtractor.extractDataBool)
+ .catch(this.restExtractor.handleError);
+ }
+
+ getUsers() {
+ return this.authHttp.get(UserService.BASE_USERS_URL)
+ .map(this.restExtractor.extractDataList)
+ .map(this.extractUsers)
+ .catch((res) => this.restExtractor.handleError(res));
+ }
+
+ removeUser(user: User) {
+ return this.authHttp.delete(UserService.BASE_USERS_URL + user.id);
+ }
+
+ private extractUsers(result: ResultList) {
+ const usersJson = result.data;
+ const totalUsers = result.total;
+ const users = [];
+ for (const userJson of usersJson) {
+ users.push(new User(userJson));
+ }
+
+ return { users, totalUsers };
+ }
+}
--- /dev/null
+export * from './user-add.component';
--- /dev/null
+<h3>Add user</h3>
+
+<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+<form role="form" (ngSubmit)="addUser()" [formGroup]="form">
+ <div class="form-group">
+ <label for="username">Username</label>
+ <input
+ type="text" class="form-control" id="username" placeholder="Username"
+ formControlName="username"
+ >
+ <div *ngIf="formErrors.username" class="alert alert-danger">
+ {{ formErrors.username }}
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label for="password">Password</label>
+ <input
+ type="password" class="form-control" id="password" placeholder="Password"
+ formControlName="password"
+ >
+ <div *ngIf="formErrors.password" class="alert alert-danger">
+ {{ formErrors.password }}
+ </div>
+ </div>
+
+ <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid">
+</form>
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { Router } from '@angular/router';
+
+import { UserService } from '../shared';
+import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared';
+
+@Component({
+ selector: 'my-user-add',
+ templateUrl: './user-add.component.html'
+})
+export class UserAddComponent extends FormReactive implements OnInit {
+ error: string = null;
+
+ form: FormGroup;
+ formErrors = {
+ 'username': '',
+ 'password': ''
+ };
+ validationMessages = {
+ 'username': USER_USERNAME.MESSAGES,
+ 'password': USER_PASSWORD.MESSAGES,
+ };
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private router: Router,
+ private userService: UserService
+ ) {
+ super();
+ }
+
+ buildForm() {
+ this.form = this.formBuilder.group({
+ username: [ '', USER_USERNAME.VALIDATORS ],
+ password: [ '', USER_PASSWORD.VALIDATORS ],
+ });
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+ }
+
+ ngOnInit() {
+ this.buildForm();
+ }
+
+ addUser() {
+ this.error = null;
+
+ const { username, password } = this.form.value;
+
+ this.userService.addUser(username, password).subscribe(
+ ok => this.router.navigate([ '/admin/users/list' ]),
+
+ err => this.error = err.text
+ );
+ }
+}
--- /dev/null
+export * from './user-list.component';
--- /dev/null
+<h3>Users list</h3>
+
+<table class="table table-hover">
+ <thead>
+ <tr>
+ <th class="table-column-id">ID</th>
+ <th>Username</th>
+ <th>Created Date</th>
+ <th class="text-right">Remove</th>
+ </tr>
+ </thead>
+
+ <tbody>
+ <tr *ngFor="let user of users">
+ <td>{{ user.id }}</td>
+ <td>{{ user.username }}</td>
+ <td>{{ user.createdDate | date: 'medium' }}</td>
+ <td class="text-right">
+ <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
+ </td>
+ </tr>
+ </tbody>
+</table>
+
+<a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']">
+ <span class="glyphicon glyphicon-plus"></span>
+ Add user
+</a>
--- /dev/null
+.glyphicon-remove {
+ cursor: pointer;
+}
+
+.add-user {
+ margin-top: 10px;
+}
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+
+import { User } from '../../../shared';
+import { UserService } from '../shared';
+
+@Component({
+ selector: 'my-user-list',
+ templateUrl: './user-list.component.html',
+ styleUrls: [ './user-list.component.scss' ]
+})
+export class UserListComponent implements OnInit {
+ totalUsers: number;
+ users: User[];
+
+ constructor(private userService: UserService) {}
+
+ ngOnInit() {
+ this.getUsers();
+ }
+
+ getUsers() {
+ this.userService.getUsers().subscribe(
+ ({ users, totalUsers }) => {
+ this.users = users;
+ this.totalUsers = totalUsers;
+ },
+
+ err => alert(err.text)
+ );
+ }
+
+
+ removeUser(user: User) {
+ if (confirm('Are you sure?')) {
+ this.userService.removeUser(user).subscribe(
+ () => this.getUsers(),
+
+ err => alert(err.text)
+ );
+ }
+ }
+}
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ template: '<router-outlet></router-outlet>'
+})
+
+export class UsersComponent {
+}
--- /dev/null
+import { Routes } from '@angular/router';
+
+import { UsersComponent } from './users.component';
+import { UserAddComponent } from './user-add';
+import { UserListComponent } from './user-list';
+
+export const UsersRoutes: Routes = [
+ {
+ path: 'users',
+ component: UsersComponent,
+ children: [
+ {
+ path: '',
+ redirectTo: 'list',
+ pathMatch: 'full'
+ },
+ {
+ path: 'list',
+ component: UserListComponent
+ },
+ {
+ path: 'add',
+ component: UserAddComponent
+ }
+ ]
+ }
+];
<div class="row">
-
- <menu class="col-md-2 col-sm-3 col-xs-3">
- <div class="panel-block">
- <div id="panel-user-login" class="panel-button">
- <span class="hidden-xs glyphicon glyphicon-user"></span>
- <a *ngIf="!isLoggedIn" [routerLink]="['/login']">Login</a>
- <a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
- </div>
- </div>
-
- <div class="panel-block">
- <div id="panel-get-videos" class="panel-button">
- <span class="hidden-xs glyphicon glyphicon-list"></span>
- <a [routerLink]="['/videos/list']">Get videos</a>
- </div>
-
- <div id="panel-upload-video" class="panel-button" *ngIf="isLoggedIn">
- <span class="hidden-xs glyphicon glyphicon-cloud-upload"></span>
- <a [routerLink]="['/videos/add']">Upload a video</a>
- </div>
- </div>
-
- <div class="panel-block" *ngIf="isLoggedIn">
- <div id="panel-make-friends" class="panel-button">
- <span class="hidden-xs glyphicon glyphicon-cloud"></span>
- <a (click)='makeFriends()'>Make friends</a>
- </div>
-
- <div id="panel-quit-friends" class="panel-button">
- <span class="hidden-xs glyphicon glyphicon-plane"></span>
- <a (click)='quitFriends()'>Quit friends</a>
- </div>
- </div>
- </menu>
+ <my-menu *ngIf="isInAdmin() === false"></my-menu>
+ <my-menu-admin *ngIf="isInAdmin() === true"></my-menu-admin>
<div class="col-md-9 col-sm-8 col-xs-8 router-outlet-container">
<router-outlet></router-outlet>
</div>
-
</div>
-
<footer>
PeerTube, CopyLeft 2015-2016
</footer>
margin-bottom: 30px;
}
-menu {
- @media screen and (max-width: 600px) {
- margin-right: 3px !important;
- padding: 3px !important;
- min-height: 400px !important;
- }
-
- min-height: 600px;
- margin-right: 20px;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
-
- .panel-button {
- margin: 8px;
- cursor: pointer;
- transition: margin 0.2s;
-
- &:hover {
- margin-left: 15px;
- }
-
- a {
- color: #333333;
- }
- }
-
- .glyphicon {
- margin: 5px;
- }
-}
-
-.panel-block:not(:last-child) {
- border-bottom: 1px solid rgba(0, 0, 0, 0.1);
-}
-
.router-outlet-container {
@media screen and (max-width: 400px) {
padding: 0 3px 0 3px;
import { Component } from '@angular/core';
-import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router';
-
-import { FriendService } from './friends';
-import {
- AuthService,
- AuthStatus,
- SearchComponent,
- SearchService
-} from './shared';
-import { VideoService } from './videos';
+import { Router } from '@angular/router';
@Component({
selector: 'my-app',
- template: require('./app.component.html'),
- styles: [ require('./app.component.scss') ],
- directives: [ ROUTER_DIRECTIVES, SearchComponent ],
- providers: [ FriendService, VideoService, SearchService ]
+ templateUrl: './app.component.html',
+ styleUrls: [ './app.component.scss' ]
})
export class AppComponent {
- choices = [];
- isLoggedIn: boolean;
-
- constructor(
- private authService: AuthService,
- private friendService: FriendService,
- private route: ActivatedRoute,
- private router: Router
- ) {
- this.isLoggedIn = this.authService.isLoggedIn();
-
- this.authService.loginChangedSource.subscribe(
- status => {
- if (status === AuthStatus.LoggedIn) {
- this.isLoggedIn = true;
- console.log('Logged in.');
- } else if (status === AuthStatus.LoggedOut) {
- this.isLoggedIn = false;
- console.log('Logged out.');
- } else {
- console.error('Unknown auth status: ' + status);
- }
- }
- );
- }
-
- logout() {
- this.authService.logout();
- }
-
- makeFriends() {
- this.friendService.makeFriends().subscribe(
- status => {
- if (status === 409) {
- alert('Already made friends!');
- } else {
- alert('Made friends!');
- }
- },
- error => alert(error)
- );
- }
+ constructor(private router: Router) {}
- quitFriends() {
- this.friendService.quitFriends().subscribe(
- status => {
- alert('Quit friends!');
- },
- error => alert(error)
- );
+ isInAdmin() {
+ return this.router.url.indexOf('/admin/') !== -1;
}
}
--- /dev/null
+import { ApplicationRef, NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { HttpModule, RequestOptions, XHRBackend } from '@angular/http';
+import { RouterModule } from '@angular/router';
+import { removeNgStyles, createNewHosts } from '@angularclass/hmr';
+
+import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
+import { ProgressbarModule } from 'ng2-bootstrap/components/progressbar';
+import { PaginationModule } from 'ng2-bootstrap/components/pagination';
+import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload';
+
+/*
+ * Platform and Environment providers/directives/pipes
+ */
+import { ENV_PROVIDERS } from './environment';
+import { routes } from './app.routes';
+// App is our top level component
+import { AppComponent } from './app.component';
+import { AppState } from './app.service';
+
+import {
+ AdminComponent,
+ FriendsComponent,
+ FriendAddComponent,
+ FriendListComponent,
+ FriendService,
+ MenuAdminComponent,
+ RequestsComponent,
+ RequestStatsComponent,
+ RequestService,
+ UsersComponent,
+ UserAddComponent,
+ UserListComponent,
+ UserService
+} from './admin';
+import { AccountComponent, AccountService } from './account';
+import { LoginComponent } from './login';
+import { MenuComponent } from './menu.component';
+import { AuthService, AuthHttp, RestExtractor, RestService, SearchComponent, SearchService } from './shared';
+import {
+ LoaderComponent,
+ VideosComponent,
+ VideoAddComponent,
+ VideoListComponent,
+ VideoMiniatureComponent,
+ VideoSortComponent,
+ VideoWatchComponent,
+ VideoService,
+ WebTorrentService
+} from './videos';
+
+// Application wide providers
+const APP_PROVIDERS = [
+ AppState,
+
+ {
+ provide: AuthHttp,
+ useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) => {
+ return new AuthHttp(backend, defaultOptions, authService);
+ },
+ deps: [ XHRBackend, RequestOptions, AuthService ]
+ },
+
+ AuthService,
+ RestExtractor,
+ RestService,
+
+ VideoService,
+ SearchService,
+ FriendService,
+ RequestService,
+ UserService,
+ AccountService,
+ WebTorrentService
+];
+/**
+ * `AppModule` is the main entry point into Angular2's bootstraping process
+ */
+@NgModule({
+ bootstrap: [ AppComponent ],
+ declarations: [
+ AccountComponent,
+ AdminComponent,
+ AppComponent,
+ BytesPipe,
+ FriendAddComponent,
+ FriendListComponent,
+ FriendsComponent,
+ LoaderComponent,
+ LoginComponent,
+ MenuAdminComponent,
+ MenuComponent,
+ RequestsComponent,
+ RequestStatsComponent,
+ SearchComponent,
+ UserAddComponent,
+ UserListComponent,
+ UsersComponent,
+ VideoAddComponent,
+ VideoListComponent,
+ VideoMiniatureComponent,
+ VideosComponent,
+ VideoSortComponent,
+ VideoWatchComponent,
+ ],
+ imports: [ // import Angular's modules
+ BrowserModule,
+ FormsModule,
+ ReactiveFormsModule,
+ HttpModule,
+ RouterModule.forRoot(routes),
+
+ ProgressbarModule,
+ PaginationModule,
+ FileUploadModule
+ ],
+ providers: [ // expose our Services and Providers into Angular's dependency injection
+ ENV_PROVIDERS,
+ APP_PROVIDERS
+ ]
+})
+export class AppModule {
+ constructor(public appRef: ApplicationRef, public appState: AppState) {}
+ hmrOnInit(store) {
+ if (!store || !store.state) return;
+ console.log('HMR store', store);
+ this.appState._state = store.state;
+ this.appRef.tick();
+ delete store.state;
+ }
+ hmrOnDestroy(store) {
+ const cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement);
+ // recreate elements
+ const state = this.appState._state;
+ store.state = state;
+ store.disposeOldHosts = createNewHosts(cmpLocation);
+ // remove styles
+ removeNgStyles();
+ }
+ hmrAfterDestroy(store) {
+ // display new elements
+ store.disposeOldHosts();
+ delete store.disposeOldHosts;
+ }
+}
-import { RouterConfig } from '@angular/router';
+import { Routes } from '@angular/router';
+import { AccountRoutes } from './account';
import { LoginRoutes } from './login';
+import { AdminRoutes } from './admin';
import { VideosRoutes } from './videos';
-export const routes: RouterConfig = [
+export const routes: Routes = [
{
path: '',
redirectTo: '/videos/list',
pathMatch: 'full'
},
-
+ ...AdminRoutes,
+ ...AccountRoutes,
...LoginRoutes,
...VideosRoutes
];
--- /dev/null
+
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class AppState {
+ _state = { };
+
+ constructor() { ; }
+
+ // already return a clone of the current state
+ get state() {
+ return this._state = this._clone(this._state);
+ }
+ // never allow mutation
+ set state(value) {
+ throw new Error('do not mutate the `.state` directly');
+ }
+
+
+ get(prop?: any) {
+ // use our state getter for the clone
+ const state = this.state;
+ return state.hasOwnProperty(prop) ? state[prop] : state;
+ }
+
+ set(prop: string, value: any) {
+ // internally mutate our state
+ return this._state[prop] = value;
+ }
+
+
+ _clone(object) {
+ // simple object clone
+ return JSON.parse(JSON.stringify( object ));
+ }
+}
--- /dev/null
+
+// Angular 2
+// rc2 workaround
+import { enableDebugTools, disableDebugTools } from '@angular/platform-browser';
+import { enableProdMode, ApplicationRef } from '@angular/core';
+// Environment Providers
+let PROVIDERS = [
+ // common env directives
+];
+
+// Angular debug tools in the dev console
+// https://github.com/angular/angular/blob/86405345b781a9dc2438c0fbe3e9409245647019/TOOLS_JS.md
+let _decorateModuleRef = function identity(value) { return value; };
+
+if ('production' === ENV) {
+ // Production
+ disableDebugTools();
+ enableProdMode();
+
+ PROVIDERS = [
+ ...PROVIDERS,
+ // custom providers in production
+ ];
+
+} else {
+
+ _decorateModuleRef = (modRef: any) => {
+ const appRef = modRef.injector.get(ApplicationRef);
+ const cmpRef = appRef.components[0];
+
+ let _ng = (<any>window).ng;
+ enableDebugTools(cmpRef);
+ (<any>window).ng.probe = _ng.probe;
+ (<any>window).ng.coreTokens = _ng.coreTokens;
+ return modRef;
+ };
+
+ // Development
+ PROVIDERS = [
+ ...PROVIDERS,
+ // custom providers in development
+ ];
+
+}
+
+export const decorateModuleRef = _decorateModuleRef;
+
+export const ENV_PROVIDERS = [
+ ...PROVIDERS
+];
+++ /dev/null
-import { Injectable } from '@angular/core';
-import { Response } from '@angular/http';
-import { Observable } from 'rxjs/Observable';
-
-import { AuthHttp, AuthService } from '../shared';
-
-@Injectable()
-export class FriendService {
- private static BASE_FRIEND_URL: string = '/api/v1/pods/';
-
- constructor (private authHttp: AuthHttp, private authService: AuthService) {}
-
- makeFriends() {
- return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends')
- .map(res => res.status)
- .catch(this.handleError);
- }
-
- quitFriends() {
- return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends')
- .map(res => res.status)
- .catch(this.handleError);
- }
-
- private handleError (error: Response): Observable<number> {
- console.error(error);
- return Observable.throw(error.json().error || 'Server error');
- }
-}
--- /dev/null
+export * from './app.module';
<h3>Login</h3>
-
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-<form role="form" (ngSubmit)="login(username.value, password.value)" #loginForm="ngForm">
+<form role="form" (ngSubmit)="login()" [formGroup]="form">
<div class="form-group">
<label for="username">Username</label>
<input
- type="text" class="form-control" name="username" id="username" placeholder="Username" required
- ngControl="username" #username="ngForm"
+ type="text" class="form-control" id="username" placeholder="Username" required
+ formControlName="username"
>
- <div [hidden]="username.valid || username.pristine" class="alert alert-danger">
- Username is required
+ <div *ngIf="formErrors.username" class="alert alert-danger">
+ {{ formErrors.username }}
</div>
</div>
<label for="password">Password</label>
<input
type="password" class="form-control" name="password" id="password" placeholder="Password" required
- ngControl="password" #password="ngForm"
+ formControlName="password"
>
- <div [hidden]="password.valid || password.pristine" class="alert alert-danger">
- Password is required
+ <div *ngIf="formErrors.password" class="alert alert-danger">
+ {{ formErrors.password }}
</div>
</div>
- <input type="submit" value="Login" class="btn btn-default" [disabled]="!loginForm.form.valid">
+ <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid">
</form>
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
-import { AuthService } from '../shared';
+import { AuthService, FormReactive } from '../shared';
@Component({
selector: 'my-login',
- template: require('./login.component.html')
+ templateUrl: './login.component.html'
})
-export class LoginComponent {
+export class LoginComponent extends FormReactive implements OnInit {
error: string = null;
+ form: FormGroup;
+ formErrors = {
+ 'username': '',
+ 'password': ''
+ };
+ validationMessages = {
+ 'username': {
+ 'required': 'Username is required.',
+ },
+ 'password': {
+ 'required': 'Password is required.'
+ }
+ };
+
constructor(
private authService: AuthService,
+ private formBuilder: FormBuilder,
private router: Router
- ) {}
+ ) {
+ super();
+ }
+
+ buildForm() {
+ this.form = this.formBuilder.group({
+ username: [ '', Validators.required ],
+ password: [ '', Validators.required ],
+ });
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data));
+ }
+
+ ngOnInit() {
+ this.buildForm();
+ }
+
+ login() {
+ this.error = null;
+
+ const { username, password } = this.form.value;
- login(username: string, password: string) {
this.authService.login(username, password).subscribe(
- result => {
- this.error = null;
+ result => this.router.navigate(['/videos/list']),
- this.router.navigate(['/videos/list']);
- },
error => {
- console.error(error);
+ console.error(error.json);
- if (error.error === 'invalid_grant') {
+ if (error.json.error === 'invalid_grant') {
this.error = 'Credentials are invalid.';
} else {
- this.error = `${error.error}: ${error.error_description}`;
+ this.error = `${error.json.error}: ${error.json.error_description}`;
}
}
);
--- /dev/null
+<menu class="col-md-2 col-sm-3 col-xs-3">
+ <div class="panel-block">
+ <div id="panel-user-login" class="panel-button">
+ <span *ngIf="!isLoggedIn" >
+ <span class="hidden-xs glyphicon glyphicon-log-in"></span>
+ <a [routerLink]="['/login']">Login</a>
+ </span>
+
+ <span *ngIf="isLoggedIn">
+ <span class="hidden-xs glyphicon glyphicon-log-out"></span>
+ <a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
+ </span>
+ </div>
+
+ <div *ngIf="isLoggedIn" id="panel-user-account" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-user"></span>
+ <a [routerLink]="['/account']">My account</a>
+ </div>
+ </div>
+
+ <div class="panel-block">
+ <div id="panel-get-videos" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-list"></span>
+ <a [routerLink]="['/videos/list']">Get videos</a>
+ </div>
+
+ <div id="panel-upload-video" class="panel-button" *ngIf="isLoggedIn">
+ <span class="hidden-xs glyphicon glyphicon-cloud-upload"></span>
+ <a [routerLink]="['/videos/add']">Upload a video</a>
+ </div>
+ </div>
+
+ <div class="panel-block" *ngIf="isUserAdmin()">
+ <div id="panel-get-videos" class="panel-button">
+ <span class="hidden-xs glyphicon glyphicon-cog"></span>
+ <a [routerLink]="['/admin']">Administration</a>
+ </div>
+ </div>
+</menu>
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { AuthService, AuthStatus } from './shared';
+
+@Component({
+ selector: 'my-menu',
+ templateUrl: './menu.component.html'
+})
+export class MenuComponent implements OnInit {
+ isLoggedIn: boolean;
+
+ constructor (
+ private authService: AuthService,
+ private router: Router
+ ) {}
+
+ ngOnInit() {
+ this.isLoggedIn = this.authService.isLoggedIn();
+
+ this.authService.loginChangedSource.subscribe(
+ status => {
+ if (status === AuthStatus.LoggedIn) {
+ this.isLoggedIn = true;
+ console.log('Logged in.');
+ } else if (status === AuthStatus.LoggedOut) {
+ this.isLoggedIn = false;
+ console.log('Logged out.');
+ } else {
+ console.error('Unknown auth status: ' + status);
+ }
+ }
+ );
+ }
+
+ isUserAdmin() {
+ return this.authService.isAdmin();
+ }
+
+ logout() {
+ this.authService.logout();
+ // Redirect to home page
+ this.router.navigate(['/videos/list']);
+ }
+}
return super.request(url, options)
.catch((err) => {
if (err.status === 401) {
- return this.handleTokenExpired(err, url, options);
+ return this.handleTokenExpired(url, options);
}
return Observable.throw(err);
return this.request(url, options);
}
- post(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {};
options.method = RequestMethod.Post;
+ options.body = body;
return this.request(url, options);
}
- put(url: string, options?: RequestOptionsArgs): Observable<Response> {
+ put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
if (!options) options = {};
options.method = RequestMethod.Put;
+ options.body = body;
return this.request(url, options);
}
- private handleTokenExpired(err: Response, url: string | Request, options: RequestOptionsArgs) {
- return this.authService.refreshAccessToken().flatMap(() => {
- this.setAuthorizationHeader(options.headers);
+ private handleTokenExpired(url: string | Request, options: RequestOptionsArgs) {
+ return this.authService.refreshAccessToken()
+ .flatMap(() => {
+ this.setAuthorizationHeader(options.headers);
- return super.request(url, options);
- });
+ return super.request(url, options);
+ });
}
private setAuthorizationHeader(headers: Headers) {
-export class User {
+import { User } from '../users';
+
+export class AuthUser extends User {
private static KEYS = {
+ ID: 'id',
+ ROLE: 'role',
USERNAME: 'username'
};
+ id: string;
+ role: string;
username: string;
tokens: Tokens;
static load() {
const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
if (usernameLocalStorage) {
- return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load());
+ return new AuthUser(
+ {
+ id: localStorage.getItem(this.KEYS.ID),
+ username: localStorage.getItem(this.KEYS.USERNAME),
+ role: localStorage.getItem(this.KEYS.ROLE)
+ },
+ Tokens.load()
+ );
}
return null;
static flush() {
localStorage.removeItem(this.KEYS.USERNAME);
+ localStorage.removeItem(this.KEYS.ID);
+ localStorage.removeItem(this.KEYS.ROLE);
Tokens.flush();
}
- constructor(username: string, hash_tokens: any) {
- this.username = username;
- this.tokens = new Tokens(hash_tokens);
+ constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) {
+ super(userHash);
+ this.tokens = new Tokens(hashTokens);
}
getAccessToken() {
}
save() {
- localStorage.setItem('username', this.username);
+ localStorage.setItem(AuthUser.KEYS.ID, this.id);
+ localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
+ localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
this.tokens.save();
}
}
-// Private class used only by User
+// Private class only used by User
class Tokens {
private static KEYS = {
ACCESS_TOKEN: 'access_token',
import { Injectable } from '@angular/core';
import { Headers, Http, Response, URLSearchParams } from '@angular/http';
+import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { AuthStatus } from './auth-status.model';
-import { User } from './user.model';
+import { AuthUser } from './auth-user.model';
+import { RestExtractor } from '../rest';
@Injectable()
export class AuthService {
- private static BASE_CLIENT_URL = '/api/v1/users/client';
+ private static BASE_CLIENT_URL = '/api/v1/clients/local';
private static BASE_TOKEN_URL = '/api/v1/users/token';
+ private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me';
loginChangedSource: Observable<AuthStatus>;
private clientId: string;
private clientSecret: string;
private loginChanged: Subject<AuthStatus>;
- private user: User = null;
+ private user: AuthUser = null;
- constructor(private http: Http) {
+ constructor(
+ private http: Http,
+ private restExtractor: RestExtractor,
+ private router: Router
+ ) {
this.loginChanged = new Subject<AuthStatus>();
this.loginChangedSource = this.loginChanged.asObservable();
// Fetch the client_id/client_secret
// FIXME: save in local storage?
this.http.get(AuthService.BASE_CLIENT_URL)
- .map(res => res.json())
- .catch(this.handleError)
+ .map(this.restExtractor.extractDataGet)
+ .catch((res) => this.restExtractor.handleError(res))
.subscribe(
result => {
this.clientId = result.client_id;
console.log('Client credentials loaded.');
},
error => {
- alert(error);
+ alert(
+ `Cannot retrieve OAuth Client credentials: ${error.text}. \n` +
+ 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.'
+ );
}
);
// Return null if there is nothing to load
- this.user = User.load();
+ this.user = AuthUser.load();
}
getRefreshToken() {
return this.user.getTokenType();
}
- getUser(): User {
+ getUser(): AuthUser {
return this.user;
}
+ isAdmin() {
+ if (this.user === null) return false;
+
+ return this.user.isAdmin();
+ }
+
isLoggedIn() {
if (this.getAccessToken()) {
return true;
};
return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
- .map(res => res.json())
+ .map(this.restExtractor.extractDataGet)
.map(res => {
res.username = username;
return res;
})
+ .flatMap(res => this.fetchUserInformations(res))
.map(res => this.handleLogin(res))
- .catch(this.handleError);
+ .catch((res) => this.restExtractor.handleError(res));
}
logout() {
// TODO: make an HTTP request to revoke the tokens
this.user = null;
- User.flush();
- this.setStatus(AuthStatus.LoggedIn);
+ AuthUser.flush();
+
+ this.setStatus(AuthStatus.LoggedOut);
}
refreshAccessToken() {
};
return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options)
- .map(res => res.json())
+ .map(this.restExtractor.extractDataGet)
.map(res => this.handleRefreshToken(res))
- .catch(this.handleError);
+ .catch((res: Response) => {
+ // The refresh token is invalid?
+ if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') {
+ console.error('Cannot refresh token -> logout...');
+ this.logout();
+ this.router.navigate(['/login']);
+
+ return Observable.throw({
+ json: '',
+ text: 'You need to reconnect.'
+ });
+ }
+
+ return this.restExtractor.handleError(res);
+ });
}
- private setStatus(status: AuthStatus) {
- this.loginChanged.next(status);
+ private fetchUserInformations (obj: any) {
+ // Do not call authHttp here to avoid circular dependencies headaches
+
+ const headers = new Headers();
+ headers.set('Authorization', `Bearer ${obj.access_token}`);
+
+ return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers })
+ .map(res => res.json())
+ .map(res => {
+ obj.id = res.id;
+ obj.role = res.role;
+ return obj;
+ }
+ );
}
private handleLogin (obj: any) {
+ const id = obj.id;
const username = obj.username;
- const hash_tokens = {
+ const role = obj.role;
+ const hashTokens = {
access_token: obj.access_token,
token_type: obj.token_type,
refresh_token: obj.refresh_token
};
- this.user = new User(username, hash_tokens);
+ this.user = new AuthUser({ id, username, role }, hashTokens);
this.user.save();
this.setStatus(AuthStatus.LoggedIn);
}
- private handleError (error: Response) {
- console.error(error);
- return Observable.throw(error.json() || { error: 'Server error' });
- }
-
private handleRefreshToken (obj: any) {
this.user.refreshTokens(obj.access_token, obj.refresh_token);
this.user.save();
}
+
+ private setStatus(status: AuthStatus) {
+ this.loginChanged.next(status);
+ }
+
}
export * from './auth-http.service';
export * from './auth-status.model';
export * from './auth.service';
-export * from './user.model';
+export * from './auth-user.model';
--- /dev/null
+import { FormGroup } from '@angular/forms';
+
+export abstract class FormReactive {
+ abstract form: FormGroup;
+ abstract formErrors: Object;
+ abstract validationMessages: Object;
+
+ abstract buildForm(): void;
+
+ protected onValueChanged(data?: any) {
+ for (const field in this.formErrors) {
+ // clear previous error message (if any)
+ this.formErrors[field] = '';
+ const control = this.form.get(field);
+
+ if (control && control.dirty && !control.valid) {
+ const messages = this.validationMessages[field];
+ for (const key in control.errors) {
+ this.formErrors[field] += messages[key] + ' ';
+ }
+ }
+ }
+ }
+}
--- /dev/null
+export * from './url.validator';
+export * from './user';
+export * from './video';
--- /dev/null
+import { FormControl } from '@angular/forms';
+
+export function validateUrl(c: FormControl) {
+ let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$');
+
+ return URL_REGEXP.test(c.value) ? null : {
+ validateUrl: {
+ valid: false
+ }
+ };
+}
--- /dev/null
+import { Validators } from '@angular/forms';
+
+export const USER_USERNAME = {
+ VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ],
+ MESSAGES: {
+ 'required': 'Username is required.',
+ 'minlength': 'Username must be at least 3 characters long.',
+ 'maxlength': 'Username cannot be more than 20 characters long.'
+ }
+};
+export const USER_PASSWORD = {
+ VALIDATORS: [ Validators.required, Validators.minLength(6) ],
+ MESSAGES: {
+ 'required': 'Password is required.',
+ 'minlength': 'Password must be at least 6 characters long.',
+ }
+};
--- /dev/null
+import { Validators } from '@angular/forms';
+
+export const VIDEO_NAME = {
+ VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ],
+ MESSAGES: {
+ 'required': 'Video name is required.',
+ 'minlength': 'Video name must be at least 3 characters long.',
+ 'maxlength': 'Video name cannot be more than 50 characters long.'
+ }
+};
+export const VIDEO_DESCRIPTION = {
+ VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ],
+ MESSAGES: {
+ 'required': 'Video description is required.',
+ 'minlength': 'Video description must be at least 3 characters long.',
+ 'maxlength': 'Video description cannot be more than 250 characters long.'
+ }
+};
+
+export const VIDEO_TAGS = {
+ VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ],
+ MESSAGES: {
+ 'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.'
+ }
+};
--- /dev/null
+export * from './form-validators';
+export * from './form-reactive';
export * from './auth';
+export * from './forms';
+export * from './rest';
export * from './search';
+export * from './users';
--- /dev/null
+export * from './rest-extractor.service';
+export * from './rest-pagination';
+export * from './rest.service';
--- /dev/null
+import { Injectable } from '@angular/core';
+import { Response } from '@angular/http';
+import { Observable } from 'rxjs/Observable';
+
+export interface ResultList {
+ data: any[];
+ total: number;
+}
+
+@Injectable()
+export class RestExtractor {
+
+ constructor () { ; }
+
+ extractDataBool(res: Response) {
+ return true;
+ }
+
+ extractDataList(res: Response) {
+ const body = res.json();
+
+ const ret: ResultList = {
+ data: body.data,
+ total: body.total
+ };
+
+ return ret;
+ }
+
+ extractDataGet(res: Response) {
+ return res.json();
+ }
+
+ handleError(res: Response) {
+ let text = 'Server error: ';
+ text += res.text();
+ let json = '';
+
+ try {
+ json = res.json();
+ } catch (err) { ; }
+
+ const error = {
+ json,
+ text
+ };
+
+ console.error(error);
+
+ return Observable.throw(error);
+ }
+}
-export interface Pagination {
+export interface RestPagination {
currentPage: number;
itemsPerPage: number;
totalItems: number;
-}
+};
--- /dev/null
+import { Injectable } from '@angular/core';
+import { URLSearchParams } from '@angular/http';
+
+import { RestPagination } from './rest-pagination';
+
+@Injectable()
+export class RestService {
+
+ buildRestGetParams(pagination?: RestPagination, sort?: string) {
+ const params = new URLSearchParams();
+
+ if (pagination) {
+ const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage;
+ const count: number = pagination.itemsPerPage;
+
+ params.set('start', start.toString());
+ params.set('count', count.toString());
+ }
+
+ if (sort) {
+ params.set('sort', sort);
+ }
+
+ return params;
+ }
+
+}
import { Component, OnInit } from '@angular/core';
-
-import { DROPDOWN_DIRECTIVES} from 'ng2-bootstrap/components/dropdown';
+import { Router } from '@angular/router';
import { Search } from './search.model';
import { SearchField } from './search-field.type';
import { SearchService } from './search.service';
@Component({
- selector: 'my-search',
- template: require('./search.component.html'),
- directives: [ DROPDOWN_DIRECTIVES ]
+ selector: 'my-search',
+ templateUrl: './search.component.html'
})
export class SearchComponent implements OnInit {
value: ''
};
- constructor(private searchService: SearchService) {}
+ constructor(private searchService: SearchService, private router: Router) {}
ngOnInit() {
- // Subscribe is the search changed
+ // Subscribe if the search changed
// Usually changed by videos list component
this.searchService.updateSearch.subscribe(
newSearchCriterias => {
}
doSearch() {
+ if (this.router.url.indexOf('/videos/list') === -1) {
+ this.router.navigate([ '/videos/list' ]);
+ }
+
this.searchService.searchUpdated.next(this.searchCriterias);
}
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
+import { ReplaySubject } from 'rxjs/ReplaySubject';
import { Search } from './search.model';
constructor() {
this.updateSearch = new Subject<Search>();
- this.searchUpdated = new Subject<Search>();
+ this.searchUpdated = new ReplaySubject<Search>(1);
}
}
--- /dev/null
+export * from './user.model';
--- /dev/null
+export class User {
+ id: string;
+ username: string;
+ role: string;
+ createdDate: Date;
+
+ constructor(hash: { id: string, username: string, role: string, createdDate?: Date }) {
+ this.id = hash.id;
+ this.username = hash.username;
+ this.role = hash.role;
+
+ if (hash.createdDate) {
+ this.createdDate = hash.createdDate;
+ }
+ }
+
+ isAdmin() {
+ return this.role === 'admin';
+ }
+}
export * from './loader';
-export * from './pagination.model';
export * from './sort-field.type';
export * from './video.model';
export * from './video.service';
@Component({
selector: 'my-loader',
- styles: [ require('./loader.component.scss') ],
- template: require('./loader.component.html')
+ styleUrls: [ './loader.component.scss' ],
+ templateUrl: './loader.component.html'
})
export class LoaderComponent {
import { Injectable } from '@angular/core';
-import { Http, Response, URLSearchParams } from '@angular/http';
+import { Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
-import { Pagination } from './pagination.model';
import { Search } from '../../shared';
import { SortField } from './sort-field.type';
-import { AuthHttp, AuthService } from '../../shared';
+import { AuthHttp, AuthService, RestExtractor, RestPagination, RestService, ResultList } from '../../shared';
import { Video } from './video.model';
@Injectable()
constructor(
private authService: AuthService,
private authHttp: AuthHttp,
- private http: Http
+ private http: Http,
+ private restExtractor: RestExtractor,
+ private restService: RestService
) {}
- getVideo(id: string) {
+ getVideo(id: string): Observable<Video> {
return this.http.get(VideoService.BASE_VIDEO_URL + id)
- .map(res => <Video> res.json())
- .catch(this.handleError);
+ .map(this.restExtractor.extractDataGet)
+ .catch((res) => this.restExtractor.handleError(res));
}
- getVideos(pagination: Pagination, sort: SortField) {
- const params = this.createPaginationParams(pagination);
-
- if (sort) params.set('sort', sort);
+ getVideos(pagination: RestPagination, sort: SortField) {
+ const params = this.restService.buildRestGetParams(pagination, sort);
return this.http.get(VideoService.BASE_VIDEO_URL, { search: params })
.map(res => res.json())
.map(this.extractVideos)
- .catch(this.handleError);
+ .catch((res) => this.restExtractor.handleError(res));
}
removeVideo(id: string) {
return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
- .map(res => <number> res.status)
- .catch(this.handleError);
+ .map(this.restExtractor.extractDataBool)
+ .catch((res) => this.restExtractor.handleError(res));
}
- searchVideos(search: Search, pagination: Pagination, sort: SortField) {
- const params = this.createPaginationParams(pagination);
+ searchVideos(search: Search, pagination: RestPagination, sort: SortField) {
+ const params = this.restService.buildRestGetParams(pagination, sort);
if (search.field) params.set('field', search.field);
- if (sort) params.set('sort', sort);
return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params })
- .map(res => res.json())
+ .map(this.restExtractor.extractDataList)
.map(this.extractVideos)
- .catch(this.handleError);
- }
-
- private createPaginationParams(pagination: Pagination) {
- const params = new URLSearchParams();
- const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage;
- const count: number = pagination.itemsPerPage;
-
- params.set('start', start.toString());
- params.set('count', count.toString());
-
- return params;
+ .catch((res) => this.restExtractor.handleError(res));
}
- private extractVideos(body: any) {
- const videos_json = body.data;
- const totalVideos = body.total;
+ private extractVideos(result: ResultList) {
+ const videosJson = result.data;
+ const totalVideos = result.total;
const videos = [];
- for (const video_json of videos_json) {
- videos.push(new Video(video_json));
+ for (const videoJson of videosJson) {
+ videos.push(new Video(videoJson));
}
return { videos, totalVideos };
}
-
- private handleError(error: Response) {
- console.error(error);
- return Observable.throw(error.json().error || 'Server error');
- }
}
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
-<form novalidate (ngSubmit)="upload()" [ngFormModel]="videoForm">
+<form novalidate (ngSubmit)="upload()" [formGroup]="form">
<div class="form-group">
<label for="name">Name</label>
<input
- type="text" class="form-control" name="name" id="name"
- ngControl="name" #name="ngForm" [(ngModel)]="video.name"
+ type="text" class="form-control" id="name"
+ formControlName="name"
>
- <div [hidden]="name.valid || name.pristine" class="alert alert-warning">
- A name is required and should be between 3 and 50 characters long
+ <div *ngIf="formErrors.name" class="alert alert-danger">
+ {{ formErrors.name }}
</div>
</div>
<div class="form-group">
<label for="tags">Tags</label>
<input
- type="text" class="form-control" name="tags" id="tags"
- ngControl="tags" #tags="ngForm" [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag"
+ type="text" class="form-control" id="currentTag"
+ formControlName="currentTag" (keyup)="onTagKeyPress($event)"
>
- <div [hidden]="tags.valid || tags.pristine" class="alert alert-warning">
- A tag should be between 2 and 10 characters (alphanumeric) long
+ <div *ngIf="formErrors.currentTag" class="alert alert-danger">
+ {{ formErrors.currentTag }}
</div>
</div>
<div class="tags">
- <div class="label label-primary tag" *ngFor="let tag of video.tags">
+ <div class="label label-primary tag" *ngFor="let tag of tags">
{{ tag }}
<span class="remove" (click)="removeTag(tag)">x</span>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea
- name="description" id="description" class="form-control" placeholder="Description..."
- ngControl="description" #description="ngForm" [(ngModel)]="video.description"
+ id="description" class="form-control" placeholder="Description..."
+ formControlName="description"
>
</textarea>
- <div [hidden]="description.valid || description.pristine" class="alert alert-warning">
- A description is required and should be between 3 and 250 characters long
+ <div *ngIf="formErrors.description" class="alert alert-danger">
+ {{ formErrors.description }}
</div>
</div>
<div class="form-group">
<input
type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()"
- [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null"
+ [disabled]="!form.valid || tags.length === 0 || filename === null"
>
</div>
</form>
-import { Control, ControlGroup, Validators } from '@angular/common';
import { Component, ElementRef, OnInit } from '@angular/core';
+import { FormBuilder, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
-import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
-import { FileSelectDirective, FileUploader } from 'ng2-file-upload/ng2-file-upload';
+import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
-import { AuthService } from '../../shared';
+import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared';
@Component({
selector: 'my-videos-add',
- styles: [ require('./video-add.component.scss') ],
- template: require('./video-add.component.html'),
- directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES ],
- pipes: [ BytesPipe ]
+ styleUrls: [ './video-add.component.scss' ],
+ templateUrl: './video-add.component.html'
})
-export class VideoAddComponent implements OnInit {
- currentTag: string; // Tag the user is writing in the input
- error: string = null;
- videoForm: ControlGroup;
+export class VideoAddComponent extends FormReactive implements OnInit {
+ tags: string[] = [];
uploader: FileUploader;
- video = {
+
+ error: string = null;
+ form: FormGroup;
+ formErrors = {
name: '',
- tags: [],
- description: ''
+ description: '',
+ currentTag: ''
+ };
+ validationMessages = {
+ name: VIDEO_NAME.MESSAGES,
+ description: VIDEO_DESCRIPTION.MESSAGES,
+ currentTag: VIDEO_TAGS.MESSAGES
};
constructor(
private authService: AuthService,
private elementRef: ElementRef,
+ private formBuilder: FormBuilder,
private router: Router
- ) {}
+ ) {
+ super();
+ }
get filename() {
if (this.uploader.queue.length === 0) {
return this.uploader.queue[0].file.name;
}
- get isTagsInputDisabled () {
- return this.video.tags.length >= 3;
+ buildForm() {
+ this.form = this.formBuilder.group({
+ name: [ '', VIDEO_NAME.VALIDATORS ],
+ description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
+ currentTag: [ '', VIDEO_TAGS.VALIDATORS ]
+ });
+
+ this.form.valueChanges.subscribe(data => this.onValueChanged(data));
}
getInvalidFieldsTitle() {
let title = '';
- const nameControl = this.videoForm.controls['name'];
- const descriptionControl = this.videoForm.controls['description'];
+ const nameControl = this.form.controls['name'];
+ const descriptionControl = this.form.controls['description'];
if (!nameControl.valid) {
title += 'A name is required\n';
}
- if (this.video.tags.length === 0) {
+ if (this.tags.length === 0) {
title += 'At least one tag is required\n';
}
}
ngOnInit() {
- this.videoForm = new ControlGroup({
- name: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(50) ])),
- description: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(250) ])),
- tags: new Control('', Validators.pattern('^[a-zA-Z0-9]{2,10}$'))
- });
-
-
this.uploader = new FileUploader({
authToken: this.authService.getRequestHeaderValue(),
queueLimit: 1,
});
this.uploader.onBuildItemForm = (item, form) => {
- form.append('name', this.video.name);
- form.append('description', this.video.description);
+ const name = this.form.value['name'];
+ const description = this.form.value['description'];
+
+ form.append('name', name);
+ form.append('description', description);
- for (let i = 0; i < this.video.tags.length; i++) {
- form.append(`tags[${i}]`, this.video.tags[i]);
+ for (let i = 0; i < this.tags.length; i++) {
+ form.append(`tags[${i}]`, this.tags[i]);
}
};
+
+ this.buildForm();
}
onTagKeyPress(event: KeyboardEvent) {
+ const currentTag = this.form.value['currentTag'];
+
// Enter press
if (event.keyCode === 13) {
// Check if the tag is valid and does not already exist
if (
- this.currentTag !== '' &&
- this.videoForm.controls['tags'].valid &&
- this.video.tags.indexOf(this.currentTag) === -1
+ currentTag !== '' &&
+ this.form.controls['currentTag'].valid &&
+ this.tags.indexOf(currentTag) === -1
) {
- this.video.tags.push(this.currentTag);
- this.currentTag = '';
+ this.tags.push(currentTag);
+ this.form.patchValue({ currentTag: '' });
+
+ if (this.tags.length >= 3) {
+ this.form.get('currentTag').disable();
+ }
}
}
}
}
removeTag(tag: string) {
- this.video.tags.splice(this.video.tags.indexOf(tag), 1);
+ this.tags.splice(this.tags.indexOf(tag), 1);
}
upload() {
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
-import { AsyncPipe } from '@angular/common';
-import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router';
+import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/components/pagination';
-
import {
- LoaderComponent,
- Pagination,
SortField,
Video,
VideoService
} from '../shared';
-import { AuthService, Search, SearchField, User } from '../../shared';
-import { VideoMiniatureComponent } from './video-miniature.component';
-import { VideoSortComponent } from './video-sort.component';
+import { AuthService, AuthUser, RestPagination, Search, SearchField } from '../../shared';
import { SearchService } from '../../shared';
@Component({
selector: 'my-videos-list',
- styles: [ require('./video-list.component.scss') ],
- pipes: [ AsyncPipe ],
- template: require('./video-list.component.html'),
- directives: [ LoaderComponent, PAGINATION_DIRECTIVES, ROUTER_DIRECTIVES, VideoMiniatureComponent, VideoSortComponent ]
+ styleUrls: [ './video-list.component.scss' ],
+ templateUrl: './video-list.component.html'
})
export class VideoListComponent implements OnInit, OnDestroy {
loading: BehaviorSubject<boolean> = new BehaviorSubject(false);
- pagination: Pagination = {
+ pagination: RestPagination = {
currentPage: 1,
itemsPerPage: 9,
totalItems: null
};
sort: SortField;
- user: User = null;
+ user: AuthUser = null;
videos: Video[] = [];
private search: Search;
ngOnInit() {
if (this.authService.isLoggedIn()) {
- this.user = User.load();
+ this.user = AuthUser.load();
}
// Subscribe to route changes
// Subscribe to search changes
this.subSearch = this.searchService.searchUpdated.subscribe(search => {
this.search = search;
+ // Reset pagination
+ this.pagination.currentPage = 1;
this.navigateToNewParams();
});
this.subSearch.unsubscribe();
}
- getVideos(detectChanges = true) {
+ getVideos() {
this.loading.next(true);
this.videos = [];
this.loading.next(false);
},
- error => alert(error)
+ error => alert(error.text)
);
}
this.sort = <SortField>routeParams['sort'] || '-createdDate';
- this.pagination.currentPage = parseInt(routeParams['page']) || 1;
+ if (routeParams['page'] !== undefined) {
+ this.pagination.currentPage = parseInt(routeParams['page']);
+ } else {
+ this.pagination.currentPage = 1;
+ }
this.changeDetector.detectChanges();
}
-import { DatePipe } from '@angular/common';
import { Component, Input, Output, EventEmitter } from '@angular/core';
-import { ROUTER_DIRECTIVES } from '@angular/router';
import { SortField, Video, VideoService } from '../shared';
import { User } from '../../shared';
@Component({
selector: 'my-video-miniature',
- styles: [ require('./video-miniature.component.scss') ],
- template: require('./video-miniature.component.html'),
- directives: [ ROUTER_DIRECTIVES ],
- pipes: [ DatePipe ]
+ styleUrls: [ './video-miniature.component.scss' ],
+ templateUrl: './video-miniature.component.html'
})
export class VideoMiniatureComponent {
if (confirm('Do you really want to remove this video?')) {
this.videoService.removeVideo(id).subscribe(
status => this.removed.emit(true),
- error => alert(error)
+ error => alert(error.text)
);
}
}
@Component({
selector: 'my-video-sort',
- template: require('./video-sort.component.html')
+ templateUrl: './video-sort.component.html'
})
export class VideoSortComponent {
-import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
+import { Component, ElementRef, NgZone, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
-
-import { LoaderComponent, Video, VideoService } from '../shared';
+import { Video, VideoService } from '../shared';
import { WebTorrentService } from './webtorrent.service';
@Component({
selector: 'my-video-watch',
- template: require('./video-watch.component.html'),
- styles: [ require('./video-watch.component.scss') ],
- providers: [ WebTorrentService ],
- directives: [ LoaderComponent ],
- pipes: [ BytesPipe ]
+ templateUrl: './video-watch.component.html',
+ styleUrls: [ './video-watch.component.scss' ]
})
export class VideoWatchComponent implements OnInit, OnDestroy {
constructor(
private elementRef: ElementRef,
+ private ngZone: NgZone,
private route: ActivatedRoute,
private videoService: VideoService,
private webTorrentService: WebTorrentService
}
});
- // Refresh each second
- this.torrentInfosInterval = setInterval(() => {
- this.downloadSpeed = torrent.downloadSpeed;
- this.numPeers = torrent.numPeers;
- this.uploadSpeed = torrent.uploadSpeed;
- }, 1000);
+ this.runInProgress(torrent);
});
}
this.video = video;
this.loadVideo();
},
- error => alert(error)
+ error => alert(error.text)
);
});
}
this.error = true;
console.error('The video load seems to be abnormally long.');
}
+
+ private runInProgress(torrent: any) {
+ // Refresh each second
+ this.torrentInfosInterval = setInterval(() => {
+ this.ngZone.run(() => {
+ this.downloadSpeed = torrent.downloadSpeed;
+ this.numPeers = torrent.numPeers;
+ this.uploadSpeed = torrent.uploadSpeed;
+ });
+ }, 1000);
+ }
}
import { Component } from '@angular/core';
-import { ROUTER_DIRECTIVES } from '@angular/router';
@Component({
- template: '<router-outlet></router-outlet>',
- directives: [ ROUTER_DIRECTIVES ]
+ template: '<router-outlet></router-outlet>'
})
export class VideosComponent {
-import { RouterConfig } from '@angular/router';
+import { Routes } from '@angular/router';
import { VideoAddComponent } from './video-add';
import { VideoListComponent } from './video-list';
import { VideosComponent } from './videos.component';
import { VideoWatchComponent } from './video-watch';
-export const VideosRoutes: RouterConfig = [
+export const VideosRoutes: Routes = [
{
path: 'videos',
component: VideosComponent,
/*
* Custom Type Definitions
* When including 3rd party modules you also need to include the type definition for the module
- * if they don't provide one within the module. You can try to install it with typings
+ * if they don't provide one within the module. You can try to install it with @types
-typings install node --save
+npm install @types/node
+npm install @types/lodash
- * If you can't find the type definition in the registry we can make an ambient definition in
+ * If you can't find the type definition in the registry we can make an ambient/global definition in
* this file for now. For example
-declare module "my-module" {
- export function doesSomething(value: string): string;
+declare module 'my-module' {
+ export function doesSomething(value: string): string;
+}
+
+ * If you are using a CommonJS module that is using module.exports then you will have to write your
+ * types using export = yourObjectOrFunction with a namespace above it
+ * notice how we have to create a namespace that is equal to the function we're
+ * assigning the export to
+
+declare module 'jwt-decode' {
+ function jwtDecode(token: string): any;
+ namespace jwtDecode {}
+ export = jwtDecode;
}
*
*
declare var assert: any;
+declare var _: any;
+declare var $: any;
*
* If you're importing a module that uses Node.js modules which are CommonJS you need to import as
+ * in the files such as main.browser.ts or any file within app/
*
import * as _ from 'lodash'
- * You can include your type definitions in this file until you create one for the typings registry
- * see https://github.com/typings/registry
+ * You can include your type definitions in this file until you create one for the @types
*
*/
+// support NodeJS modules without type definitions
+declare module '*';
// Extra variables that live on Global that will be replaced by webpack DefinePlugin
declare var ENV: string;
declare var HMR: boolean;
+declare var System: SystemJS;
+
+interface SystemJS {
+ import: (path?: string) => Promise<any>;
+}
+
interface GlobalEnvironment {
ENV;
HMR;
+ SystemJS: SystemJS;
+ System: SystemJS;
}
+interface Es6PromiseLoader {
+ (id: string): (exportName?: string) => Promise<any>;
+}
+
+type FactoryEs6PromiseLoader = () => Es6PromiseLoader;
+type FactoryPromise = () => Promise<any>;
+
+type AsyncRoutes = {
+ [component: string]: Es6PromiseLoader |
+ Function |
+ FactoryEs6PromiseLoader |
+ FactoryPromise
+};
+
+
+type IdleCallbacks = Es6PromiseLoader |
+ Function |
+ FactoryEs6PromiseLoader |
+ FactoryPromise ;
+
interface WebpackModule {
hot: {
data?: any,
idle: any,
accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void;
- decline(dependencies?: string | string[]): void;
+ decline(deps?: any | string | string[]): void;
dispose(callback?: (data?: any) => void): void;
addDisposeHandler(callback?: (data?: any) => void): void;
removeDisposeHandler(callback?: (data?: any) => void): void;
};
}
+
interface WebpackRequire {
- context(file: string, flag?: boolean, exp?: RegExp): any;
+ (id: string): any;
+ (paths: string[], callback: (...modules: any[]) => void): void;
+ ensure(ids: string[], callback: (req: WebpackRequire) => void, chunkName?: string): void;
+ context(directory: string, useSubDirectories?: boolean, regExp?: RegExp): WebpackContext;
}
+interface WebpackContext extends WebpackRequire {
+ keys(): string[];
+}
interface ErrorStackTraceLimit {
stackTraceLimit: number;
}
-
// Extend typings
interface NodeRequire extends WebpackRequire {}
interface ErrorConstructor extends ErrorStackTraceLimit {}
+interface NodeRequireFunction extends Es6PromiseLoader {}
interface NodeModule extends WebpackModule {}
interface Global extends GlobalEnvironment {}
-
-
-declare namespace Reflect {
- function decorate(decorators: ClassDecorator[], target: Function): Function;
- function decorate(
- decorators: (PropertyDecorator | MethodDecorator)[],
- target: Object,
- targetKey: string | symbol,
- descriptor?: PropertyDescriptor): PropertyDescriptor;
-
- function metadata(metadataKey: any, metadataValue: any): {
- (target: Function): void;
- (target: Object, propertyKey: string | symbol): void;
- };
- function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void;
- function defineMetadata(
- metadataKey: any,
- metadataValue: any,
- target: Object,
- targetKey: string | symbol): void;
- function hasMetadata(metadataKey: any, target: Object): boolean;
- function hasMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean;
- function hasOwnMetadata(metadataKey: any, target: Object): boolean;
- function hasOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean;
- function getMetadata(metadataKey: any, target: Object): any;
- function getMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any;
- function getOwnMetadata(metadataKey: any, target: Object): any;
- function getOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any;
- function getMetadataKeys(target: Object): any[];
- function getMetadataKeys(target: Object, targetKey: string | symbol): any[];
- function getOwnMetadataKeys(target: Object): any[];
- function getOwnMetadataKeys(target: Object, targetKey: string | symbol): any[];
- function deleteMetadata(metadataKey: any, target: Object): boolean;
- function deleteMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean;
-}
-
-
-// We need this here since there is a problem with Zone.js typings
-interface Thenable<T> {
- then<U>(
- onFulfilled?: (value: T) => U | Thenable<U>,
- onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
- then<U>(
- onFulfilled?: (value: T) => U | Thenable<U>,
- onRejected?: (error: any) => void): Thenable<U>;
- catch<U>(onRejected?: (error: any) => U | Thenable<U>): Thenable<U>;
-}
+<!DOCTYPE html>
<html>
<head>
<base href="/">
-import { enableProdMode, provide } from '@angular/core';
-import {
- HTTP_PROVIDERS,
- RequestOptions,
- XHRBackend
-} from '@angular/http';
-import { bootstrap } from '@angular/platform-browser-dynamic';
-import { provideRouter } from '@angular/router';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+import { decorateModuleRef } from './app/environment';
+import { bootloader } from '@angularclass/hmr';
+/*
+ * App Module
+ * our top level module that holds all of our components
+ */
+import { AppModule } from './app';
-import { AppComponent } from './app/app.component';
-import { routes } from './app/app.routes';
-import { AuthHttp, AuthService } from './app/shared';
-
-if (process.env.ENV === 'production') {
- enableProdMode();
+/*
+ * Bootstrap our Angular app with a top level NgModule
+ */
+export function main(): Promise<any> {
+ return platformBrowserDynamic()
+ .bootstrapModule(AppModule)
+ .then(decorateModuleRef)
+ .catch(err => console.error(err));
}
-bootstrap(AppComponent, [
- HTTP_PROVIDERS,
- provide(AuthHttp, {
- useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) => {
- return new AuthHttp(backend, defaultOptions, authService);
- },
- deps: [ XHRBackend, RequestOptions, AuthService ]
- }),
- AuthService,
- provideRouter(routes)
-]);
+bootloader(main);
import 'ie-shim'; // Internet Explorer
// Prefer CoreJS over the polyfills above
-import 'core-js/es6';
+import 'core-js/es6/symbol';
+import 'core-js/es6/object';
+import 'core-js/es6/function';
+import 'core-js/es6/parse-int';
+import 'core-js/es6/parse-float';
+import 'core-js/es6/number';
+import 'core-js/es6/math';
+import 'core-js/es6/string';
+import 'core-js/es6/date';
+import 'core-js/es6/array';
+import 'core-js/es6/regexp';
+import 'core-js/es6/map';
+import 'core-js/es6/set';
+import 'core-js/es6/weak-map';
+import 'core-js/es6/weak-set';
+import 'core-js/es6/typed';
+import 'core-js/es6/reflect';
+// see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709
+// import 'core-js/es6/promise';
+
import 'core-js/es7/reflect';
-require('zone.js/dist/zone');
+import 'zone.js/dist/zone';
// Typescript emit helpers polyfill
import 'ts-helpers';
}
}
+menu {
+ @media screen and (max-width: 600px) {
+ margin-right: 3px !important;
+ padding: 3px !important;
+ min-height: 400px !important;
+ }
+
+ min-height: 600px;
+ margin-right: 20px;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+
+ .panel-block:not(:last-child) {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+ }
+
+ .panel-button {
+ margin: 8px;
+ cursor: pointer;
+ transition: margin 0.2s;
+
+ &:hover {
+ margin-left: 15px;
+ }
+
+ a {
+ color: #333333;
+ }
+ }
+
+ .glyphicon {
+ margin: 5px;
+ }
+}
+
+.table-column-id {
+ width: 200px;
+}
+
+
footer {
border-top: 1px solid rgba(0, 0, 0, 0.2);
padding-top: 10px;
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
+import '@angular/forms';
import '@angular/http';
import '@angular/router';
+import '@angularclass/hmr';
+
// RxJS
import 'rxjs/Observable';
import 'rxjs/Subject';
import 'rxjs/add/operator/catch';
+import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
- "sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
- "noImplicitAny": false,
- "noEmitHelpers": true
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ "noEmitHelpers": true,
+ "strictNullChecks": false,
+ "baseUrl": "./src",
+ "paths": [
+ ],
+ "lib": [
+ "dom",
+ "es6"
+ ],
+ "types": [
+ "node",
+ "source-map",
+ "uglify-js",
+ "webpack"
+ ]
},
+ "exclude": [
+ "node_modules",
+ "dist"
+ ],
"awesomeTypescriptLoaderOptions": {
- "forkChecker": true
+ "forkChecker": true,
+ "useWebpackText": true
},
"compileOnSave": false,
"buildOnSave": false,
- "atom": {
- "rewriteTsconfig": true
- },
- "filesGlob": [
- "**/*.ts",
- "!node_modules/**"
- ],
- "exclude": [
- "node_modules",
- "typings/main",
- "typings/main.d.ts"
- ],
- "files": [
- "src/app/app.component.ts",
- "src/app/app.routes.ts",
- "src/app/friends/friend.service.ts",
- "src/app/friends/index.ts",
- "src/app/login/index.ts",
- "src/app/login/login.component.ts",
- "src/app/login/login.routes.ts",
- "src/app/shared/auth/auth-http.service.ts",
- "src/app/shared/auth/auth-status.model.ts",
- "src/app/shared/auth/auth.service.ts",
- "src/app/shared/auth/index.ts",
- "src/app/shared/auth/user.model.ts",
- "src/app/shared/index.ts",
- "src/app/shared/search/index.ts",
- "src/app/shared/search/search-field.type.ts",
- "src/app/shared/search/search.component.ts",
- "src/app/shared/search/search.model.ts",
- "src/app/shared/search/search.service.ts",
- "src/app/videos/index.ts",
- "src/app/videos/shared/index.ts",
- "src/app/videos/shared/loader/index.ts",
- "src/app/videos/shared/loader/loader.component.ts",
- "src/app/videos/shared/pagination.model.ts",
- "src/app/videos/shared/sort-field.type.ts",
- "src/app/videos/shared/video.model.ts",
- "src/app/videos/shared/video.service.ts",
- "src/app/videos/video-add/index.ts",
- "src/app/videos/video-add/video-add.component.ts",
- "src/app/videos/video-list/index.ts",
- "src/app/videos/video-list/video-list.component.ts",
- "src/app/videos/video-list/video-miniature.component.ts",
- "src/app/videos/video-list/video-sort.component.ts",
- "src/app/videos/video-watch/index.ts",
- "src/app/videos/video-watch/video-watch.component.ts",
- "src/app/videos/video-watch/webtorrent.service.ts",
- "src/app/videos/videos.component.ts",
- "src/app/videos/videos.routes.ts",
- "src/custom-typings.d.ts",
- "src/main.ts",
- "src/polyfills.ts",
- "src/vendor.ts",
- "typings/globals/es6-shim/index.d.ts",
- "typings/globals/jasmine/index.d.ts",
- "typings/globals/node/index.d.ts",
- "typings/index.d.ts"
- ]
+ "atom": { "rewriteTsconfig": false }
}
+++ /dev/null
-{
- "globalDependencies": {
- "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
- "jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
- "node": "registry:dt/node#4.0.0+20160509154515"
- }
-}
switch (process.env.NODE_ENV) {
case 'prod':
case 'production':
- module.exports = require('./config/webpack.prod')
+ module.exports = require('./config/webpack.prod')({env: 'production'})
break
case 'test':
case 'testing':
- module.exports = require('./config/webpack.test')
+ module.exports = require('./config/webpack.test')({env: 'test'})
break
case 'dev':
case 'development':
default:
- module.exports = require('./config/webpack.dev')
+ module.exports = require('./config/webpack.dev')({env: 'development'})
}
thumbnails: 'thumbnails/'
torrents: 'torrents/'
-network:
- friends: []
-
electron:
debug: false
+# Correspond to your reverse proxy "listen" configuration
webserver:
https: false
host: 'example.com'
port: 80
+
+database:
+ suffix: '-prod'
logs: 'test1/logs/'
thumbnails: 'test1/thumbnails/'
torrents: 'test1/torrents/'
-
-network:
- friends:
- - 'http://localhost:9002'
logs: 'test2/logs/'
thumbnails: 'test2/thumbnails/'
torrents: 'test2/torrents/'
-
-network:
- friends:
- - 'http://localhost:9003'
logs: 'test3/logs/'
thumbnails: 'test3/thumbnails/'
torrents: 'test3/torrents/'
-
-network:
- friends:
- - 'http://localhost:9001'
logs: 'test4/logs/'
thumbnails: 'test4/thumbnails/'
torrents: 'test4/torrents/'
-
-network:
- friends:
- - 'http://localhost:9002'
logs: 'test5/logs/'
thumbnails: 'test5/thumbnails/'
torrents: 'test5/torrents/'
-
-network:
- friends:
- - 'http://localhost:9001'
- - 'http://localhost:9004'
logs: 'test6/logs/'
thumbnails: 'test6/thumbnails/'
torrents: 'test6/torrents/'
-
-network:
- friends:
- - 'http://localhost:9001'
- - 'http://localhost:9002'
- - 'http://localhost:9003'
},
"dependencies": {
"async": "^2.0.0",
+ "bcrypt": "^0.8.7",
"bittorrent-tracker": "^8.0.0",
"body-parser": "^1.12.4",
"concurrently": "^2.0.0",
"request": "^2.57.0",
"request-replay": "^1.0.2",
"scripty": "^1.5.0",
- "segfault-handler": "^1.0.0",
"ursa": "^0.9.1",
"winston": "^2.1.1",
"ws": "^1.1.1"
"devDependencies": {
"chai": "^3.3.0",
"commander": "^2.9.0",
- "mocha": "^2.3.3",
- "standard": "^7.0.1",
- "supertest": "^1.1.0"
+ "mocha": "^3.0.1",
+ "standard": "^8.0.0",
+ "supertest": "^2.0.0"
},
"standard": {
"ignore": [
cd client || exit -1
-npm run webpack -- --config config/webpack.prod.js --progress --profile --colors --display-error-details --display-cached --bail
+npm run webpack -- --config config/webpack.prod.js --progress --profile --bail
// ----------- PeerTube modules -----------
const customValidators = require('./server/helpers/custom-validators')
const installer = require('./server/initializers/installer')
+const migrator = require('./server/initializers/migrator')
const mongoose = require('mongoose')
const routes = require('./server/controllers')
const Request = mongoose.model('Request')
// For the logger
app.use(morgan('combined', { stream: logger.stream }))
// For body requests
-app.use(bodyParser.json())
+app.use(bodyParser.json({ limit: '500kb' }))
app.use(bodyParser.urlencoded({ extended: false }))
// Validate some params for the API
app.use(expressValidator({
- customValidators: customValidators
+ customValidators: Object.assign(
+ {},
+ customValidators.misc,
+ customValidators.pods,
+ customValidators.users,
+ customValidators.videos
+ )
}))
// ----------- Views, routes and static files -----------
-// Catch sefaults
-require('segfault-handler').registerHandler()
-
// API routes
const apiRoute = '/api/' + constants.API_VERSION
app.use(apiRoute, routes.api)
installer.installApplication(function (err) {
if (err) throw err
- // ----------- Make the server listening -----------
- server.listen(port, function () {
- // Activate the pool requests
- Request.activate()
+ // Run the migration scripts if needed
+ migrator.migrate(function (err) {
+ if (err) throw err
+
+ // ----------- Make the server listening -----------
+ server.listen(port, function () {
+ // Activate the pool requests
+ Request.activate()
- logger.info('Seeded all the videos')
- logger.info('Server listening on port %d', port)
- app.emit('ready')
+ logger.info('Seeded all the videos')
+ logger.info('Server listening on port %d', port)
+ app.emit('ready')
+ })
})
})
--- /dev/null
+'use strict'
+
+const express = require('express')
+const mongoose = require('mongoose')
+
+const constants = require('../../../initializers/constants')
+
+const Client = mongoose.model('OAuthClient')
+
+const router = express.Router()
+
+router.get('/local', getLocalClient)
+
+// Get the client credentials for the PeerTube front end
+function getLocalClient (req, res, next) {
+ const serverHost = constants.CONFIG.WEBSERVER.HOST
+ const serverPort = constants.CONFIG.WEBSERVER.PORT
+ let headerHostShouldBe = serverHost
+ if (serverPort !== 80 && serverPort !== 443) {
+ headerHostShouldBe += ':' + serverPort
+ }
+
+ // Don't make this check if this is a test instance
+ if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) {
+ return res.type('json').status(403).end()
+ }
+
+ Client.loadFirstClient(function (err, client) {
+ if (err) return next(err)
+ if (!client) return next(new Error('No client available.'))
+
+ res.json({
+ client_id: client._id,
+ client_secret: client.clientSecret
+ })
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
const router = express.Router()
+const clientsController = require('./clients')
const podsController = require('./pods')
const remoteController = require('./remote')
+const requestsController = require('./requests')
const usersController = require('./users')
const videosController = require('./videos')
+router.use('/clients', clientsController)
router.use('/pods', podsController)
router.use('/remote', remoteController)
+router.use('/requests', requestsController)
router.use('/users', usersController)
router.use('/videos', videosController)
router.use('/*', badRequest)
const logger = require('../../../helpers/logger')
const friends = require('../../../lib/friends')
const middlewares = require('../../../middlewares')
+const admin = middlewares.admin
const oAuth = middlewares.oauth
+const podsMiddleware = middlewares.pods
+const checkSignature = middlewares.secure.checkSignature
const validators = middlewares.validators.pods
const signatureValidator = middlewares.validators.remote.signature
const Pod = mongoose.model('Pod')
const Video = mongoose.model('Video')
-router.get('/', listPodsUrl)
-router.post('/', validators.podsAdd, addPods)
-router.get('/makefriends', oAuth.authenticate, validators.makeFriends, makeFriends)
-router.get('/quitfriends', oAuth.authenticate, quitFriends)
+router.get('/', listPods)
+router.post('/',
+ validators.podsAdd,
+ podsMiddleware.setBodyUrlPort,
+ addPods
+)
+router.post('/makefriends',
+ oAuth.authenticate,
+ admin.ensureIsAdmin,
+ validators.makeFriends,
+ podsMiddleware.setBodyUrlsPort,
+ makeFriends
+)
+router.get('/quitfriends',
+ oAuth.authenticate,
+ admin.ensureIsAdmin,
+ quitFriends
+)
// Post because this is a secured request
-router.post('/remove', signatureValidator, removePods)
+router.post('/remove',
+ signatureValidator,
+ checkSignature,
+ removePods
+)
// ---------------------------------------------------------------------------
})
}
-function listPodsUrl (req, res, next) {
- Pod.listOnlyUrls(function (err, podsUrlList) {
+function listPods (req, res, next) {
+ Pod.list(function (err, podsUrlList) {
if (err) return next(err)
- res.json(podsUrlList)
+ res.json(getFormatedPods(podsUrlList))
})
}
function makeFriends (req, res, next) {
- friends.makeFriends(function (err) {
- if (err) return next(err)
+ const urls = req.body.urls
- res.type('json').status(204).end()
+ friends.makeFriends(urls, function (err) {
+ if (err) {
+ logger.error('Could not make friends.', { error: err })
+ return
+ }
+
+ logger.info('Made friends!')
})
+
+ res.type('json').status(204).end()
}
function removePods (req, res, next) {
res.type('json').status(204).end()
})
}
+
+// ---------------------------------------------------------------------------
+
+function getFormatedPods (pods) {
+ const formatedPods = []
+
+ pods.forEach(function (pod) {
+ formatedPods.push(pod.toFormatedJSON())
+ })
+
+ return formatedPods
+}
router.post('/videos',
validators.signature,
validators.dataToDecrypt,
+ secureMiddleware.checkSignature,
secureMiddleware.decryptBody,
validators.remoteVideos,
remoteVideos
--- /dev/null
+'use strict'
+
+const express = require('express')
+const mongoose = require('mongoose')
+
+const constants = require('../../../initializers/constants')
+const middlewares = require('../../../middlewares')
+const admin = middlewares.admin
+const oAuth = middlewares.oauth
+
+const Request = mongoose.model('Request')
+
+const router = express.Router()
+
+router.get('/stats',
+ oAuth.authenticate,
+ admin.ensureIsAdmin,
+ getStatsRequests
+)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function getStatsRequests (req, res, next) {
+ Request.list(function (err, requests) {
+ if (err) return next(err)
+
+ return res.json({
+ requests: requests,
+ maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL,
+ remainingMilliSeconds: Request.remainingMilliSeconds(),
+ milliSecondsInterval: constants.REQUESTS_INTERVAL
+ })
+ })
+}
'use strict'
-const config = require('config')
-const mongoose = require('mongoose')
+const each = require('async/each')
const express = require('express')
+const mongoose = require('mongoose')
+const waterfall = require('async/waterfall')
-const oAuth = require('../../../middlewares').oauth
+const constants = require('../../../initializers/constants')
+const friends = require('../../../lib/friends')
+const logger = require('../../../helpers/logger')
+const middlewares = require('../../../middlewares')
+const admin = middlewares.admin
+const oAuth = middlewares.oauth
+const pagination = middlewares.pagination
+const sort = middlewares.sort
+const validatorsPagination = middlewares.validators.pagination
+const validatorsSort = middlewares.validators.sort
+const validatorsUsers = middlewares.validators.users
-const Client = mongoose.model('OAuthClient')
+const User = mongoose.model('User')
+const Video = mongoose.model('Video')
const router = express.Router()
-router.get('/client', getAngularClient)
+router.get('/me', oAuth.authenticate, getUserInformation)
+
+router.get('/',
+ validatorsPagination.pagination,
+ validatorsSort.usersSort,
+ sort.setUsersSort,
+ pagination.setPagination,
+ listUsers
+)
+
+router.post('/',
+ oAuth.authenticate,
+ admin.ensureIsAdmin,
+ validatorsUsers.usersAdd,
+ createUser
+)
+
+router.put('/:id',
+ oAuth.authenticate,
+ validatorsUsers.usersUpdate,
+ updateUser
+)
+
+router.delete('/:id',
+ oAuth.authenticate,
+ admin.ensureIsAdmin,
+ validatorsUsers.usersRemove,
+ removeUser
+)
+
router.post('/token', oAuth.token, success)
-// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route
+// TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-function getAngularClient (req, res, next) {
- const serverHost = config.get('webserver.host')
- const serverPort = config.get('webserver.port')
- let headerHostShouldBe = serverHost
- if (serverPort !== 80 && serverPort !== 443) {
- headerHostShouldBe += ':' + serverPort
- }
+function createUser (req, res, next) {
+ const user = new User({
+ username: req.body.username,
+ password: req.body.password,
+ role: constants.USER_ROLES.USER
+ })
- // Don't make this check if this is a test instance
- if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) {
- return res.type('json').status(403).end()
- }
+ user.save(function (err, createdUser) {
+ if (err) return next(err)
- Client.loadFirstClient(function (err, client) {
+ return res.type('json').status(204).end()
+ })
+}
+
+function getUserInformation (req, res, next) {
+ User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
if (err) return next(err)
- if (!client) return next(new Error('No client available.'))
- res.json({
- client_id: client._id,
- client_secret: client.clientSecret
+ return res.json(user.toFormatedJSON())
+ })
+}
+
+function listUsers (req, res, next) {
+ User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
+ if (err) return next(err)
+
+ res.json(getFormatedUsers(usersList, usersTotal))
+ })
+}
+
+function removeUser (req, res, next) {
+ waterfall([
+ function getUser (callback) {
+ User.loadById(req.params.id, callback)
+ },
+
+ function getVideos (user, callback) {
+ Video.listOwnedByAuthor(user.username, function (err, videos) {
+ return callback(err, user, videos)
+ })
+ },
+
+ function removeVideosFromDB (user, videos, callback) {
+ each(videos, function (video, callbackEach) {
+ video.remove(callbackEach)
+ }, function (err) {
+ return callback(err, user, videos)
+ })
+ },
+
+ function sendInformationToFriends (user, videos, callback) {
+ videos.forEach(function (video) {
+ const params = {
+ name: video.name,
+ magnetUri: video.magnetUri
+ }
+
+ friends.removeVideoToFriends(params)
+ })
+
+ return callback(null, user)
+ },
+
+ function removeUserFromDB (user, callback) {
+ user.remove(callback)
+ }
+ ], function andFinally (err) {
+ if (err) {
+ logger.error('Errors when removed the user.', { error: err })
+ return next(err)
+ }
+
+ return res.sendStatus(204)
+ })
+}
+
+function updateUser (req, res, next) {
+ User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
+ if (err) return next(err)
+
+ user.password = req.body.password
+ user.save(function (err) {
+ if (err) return next(err)
+
+ return res.sendStatus(204)
})
})
}
function success (req, res, next) {
res.end()
}
+
+// ---------------------------------------------------------------------------
+
+function getFormatedUsers (users, usersTotal) {
+ const formatedUsers = []
+
+ users.forEach(function (user) {
+ formatedUsers.push(user.toFormatedJSON())
+ })
+
+ return {
+ total: usersTotal,
+ data: formatedUsers
+ }
+}
'use strict'
-const config = require('config')
const express = require('express')
const mongoose = require('mongoose')
const multer = require('multer')
const waterfall = require('async/waterfall')
+const constants = require('../../../initializers/constants')
const logger = require('../../../helpers/logger')
const friends = require('../../../lib/friends')
const middlewares = require('../../../middlewares')
const utils = require('../../../helpers/utils')
const router = express.Router()
-const uploads = config.get('storage.uploads')
const Video = mongoose.model('Video')
// multer configuration
const storage = multer.diskStorage({
destination: function (req, file, cb) {
- cb(null, uploads)
+ cb(null, constants.CONFIG.STORAGE.UPLOAD_DIR)
},
filename: function (req, file, cb) {
}
function listVideos (req, res, next) {
- Video.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
+ Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
if (err) return next(err)
res.json(getFormatedVideos(videosList, videosTotal))
--- /dev/null
+'use strict'
+
+const miscValidators = require('./misc')
+const podsValidators = require('./pods')
+const usersValidators = require('./users')
+const videosValidators = require('./videos')
+
+const validators = {
+ misc: miscValidators,
+ pods: podsValidators,
+ users: usersValidators,
+ videos: videosValidators
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validators
--- /dev/null
+'use strict'
+
+const miscValidators = {
+ exists,
+ isArray
+}
+
+function exists (value) {
+ return value !== undefined && value !== null
+}
+
+function isArray (value) {
+ return Array.isArray(value)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = miscValidators
--- /dev/null
+'use strict'
+
+const validator = require('express-validator').validator
+
+const miscValidators = require('./misc')
+
+const podsValidators = {
+ isEachUniqueUrlValid
+}
+
+function isEachUniqueUrlValid (urls) {
+ return miscValidators.isArray(urls) &&
+ urls.length !== 0 &&
+ urls.every(function (url) {
+ return validator.isURL(url) && urls.indexOf(url) === urls.lastIndexOf(url)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = podsValidators
--- /dev/null
+'use strict'
+
+const validator = require('express-validator').validator
+const values = require('lodash/values')
+
+const constants = require('../../initializers/constants')
+const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS
+
+const usersValidators = {
+ isUserPasswordValid,
+ isUserRoleValid,
+ isUserUsernameValid
+}
+
+function isUserPasswordValid (value) {
+ return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD)
+}
+
+function isUserRoleValid (value) {
+ return values(constants.USER_ROLES).indexOf(value) !== -1
+}
+
+function isUserUsernameValid (value) {
+ const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max
+ const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min
+ return validator.matches(value, new RegExp(`^[a-zA-Z0-9._]{${min},${max}}$`))
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = usersValidators
const validator = require('express-validator').validator
-const constants = require('../initializers/constants')
-const VIDEOS_CONSTRAINTS_FIELDS = constants.VIDEOS_CONSTRAINTS_FIELDS
-
-const customValidators = {
- exists: exists,
- isEachRemoteVideosValid: isEachRemoteVideosValid,
- isArray: isArray,
- isVideoAuthorValid: isVideoAuthorValid,
- isVideoDateValid: isVideoDateValid,
- isVideoDescriptionValid: isVideoDescriptionValid,
- isVideoDurationValid: isVideoDurationValid,
- isVideoMagnetUriValid: isVideoMagnetUriValid,
- isVideoNameValid: isVideoNameValid,
- isVideoPodUrlValid: isVideoPodUrlValid,
- isVideoTagsValid: isVideoTagsValid,
- isVideoThumbnailValid: isVideoThumbnailValid,
- isVideoThumbnail64Valid: isVideoThumbnail64Valid
-}
-
-function exists (value) {
- return value !== undefined && value !== null
+const constants = require('../../initializers/constants')
+const usersValidators = require('./users')
+const miscValidators = require('./misc')
+const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS
+
+const videosValidators = {
+ isEachRemoteVideosValid,
+ isVideoAuthorValid,
+ isVideoDateValid,
+ isVideoDescriptionValid,
+ isVideoDurationValid,
+ isVideoMagnetUriValid,
+ isVideoNameValid,
+ isVideoPodUrlValid,
+ isVideoTagsValid,
+ isVideoThumbnailValid,
+ isVideoThumbnail64Valid
}
function isEachRemoteVideosValid (requests) {
- return requests.every(function (request) {
- const video = request.data
- return (
- isRequestTypeAddValid(request.type) &&
- isVideoAuthorValid(video.author) &&
- isVideoDateValid(video.createdDate) &&
- isVideoDescriptionValid(video.description) &&
- isVideoDurationValid(video.duration) &&
- isVideoMagnetUriValid(video.magnetUri) &&
- isVideoNameValid(video.name) &&
- isVideoPodUrlValid(video.podUrl) &&
- isVideoTagsValid(video.tags) &&
- isVideoThumbnail64Valid(video.thumbnailBase64)
- ) ||
- (
- isRequestTypeRemoveValid(request.type) &&
- isVideoNameValid(video.name) &&
- isVideoMagnetUriValid(video.magnetUri)
- )
- })
-}
-
-function isArray (value) {
- return Array.isArray(value)
-}
-
-function isRequestTypeAddValid (value) {
- return value === 'add'
-}
-
-function isRequestTypeRemoveValid (value) {
- return value === 'remove'
+ return miscValidators.isArray(requests) &&
+ requests.every(function (request) {
+ const video = request.data
+ return (
+ isRequestTypeAddValid(request.type) &&
+ isVideoAuthorValid(video.author) &&
+ isVideoDateValid(video.createdDate) &&
+ isVideoDescriptionValid(video.description) &&
+ isVideoDurationValid(video.duration) &&
+ isVideoMagnetUriValid(video.magnetUri) &&
+ isVideoNameValid(video.name) &&
+ isVideoPodUrlValid(video.podUrl) &&
+ isVideoTagsValid(video.tags) &&
+ isVideoThumbnail64Valid(video.thumbnailBase64)
+ ) ||
+ (
+ isRequestTypeRemoveValid(request.type) &&
+ isVideoNameValid(video.name) &&
+ isVideoMagnetUriValid(video.magnetUri)
+ )
+ })
}
function isVideoAuthorValid (value) {
- return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.AUTHOR)
+ return usersValidators.isUserUsernameValid(value)
}
function isVideoDateValid (value) {
}
function isVideoTagsValid (tags) {
- return isArray(tags) &&
+ return miscValidators.isArray(tags) &&
validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) &&
tags.every(function (tag) {
return validator.isAlphanumeric(tag) &&
// ---------------------------------------------------------------------------
-module.exports = customValidators
+module.exports = videosValidators
// ---------------------------------------------------------------------------
+
+function isRequestTypeAddValid (value) {
+ return value === 'add'
+}
+
+function isRequestTypeRemoveValid (value) {
+ return value === 'remove'
+}
// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
'use strict'
-const config = require('config')
const mkdirp = require('mkdirp')
const path = require('path')
const winston = require('winston')
winston.emitErrs = true
-const logDir = path.join(__dirname, '..', '..', config.get('storage.logs'))
-const label = config.get('webserver.host') + ':' + config.get('webserver.port')
+const constants = require('../initializers/constants')
+
+const label = constants.CONFIG.WEBSERVER.HOST + ':' + constants.CONFIG.WEBSERVER.PORT
// Create the directory if it does not exist
-mkdirp.sync(logDir)
+mkdirp.sync(constants.CONFIG.STORAGE.LOG_DIR)
const logger = new winston.Logger({
transports: [
new winston.transports.File({
level: 'debug',
- filename: path.join(logDir, 'all-logs.log'),
+ filename: path.join(constants.CONFIG.STORAGE.LOG_DIR, 'all-logs.log'),
handleExceptions: true,
json: true,
maxsize: 5242880,
'use strict'
-const config = require('config')
+const bcrypt = require('bcrypt')
const crypto = require('crypto')
const fs = require('fs')
const openssl = require('openssl-wrapper')
-const path = require('path')
const ursa = require('ursa')
+const constants = require('../initializers/constants')
const logger = require('./logger')
-const certDir = path.join(__dirname, '..', '..', config.get('storage.certs'))
const algorithm = 'aes-256-ctr'
const peertubeCrypto = {
- checkSignature: checkSignature,
- createCertsIfNotExist: createCertsIfNotExist,
- decrypt: decrypt,
- encrypt: encrypt,
- getCertDir: getCertDir,
- sign: sign
+ checkSignature,
+ comparePassword,
+ createCertsIfNotExist,
+ cryptPassword,
+ decrypt,
+ encrypt,
+ sign
}
function checkSignature (publicKey, rawData, hexSignature) {
return isValid
}
+function comparePassword (plainPassword, hashPassword, callback) {
+ bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) {
+ if (err) return callback(err)
+
+ return callback(null, isPasswordMatch)
+ })
+}
+
function createCertsIfNotExist (callback) {
certsExist(function (exist) {
if (exist === true) {
})
}
+function cryptPassword (password, callback) {
+ bcrypt.genSalt(constants.BCRYPT_SALT_SIZE, function (err, salt) {
+ if (err) return callback(err)
+
+ bcrypt.hash(password, salt, function (err, hash) {
+ return callback(err, hash)
+ })
+ })
+}
+
function decrypt (key, data, callback) {
- fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
+ fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (err, file) {
if (err) return callback(err)
const myPrivateKey = ursa.createPrivateKey(file)
})
}
-function getCertDir () {
- return certDir
-}
-
function sign (data) {
- const myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
+ const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem'))
const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
return signature
// ---------------------------------------------------------------------------
function certsExist (callback) {
- fs.exists(certDir + 'peertube.key.pem', function (exists) {
+ fs.exists(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (exists) {
return callback(exists)
})
}
}
logger.info('Generating a RSA key...')
- openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
+
+ let options = {
+ 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem',
+ '2048': false
+ }
+ openssl.exec('genrsa', options, function (err) {
if (err) {
logger.error('Cannot create private key on this pod.')
return callback(err)
}
logger.info('RSA key generated.')
+ options = {
+ 'in': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem',
+ 'pubout': true,
+ 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub'
+ }
logger.info('Manage public key...')
- openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
+ openssl.exec('rsa', options, function (err) {
if (err) {
logger.error('Cannot create public key on this pod.')
return callback(err)
'use strict'
-const config = require('config')
const replay = require('request-replay')
const request = require('request')
const constants = require('../initializers/constants')
const peertubeCrypto = require('./peertube-crypto')
-const http = config.get('webserver.https') ? 'https' : 'http'
-const host = config.get('webserver.host')
-const port = config.get('webserver.port')
-
const requests = {
- makeRetryRequest: makeRetryRequest,
- makeSecureRequest: makeSecureRequest
+ makeRetryRequest,
+ makeSecureRequest
}
function makeRetryRequest (params, callback) {
}
function makeSecureRequest (params, callback) {
- const myUrl = http + '://' + host + ':' + port
-
const requestParams = {
url: params.toPod.url + params.path
}
// Add signature if it is specified in the params
if (params.sign === true) {
requestParams.json.signature = {
- url: myUrl,
- signature: peertubeCrypto.sign(myUrl)
+ url: constants.CONFIG.WEBSERVER.URL,
+ signature: peertubeCrypto.sign(constants.CONFIG.WEBSERVER.URL)
}
}
const logger = require('./logger')
const utils = {
- cleanForExit: cleanForExit,
- generateRandomString: generateRandomString
+ cleanForExit,
+ generateRandomString
}
function generateRandomString (size, callback) {
const User = mongoose.model('User')
const checker = {
- checkConfig: checkConfig,
- clientsExist: clientsExist,
- usersExist: usersExist
+ checkConfig,
+ clientsExist,
+ usersExist
}
// Check the config files
const required = [ 'listen.port',
'webserver.https', 'webserver.host', 'webserver.port',
'database.host', 'database.port', 'database.suffix',
- 'storage.certs', 'storage.uploads', 'storage.logs',
- 'network.friends', 'electron.debug' ]
+ 'storage.certs', 'storage.uploads', 'storage.logs', 'storage.thumbnails',
+ 'electron.debug' ]
const miss = []
for (const key of required) {
}
function usersExist (callback) {
- User.list(function (err, users) {
+ User.countTotal(function (err, totalUsers) {
if (err) return callback(err)
- return callback(null, users.length !== 0)
+ return callback(null, totalUsers !== 0)
})
}
'use strict'
-// API version of our pod
+const config = require('config')
+const path = require('path')
+
+// ---------------------------------------------------------------------------
+
+// API version
const API_VERSION = 'v1'
-// Score a pod has when we create it as a friend
-const FRIEND_SCORE = {
- BASE: 100,
- MAX: 1000
+// Number of results by default for the pagination
+const PAGINATION_COUNT_DEFAULT = 15
+
+// Sortable columns per schema
+const SEARCHABLE_COLUMNS = {
+ VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ]
}
-// Time to wait between requests to the friends (10 min)
-let INTERVAL = 600000
+// Sortable columns per schema
+const SORTABLE_COLUMNS = {
+ USERS: [ 'username', '-username', 'createdDate', '-createdDate' ],
+ VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ]
+}
const OAUTH_LIFETIME = {
ACCESS_TOKEN: 3600 * 4, // 4 hours
REFRESH_TOKEN: 1209600 // 2 weeks
}
-// Number of results by default for the pagination
-const PAGINATION_COUNT_DEFAULT = 15
+// ---------------------------------------------------------------------------
+
+const CONFIG = {
+ DATABASE: {
+ DBNAME: 'peertube' + config.get('database.suffix'),
+ HOST: config.get('database.host'),
+ PORT: config.get('database.port')
+ },
+ ELECTRON: {
+ DEBUG: config.get('electron.debug')
+ },
+ STORAGE: {
+ CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')),
+ LOG_DIR: path.join(__dirname, '..', '..', config.get('storage.logs')),
+ UPLOAD_DIR: path.join(__dirname, '..', '..', config.get('storage.uploads')),
+ THUMBNAILS_DIR: path.join(__dirname, '..', '..', config.get('storage.thumbnails'))
+ },
+ WEBSERVER: {
+ SCHEME: config.get('webserver.https') === true ? 'https' : 'http',
+ HOST: config.get('webserver.host'),
+ PORT: config.get('webserver.port')
+ }
+}
+CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOST + ':' + CONFIG.WEBSERVER.PORT
+
+// ---------------------------------------------------------------------------
+
+const CONSTRAINTS_FIELDS = {
+ USERS: {
+ USERNAME: { min: 3, max: 20 }, // Length
+ PASSWORD: { min: 6, max: 255 } // Length
+ },
+ VIDEOS: {
+ NAME: { min: 3, max: 50 }, // Length
+ DESCRIPTION: { min: 3, max: 250 }, // Length
+ MAGNET_URI: { min: 10 }, // Length
+ DURATION: { min: 1, max: 7200 }, // Number
+ TAGS: { min: 1, max: 3 }, // Number of total tags
+ TAG: { min: 2, max: 10 }, // Length
+ THUMBNAIL: { min: 2, max: 30 },
+ THUMBNAIL64: { min: 0, max: 20000 } // Bytes
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+// Score a pod has when we create it as a friend
+const FRIEND_SCORE = {
+ BASE: 100,
+ MAX: 1000
+}
+
+// ---------------------------------------------------------------------------
+
+const MONGO_MIGRATION_SCRIPTS = [
+ {
+ script: '0005-create-application',
+ version: 5
+ },
+ {
+ script: '0010-users-password',
+ version: 10
+ },
+ {
+ script: '0015-admin-role',
+ version: 15
+ }
+]
+const LAST_MONGO_SCHEMA_VERSION = 15
+
+// ---------------------------------------------------------------------------
// Number of points we add/remove from a friend after a successful/bad request
const PODS_SCORE = {
BONUS: 10
}
+// Time to wait between requests to the friends (10 min)
+let REQUESTS_INTERVAL = 600000
+
// Number of requests in parallel we can make
const REQUESTS_IN_PARALLEL = 10
-// How many requests we put in request (request scheduler)
+// How many requests we put in request
const REQUESTS_LIMIT = 10
// Number of requests to retry for replay requests module
const RETRY_REQUESTS = 5
-// Sortable columns per schema
-const SEARCHABLE_COLUMNS = {
- VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ]
-}
-
-// Seeds in parallel we send to electron when "seed all"
-// Once a video is in seeding state we seed another video etc
-const SEEDS_IN_PARALLEL = 3
+// ---------------------------------------------------------------------------
-// Sortable columns per schema
-const SORTABLE_COLUMNS = {
- VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ]
-}
+// Password encryption
+const BCRYPT_SALT_SIZE = 10
// Express static paths (router)
const STATIC_PATHS = {
// Videos thumbnail size
const THUMBNAILS_SIZE = '200x110'
-const VIDEOS_CONSTRAINTS_FIELDS = {
- NAME: { min: 3, max: 50 }, // Length
- DESCRIPTION: { min: 3, max: 250 }, // Length
- MAGNET_URI: { min: 10 }, // Length
- DURATION: { min: 1, max: 7200 }, // Number
- AUTHOR: { min: 3, max: 20 }, // Length
- TAGS: { min: 1, max: 3 }, // Number of total tags
- TAG: { min: 2, max: 10 }, // Length
- THUMBNAIL: { min: 2, max: 30 },
- THUMBNAIL64: { min: 0, max: 20000 } // Bytes
+const USER_ROLES = {
+ ADMIN: 'admin',
+ USER: 'user'
}
+// Seeds in parallel we send to electron when "seed all"
+// Once a video is in seeding state we seed another video etc
+const SEEDS_IN_PARALLEL = 3
+
+// ---------------------------------------------------------------------------
+
// Special constants for a test instance
if (isTestInstance() === true) {
+ CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14
FRIEND_SCORE.BASE = 20
- INTERVAL = 10000
- VIDEOS_CONSTRAINTS_FIELDS.DURATION.max = 14
+ REQUESTS_INTERVAL = 10000
}
// ---------------------------------------------------------------------------
module.exports = {
- API_VERSION: API_VERSION,
- FRIEND_SCORE: FRIEND_SCORE,
- INTERVAL: INTERVAL,
- OAUTH_LIFETIME: OAUTH_LIFETIME,
- PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT,
- PODS_SCORE: PODS_SCORE,
- REQUESTS_IN_PARALLEL: REQUESTS_IN_PARALLEL,
- REQUESTS_LIMIT: REQUESTS_LIMIT,
- RETRY_REQUESTS: RETRY_REQUESTS,
- SEARCHABLE_COLUMNS: SEARCHABLE_COLUMNS,
- SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL,
- SORTABLE_COLUMNS: SORTABLE_COLUMNS,
- STATIC_PATHS: STATIC_PATHS,
- THUMBNAILS_SIZE: THUMBNAILS_SIZE,
- VIDEOS_CONSTRAINTS_FIELDS: VIDEOS_CONSTRAINTS_FIELDS
+ API_VERSION,
+ BCRYPT_SALT_SIZE,
+ CONFIG,
+ CONSTRAINTS_FIELDS,
+ FRIEND_SCORE,
+ LAST_MONGO_SCHEMA_VERSION,
+ MONGO_MIGRATION_SCRIPTS,
+ OAUTH_LIFETIME,
+ PAGINATION_COUNT_DEFAULT,
+ PODS_SCORE,
+ REQUESTS_IN_PARALLEL,
+ REQUESTS_INTERVAL,
+ REQUESTS_LIMIT,
+ RETRY_REQUESTS,
+ SEARCHABLE_COLUMNS,
+ SEEDS_IN_PARALLEL,
+ SORTABLE_COLUMNS,
+ STATIC_PATHS,
+ THUMBNAILS_SIZE,
+ USER_ROLES
}
// ---------------------------------------------------------------------------
'use strict'
-const config = require('config')
const mongoose = require('mongoose')
+const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
// Bootstrap models
+require('../models/application')
+require('../models/oauth-token')
require('../models/user')
require('../models/oauth-client')
-require('../models/oauth-token')
require('../models/pods')
require('../models/video')
// Request model needs Video model
require('../models/request')
-const dbname = 'peertube' + config.get('database.suffix')
-const host = config.get('database.host')
-const port = config.get('database.port')
-
const database = {
connect: connect
}
function connect () {
mongoose.Promise = global.Promise
- mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
+ mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOST + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME)
mongoose.connection.on('error', function () {
throw new Error('Mongodb connection error.')
})
const series = require('async/series')
const checker = require('./checker')
+const constants = require('./constants')
const logger = require('../helpers/logger')
const peertubeCrypto = require('../helpers/peertube-crypto')
+const Application = mongoose.model('Application')
const Client = mongoose.model('OAuthClient')
const User = mongoose.model('User')
const installer = {
- installApplication: installApplication
+ installApplication
}
function installApplication (callback) {
},
function createOAuthUser (callbackAsync) {
- createOAuthUserIfNotExist(callbackAsync)
+ createOAuthAdminIfNotExist(callbackAsync)
}
], callback)
}
})
}
-function createOAuthUserIfNotExist (callback) {
+function createOAuthAdminIfNotExist (callback) {
checker.usersExist(function (err, exist) {
if (err) return callback(err)
logger.info('Creating the administrator.')
const username = 'root'
+ const role = constants.USER_ROLES.ADMIN
let password = ''
// Do not generate a random password for tests
}
const user = new User({
- username: username,
- password: password
+ username,
+ password,
+ role
})
user.save(function (err, createdUser) {
if (err) return callback(err)
- logger.info('Username: ' + createdUser.username)
- logger.info('User password: ' + createdUser.password)
+ logger.info('Username: ' + username)
+ logger.info('User password: ' + password)
- return callback(null)
+ logger.info('Creating Application collection.')
+ const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION })
+ application.save(callback)
})
})
}
--- /dev/null
+/*
+ Create the application collection in MongoDB.
+ Used to store the actual MongoDB scheme version.
+*/
+
+const mongoose = require('mongoose')
+
+const Application = mongoose.model('Application')
+
+exports.up = function (callback) {
+ const application = new Application()
+ application.save(callback)
+}
+
+exports.down = function (callback) {
+ throw new Error('Not implemented.')
+}
--- /dev/null
+/*
+ Convert plain user password to encrypted user password.
+*/
+
+const eachSeries = require('async/eachSeries')
+const mongoose = require('mongoose')
+
+const User = mongoose.model('User')
+
+exports.up = function (callback) {
+ User.list(function (err, users) {
+ if (err) return callback(err)
+
+ eachSeries(users, function (user, callbackEach) {
+ user.save(callbackEach)
+ }, callback)
+ })
+}
+
+exports.down = function (callback) {
+ throw new Error('Not implemented.')
+}
--- /dev/null
+/*
+ Set the admin role to the root user.
+*/
+
+const constants = require('../constants')
+const mongoose = require('mongoose')
+
+const User = mongoose.model('User')
+
+exports.up = function (callback) {
+ User.update({ username: 'root' }, { role: constants.USER_ROLES.ADMIN }, callback)
+}
+
+exports.down = function (callback) {
+ throw new Error('Not implemented.')
+}
--- /dev/null
+'use strict'
+
+const eachSeries = require('async/eachSeries')
+const mongoose = require('mongoose')
+const path = require('path')
+
+const constants = require('./constants')
+const logger = require('../helpers/logger')
+
+const Application = mongoose.model('Application')
+
+const migrator = {
+ migrate: migrate
+}
+
+function migrate (callback) {
+ Application.loadMongoSchemaVersion(function (err, actualVersion) {
+ if (err) return callback(err)
+
+ // If there are a new mongo schemas
+ if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) {
+ logger.info('Begin migrations.')
+
+ eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) {
+ const versionScript = entity.version
+
+ // Do not execute old migration scripts
+ if (versionScript <= actualVersion) return callbackEach(null)
+
+ // Load the migration module and run it
+ const migrationScriptName = entity.script
+ logger.info('Executing %s migration script.', migrationScriptName)
+
+ const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName))
+ migrationScript.up(function (err) {
+ if (err) return callbackEach(err)
+
+ // Update the new mongo version schema
+ Application.updateMongoSchemaVersion(versionScript, callbackEach)
+ })
+ }, function (err) {
+ if (err) return callback(err)
+
+ logger.info('Migrations finished. New mongo version schema: %s', constants.LAST_MONGO_SCHEMA_VERSION)
+ return callback(null)
+ })
+ } else {
+ return callback(null)
+ }
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = migrator
+
'use strict'
-const config = require('config')
const each = require('async/each')
const eachLimit = require('async/eachLimit')
const eachSeries = require('async/eachSeries')
const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
-const peertubeCrypto = require('../helpers/peertube-crypto')
const requests = require('../helpers/requests')
-const http = config.get('webserver.https') ? 'https' : 'http'
-const host = config.get('webserver.host')
-const port = config.get('webserver.port')
const Pod = mongoose.model('Pod')
const Request = mongoose.model('Request')
const Video = mongoose.model('Video')
const friends = {
- addVideoToFriends: addVideoToFriends,
- hasFriends: hasFriends,
- getMyCertificate: getMyCertificate,
- makeFriends: makeFriends,
- quitFriends: quitFriends,
- removeVideoToFriends: removeVideoToFriends,
- sendOwnedVideosToPod: sendOwnedVideosToPod
+ addVideoToFriends,
+ hasFriends,
+ getMyCertificate,
+ makeFriends,
+ quitFriends,
+ removeVideoToFriends,
+ sendOwnedVideosToPod
}
function addVideoToFriends (video) {
}
function getMyCertificate (callback) {
- fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', callback)
+ fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback)
}
-function makeFriends (callback) {
+function makeFriends (urls, callback) {
const podsScore = {}
logger.info('Make friends!')
return callback(err)
}
- const urls = config.get('network.friends')
-
eachSeries(urls, function (url, callbackEach) {
computeForeignPodsList(url, podsScore, callbackEach)
}, function (err) {
request.get(url + path, function (err, response, body) {
if (err) return callback(err)
- callback(null, JSON.parse(body))
+ try {
+ const json = JSON.parse(body)
+ return callback(null, json)
+ } catch (err) {
+ return callback(err)
+ }
})
}
url: pod.url + '/api/' + constants.API_VERSION + '/pods/',
method: 'POST',
json: {
- url: http + '://' + host + ':' + port,
+ url: constants.CONFIG.WEBSERVER.URL,
publicKey: cert
}
}
// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
const OAuthModel = {
- getAccessToken: getAccessToken,
- getClient: getClient,
- getRefreshToken: getRefreshToken,
- getUser: getUser,
- revokeToken: revokeToken,
- saveToken: saveToken
+ getAccessToken,
+ getClient,
+ getRefreshToken,
+ getUser,
+ revokeToken,
+ saveToken
}
// ---------------------------------------------------------------------------
function getUser (username, password) {
logger.debug('Getting User (username: ' + username + ', password: ' + password + ').')
- return User.getByUsernameAndPassword(username, password)
+ return User.getByUsername(username).then(function (user) {
+ if (!user) return null
+
+ // We need to return a promise
+ return new Promise(function (resolve, reject) {
+ return user.isPasswordMatch(password, function (err, isPasswordMatch) {
+ if (err) return reject(err)
+
+ if (isPasswordMatch === true) {
+ return resolve(user)
+ }
+
+ return resolve(null)
+ })
+ })
+ })
}
function revokeToken (token) {
--- /dev/null
+'use strict'
+
+const constants = require('../initializers/constants')
+const logger = require('../helpers/logger')
+
+const adminMiddleware = {
+ ensureIsAdmin
+}
+
+function ensureIsAdmin (req, res, next) {
+ const user = res.locals.oauth.token.user
+ if (user.role !== constants.USER_ROLES.ADMIN) {
+ logger.info('A non admin user is trying to access to an admin content.')
+ return res.sendStatus(403)
+ }
+
+ return next()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = adminMiddleware
'use strict'
-const oauth = require('./oauth')
-const pagination = require('./pagination')
+const adminMiddleware = require('./admin')
+const oauthMiddleware = require('./oauth')
+const paginationMiddleware = require('./pagination')
+const podsMiddleware = require('./pods')
const validatorsMiddleware = require('./validators')
-const search = require('./search')
-const sort = require('./sort')
+const searchMiddleware = require('./search')
+const sortMiddleware = require('./sort')
const secureMiddleware = require('./secure')
const middlewares = {
- oauth: oauth,
- pagination: pagination,
- validators: validatorsMiddleware,
- search: search,
- sort: sort,
- secure: secureMiddleware
+ admin: adminMiddleware,
+ oauth: oauthMiddleware,
+ pagination: paginationMiddleware,
+ pods: podsMiddleware,
+ search: searchMiddleware,
+ secure: secureMiddleware,
+ sort: sortMiddleware,
+ validators: validatorsMiddleware
}
// ---------------------------------------------------------------------------
})
const oAuth = {
- authenticate: authenticate,
- token: token
+ authenticate,
+ token
}
function authenticate (req, res, next) {
return res.sendStatus(500)
}
- if (res.statusCode === 401 || res.statusCode === 400) return res.end()
+ if (res.statusCode === 401 || res.statusCode === 400 || res.statusCode === 503) return res.end()
return next()
})
const constants = require('../initializers/constants')
const paginationMiddleware = {
- setPagination: setPagination
+ setPagination
}
function setPagination (req, res, next) {
--- /dev/null
+'use strict'
+
+const urlModule = require('url')
+
+const logger = require('../helpers/logger')
+
+const podsMiddleware = {
+ setBodyUrlsPort,
+ setBodyUrlPort
+}
+
+function setBodyUrlsPort (req, res, next) {
+ for (let i = 0; i < req.body.urls.length; i++) {
+ const urlWithPort = getUrlWithPort(req.body.urls[i])
+
+ // Problem with the url parsing?
+ if (urlWithPort === null) {
+ return res.sendStatus(500)
+ }
+
+ req.body.urls[i] = urlWithPort
+ }
+
+ return next()
+}
+
+function setBodyUrlPort (req, res, next) {
+ const urlWithPort = getUrlWithPort(req.body.url)
+
+ // Problem with the url parsing?
+ if (urlWithPort === null) {
+ return res.sendStatus(500)
+ }
+
+ req.body.url = urlWithPort
+
+ return next()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = podsMiddleware
+
+// ---------------------------------------------------------------------------
+
+function getUrlWithPort (url) {
+ const urlObj = urlModule.parse(url)
+
+ // Add the port if it is not specified
+ if (urlObj.port === null) {
+ if (urlObj.protocol === 'http:') {
+ return url + ':80'
+ } else if (urlObj.protocol === 'https:') {
+ return url + ':443'
+ } else {
+ logger.error('Unknown url protocol: ' + urlObj.protocol)
+ return null
+ }
+ }
+
+ return url
+}
'use strict'
const searchMiddleware = {
- setVideosSearch: setVideosSearch
+ setVideosSearch
}
function setVideosSearch (req, res, next) {
const Pod = mongoose.model('Pod')
const secureMiddleware = {
- decryptBody: decryptBody
+ checkSignature,
+ decryptBody
}
-function decryptBody (req, res, next) {
+function checkSignature (req, res, next) {
const url = req.body.signature.url
Pod.loadByUrl(url, function (err, pod) {
if (err) {
const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
if (signatureOk === true) {
- peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
- if (err) {
- logger.error('Cannot decrypt data.', { error: err })
- return res.sendStatus(500)
- }
-
- req.body.data = JSON.parse(decrypted)
- delete req.body.key
-
- next()
- })
- } else {
- logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
- return res.sendStatus(403)
+ return next()
+ }
+
+ logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
+ return res.sendStatus(403)
+ })
+}
+
+function decryptBody (req, res, next) {
+ peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
+ if (err) {
+ logger.error('Cannot decrypt data.', { error: err })
+ return res.sendStatus(500)
}
+
+ try {
+ req.body.data = JSON.parse(decrypted)
+ delete req.body.key
+ } catch (err) {
+ logger.error('Error in JSON.parse', { error: err })
+ return res.sendStatus(500)
+ }
+
+ next()
})
}
'use strict'
const sortMiddleware = {
- setVideosSort: setVideosSort
+ setUsersSort,
+ setVideosSort
+}
+
+function setUsersSort (req, res, next) {
+ if (!req.query.sort) req.query.sort = '-createdDate'
+
+ return next()
}
function setVideosSort (req, res, next) {
const podsValidators = require('./pods')
const remoteValidators = require('./remote')
const sortValidators = require('./sort')
+const usersValidators = require('./users')
const videosValidators = require('./videos')
const validators = {
pods: podsValidators,
remote: remoteValidators,
sort: sortValidators,
+ users: usersValidators,
videos: videosValidators
}
const logger = require('../../helpers/logger')
const validatorsPagination = {
- pagination: pagination
+ pagination
}
function pagination (req, res, next) {
const logger = require('../../helpers/logger')
const validatorsPod = {
- makeFriends: makeFriends,
- podsAdd: podsAdd
+ makeFriends,
+ podsAdd
}
function makeFriends (req, res, next) {
- friends.hasFriends(function (err, hasFriends) {
- if (err) {
- logger.error('Cannot know if we have friends.', { error: err })
- res.sendStatus(500)
- }
-
- if (hasFriends === true) {
- // We need to quit our friends before make new ones
- res.sendStatus(409)
- } else {
- return next()
- }
+ req.checkBody('urls', 'Should have an array of unique urls').isEachUniqueUrlValid()
+
+ logger.debug('Checking makeFriends parameters', { parameters: req.body })
+
+ checkErrors(req, res, function () {
+ friends.hasFriends(function (err, hasFriends) {
+ if (err) {
+ logger.error('Cannot know if we have friends.', { error: err })
+ res.sendStatus(500)
+ }
+
+ if (hasFriends === true) {
+ // We need to quit our friends before make new ones
+ res.sendStatus(409)
+ } else {
+ return next()
+ }
+ })
})
}
const logger = require('../../helpers/logger')
const validatorsRemote = {
- dataToDecrypt: dataToDecrypt,
- remoteVideos: remoteVideos,
- signature: signature
+ dataToDecrypt,
+ remoteVideos,
+ signature
}
function dataToDecrypt (req, res, next) {
}
function remoteVideos (req, res, next) {
- req.checkBody('data').isArray()
req.checkBody('data').isEachRemoteVideosValid()
logger.debug('Checking remoteVideos parameters', { parameters: req.body })
const logger = require('../../helpers/logger')
const validatorsSort = {
- videosSort: videosSort
+ usersSort,
+ videosSort
+}
+
+function usersSort (req, res, next) {
+ const sortableColumns = constants.SORTABLE_COLUMNS.USERS
+
+ req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns)
+
+ logger.debug('Checking sort parameters', { parameters: req.query })
+
+ checkErrors(req, res, next)
}
function videosSort (req, res, next) {
--- /dev/null
+'use strict'
+
+const mongoose = require('mongoose')
+
+const checkErrors = require('./utils').checkErrors
+const logger = require('../../helpers/logger')
+
+const User = mongoose.model('User')
+
+const validatorsUsers = {
+ usersAdd,
+ usersRemove,
+ usersUpdate
+}
+
+function usersAdd (req, res, next) {
+ req.checkBody('username', 'Should have a valid username').isUserUsernameValid()
+ req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
+
+ logger.debug('Checking usersAdd parameters', { parameters: req.body })
+
+ checkErrors(req, res, function () {
+ User.loadByUsername(req.body.username, function (err, user) {
+ if (err) {
+ logger.error('Error in usersAdd request validator.', { error: err })
+ return res.sendStatus(500)
+ }
+
+ if (user) return res.status(409).send('User already exists.')
+
+ next()
+ })
+ })
+}
+
+function usersRemove (req, res, next) {
+ req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+
+ logger.debug('Checking usersRemove parameters', { parameters: req.params })
+
+ checkErrors(req, res, function () {
+ User.loadById(req.params.id, function (err, user) {
+ if (err) {
+ logger.error('Error in usersRemove request validator.', { error: err })
+ return res.sendStatus(500)
+ }
+
+ if (!user) return res.status(404).send('User not found')
+
+ next()
+ })
+ })
+}
+
+function usersUpdate (req, res, next) {
+ req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+ // Add old password verification
+ req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
+
+ logger.debug('Checking usersUpdate parameters', { parameters: req.body })
+
+ checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = validatorsUsers
const logger = require('../../helpers/logger')
const validatorsUtils = {
- checkErrors: checkErrors
+ checkErrors
}
function checkErrors (req, res, next, statusCode) {
const checkErrors = require('./utils').checkErrors
const constants = require('../../initializers/constants')
-const customValidators = require('../../helpers/custom-validators')
+const customVideosValidators = require('../../helpers/custom-validators').videos
const logger = require('../../helpers/logger')
const Video = mongoose.model('Video')
const validatorsVideos = {
- videosAdd: videosAdd,
- videosGet: videosGet,
- videosRemove: videosRemove,
- videosSearch: videosSearch
+ videosAdd,
+ videosGet,
+ videosRemove,
+ videosSearch
}
function videosAdd (req, res, next) {
req.checkFiles('videofile[0].originalname', 'Should have an input video').notEmpty()
+ // TODO: move to constants and function
req.checkFiles('videofile[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
req.checkBody('name', 'Should have a valid name').isVideoNameValid()
req.checkBody('description', 'Should have a valid description').isVideoDescriptionValid()
return res.status(400).send('Cannot retrieve metadata of the file.')
}
- if (!customValidators.isVideoDurationValid(duration)) {
- return res.status(400).send('Duration of the video file is too big (max: ' + constants.VIDEOS_CONSTRAINTS_FIELDS.DURATION.max + 's).')
+ if (!customVideosValidators.isVideoDurationValid(duration)) {
+ return res.status(400).send('Duration of the video file is too big (max: ' + constants.CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).')
}
videoFile.duration = duration
if (!video) return res.status(404).send('Video not found')
else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod')
+ else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user')
next()
})
--- /dev/null
+const mongoose = require('mongoose')
+
+// ---------------------------------------------------------------------------
+
+const ApplicationSchema = mongoose.Schema({
+ mongoSchemaVersion: {
+ type: Number,
+ default: 0
+ }
+})
+
+ApplicationSchema.statics = {
+ loadMongoSchemaVersion,
+ updateMongoSchemaVersion
+}
+
+mongoose.model('Application', ApplicationSchema)
+
+// ---------------------------------------------------------------------------
+
+function loadMongoSchemaVersion (callback) {
+ return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) {
+ const version = data ? data.mongoSchemaVersion : 0
+
+ return callback(err, version)
+ })
+}
+
+function updateMongoSchemaVersion (newVersion, callback) {
+ return this.update({}, { mongoSchemaVersion: newVersion }, callback)
+}
OAuthClientSchema.path('clientSecret').required(true)
OAuthClientSchema.statics = {
- getByIdAndSecret: getByIdAndSecret,
- list: list,
- loadFirstClient: loadFirstClient
+ getByIdAndSecret,
+ list,
+ loadFirstClient
}
mongoose.model('OAuthClient', OAuthClientSchema)
OAuthTokenSchema.path('user').required(true)
OAuthTokenSchema.statics = {
- getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient,
- getByTokenAndPopulateUser: getByTokenAndPopulateUser,
- getByRefreshToken: getByRefreshToken
+ getByRefreshTokenAndPopulateClient,
+ getByTokenAndPopulateUser,
+ getByRefreshToken,
+ removeByUserId
}
mongoose.model('OAuthToken', OAuthTokenSchema)
function getByRefreshToken (refreshToken) {
return this.findOne({ refreshToken: refreshToken }).exec()
}
+
+function removeByUserId (userId, callback) {
+ return this.remove({ user: userId }, callback)
+}
const PodSchema = mongoose.Schema({
url: String,
publicKey: String,
- score: { type: Number, max: constants.FRIEND_SCORE.MAX }
+ score: { type: Number, max: constants.FRIEND_SCORE.MAX },
+ createdDate: {
+ type: Date,
+ default: Date.now
+ }
})
// TODO: set options (TLD...)
PodSchema.path('publicKey').required(true)
PodSchema.path('score').validate(function (value) { return !isNaN(value) })
+PodSchema.methods = {
+ toFormatedJSON
+}
+
PodSchema.statics = {
- countAll: countAll,
- incrementScores: incrementScores,
- list: list,
- listAllIds: listAllIds,
- listOnlyUrls: listOnlyUrls,
- listBadPods: listBadPods,
- load: load,
- loadByUrl: loadByUrl,
- removeAll: removeAll
+ countAll,
+ incrementScores,
+ list,
+ listAllIds,
+ listBadPods,
+ load,
+ loadByUrl,
+ removeAll
}
PodSchema.pre('save', function (next) {
const Pod = mongoose.model('Pod', PodSchema)
+// ------------------------------ METHODS ------------------------------
+
+function toFormatedJSON () {
+ const json = {
+ id: this._id,
+ url: this.url,
+ score: this.score,
+ createdDate: this.createdDate
+ }
+
+ return json
+}
+
// ------------------------------ Statics ------------------------------
function countAll (callback) {
})
}
-function listOnlyUrls (callback) {
- return this.find({}, { _id: 0, url: 1 }, callback)
-}
-
function listBadPods (callback) {
return this.find({ score: 0 }, callback)
}
const Video = mongoose.model('Video')
let timer = null
+let lastRequestTimestamp = 0
// ---------------------------------------------------------------------------
const RequestSchema = mongoose.Schema({
request: mongoose.Schema.Types.Mixed,
- to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'users' } ]
+ to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Pod' } ]
})
RequestSchema.statics = {
activate,
deactivate,
flush,
- forceSend
+ forceSend,
+ list,
+ remainingMilliSeconds
}
RequestSchema.pre('save', function (next) {
function activate () {
logger.info('Requests scheduler activated.')
- timer = setInterval(makeRequests.bind(this), constants.INTERVAL)
+ lastRequestTimestamp = Date.now()
+
+ const self = this
+ timer = setInterval(function () {
+ lastRequestTimestamp = Date.now()
+ makeRequests.call(self)
+ }, constants.REQUESTS_INTERVAL)
}
function deactivate () {
logger.info('Requests scheduler deactivated.')
clearInterval(timer)
+ timer = null
}
function flush () {
makeRequests.call(this)
}
+function list (callback) {
+ this.find({ }, callback)
+}
+
+function remainingMilliSeconds () {
+ if (timer === null) return -1
+
+ return constants.REQUESTS_INTERVAL - (Date.now() - lastRequestTimestamp)
+}
+
// ---------------------------------------------------------------------------
// Make a requests to friends of a certain type
// The function fire some useful callbacks
requests.makeSecureRequest(params, function (err, res) {
if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) {
- logger.error('Error sending secure request to %s pod.', toPod.url, { error: err || new Error('Status code not 20x') })
+ logger.error(
+ 'Error sending secure request to %s pod.',
+ toPod.url,
+ {
+ error: err || new Error('Status code not 20x : ' + res.statusCode)
+ }
+ )
return callback(false)
}
return callbackEach()
}
- // Maybe the pod is not our friend anymore so simply remove them
+ // Maybe the pod is not our friend anymore so simply remove it
if (!toPod) {
+ logger.info('Removing %d requests of unexisting pod %s.', requestToMake.ids.length, toPodId)
removePodOf.call(self, requestToMake.ids, toPodId)
return callbackEach()
}
makeRequest(toPod, requestToMake.datas, function (success) {
- if (err) {
- logger.error('Errors when sent request to %s.', toPod.url, { error: err })
- // Do not stop the process just for one error
- return callbackEach()
- }
-
if (success === true) {
logger.debug('Removing requests for %s pod.', toPodId, { requestsIds: requestToMake.ids })
const mongoose = require('mongoose')
+const customUsersValidators = require('../helpers/custom-validators').users
+const modelUtils = require('./utils')
+const peertubeCrypto = require('../helpers/peertube-crypto')
+
+const OAuthToken = mongoose.model('OAuthToken')
+
// ---------------------------------------------------------------------------
const UserSchema = mongoose.Schema({
+ createdDate: {
+ type: Date,
+ default: Date.now
+ },
password: String,
- username: String
+ username: String,
+ role: String
})
-UserSchema.path('password').required(true)
-UserSchema.path('username').required(true)
+UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
+UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
+UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
+
+UserSchema.methods = {
+ isPasswordMatch,
+ toFormatedJSON
+}
UserSchema.statics = {
- getByUsernameAndPassword: getByUsernameAndPassword,
- list: list
+ countTotal,
+ getByUsername,
+ list,
+ listForApi,
+ loadById,
+ loadByUsername
}
+UserSchema.pre('save', function (next) {
+ const user = this
+
+ peertubeCrypto.cryptPassword(this.password, function (err, hash) {
+ if (err) return next(err)
+
+ user.password = hash
+
+ return next()
+ })
+})
+
+UserSchema.pre('remove', function (next) {
+ const user = this
+
+ OAuthToken.removeByUserId(user._id, next)
+})
+
mongoose.model('User', UserSchema)
-// ---------------------------------------------------------------------------
+// ------------------------------ METHODS ------------------------------
+
+function isPasswordMatch (password, callback) {
+ return peertubeCrypto.comparePassword(password, this.password, callback)
+}
+
+function toFormatedJSON () {
+ return {
+ id: this._id,
+ username: this.username,
+ role: this.role,
+ createdDate: this.createdDate
+ }
+}
+// ------------------------------ STATICS ------------------------------
+
+function countTotal (callback) {
+ return this.count(callback)
+}
+
+function getByUsername (username) {
+ return this.findOne({ username: username })
+}
function list (callback) {
return this.find(callback)
}
-function getByUsernameAndPassword (username, password) {
- return this.findOne({ username: username, password: password })
+function listForApi (start, count, sort, callback) {
+ const query = {}
+ return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
+}
+
+function loadById (id, callback) {
+ return this.findById(id, callback)
+}
+
+function loadByUsername (username, callback) {
+ return this.findOne({ username: username }, callback)
}
--- /dev/null
+'use strict'
+
+const parallel = require('async/parallel')
+
+const utils = {
+ listForApiWithCount
+}
+
+function listForApiWithCount (query, start, count, sort, callback) {
+ const self = this
+
+ parallel([
+ function (asyncCallback) {
+ self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
+ },
+ function (asyncCallback) {
+ self.count(query, asyncCallback)
+ }
+ ], function (err, results) {
+ if (err) return callback(err)
+
+ const data = results[0]
+ const total = results[1]
+ return callback(null, data, total)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = utils
const mongoose = require('mongoose')
const constants = require('../initializers/constants')
-const customValidators = require('../helpers/custom-validators')
+const customVideosValidators = require('../helpers/custom-validators').videos
const logger = require('../helpers/logger')
+const modelUtils = require('./utils')
const utils = require('../helpers/utils')
const http = config.get('webserver.https') === true ? 'https' : 'http'
}
})
-VideoSchema.path('name').validate(customValidators.isVideoNameValid)
-VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid)
-VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid)
-VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid)
-VideoSchema.path('author').validate(customValidators.isVideoAuthorValid)
-VideoSchema.path('duration').validate(customValidators.isVideoDurationValid)
+VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
+VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
+VideoSchema.path('magnetUri').validate(customVideosValidators.isVideoMagnetUriValid)
+VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid)
+VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
+VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
// The tumbnail can be the path or the data in base 64
// The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename
VideoSchema.path('thumbnail').validate(function (value) {
- return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value)
+ return customVideosValidators.isVideoThumbnailValid(value) || customVideosValidators.isVideoThumbnail64Valid(value)
})
-VideoSchema.path('tags').validate(customValidators.isVideoTagsValid)
+VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
VideoSchema.methods = {
- isOwned: isOwned,
- toFormatedJSON: toFormatedJSON,
- toRemoteJSON: toRemoteJSON
+ isOwned,
+ toFormatedJSON,
+ toRemoteJSON
}
VideoSchema.statics = {
- getDurationFromFile: getDurationFromFile,
- list: list,
- listByUrlAndMagnet: listByUrlAndMagnet,
- listByUrls: listByUrls,
- listOwned: listOwned,
- listRemotes: listRemotes,
- load: load,
- search: search
+ getDurationFromFile,
+ listForApi,
+ listByUrlAndMagnet,
+ listByUrls,
+ listOwned,
+ listOwnedByAuthor,
+ listRemotes,
+ load,
+ search
}
VideoSchema.pre('remove', function (next) {
const tasks = []
if (video.isOwned()) {
- const videoPath = pathUtils.join(uploadsDir, video.filename)
- this.podUrl = http + '://' + host + ':' + port
+ const videoPath = pathUtils.join(constants.CONFIG.STORAGE.UPLOAD_DIR, video.filename)
+ this.podUrl = constants.CONFIG.WEBSERVER.URL
tasks.push(
// TODO: refractoring
const self = this
// Convert thumbnail to base64
- fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) {
+ fs.readFile(pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.thumbnail), function (err, thumbnailData) {
if (err) {
logger.error('Cannot read the thumbnail of the video')
return callback(err)
})
}
-function list (start, count, sort, callback) {
+function listForApi (start, count, sort, callback) {
const query = {}
- return findWithCount.call(this, query, start, count, sort, callback)
+ return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
}
function listByUrlAndMagnet (fromUrl, magnetUri, callback) {
this.find({ filename: { $ne: null } }, callback)
}
+function listOwnedByAuthor (author, callback) {
+ this.find({ filename: { $ne: null }, author: author }, callback)
+}
+
function listRemotes (callback) {
this.find({ filename: null }, callback)
}
query[field] = new RegExp(value)
}
- findWithCount.call(this, query, start, count, sort, callback)
+ modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
}
// ---------------------------------------------------------------------------
-function findWithCount (query, start, count, sort, callback) {
- const self = this
-
- parallel([
- function (asyncCallback) {
- self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback)
- },
- function (asyncCallback) {
- self.count(query, asyncCallback)
- }
- ], function (err, results) {
- if (err) return callback(err)
-
- const videos = results[0]
- const totalVideos = results[1]
- return callback(null, videos, totalVideos)
- })
-}
-
function removeThumbnail (video, callback) {
- fs.unlink(thumbnailsDir + video.thumbnail, callback)
+ fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.thumbnail, callback)
}
function removeFile (video, callback) {
- fs.unlink(uploadsDir + video.filename, callback)
+ fs.unlink(constants.CONFIG.STORAGE.UPLOAD_DIR + video.filename, callback)
}
// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
})
.thumbnail({
count: 1,
- folder: thumbnailsDir,
+ folder: constants.CONFIG.STORAGE.THUMBNAILS_DIR,
size: constants.THUMBNAILS_SIZE,
filename: filename
})
if (err) return callback(err)
const thumbnailName = randomString + '.jpg'
- fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) {
+ fs.writeFile(constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName, data, { encoding: 'base64' }, function (err) {
if (err) return callback(err)
return callback(null, thumbnailName)
--- /dev/null
+'use strict'
+
+const chai = require('chai')
+const expect = chai.expect
+const pathUtils = require('path')
+const request = require('supertest')
+const series = require('async/series')
+
+const loginUtils = require('../utils/login')
+const requestsUtils = require('../utils/requests')
+const serversUtils = require('../utils/servers')
+const usersUtils = require('../utils/users')
+
+describe('Test parameters validator', function () {
+ let server = null
+ let userAccessToken = null
+
+ // ---------------------------------------------------------------
+
+ before(function (done) {
+ this.timeout(20000)
+
+ series([
+ function (next) {
+ serversUtils.flushTests(next)
+ },
+ function (next) {
+ serversUtils.runServer(1, function (server1) {
+ server = server1
+
+ next()
+ })
+ },
+ function (next) {
+ loginUtils.loginAndGetAccessToken(server, function (err, token) {
+ if (err) throw err
+ server.accessToken = token
+
+ next()
+ })
+ }
+ ], done)
+ })
+
+ describe('Of the pods API', function () {
+ const path = '/api/v1/pods/'
+
+ describe('When making friends', function () {
+ let userAccessToken = null
+
+ before(function (done) {
+ usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () {
+ server.user = {
+ username: 'user1',
+ password: 'password'
+ }
+
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
+ if (err) throw err
+
+ userAccessToken = accessToken
+
+ done()
+ })
+ })
+ })
+
+ describe('When making friends', function () {
+ const body = {
+ urls: [ 'http://localhost:9002' ]
+ }
+
+ it('Should fail without urls', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with urls is not an array', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .send({ urls: 'http://localhost:9002' })
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail if the array is not composed by urls', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .send({ urls: [ 'http://localhost:9002', 'localhost:coucou' ] })
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail if urls are not unique', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .send({ urls: [ 'http://localhost:9002', 'http://localhost:9002' ] })
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with a invalid token', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .send(body)
+ .set('Authorization', 'Bearer faketoken')
+ .set('Accept', 'application/json')
+ .expect(401, done)
+ })
+
+ it('Should fail if the user is not an administrator', function (done) {
+ request(server.url)
+ .post(path + '/makefriends')
+ .send(body)
+ .set('Authorization', 'Bearer ' + userAccessToken)
+ .set('Accept', 'application/json')
+ .expect(403, done)
+ })
+ })
+
+ describe('When quitting friends', function () {
+ it('Should fail with a invalid token', function (done) {
+ request(server.url)
+ .get(path + '/quitfriends')
+ .query({ start: 'hello' })
+ .set('Authorization', 'Bearer faketoken')
+ .set('Accept', 'application/json')
+ .expect(401, done)
+ })
+
+ it('Should fail if the user is not an administrator', function (done) {
+ request(server.url)
+ .get(path + '/quitfriends')
+ .query({ start: 'hello' })
+ .set('Authorization', 'Bearer ' + userAccessToken)
+ .set('Accept', 'application/json')
+ .expect(403, done)
+ })
+ })
+ })
+
+ describe('When adding a pod', function () {
+ it('Should fail with nothing', function (done) {
+ const data = {}
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+ })
+
+ it('Should fail without public key', function (done) {
+ const data = {
+ url: 'http://coucou.com'
+ }
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+ })
+
+ it('Should fail without an url', function (done) {
+ const data = {
+ publicKey: 'mysuperpublickey'
+ }
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+ })
+
+ it('Should fail with an incorrect url', function (done) {
+ const data = {
+ url: 'coucou.com',
+ publicKey: 'mysuperpublickey'
+ }
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
+ data.url = 'http://coucou'
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
+ data.url = 'coucou'
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
+ })
+ })
+ })
+
+ it('Should succeed with the correct parameters', function (done) {
+ const data = {
+ url: 'http://coucou.com',
+ publicKey: 'mysuperpublickey'
+ }
+ requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200)
+ })
+ })
+ })
+
+ describe('Of the videos API', function () {
+ const path = '/api/v1/videos/'
+
+ describe('When listing a video', function () {
+ it('Should fail with a bad start pagination', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ start: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with a bad count pagination', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ count: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with an incorrect sort', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ sort: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+ })
+
+ describe('When searching a video', function () {
+ it('Should fail with nothing', function (done) {
+ request(server.url)
+ .get(pathUtils.join(path, 'search'))
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with a bad start pagination', function (done) {
+ request(server.url)
+ .get(pathUtils.join(path, 'search', 'test'))
+ .query({ start: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with a bad count pagination', function (done) {
+ request(server.url)
+ .get(pathUtils.join(path, 'search', 'test'))
+ .query({ count: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with an incorrect sort', function (done) {
+ request(server.url)
+ .get(pathUtils.join(path, 'search', 'test'))
+ .query({ sort: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+ })
+
+ describe('When adding a video', function () {
+ it('Should fail with nothing', function (done) {
+ const data = {}
+ const attach = {}
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail without name', function (done) {
+ const data = {
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with a long name', function (done) {
+ const data = {
+ name: 'My very very very very very very very very very very very very very very very very long name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail without description', function (done) {
+ const data = {
+ name: 'my super name',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with a long description', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description which is very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very long',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail without tags', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description'
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with too many tags', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with not enough tags', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with a tag length too low', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 't' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with a tag length too big', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'mysupertagtoolong', 'tag1' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with malformed tags', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'my tag' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail without an input file', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {}
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail without an incorrect input file', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should fail with a too big duration', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
+ })
+
+ it('Should succeed with the correct parameters', function (done) {
+ const data = {
+ name: 'my super name',
+ description: 'my super description',
+ tags: [ 'tag1', 'tag2' ]
+ }
+ const attach = {
+ 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+ }
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
+ attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
+ attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
+ requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204)
+ }, false)
+ }, false)
+ })
+ })
+
+ describe('When getting a video', function () {
+ it('Should return the list of the videos with nothing', function (done) {
+ request(server.url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(function (err, res) {
+ if (err) throw err
+
+ expect(res.body.data).to.be.an('array')
+ expect(res.body.data.length).to.equal(3)
+
+ done()
+ })
+ })
+
+ it('Should fail without a mongodb id', function (done) {
+ request(server.url)
+ .get(path + 'coucou')
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should return 404 with an incorrect video', function (done) {
+ request(server.url)
+ .get(path + '123456789012345678901234')
+ .set('Accept', 'application/json')
+ .expect(404, done)
+ })
+
+ it('Should succeed with the correct parameters')
+ })
+
+ describe('When removing a video', function () {
+ it('Should have 404 with nothing', function (done) {
+ request(server.url)
+ .delete(path)
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(400, done)
+ })
+
+ it('Should fail without a mongodb id', function (done) {
+ request(server.url)
+ .delete(path + 'hello')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(400, done)
+ })
+
+ it('Should fail with a video which does not exist', function (done) {
+ request(server.url)
+ .delete(path + '123456789012345678901234')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(404, done)
+ })
+
+ it('Should fail with a video of another user')
+
+ it('Should fail with a video of another pod')
+
+ it('Should succeed with the correct parameters')
+ })
+ })
+
+ describe('Of the users API', function () {
+ const path = '/api/v1/users/'
+ let userId = null
+
+ describe('When listing users', function () {
+ it('Should fail with a bad start pagination', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ start: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with a bad count pagination', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ count: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+
+ it('Should fail with an incorrect sort', function (done) {
+ request(server.url)
+ .get(path)
+ .query({ sort: 'hello' })
+ .set('Accept', 'application/json')
+ .expect(400, done)
+ })
+ })
+
+ describe('When adding a new user', function () {
+ it('Should fail with a too small username', function (done) {
+ const data = {
+ username: 'ji',
+ password: 'mysuperpassword'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+ })
+
+ it('Should fail with a too long username', function (done) {
+ const data = {
+ username: 'mysuperusernamewhichisverylong',
+ password: 'mysuperpassword'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+ })
+
+ it('Should fail with an incorrect username', function (done) {
+ const data = {
+ username: 'my username',
+ password: 'mysuperpassword'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+ })
+
+ it('Should fail with a too small password', function (done) {
+ const data = {
+ username: 'myusername',
+ password: 'bla'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+ })
+
+ it('Should fail with a too long password', function (done) {
+ const data = {
+ username: 'myusername',
+ password: 'my super long password which is very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very veryv very very very very' +
+ 'very very very very very very very very very very very very very very very very very very very very long'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
+ })
+
+ it('Should fail with an non authenticated user', function (done) {
+ const data = {
+ username: 'myusername',
+ password: 'my super password'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401)
+ })
+
+ it('Should fail if we add a user with the same username', function (done) {
+ const data = {
+ username: 'user1',
+ password: 'my super password'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409)
+ })
+
+ it('Should succeed with the correct params', function (done) {
+ const data = {
+ username: 'user2',
+ password: 'my super password'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204)
+ })
+
+ it('Should fail with a non admin user', function (done) {
+ server.user = {
+ username: 'user1',
+ password: 'password'
+ }
+
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
+ if (err) throw err
+
+ userAccessToken = accessToken
+
+ const data = {
+ username: 'user3',
+ password: 'my super password'
+ }
+
+ requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403)
+ })
+ })
+ })
+
+ describe('When updating a user', function () {
+ before(function (done) {
+ usersUtils.getUsersList(server.url, function (err, res) {
+ if (err) throw err
+
+ userId = res.body.data[1].id
+ done()
+ })
+ })
+
+ it('Should fail with a too small password', function (done) {
+ const data = {
+ password: 'bla'
+ }
+
+ requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
+ })
+
+ it('Should fail with a too long password', function (done) {
+ const data = {
+ password: 'my super long password which is very very very very very very very very very very very very very very' +
+ 'very very very very very very very very very very very very very very very veryv very very very very' +
+ 'very very very very very very very very very very very very very very very very very very very very long'
+ }
+
+ requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
+ })
+
+ it('Should fail with an non authenticated user', function (done) {
+ const data = {
+ password: 'my super password'
+ }
+
+ requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401)
+ })
+
+ it('Should succeed with the correct params', function (done) {
+ const data = {
+ password: 'my super password'
+ }
+
+ requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204)
+ })
+ })
+
+ describe('When getting my information', function () {
+ it('Should fail with a non authenticated user', function (done) {
+ request(server.url)
+ .get(path + 'me')
+ .set('Authorization', 'Bearer faketoken')
+ .set('Accept', 'application/json')
+ .expect(401, done)
+ })
+
+ it('Should success with the correct parameters', function (done) {
+ request(server.url)
+ .get(path + 'me')
+ .set('Authorization', 'Bearer ' + userAccessToken)
+ .set('Accept', 'application/json')
+ .expect(200, done)
+ })
+ })
+
+ describe('When removing an user', function () {
+ it('Should fail with an incorrect id', function (done) {
+ request(server.url)
+ .delete(path + 'bla-bla')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(400, done)
+ })
+
+ it('Should return 404 with a non existing id', function (done) {
+ request(server.url)
+ .delete(path + '579f982228c99c221d8092b8')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(404, done)
+ })
+ })
+ })
+
+ describe('Of the remote videos API', function () {
+ describe('When making a secure request', function () {
+ it('Should check a secure request')
+ })
+
+ describe('When adding a video', function () {
+ it('Should check when adding a video')
+ })
+
+ describe('When removing a video', function () {
+ it('Should check when removing a video')
+ })
+ })
+
+ describe('Of the requests API', function () {
+ const path = '/api/v1/requests/stats'
+
+ it('Should fail with an non authenticated user', function (done) {
+ request(server.url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(401, done)
+ })
+
+ it('Should fail with a non admin user', function (done) {
+ request(server.url)
+ .get(path)
+ .set('Authorization', 'Bearer ' + userAccessToken)
+ .set('Accept', 'application/json')
+ .expect(403, done)
+ })
+ })
+
+ after(function (done) {
+ process.kill(-server.app.pid)
+
+ // Keep the logs if the test failed
+ if (this.ok) {
+ serversUtils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
+++ /dev/null
-'use strict'
-
-const chai = require('chai')
-const expect = chai.expect
-const pathUtils = require('path')
-const request = require('supertest')
-const series = require('async/series')
-
-const utils = require('./utils')
-
-describe('Test parameters validator', function () {
- let server = null
-
- function makePostRequest (path, token, fields, attaches, done, fail) {
- let statusCode = 400
- if (fail !== undefined && fail === false) statusCode = 204
-
- const req = request(server.url)
- .post(path)
- .set('Accept', 'application/json')
-
- if (token) req.set('Authorization', 'Bearer ' + token)
-
- Object.keys(fields).forEach(function (field) {
- const value = fields[field]
-
- if (Array.isArray(value)) {
- for (let i = 0; i < value.length; i++) {
- req.field(field + '[' + i + ']', value[i])
- }
- } else {
- req.field(field, value)
- }
- })
-
- Object.keys(attaches).forEach(function (attach) {
- const value = attaches[attach]
- req.attach(attach, value)
- })
-
- req.expect(statusCode, done)
- }
-
- function makePostBodyRequest (path, fields, done, fail) {
- let statusCode = 400
- if (fail !== undefined && fail === false) statusCode = 200
-
- request(server.url)
- .post(path)
- .set('Accept', 'application/json')
- .send(fields)
- .expect(statusCode, done)
- }
-
- // ---------------------------------------------------------------
-
- before(function (done) {
- this.timeout(20000)
-
- series([
- function (next) {
- utils.flushTests(next)
- },
- function (next) {
- utils.runServer(1, function (server1) {
- server = server1
-
- next()
- })
- },
- function (next) {
- utils.loginAndGetAccessToken(server, function (err, token) {
- if (err) throw err
- server.accessToken = token
-
- next()
- })
- }
- ], done)
- })
-
- describe('Of the pods API', function () {
- const path = '/api/v1/pods/'
-
- describe('When adding a pod', function () {
- it('Should fail with nothing', function (done) {
- const data = {}
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail without public key', function (done) {
- const data = {
- url: 'http://coucou.com'
- }
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail without an url', function (done) {
- const data = {
- publicKey: 'mysuperpublickey'
- }
- makePostBodyRequest(path, data, done)
- })
-
- it('Should fail with an incorrect url', function (done) {
- const data = {
- url: 'coucou.com',
- publicKey: 'mysuperpublickey'
- }
- makePostBodyRequest(path, data, function () {
- data.url = 'http://coucou'
- makePostBodyRequest(path, data, function () {
- data.url = 'coucou'
- makePostBodyRequest(path, data, done)
- })
- })
- })
-
- it('Should succeed with the correct parameters', function (done) {
- const data = {
- url: 'http://coucou.com',
- publicKey: 'mysuperpublickey'
- }
- makePostBodyRequest(path, data, done, false)
- })
- })
- })
-
- describe('Of the videos API', function () {
- const path = '/api/v1/videos/'
-
- describe('When listing a video', function () {
- it('Should fail with a bad start pagination', function (done) {
- request(server.url)
- .get(path)
- .query({ start: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should fail with a bad count pagination', function (done) {
- request(server.url)
- .get(path)
- .query({ count: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should fail with an incorrect sort', function (done) {
- request(server.url)
- .get(path)
- .query({ sort: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
- })
-
- describe('When searching a video', function () {
- it('Should fail with nothing', function (done) {
- request(server.url)
- .get(pathUtils.join(path, 'search'))
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should fail with a bad start pagination', function (done) {
- request(server.url)
- .get(pathUtils.join(path, 'search', 'test'))
- .query({ start: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should fail with a bad count pagination', function (done) {
- request(server.url)
- .get(pathUtils.join(path, 'search', 'test'))
- .query({ count: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should fail with an incorrect sort', function (done) {
- request(server.url)
- .get(pathUtils.join(path, 'search', 'test'))
- .query({ sort: 'hello' })
- .set('Accept', 'application/json')
- .expect(400, done)
- })
- })
-
- describe('When adding a video', function () {
- it('Should fail with nothing', function (done) {
- const data = {}
- const attach = {}
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail without name', function (done) {
- const data = {
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with a long name', function (done) {
- const data = {
- name: 'My very very very very very very very very very very very very very very very very long name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail without description', function (done) {
- const data = {
- name: 'my super name',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with a long description', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description which is very very very very very very very very very very very very very very' +
- 'very very very very very very very very very very very very very very very very very very very very very' +
- 'very very very very very very very very very very very very very very very long',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail without tags', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description'
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with too many tags', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with not enough tags', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with a tag length too low', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 't' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with a tag length too big', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'mysupertagtoolong', 'tag1' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with malformed tags', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'my tag' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail without an input file', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {}
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail without an incorrect input file', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should fail with a too big duration', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, done)
- })
-
- it('Should succeed with the correct parameters', function (done) {
- const data = {
- name: 'my super name',
- description: 'my super description',
- tags: [ 'tag1', 'tag2' ]
- }
- const attach = {
- 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
- }
- makePostRequest(path, server.accessToken, data, attach, function () {
- attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
- makePostRequest(path, server.accessToken, data, attach, function () {
- attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
- makePostRequest(path, server.accessToken, data, attach, done, false)
- }, false)
- }, false)
- })
- })
-
- describe('When getting a video', function () {
- it('Should return the list of the videos with nothing', function (done) {
- request(server.url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(function (err, res) {
- if (err) throw err
-
- expect(res.body.data).to.be.an('array')
- expect(res.body.data.length).to.equal(3)
-
- done()
- })
- })
-
- it('Should fail without a mongodb id', function (done) {
- request(server.url)
- .get(path + 'coucou')
- .set('Accept', 'application/json')
- .expect(400, done)
- })
-
- it('Should return 404 with an incorrect video', function (done) {
- request(server.url)
- .get(path + '123456789012345678901234')
- .set('Accept', 'application/json')
- .expect(404, done)
- })
-
- it('Should succeed with the correct parameters')
- })
-
- describe('When removing a video', function () {
- it('Should have 404 with nothing', function (done) {
- request(server.url)
- .delete(path)
- .set('Authorization', 'Bearer ' + server.accessToken)
- .expect(400, done)
- })
-
- it('Should fail without a mongodb id', function (done) {
- request(server.url)
- .delete(path + 'hello')
- .set('Authorization', 'Bearer ' + server.accessToken)
- .expect(400, done)
- })
-
- it('Should fail with a video which does not exist', function (done) {
- request(server.url)
- .delete(path + '123456789012345678901234')
- .set('Authorization', 'Bearer ' + server.accessToken)
- .expect(404, done)
- })
-
- it('Should fail with a video of another pod')
-
- it('Should succeed with the correct parameters')
- })
- })
-
- describe('Of the remote videos API', function () {
- describe('When making a secure request', function () {
- it('Should check a secure request')
- })
-
- describe('When adding a video', function () {
- it('Should check when adding a video')
- })
-
- describe('When removing a video', function () {
- it('Should check when removing a video')
- })
- })
-
- after(function (done) {
- process.kill(-server.app.pid)
-
- // Keep the logs if the test failed
- if (this.ok) {
- utils.flushTests(done)
- } else {
- done()
- }
- })
-})
const expect = chai.expect
const series = require('async/series')
-const utils = require('./utils')
+const loginUtils = require('../utils/login')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
describe('Test advanced friends', function () {
let servers = []
function makeFriends (podNumber, callback) {
const server = servers[podNumber - 1]
- return utils.makeFriends(server.url, server.accessToken, callback)
+ return podsUtils.makeFriends(server.url, server.accessToken, callback)
}
function quitFriends (podNumber, callback) {
const server = servers[podNumber - 1]
- return utils.quitFriends(server.url, server.accessToken, callback)
+ return podsUtils.quitFriends(server.url, server.accessToken, callback)
}
function getFriendsList (podNumber, end) {
const server = servers[podNumber - 1]
- return utils.getFriendsList(server.url, end)
+ return podsUtils.getFriendsList(server.url, end)
}
function uploadVideo (podNumber, callback) {
const fixture = 'video_short.webm'
const server = servers[podNumber - 1]
- return utils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback)
+ return videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback)
}
function getVideos (podNumber, callback) {
- return utils.getVideosList(servers[podNumber - 1].url, callback)
+ return videosUtils.getVideosList(servers[podNumber - 1].url, callback)
}
// ---------------------------------------------------------------
before(function (done) {
this.timeout(30000)
- utils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) {
+ serversUtils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) {
servers = serversRun
each(servers, function (server, callbackEach) {
- utils.loginAndGetAccessToken(server, function (err, accessToken) {
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
if (err) return callbackEach(err)
server.accessToken = accessToken
},
// Rerun server 4
function (next) {
- utils.runServer(4, function (server) {
+ serversUtils.runServer(4, function (server) {
servers[3].app = server.app
next()
})
})
if (this.ok) {
- utils.flushTests(done)
+ serversUtils.flushTests(done)
} else {
done()
}
const expect = chai.expect
const series = require('async/series')
-const utils = require('./utils')
+const loginUtils = require('../utils/login')
+const miscsUtils = require('../utils/miscs')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
describe('Test basic friends', function () {
let servers = []
function makeFriends (podNumber, callback) {
const server = servers[podNumber - 1]
- return utils.makeFriends(server.url, server.accessToken, callback)
+ return podsUtils.makeFriends(server.url, server.accessToken, callback)
}
function testMadeFriends (servers, serverToTest, callback) {
friends.push(servers[i].url)
}
- utils.getFriendsList(serverToTest.url, function (err, res) {
+ podsUtils.getFriendsList(serverToTest.url, function (err, res) {
if (err) throw err
const result = res.body
before(function (done) {
this.timeout(20000)
- utils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) {
+ serversUtils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) {
servers = serversRun
each(servers, function (server, callbackEach) {
- utils.loginAndGetAccessToken(server, function (err, accessToken) {
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
if (err) return callbackEach(err)
server.accessToken = accessToken
it('Should not have friends', function (done) {
each(servers, function (server, callback) {
- utils.getFriendsList(server.url, function (err, res) {
+ podsUtils.getFriendsList(server.url, function (err, res) {
if (err) throw err
const result = res.body
})
it('Should make friends', function (done) {
- this.timeout(10000)
+ this.timeout(40000)
series([
// The second pod make friend with the third
},
// Wait for the request between pods
function (next) {
- setTimeout(next, 1000)
+ setTimeout(next, 11000)
},
// The second pod should have the third as a friend
function (next) {
- utils.getFriendsList(servers[1].url, function (err, res) {
+ podsUtils.getFriendsList(servers[1].url, function (err, res) {
if (err) throw err
const result = res.body
expect(result).to.be.an('array')
expect(result.length).to.equal(1)
- expect(result[0].url).to.be.equal(servers[2].url)
+
+ const pod = result[0]
+ expect(pod.url).to.equal(servers[2].url)
+ expect(pod.score).to.equal(20)
+ expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true
next()
})
},
// Same here, the third pod should have the second pod as a friend
function (next) {
- utils.getFriendsList(servers[2].url, function (err, res) {
+ podsUtils.getFriendsList(servers[2].url, function (err, res) {
if (err) throw err
const result = res.body
expect(result).to.be.an('array')
expect(result.length).to.equal(1)
- expect(result[0].url).to.be.equal(servers[1].url)
+
+ const pod = result[0]
+ expect(pod.url).to.equal(servers[1].url)
+ expect(pod.score).to.equal(20)
+ expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true
next()
})
},
// Wait for the request between pods
function (next) {
- setTimeout(next, 1000)
+ setTimeout(next, 11000)
}
],
// Now each pod should be friend with the other ones
it('Should not be allowed to make friend again', function (done) {
const server = servers[1]
- utils.makeFriends(server.url, server.accessToken, 409, done)
+ podsUtils.makeFriends(server.url, server.accessToken, 409, done)
})
it('Should quit friends of pod 2', function (done) {
// Pod 1 quit friends
function (next) {
const server = servers[1]
- utils.quitFriends(server.url, server.accessToken, next)
+ podsUtils.quitFriends(server.url, server.accessToken, next)
},
// Pod 1 should not have friends anymore
function (next) {
- utils.getFriendsList(servers[1].url, function (err, res) {
+ podsUtils.getFriendsList(servers[1].url, function (err, res) {
if (err) throw err
const result = res.body
// Other pods shouldn't have pod 1 too
function (next) {
each([ servers[0].url, servers[2].url ], function (url, callback) {
- utils.getFriendsList(url, function (err, res) {
+ podsUtils.getFriendsList(url, function (err, res) {
if (err) throw err
const result = res.body
})
it('Should allow pod 2 to make friend again', function (done) {
+ this.timeout(20000)
+
const server = servers[1]
- utils.makeFriends(server.url, server.accessToken, function () {
- each(servers, function (server, callback) {
- testMadeFriends(servers, server, callback)
- }, done)
+ podsUtils.makeFriends(server.url, server.accessToken, function () {
+ setTimeout(function () {
+ each(servers, function (server, callback) {
+ testMadeFriends(servers, server, callback)
+ }, done)
+ }, 11000)
})
})
})
if (this.ok) {
- utils.flushTests(done)
+ serversUtils.flushTests(done)
} else {
done()
}
'use strict'
// Order of the tests we want to execute
-require('./checkParams')
-require('./friendsBasic')
+require('./check-params')
+require('./friends-basic')
require('./users')
-require('./singlePod')
-require('./multiplePods')
-require('./friendsAdvanced')
+require('./single-pod')
+require('./multiple-pods')
+require('./friends-advanced')
const pathUtils = require('path')
const series = require('async/series')
-const utils = require('./utils')
+const loginUtils = require('../utils/login')
+const miscsUtils = require('../utils/miscs')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
webtorrent.silent = true
series([
// Run servers
function (next) {
- utils.flushAndRunMultipleServers(3, function (serversRun) {
+ serversUtils.flushAndRunMultipleServers(3, function (serversRun) {
servers = serversRun
next()
})
// Get the access tokens
function (next) {
each(servers, function (server, callbackEach) {
- utils.loginAndGetAccessToken(server, function (err, accessToken) {
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
if (err) return callbackEach(err)
server.accessToken = accessToken
// The second pod make friend with the third
function (next) {
const server = servers[1]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
// Wait for the request between pods
function (next) {
// Pod 1 make friends too
function (next) {
const server = servers[0]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
webtorrent.create({ host: 'client', port: '1' }, next)
it('Should not have videos for all pods', function (done) {
each(servers, function (server, callback) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
const description = 'my super description for pod 1'
const tags = [ 'tag1p1', 'tag2p1' ]
const file = 'video_short1.webm'
- utils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next)
+ videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next)
},
function (next) {
setTimeout(next, 11000)
each(servers, function (server, callback) {
let baseMagnet = null
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
expect(video.magnetUri).to.exist
expect(video.duration).to.equal(10)
expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
expect(video.author).to.equal('root')
if (server.url !== 'http://localhost:9001') {
expect(video.magnetUri).to.equal.magnetUri
}
- utils.testImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
const description = 'my super description for pod 2'
const tags = [ 'tag1p2', 'tag2p2', 'tag3p2' ]
const file = 'video_short2.webm'
- utils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next)
+ videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next)
},
function (next) {
setTimeout(next, 11000)
each(servers, function (server, callback) {
let baseMagnet = null
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
expect(video.magnetUri).to.exist
expect(video.duration).to.equal(5)
expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
expect(video.author).to.equal('root')
if (server.url !== 'http://localhost:9002') {
expect(video.magnetUri).to.equal.magnetUri
}
- utils.testImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
const description = 'my super description for pod 3'
const tags = [ 'tag1p3' ]
const file = 'video_short3.webm'
- utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next)
+ videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next)
},
function (next) {
const name = 'my super name for pod 3-2'
const description = 'my super description for pod 3-2'
const tags = [ 'tag2p3', 'tag3p3', 'tag4p3' ]
const file = 'video_short.webm'
- utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next)
+ videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next)
},
function (next) {
setTimeout(next, 22000)
let baseMagnet = null
// All pods should have this video
each(servers, function (server, callback) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
expect(video1.duration).to.equal(5)
expect(video1.tags).to.deep.equal([ 'tag1p3' ])
expect(video1.author).to.equal('root')
- expect(utils.dateIsValid(video1.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true
expect(video2.name).to.equal('my super name for pod 3-2')
expect(video2.description).to.equal('my super description for pod 3-2')
expect(video2.duration).to.equal(5)
expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
expect(video2.author).to.equal('root')
- expect(utils.dateIsValid(video2.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true
if (server.url !== 'http://localhost:9003') {
expect(video1.isLocal).to.be.false
expect(video2.magnetUri).to.equal.magnetUri
}
- utils.testImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
- utils.testImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
// Yes, this could be long
this.timeout(200000)
- utils.getVideosList(servers[2].url, function (err, res) {
+ videosUtils.getVideosList(servers[2].url, function (err, res) {
if (err) throw err
const video = res.body.data[0]
// Yes, this could be long
this.timeout(200000)
- utils.getVideosList(servers[0].url, function (err, res) {
+ videosUtils.getVideosList(servers[0].url, function (err, res) {
if (err) throw err
const video = res.body.data[1]
// Yes, this could be long
this.timeout(200000)
- utils.getVideosList(servers[1].url, function (err, res) {
+ videosUtils.getVideosList(servers[1].url, function (err, res) {
if (err) throw err
const video = res.body.data[2]
// Yes, this could be long
this.timeout(200000)
- utils.getVideosList(servers[0].url, function (err, res) {
+ videosUtils.getVideosList(servers[0].url, function (err, res) {
if (err) throw err
const video = res.body.data[3]
series([
function (next) {
- utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next)
+ videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next)
},
function (next) {
- utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next)
+ videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next)
}],
function (err) {
if (err) throw err
it('Should have videos 1 and 3 on each pod', function (done) {
each(servers, function (server, callback) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
// Keep the logs if the test failed
if (this.ok) {
- utils.flushTests(done)
+ serversUtils.flushTests(done)
} else {
done()
}
--- /dev/null
+'use strict'
+
+const chai = require('chai')
+const each = require('async/each')
+const expect = chai.expect
+const request = require('supertest')
+
+const loginUtils = require('../utils/login')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
+
+describe('Test requests stats', function () {
+ const path = '/api/v1/requests/stats'
+ let servers = []
+
+ function uploadVideo (server, callback) {
+ const name = 'my super video'
+ const description = 'my super description'
+ const tags = [ 'tag1', 'tag2' ]
+ const fixture = 'video_short.webm'
+
+ videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback)
+ }
+
+ function getRequestsStats (server, callback) {
+ request(server.url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + server.accessToken)
+ .expect(200)
+ .end(callback)
+ }
+
+ // ---------------------------------------------------------------
+
+ before(function (done) {
+ this.timeout(20000)
+ serversUtils.flushAndRunMultipleServers(2, function (serversRun, urlsRun) {
+ servers = serversRun
+
+ each(servers, function (server, callbackEach) {
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
+ if (err) return callbackEach(err)
+
+ server.accessToken = accessToken
+ callbackEach()
+ })
+ }, function (err) {
+ if (err) throw err
+
+ const server1 = servers[0]
+ podsUtils.makeFriends(server1.url, server1.accessToken, done)
+ })
+ })
+ })
+
+ it('Should have a correct timer', function (done) {
+ const server = servers[0]
+
+ getRequestsStats(server, function (err, res) {
+ if (err) throw err
+
+ const body = res.body
+ expect(body.remainingMilliSeconds).to.be.at.least(0)
+ expect(body.remainingMilliSeconds).to.be.at.most(10000)
+
+ done()
+ })
+ })
+
+ it('Should have the correct request', function (done) {
+ this.timeout(15000)
+
+ const server = servers[0]
+ // Ensure the requests of pod 1 won't be made
+ servers[1].app.kill()
+
+ uploadVideo(server, function (err) {
+ if (err) throw err
+
+ getRequestsStats(server, function (err, res) {
+ if (err) throw err
+
+ const body = res.body
+ expect(body.requests).to.have.lengthOf(1)
+
+ const request = body.requests[0]
+ expect(request.to).to.have.lengthOf(1)
+ expect(request.request.type).to.equal('add')
+
+ // Wait one cycle
+ setTimeout(done, 10000)
+ })
+ })
+ })
+
+ it('Should have the correct requests', function (done) {
+ const server = servers[0]
+
+ uploadVideo(server, function (err) {
+ if (err) throw err
+
+ getRequestsStats(server, function (err, res) {
+ if (err) throw err
+
+ const body = res.body
+ expect(body.requests).to.have.lengthOf(2)
+
+ const request = body.requests[1]
+ expect(request.to).to.have.lengthOf(1)
+ expect(request.request.type).to.equal('add')
+
+ done()
+ })
+ })
+ })
+
+ after(function (done) {
+ process.kill(-servers[0].app.pid)
+
+ if (this.ok) {
+ serversUtils.flushTests(done)
+ } else {
+ done()
+ }
+ })
+})
const pathUtils = require('path')
const series = require('async/series')
+const loginUtils = require('../utils/login')
+const miscsUtils = require('../utils/miscs')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
webtorrent.silent = true
-const utils = require('./utils')
-
describe('Test a single pod', function () {
let server = null
let videoId = -1
series([
function (next) {
- utils.flushTests(next)
+ serversUtils.flushTests(next)
},
function (next) {
- utils.runServer(1, function (server1) {
+ serversUtils.runServer(1, function (server1) {
server = server1
next()
})
},
function (next) {
- utils.loginAndGetAccessToken(server, function (err, token) {
+ loginUtils.loginAndGetAccessToken(server, function (err, token) {
if (err) throw err
server.accessToken = token
next()
})
it('Should not have videos', function (done) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(0)
const description = 'my super description'
const tags = [ 'tag1', 'tag2', 'tag3' ]
const file = 'video_short.webm'
- utils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done)
+ videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done)
})
it('Should seed the uploaded video', function (done) {
// Yes, this could be long
this.timeout(60000)
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(1)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
- utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
expect(torrent.files.length).to.equal(1)
expect(torrent.files[0].path).to.exist.and.to.not.equal('')
- // We remove it because we'll add it again
- webtorrent.remove(video.magnetUri, done)
+ done()
})
})
})
// Yes, this could be long
this.timeout(60000)
- utils.getVideo(server.url, videoId, function (err, res) {
+ videosUtils.getVideo(server.url, videoId, function (err, res) {
if (err) throw err
const video = res.body
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
- utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
- webtorrent.add(video.magnetUri, function (torrent) {
- expect(torrent.files).to.exist
- expect(torrent.files.length).to.equal(1)
- expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
- done()
- })
+ done()
})
})
})
it('Should search the video by name by default', function (done) {
- utils.searchVideo(server.url, 'my', function (err, res) {
+ videosUtils.searchVideo(server.url, 'my', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(1)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
- utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
})
it('Should search the video by podUrl', function (done) {
- utils.searchVideo(server.url, '9001', 'podUrl', function (err, res) {
+ videosUtils.searchVideo(server.url, '9001', 'podUrl', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(1)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
- utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
})
it('Should search the video by tag', function (done) {
- utils.searchVideo(server.url, 'tag1', 'tags', function (err, res) {
+ videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(1)
expect(video.author).to.equal('root')
expect(video.isLocal).to.be.true
expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
- expect(utils.dateIsValid(video.createdDate)).to.be.true
+ expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true
- utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
})
it('Should not find a search by name by default', function (done) {
- utils.searchVideo(server.url, 'hello', function (err, res) {
+ videosUtils.searchVideo(server.url, 'hello', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(0)
})
it('Should not find a search by author', function (done) {
- utils.searchVideo(server.url, 'hello', 'author', function (err, res) {
+ videosUtils.searchVideo(server.url, 'hello', 'author', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(0)
})
it('Should not find a search by tag', function (done) {
- utils.searchVideo(server.url, 'tag', 'tags', function (err, res) {
+ videosUtils.searchVideo(server.url, 'tag', 'tags', function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(0)
})
it('Should remove the video', function (done) {
- utils.removeVideo(server.url, server.accessToken, videoId, function (err) {
+ videosUtils.removeVideo(server.url, server.accessToken, videoId, function (err) {
if (err) throw err
fs.readdir(pathUtils.join(__dirname, '../../../test1/uploads/'), function (err, files) {
})
it('Should not have videos', function (done) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(0)
const description = video + ' description'
const tags = [ 'tag1', 'tag2', 'tag3' ]
- utils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach)
+ videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach)
}, done)
})
it('Should have the correct durations', function (done) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
expect(res.body.total).to.equal(6)
})
it('Should have the correct thumbnails', function (done) {
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const videos = res.body.data
if (err) throw err
const videoName = video.name.replace(' name', '')
- utils.testImage(server.url, videoName, video.thumbnailPath, function (err, test) {
+ videosUtils.testVideoImage(server.url, videoName, video.thumbnailPath, function (err, test) {
if (err) throw err
expect(test).to.equal(true)
})
it('Should list only the two first videos', function (done) {
- utils.getVideosListPagination(server.url, 0, 2, function (err, res) {
+ videosUtils.getVideosListPagination(server.url, 0, 2, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should list only the next three videos', function (done) {
- utils.getVideosListPagination(server.url, 2, 3, function (err, res) {
+ videosUtils.getVideosListPagination(server.url, 2, 3, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should list the last video', function (done) {
- utils.getVideosListPagination(server.url, 5, 6, function (err, res) {
+ videosUtils.getVideosListPagination(server.url, 5, 6, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search the first video', function (done) {
- utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search the last two videos', function (done) {
- utils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search all the webm videos', function (done) {
- utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search all the root author videos', function (done) {
- utils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search all the 9001 port videos', function (done) {
- utils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search all the localhost videos', function (done) {
- utils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
it('Should search the good magnetUri video', function (done) {
const video = videosListBase[0]
- utils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) {
+ videosUtils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should list and sort by name in descending order', function (done) {
- utils.getVideosListSort(server.url, '-name', function (err, res) {
+ videosUtils.getVideosListSort(server.url, '-name', function (err, res) {
if (err) throw err
const videos = res.body.data
})
it('Should search and sort by name in ascending order', function (done) {
- utils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) {
+ videosUtils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) {
if (err) throw err
const videos = res.body.data
// Keep the logs if the test failed
if (this.ok) {
- utils.flushTests(done)
+ serversUtils.flushTests(done)
} else {
done()
}
const pathUtils = require('path')
const series = require('async/series')
+const loginUtils = require('../utils/login')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const usersUtils = require('../utils/users')
+const videosUtils = require('../utils/videos')
const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
webtorrent.silent = true
-const utils = require('./utils')
-
describe('Test users', function () {
let server = null
let accessToken = null
- let videoId
+ let accessTokenUser = null
+ let videoId = null
+ let userId = null
before(function (done) {
this.timeout(20000)
series([
function (next) {
- utils.flushTests(next)
+ serversUtils.flushTests(next)
},
function (next) {
- utils.runServer(1, function (server1) {
+ serversUtils.runServer(1, function (server1) {
server = server1
next()
})
it('Should not login with an invalid client id', function (done) {
const client = { id: 'client', password: server.client.secret }
- utils.login(server.url, client, server.user, 400, function (err, res) {
+ loginUtils.login(server.url, client, server.user, 400, function (err, res) {
if (err) throw err
expect(res.body.error).to.equal('invalid_client')
it('Should not login with an invalid client password', function (done) {
const client = { id: server.client.id, password: 'coucou' }
- utils.login(server.url, client, server.user, 400, function (err, res) {
+ loginUtils.login(server.url, client, server.user, 400, function (err, res) {
if (err) throw err
expect(res.body.error).to.equal('invalid_client')
it('Should not login with an invalid username', function (done) {
const user = { username: 'captain crochet', password: server.user.password }
- utils.login(server.url, server.client, user, 400, function (err, res) {
+ loginUtils.login(server.url, server.client, user, 400, function (err, res) {
if (err) throw err
expect(res.body.error).to.equal('invalid_grant')
it('Should not login with an invalid password', function (done) {
const user = { username: server.user.username, password: 'mewthree' }
- utils.login(server.url, server.client, user, 400, function (err, res) {
+ loginUtils.login(server.url, server.client, user, 400, function (err, res) {
if (err) throw err
expect(res.body.error).to.equal('invalid_grant')
const description = 'my super description'
const tags = [ 'tag1', 'tag2' ]
const video = 'video_short.webm'
- utils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done)
+ videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done)
})
it('Should not be able to make friends', function (done) {
accessToken = 'mysupertoken'
- utils.makeFriends(server.url, accessToken, 401, done)
+ podsUtils.makeFriends(server.url, accessToken, 401, done)
})
it('Should not be able to quit friends', function (done) {
accessToken = 'mysupertoken'
- utils.quitFriends(server.url, accessToken, 401, done)
+ podsUtils.quitFriends(server.url, accessToken, 401, done)
})
it('Should be able to login', function (done) {
- utils.login(server.url, server.client, server.user, 200, function (err, res) {
+ loginUtils.login(server.url, server.client, server.user, 200, function (err, res) {
if (err) throw err
accessToken = res.body.access_token
const description = 'my super description'
const tags = [ 'tag1', 'tag2' ]
const video = 'video_short.webm'
- utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) {
+ videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) {
if (err) throw err
- utils.getVideosList(server.url, function (err, res) {
+ videosUtils.getVideosList(server.url, function (err, res) {
if (err) throw err
const video = res.body.data[0]
const description = 'my super description 2'
const tags = [ 'tag1' ]
const video = 'video_short.webm'
- utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done)
+ videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done)
})
it('Should not be able to remove the video with an incorrect token', function (done) {
- utils.removeVideo(server.url, 'bad_token', videoId, 401, done)
+ videosUtils.removeVideo(server.url, 'bad_token', videoId, 401, done)
})
it('Should not be able to remove the video with the token of another account')
it('Should be able to remove the video with the correct token', function (done) {
- utils.removeVideo(server.url, accessToken, videoId, done)
+ videosUtils.removeVideo(server.url, accessToken, videoId, done)
})
it('Should logout (revoke token)')
it('Should be able to upload a video again')
+ it('Should be able to create a new user', function (done) {
+ usersUtils.createUser(server.url, accessToken, 'user_1', 'super password', done)
+ })
+
+ it('Should be able to login with this user', function (done) {
+ server.user = {
+ username: 'user_1',
+ password: 'super password'
+ }
+
+ loginUtils.loginAndGetAccessToken(server, function (err, token) {
+ if (err) throw err
+
+ accessTokenUser = token
+
+ done()
+ })
+ })
+
+ it('Should be able to get the user informations', function (done) {
+ usersUtils.getUserInformation(server.url, accessTokenUser, function (err, res) {
+ if (err) throw err
+
+ const user = res.body
+
+ expect(user.username).to.equal('user_1')
+ expect(user.id).to.exist
+
+ done()
+ })
+ })
+
+ it('Should be able to upload a video with this user', function (done) {
+ this.timeout(5000)
+
+ const name = 'my super name'
+ const description = 'my super description'
+ const tags = [ 'tag1', 'tag2', 'tag3' ]
+ const file = 'video_short.webm'
+ videosUtils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done)
+ })
+
+ it('Should list all the users', function (done) {
+ usersUtils.getUsersList(server.url, function (err, res) {
+ if (err) throw err
+
+ const result = res.body
+ const total = result.total
+ const users = result.data
+
+ expect(total).to.equal(2)
+ expect(users).to.be.an('array')
+ expect(users.length).to.equal(2)
+
+ const user = users[0]
+ expect(user.username).to.equal('user_1')
+
+ const rootUser = users[1]
+ expect(rootUser.username).to.equal('root')
+ userId = user.id
+
+ done()
+ })
+ })
+
+ it('Should list only the first user by username asc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, 'username', function (err, res) {
+ if (err) throw err
+
+ const result = res.body
+ const total = result.total
+ const users = result.data
+
+ expect(total).to.equal(2)
+ expect(users.length).to.equal(1)
+
+ const user = users[0]
+ expect(user.username).to.equal('root')
+
+ done()
+ })
+ })
+
+ it('Should list only the first user by username desc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-username', function (err, res) {
+ if (err) throw err
+
+ const result = res.body
+ const total = result.total
+ const users = result.data
+
+ expect(total).to.equal(2)
+ expect(users.length).to.equal(1)
+
+ const user = users[0]
+ expect(user.username).to.equal('user_1')
+
+ done()
+ })
+ })
+
+ it('Should list only the second user by createdDate desc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) {
+ if (err) throw err
+
+ const result = res.body
+ const total = result.total
+ const users = result.data
+
+ expect(total).to.equal(2)
+ expect(users.length).to.equal(1)
+
+ const user = users[0]
+ expect(user.username).to.equal('user_1')
+
+ done()
+ })
+ })
+
+ it('Should list all the users by createdDate asc', function (done) {
+ usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) {
+ if (err) throw err
+
+ const result = res.body
+ const total = result.total
+ const users = result.data
+
+ expect(total).to.equal(2)
+ expect(users.length).to.equal(2)
+
+ expect(users[0].username).to.equal('root')
+ expect(users[1].username).to.equal('user_1')
+
+ done()
+ })
+ })
+
+ it('Should update the user password', function (done) {
+ usersUtils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) {
+ if (err) throw err
+
+ server.user.password = 'new password'
+ loginUtils.login(server.url, server.client, server.user, 200, done)
+ })
+ })
+
+ it('Should be able to remove this user', function (done) {
+ usersUtils.removeUser(server.url, userId, accessToken, done)
+ })
+
+ it('Should not be able to login with this user', function (done) {
+ // server.user is already set to user 1
+ loginUtils.login(server.url, server.client, server.user, 400, done)
+ })
+
+ it('Should not have videos of this user', function (done) {
+ videosUtils.getVideosList(server.url, function (err, res) {
+ if (err) throw err
+
+ expect(res.body.total).to.equal(1)
+ const video = res.body.data[0]
+ expect(video.author).to.equal('root')
+
+ done()
+ })
+ })
+
after(function (done) {
process.kill(-server.app.pid)
// Keep the logs if the test failed
if (this.ok) {
- utils.flushTests(done)
+ serversUtils.flushTests(done)
} else {
done()
}
+++ /dev/null
-'use strict'
-
-const childProcess = require('child_process')
-const exec = childProcess.exec
-const fork = childProcess.fork
-const fs = require('fs')
-const pathUtils = require('path')
-const request = require('supertest')
-
-const testUtils = {
- dateIsValid: dateIsValid,
- flushTests: flushTests,
- getAllVideosListBy: getAllVideosListBy,
- getClient: getClient,
- getFriendsList: getFriendsList,
- getVideo: getVideo,
- getVideosList: getVideosList,
- getVideosListPagination: getVideosListPagination,
- getVideosListSort: getVideosListSort,
- login: login,
- loginAndGetAccessToken: loginAndGetAccessToken,
- makeFriends: makeFriends,
- quitFriends: quitFriends,
- removeVideo: removeVideo,
- flushAndRunMultipleServers: flushAndRunMultipleServers,
- runServer: runServer,
- searchVideo: searchVideo,
- searchVideoWithPagination: searchVideoWithPagination,
- searchVideoWithSort: searchVideoWithSort,
- testImage: testImage,
- uploadVideo: uploadVideo
-}
-
-// ---------------------- Export functions --------------------
-
-function dateIsValid (dateString) {
- const dateToCheck = new Date(dateString)
- const now = new Date()
-
- // Check if the interval is more than 2 minutes
- if (now - dateToCheck > 120000) return false
-
- return true
-}
-
-function flushTests (callback) {
- exec('npm run clean:server:test', callback)
-}
-
-function getAllVideosListBy (url, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path)
- .query({ sort: 'createdDate' })
- .query({ start: 0 })
- .query({ count: 10000 })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getClient (url, end) {
- const path = '/api/v1/users/client'
-
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getFriendsList (url, end) {
- const path = '/api/v1/pods/'
-
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getVideo (url, id, end) {
- const path = '/api/v1/videos/' + id
-
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getVideosList (url, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path)
- .query({ sort: 'name' })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getVideosListPagination (url, start, count, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path)
- .query({ start: start })
- .query({ count: count })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function getVideosListSort (url, sort, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path)
- .query({ sort: sort })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function login (url, client, user, expectedStatus, end) {
- if (!end) {
- end = expectedStatus
- expectedStatus = 200
- }
-
- const path = '/api/v1/users/token'
-
- const body = {
- client_id: client.id,
- client_secret: client.secret,
- username: user.username,
- password: user.password,
- response_type: 'code',
- grant_type: 'password',
- scope: 'upload'
- }
-
- request(url)
- .post(path)
- .type('form')
- .send(body)
- .expect(expectedStatus)
- .end(end)
-}
-
-function loginAndGetAccessToken (server, callback) {
- login(server.url, server.client, server.user, 200, function (err, res) {
- if (err) return callback(err)
-
- return callback(null, res.body.access_token)
- })
-}
-
-function makeFriends (url, accessToken, expectedStatus, callback) {
- if (!callback) {
- callback = expectedStatus
- expectedStatus = 204
- }
-
- const path = '/api/v1/pods/makefriends'
-
- // The first pod make friend with the third
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + accessToken)
- .expect(expectedStatus)
- .end(function (err, res) {
- if (err) throw err
-
- // Wait for the request between pods
- setTimeout(callback, 1000)
- })
-}
-
-function quitFriends (url, accessToken, expectedStatus, callback) {
- if (!callback) {
- callback = expectedStatus
- expectedStatus = 204
- }
-
- const path = '/api/v1/pods/quitfriends'
-
- // The first pod make friend with the third
- request(url)
- .get(path)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + accessToken)
- .expect(expectedStatus)
- .end(function (err, res) {
- if (err) throw err
-
- // Wait for the request between pods
- setTimeout(callback, 1000)
- })
-}
-
-function removeVideo (url, token, id, expectedStatus, end) {
- if (!end) {
- end = expectedStatus
- expectedStatus = 204
- }
-
- const path = '/api/v1/videos'
-
- request(url)
- .delete(path + '/' + id)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + token)
- .expect(expectedStatus)
- .end(end)
-}
-
-function flushAndRunMultipleServers (totalServers, serversRun) {
- let apps = []
- let urls = []
- let i = 0
-
- function anotherServerDone (number, app, url) {
- apps[number - 1] = app
- urls[number - 1] = url
- i++
- if (i === totalServers) {
- serversRun(apps, urls)
- }
- }
-
- flushTests(function () {
- for (let j = 1; j <= totalServers; j++) {
- // For the virtual buffer
- setTimeout(function () {
- runServer(j, function (app, url) {
- anotherServerDone(j, app, url)
- })
- }, 1000 * j)
- }
- })
-}
-
-function runServer (number, callback) {
- const server = {
- app: null,
- url: `http://localhost:${9000 + number}`,
- client: {
- id: null,
- secret: null
- },
- user: {
- username: null,
- password: null
- }
- }
-
- // These actions are async so we need to be sure that they have both been done
- const serverRunString = {
- 'Connected to mongodb': false,
- 'Server listening on port': false
- }
-
- const regexps = {
- client_id: 'Client id: ([a-f0-9]+)',
- client_secret: 'Client secret: (.+)',
- user_username: 'Username: (.+)',
- user_password: 'User password: (.+)'
- }
-
- // Share the environment
- const env = Object.create(process.env)
- env.NODE_ENV = 'test'
- env.NODE_APP_INSTANCE = number
- const options = {
- silent: true,
- env: env,
- detached: true
- }
-
- server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options)
- server.app.stdout.on('data', function onStdout (data) {
- let dontContinue = false
-
- // Capture things if we want to
- for (const key of Object.keys(regexps)) {
- const regexp = regexps[key]
- const matches = data.toString().match(regexp)
- if (matches !== null) {
- if (key === 'client_id') server.client.id = matches[1]
- else if (key === 'client_secret') server.client.secret = matches[1]
- else if (key === 'user_username') server.user.username = matches[1]
- else if (key === 'user_password') server.user.password = matches[1]
- }
- }
-
- // Check if all required sentences are here
- for (const key of Object.keys(serverRunString)) {
- if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
- if (serverRunString[key] === false) dontContinue = true
- }
-
- // If no, there is maybe one thing not already initialized (mongodb...)
- if (dontContinue === true) return
-
- server.app.stdout.removeListener('data', onStdout)
- callback(server)
- })
-}
-
-function searchVideo (url, search, field, end) {
- if (!end) {
- end = field
- field = null
- }
-
- const path = '/api/v1/videos'
- const req = request(url)
- .get(path + '/search/' + search)
- .set('Accept', 'application/json')
-
- if (field) req.query({ field: field })
- req.expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function searchVideoWithPagination (url, search, field, start, count, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path + '/search/' + search)
- .query({ start: start })
- .query({ count: count })
- .query({ field: field })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function searchVideoWithSort (url, search, sort, end) {
- const path = '/api/v1/videos'
-
- request(url)
- .get(path + '/search/' + search)
- .query({ sort: sort })
- .set('Accept', 'application/json')
- .expect(200)
- .expect('Content-Type', /json/)
- .end(end)
-}
-
-function testImage (url, videoName, imagePath, callback) {
- // Don't test images if the node env is not set
- // Because we need a special ffmpeg version for this test
- if (process.env.NODE_TEST_IMAGE) {
- request(url)
- .get(imagePath)
- .expect(200)
- .end(function (err, res) {
- if (err) return callback(err)
-
- fs.readFile(pathUtils.join(__dirname, 'fixtures', videoName + '.jpg'), function (err, data) {
- if (err) return callback(err)
-
- callback(null, data.equals(res.body))
- })
- })
- } else {
- console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.')
- callback(null, true)
- }
-}
-
-function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) {
- if (!end) {
- end = specialStatus
- specialStatus = 204
- }
-
- const path = '/api/v1/videos'
-
- const req = request(url)
- .post(path)
- .set('Accept', 'application/json')
- .set('Authorization', 'Bearer ' + accessToken)
- .field('name', name)
- .field('description', description)
-
- for (let i = 0; i < tags.length; i++) {
- req.field('tags[' + i + ']', tags[i])
- }
-
- let filepath = ''
- if (pathUtils.isAbsolute(fixture)) {
- filepath = fixture
- } else {
- filepath = pathUtils.join(__dirname, 'fixtures', fixture)
- }
-
- req.attach('videofile', filepath)
- .expect(specialStatus)
- .end(end)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = testUtils
'use strict'
-const each = require('each')
+const each = require('async/each')
const isEqual = require('lodash/isEqual')
const program = require('commander')
const series = require('async/series')
process.env.NODE_ENV = 'test'
const constants = require('../../initializers/constants')
-const utils = require('../api/utils')
+const loginUtils = require('../utils/login')
+const podsUtils = require('../utils/pods')
+const serversUtils = require('../utils/servers')
+const videosUtils = require('../utils/videos')
program
.option('-c, --create [weight]', 'Weight for creating videos')
series([
// Run servers
function (next) {
- utils.flushAndRunMultipleServers(numberOfPods, function (serversRun) {
+ serversUtils.flushAndRunMultipleServers(numberOfPods, function (serversRun) {
servers = serversRun
next()
})
// Get the access tokens
function (next) {
each(servers, function (server, callbackEach) {
- utils.loginAndGetAccessToken(server, function (err, accessToken) {
+ loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
if (err) return callbackEach(err)
server.accessToken = accessToken
},
function (next) {
const server = servers[1]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
const server = servers[0]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
setTimeout(next, 1000)
},
function (next) {
const server = servers[3]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
const server = servers[5]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
const server = servers[4]
- utils.makeFriends(server.url, server.accessToken, next)
+ podsUtils.makeFriends(server.url, server.accessToken, next)
},
function (next) {
setTimeout(next, 1000)
if (server.app) process.kill(-server.app.pid)
})
- if (flushAtExit) utils.flushTests(callback)
+ if (flushAtExit) serversUtils.flushTests(callback)
}
function upload (servers, numServer, callback) {
console.log('Upload video to server ' + numServer)
- utils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback)
+ videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback)
}
function remove (servers, numServer, callback) {
if (!callback) callback = function () {}
- utils.getVideosList(servers[numServer].url, function (err, res) {
+ videosUtils.getVideosList(servers[numServer].url, function (err, res) {
if (err) throw err
const videos = res.body.data
const toRemove = videos[getRandomInt(0, videos.length)].id
console.log('Removing video from server ' + numServer)
- utils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback)
+ videosUtils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback)
})
}
function checkIntegrity (servers, callback) {
const videos = []
each(servers, function (server, callback) {
- utils.getAllVideosListBy(server.url, function (err, res) {
+ videosUtils.getAllVideosListBy(server.url, function (err, res) {
if (err) throw err
const serverVideos = res.body.data
for (const serverVideo of serverVideos) {
--- /dev/null
+'use strict'
+
+const request = require('supertest')
+
+const clientsUtils = {
+ getClient: getClient
+}
+
+// ---------------------- Export functions --------------------
+
+function getClient (url, end) {
+ const path = '/api/v1/users/client'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = clientsUtils
--- /dev/null
+'use strict'
+
+const request = require('supertest')
+
+const loginUtils = {
+ login,
+ loginAndGetAccessToken
+}
+
+// ---------------------- Export functions --------------------
+
+function login (url, client, user, expectedStatus, end) {
+ if (!end) {
+ end = expectedStatus
+ expectedStatus = 200
+ }
+
+ const path = '/api/v1/users/token'
+
+ const body = {
+ client_id: client.id,
+ client_secret: client.secret,
+ username: user.username,
+ password: user.password,
+ response_type: 'code',
+ grant_type: 'password',
+ scope: 'upload'
+ }
+
+ request(url)
+ .post(path)
+ .type('form')
+ .send(body)
+ .expect(expectedStatus)
+ .end(end)
+}
+
+function loginAndGetAccessToken (server, callback) {
+ login(server.url, server.client, server.user, 200, function (err, res) {
+ if (err) return callback(err)
+
+ return callback(null, res.body.access_token)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = loginUtils
--- /dev/null
+'use strict'
+
+const miscsUtils = {
+ dateIsValid
+}
+
+// ---------------------- Export functions --------------------
+
+function dateIsValid (dateString) {
+ const dateToCheck = new Date(dateString)
+ const now = new Date()
+
+ // Check if the interval is more than 2 minutes
+ if (now - dateToCheck > 120000) return false
+
+ return true
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = miscsUtils
--- /dev/null
+'use strict'
+
+const request = require('supertest')
+
+const podsUtils = {
+ getFriendsList,
+ makeFriends,
+ quitFriends
+}
+
+// ---------------------- Export functions --------------------
+
+function getFriendsList (url, end) {
+ const path = '/api/v1/pods/'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function makeFriends (url, accessToken, expectedStatus, end) {
+ if (!end) {
+ end = expectedStatus
+ expectedStatus = 204
+ }
+
+ // Which pod makes friends with which pod
+ const friendsMatrix = {
+ 'http://localhost:9001': [
+ 'http://localhost:9002'
+ ],
+ 'http://localhost:9002': [
+ 'http://localhost:9003'
+ ],
+ 'http://localhost:9003': [
+ 'http://localhost:9001'
+ ],
+ 'http://localhost:9004': [
+ 'http://localhost:9002'
+ ],
+ 'http://localhost:9005': [
+ 'http://localhost:9001',
+ 'http://localhost:9004'
+ ],
+ 'http://localhost:9006': [
+ 'http://localhost:9001',
+ 'http://localhost:9002',
+ 'http://localhost:9003'
+ ]
+ }
+ const path = '/api/v1/pods/makefriends'
+
+ // The first pod make friend with the third
+ request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .send({ 'urls': friendsMatrix[url] })
+ .expect(expectedStatus)
+ .end(function (err, res) {
+ if (err) throw err
+
+ // Wait for the request between pods
+ setTimeout(end, 1000)
+ })
+}
+
+function quitFriends (url, accessToken, expectedStatus, end) {
+ if (!end) {
+ end = expectedStatus
+ expectedStatus = 204
+ }
+
+ const path = '/api/v1/pods/quitfriends'
+
+ // The first pod make friend with the third
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .expect(expectedStatus)
+ .end(function (err, res) {
+ if (err) throw err
+
+ // Wait for the request between pods
+ setTimeout(end, 1000)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = podsUtils
--- /dev/null
+'use strict'
+
+const request = require('supertest')
+
+const requestsUtils = {
+ makePostUploadRequest,
+ makePostBodyRequest,
+ makePutBodyRequest
+}
+
+// ---------------------- Export functions --------------------
+
+function makePostUploadRequest (url, path, token, fields, attaches, done, statusCodeExpected) {
+ if (!statusCodeExpected) statusCodeExpected = 400
+
+ const req = request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+
+ if (token) req.set('Authorization', 'Bearer ' + token)
+
+ Object.keys(fields).forEach(function (field) {
+ const value = fields[field]
+
+ if (Array.isArray(value)) {
+ for (let i = 0; i < value.length; i++) {
+ req.field(field + '[' + i + ']', value[i])
+ }
+ } else {
+ req.field(field, value)
+ }
+ })
+
+ Object.keys(attaches).forEach(function (attach) {
+ const value = attaches[attach]
+ req.attach(attach, value)
+ })
+
+ req.expect(statusCodeExpected, done)
+}
+
+function makePostBodyRequest (url, path, token, fields, done, statusCodeExpected) {
+ if (!statusCodeExpected) statusCodeExpected = 400
+
+ const req = request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+
+ if (token) req.set('Authorization', 'Bearer ' + token)
+
+ req.send(fields).expect(statusCodeExpected, done)
+}
+
+function makePutBodyRequest (url, path, token, fields, done, statusCodeExpected) {
+ if (!statusCodeExpected) statusCodeExpected = 400
+
+ const req = request(url)
+ .put(path)
+ .set('Accept', 'application/json')
+
+ if (token) req.set('Authorization', 'Bearer ' + token)
+
+ req.send(fields).expect(statusCodeExpected, done)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = requestsUtils
--- /dev/null
+'use strict'
+
+const childProcess = require('child_process')
+const exec = childProcess.exec
+const fork = childProcess.fork
+const pathUtils = require('path')
+
+const serversUtils = {
+ flushAndRunMultipleServers,
+ flushTests,
+ runServer
+}
+
+// ---------------------- Export functions --------------------
+
+function flushAndRunMultipleServers (totalServers, serversRun) {
+ let apps = []
+ let urls = []
+ let i = 0
+
+ function anotherServerDone (number, app, url) {
+ apps[number - 1] = app
+ urls[number - 1] = url
+ i++
+ if (i === totalServers) {
+ serversRun(apps, urls)
+ }
+ }
+
+ flushTests(function () {
+ for (let j = 1; j <= totalServers; j++) {
+ // For the virtual buffer
+ setTimeout(function () {
+ runServer(j, function (app, url) {
+ anotherServerDone(j, app, url)
+ })
+ }, 1000 * j)
+ }
+ })
+}
+
+function flushTests (callback) {
+ exec('npm run clean:server:test', callback)
+}
+
+function runServer (number, callback) {
+ const server = {
+ app: null,
+ url: `http://localhost:${9000 + number}`,
+ client: {
+ id: null,
+ secret: null
+ },
+ user: {
+ username: null,
+ password: null
+ }
+ }
+
+ // These actions are async so we need to be sure that they have both been done
+ const serverRunString = {
+ 'Connected to mongodb': false,
+ 'Server listening on port': false
+ }
+
+ const regexps = {
+ client_id: 'Client id: ([a-f0-9]+)',
+ client_secret: 'Client secret: (.+)',
+ user_username: 'Username: (.+)',
+ user_password: 'User password: (.+)'
+ }
+
+ // Share the environment
+ const env = Object.create(process.env)
+ env.NODE_ENV = 'test'
+ env.NODE_APP_INSTANCE = number
+ const options = {
+ silent: true,
+ env: env,
+ detached: true
+ }
+
+ server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options)
+ server.app.stdout.on('data', function onStdout (data) {
+ let dontContinue = false
+
+ // Capture things if we want to
+ for (const key of Object.keys(regexps)) {
+ const regexp = regexps[key]
+ const matches = data.toString().match(regexp)
+ if (matches !== null) {
+ if (key === 'client_id') server.client.id = matches[1]
+ else if (key === 'client_secret') server.client.secret = matches[1]
+ else if (key === 'user_username') server.user.username = matches[1]
+ else if (key === 'user_password') server.user.password = matches[1]
+ }
+ }
+
+ // Check if all required sentences are here
+ for (const key of Object.keys(serverRunString)) {
+ if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
+ if (serverRunString[key] === false) dontContinue = true
+ }
+
+ // If no, there is maybe one thing not already initialized (mongodb...)
+ if (dontContinue === true) return
+
+ server.app.stdout.removeListener('data', onStdout)
+ callback(server)
+ })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = serversUtils
--- /dev/null
+'use strict'
+
+const request = require('supertest')
+
+const usersUtils = {
+ createUser,
+ getUserInformation,
+ getUsersList,
+ getUsersListPaginationAndSort,
+ removeUser,
+ updateUser
+}
+
+// ---------------------- Export functions --------------------
+
+function createUser (url, accessToken, username, password, specialStatus, end) {
+ if (!end) {
+ end = specialStatus
+ specialStatus = 204
+ }
+
+ const path = '/api/v1/users'
+
+ request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .send({ username: username, password: password })
+ .expect(specialStatus)
+ .end(end)
+}
+
+function getUserInformation (url, accessToken, end) {
+ const path = '/api/v1/users/me'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getUsersList (url, end) {
+ const path = '/api/v1/users'
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getUsersListPaginationAndSort (url, start, count, sort, end) {
+ const path = '/api/v1/users'
+
+ request(url)
+ .get(path)
+ .query({ start: start })
+ .query({ count: count })
+ .query({ sort: sort })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function removeUser (url, userId, accessToken, expectedStatus, end) {
+ if (!end) {
+ end = expectedStatus
+ expectedStatus = 204
+ }
+
+ const path = '/api/v1/users'
+
+ request(url)
+ .delete(path + '/' + userId)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .expect(expectedStatus)
+ .end(end)
+}
+
+function updateUser (url, userId, accessToken, newPassword, end) {
+ const path = '/api/v1/users/' + userId
+
+ request(url)
+ .put(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .send({ password: newPassword })
+ .expect(204)
+ .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = usersUtils
--- /dev/null
+'use strict'
+
+const fs = require('fs')
+const pathUtils = require('path')
+const request = require('supertest')
+
+const videosUtils = {
+ getAllVideosListBy,
+ getVideo,
+ getVideosList,
+ getVideosListPagination,
+ getVideosListSort,
+ removeVideo,
+ searchVideo,
+ searchVideoWithPagination,
+ searchVideoWithSort,
+ testVideoImage,
+ uploadVideo
+}
+
+// ---------------------- Export functions --------------------
+
+function getAllVideosListBy (url, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path)
+ .query({ sort: 'createdDate' })
+ .query({ start: 0 })
+ .query({ count: 10000 })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getVideo (url, id, end) {
+ const path = '/api/v1/videos/' + id
+
+ request(url)
+ .get(path)
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getVideosList (url, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path)
+ .query({ sort: 'name' })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getVideosListPagination (url, start, count, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path)
+ .query({ start: start })
+ .query({ count: count })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function getVideosListSort (url, sort, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path)
+ .query({ sort: sort })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function removeVideo (url, token, id, expectedStatus, end) {
+ if (!end) {
+ end = expectedStatus
+ expectedStatus = 204
+ }
+
+ const path = '/api/v1/videos'
+
+ request(url)
+ .delete(path + '/' + id)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + token)
+ .expect(expectedStatus)
+ .end(end)
+}
+
+function searchVideo (url, search, field, end) {
+ if (!end) {
+ end = field
+ field = null
+ }
+
+ const path = '/api/v1/videos'
+ const req = request(url)
+ .get(path + '/search/' + search)
+ .set('Accept', 'application/json')
+
+ if (field) req.query({ field: field })
+ req.expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function searchVideoWithPagination (url, search, field, start, count, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path + '/search/' + search)
+ .query({ start: start })
+ .query({ count: count })
+ .query({ field: field })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function searchVideoWithSort (url, search, sort, end) {
+ const path = '/api/v1/videos'
+
+ request(url)
+ .get(path + '/search/' + search)
+ .query({ sort: sort })
+ .set('Accept', 'application/json')
+ .expect(200)
+ .expect('Content-Type', /json/)
+ .end(end)
+}
+
+function testVideoImage (url, videoName, imagePath, callback) {
+ // Don't test images if the node env is not set
+ // Because we need a special ffmpeg version for this test
+ if (process.env.NODE_TEST_IMAGE) {
+ request(url)
+ .get(imagePath)
+ .expect(200)
+ .end(function (err, res) {
+ if (err) return callback(err)
+
+ fs.readFile(pathUtils.join(__dirname, '..', 'api', 'fixtures', videoName + '.jpg'), function (err, data) {
+ if (err) return callback(err)
+
+ callback(null, data.equals(res.body))
+ })
+ })
+ } else {
+ console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.')
+ callback(null, true)
+ }
+}
+
+function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) {
+ if (!end) {
+ end = specialStatus
+ specialStatus = 204
+ }
+
+ const path = '/api/v1/videos'
+
+ const req = request(url)
+ .post(path)
+ .set('Accept', 'application/json')
+ .set('Authorization', 'Bearer ' + accessToken)
+ .field('name', name)
+ .field('description', description)
+
+ for (let i = 0; i < tags.length; i++) {
+ req.field('tags[' + i + ']', tags[i])
+ }
+
+ let filepath = ''
+ if (pathUtils.isAbsolute(fixture)) {
+ filepath = fixture
+ } else {
+ filepath = pathUtils.join(__dirname, '..', 'api', 'fixtures', fixture)
+ }
+
+ req.attach('videofile', filepath)
+ .expect(specialStatus)
+ .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = videosUtils