diff options
186 files changed, 5670 insertions, 2501 deletions
diff --git a/.gitignore b/.gitignore index ec5b4b2f0..7ca89dca8 100644 --- a/.gitignore +++ b/.gitignore | |||
@@ -14,4 +14,11 @@ uploads | |||
14 | thumbnails | 14 | thumbnails |
15 | config/production.yaml | 15 | config/production.yaml |
16 | ffmpeg | 16 | ffmpeg |
17 | <<<<<<< HEAD | ||
17 | torrents | 18 | torrents |
19 | ======= | ||
20 | .tags | ||
21 | *.sublime-project | ||
22 | *.sublime-workspace | ||
23 | torrents/ | ||
24 | >>>>>>> master | ||
diff --git a/.travis.yml b/.travis.yml index e6a92d883..7b025f0b9 100644 --- a/.travis.yml +++ b/.travis.yml | |||
@@ -1,8 +1,8 @@ | |||
1 | language: node_js | 1 | language: node_js |
2 | 2 | ||
3 | node_js: | 3 | node_js: |
4 | - "4.4" | 4 | - "4.5" |
5 | - "6.2" | 5 | - "6.6" |
6 | 6 | ||
7 | env: | 7 | env: |
8 | - CXX=g++-4.8 | 8 | - CXX=g++-4.8 |
@@ -19,8 +19,10 @@ sudo: false | |||
19 | services: | 19 | services: |
20 | - mongodb | 20 | - mongodb |
21 | 21 | ||
22 | before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi | ||
23 | |||
22 | before_script: | 24 | before_script: |
23 | - npm install electron-prebuilt -g | 25 | - npm install electron -g |
24 | - npm run build | 26 | - npm run build |
25 | - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-3.0.2-64bit-static.tar.xz" | 27 | - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-3.0.2-64bit-static.tar.xz" |
26 | - tar xf ffmpeg-release-3.0.2-64bit-static.tar.xz | 28 | - tar xf ffmpeg-release-3.0.2-64bit-static.tar.xz |
@@ -60,6 +60,7 @@ Want to see in action? | |||
60 | 60 | ||
61 | * 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) | 61 | * 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) |
62 | * You can find [a video](https://vimeo.com/164881662 "Yes Vimeo, please don't judge me") to see how the "decentralization feature" looks like | 62 | * You can find [a video](https://vimeo.com/164881662 "Yes Vimeo, please don't judge me") to see how the "decentralization feature" looks like |
63 | * 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. | ||
63 | 64 | ||
64 | ## Why | 65 | ## Why |
65 | 66 | ||
@@ -95,10 +96,12 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t | |||
95 | - [ ] Validate the prototype (test PeerTube in a real world with many pods and videos) | 96 | - [ ] Validate the prototype (test PeerTube in a real world with many pods and videos) |
96 | - [ ] Manage API breaks | 97 | - [ ] Manage API breaks |
97 | - [ ] Add "DDOS" security (check if a pod don't send too many requests for example) | 98 | - [ ] Add "DDOS" security (check if a pod don't send too many requests for example) |
98 | - [ ] Admin panel | 99 | - [X] Admin panel |
99 | - [ ] Stats about the network (how many friends, how many requests per hour...) | 100 | - [X] Stats |
100 | - [ ] Stats about videos | 101 | - [X] Friends list |
101 | - [ ] Manage users (create/remove) | 102 | - [X] Manage users (create/remove) |
103 | - [ ] User playlists | ||
104 | - [ ] User subscriptions (by tags, author...) | ||
102 | 105 | ||
103 | 106 | ||
104 | ## Installation | 107 | ## Installation |
@@ -111,6 +114,7 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t | |||
111 | ### Dependencies | 114 | ### Dependencies |
112 | 115 | ||
113 | * **NodeJS >= 4.2** | 116 | * **NodeJS >= 4.2** |
117 | * **npm >= 3.0** | ||
114 | * OpenSSL (cli) | 118 | * OpenSSL (cli) |
115 | * MongoDB | 119 | * MongoDB |
116 | * ffmpeg xvfb-run libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin (for electron) | 120 | * ffmpeg xvfb-run libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin (for electron) |
@@ -123,7 +127,8 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t | |||
123 | 127 | ||
124 | # apt-get update | 128 | # apt-get update |
125 | # apt-get install ffmpeg mongodb openssl xvfb curl sudo git build-essential libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin | 129 | # apt-get install ffmpeg mongodb openssl xvfb curl sudo git build-essential libgtk2.0-0 libgconf-2-4 libnss3 libasound2 libxtst6 libxss1 libnotify-bin |
126 | # npm install -g electron-prebuilt | 130 | # npm install -g npm@3 |
131 | # npm install -g electron | ||
127 | 132 | ||
128 | #### Other distribution... (PR welcome) | 133 | #### Other distribution... (PR welcome) |
129 | 134 | ||
@@ -160,6 +165,10 @@ Finally, run the server with the `production` `NODE_ENV` variable set. | |||
160 | 165 | ||
161 | $ NODE_ENV=production npm start | 166 | $ NODE_ENV=production npm start |
162 | 167 | ||
168 | **Nginx template** (reverse proxy): https://github.com/Chocobozzz/PeerTube/tree/master/support/nginx | ||
169 | |||
170 | **Systemd template**: https://github.com/Chocobozzz/PeerTube/tree/master/support/systemd | ||
171 | |||
163 | ### Other commands | 172 | ### Other commands |
164 | 173 | ||
165 | To print all available command run: | 174 | To print all available command run: |
diff --git a/client/config/helpers.js b/client/config/helpers.js index 24d7dae9f..6268d2628 100644 --- a/client/config/helpers.js +++ b/client/config/helpers.js | |||
@@ -8,10 +8,15 @@ function hasProcessFlag (flag) { | |||
8 | return process.argv.join('').indexOf(flag) > -1 | 8 | return process.argv.join('').indexOf(flag) > -1 |
9 | } | 9 | } |
10 | 10 | ||
11 | function isWebpackDevServer () { | ||
12 | return process.argv[1] && !!(/webpack-dev-server$/.exec(process.argv[1])) | ||
13 | } | ||
14 | |||
11 | function root (args) { | 15 | function root (args) { |
12 | args = Array.prototype.slice.call(arguments, 0) | 16 | args = Array.prototype.slice.call(arguments, 0) |
13 | return path.join.apply(path, [ROOT].concat(args)) | 17 | return path.join.apply(path, [ROOT].concat(args)) |
14 | } | 18 | } |
15 | 19 | ||
16 | exports.hasProcessFlag = hasProcessFlag | 20 | exports.hasProcessFlag = hasProcessFlag |
21 | exports.isWebpackDevServer = isWebpackDevServer | ||
17 | exports.root = root | 22 | exports.root = root |
diff --git a/client/config/webpack.common.js b/client/config/webpack.common.js index 2ff3a1506..882013a9e 100644 --- a/client/config/webpack.common.js +++ b/client/config/webpack.common.js | |||
@@ -5,9 +5,11 @@ const helpers = require('./helpers') | |||
5 | * Webpack Plugins | 5 | * Webpack Plugins |
6 | */ | 6 | */ |
7 | 7 | ||
8 | var CopyWebpackPlugin = (CopyWebpackPlugin = require('copy-webpack-plugin'), CopyWebpackPlugin.default || CopyWebpackPlugin) | 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') |
9 | const HtmlWebpackPlugin = require('html-webpack-plugin') | 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') |
10 | const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin | 10 | const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin |
11 | const AssetsPlugin = require('assets-webpack-plugin') | ||
12 | const ContextReplacementPlugin = require('webpack/lib/ContextReplacementPlugin') | ||
11 | const WebpackNotifierPlugin = require('webpack-notifier') | 13 | const WebpackNotifierPlugin = require('webpack-notifier') |
12 | 14 | ||
13 | /* | 15 | /* |
@@ -15,7 +17,8 @@ const WebpackNotifierPlugin = require('webpack-notifier') | |||
15 | */ | 17 | */ |
16 | const METADATA = { | 18 | const METADATA = { |
17 | title: 'PeerTube', | 19 | title: 'PeerTube', |
18 | baseUrl: '/' | 20 | baseUrl: '/', |
21 | isDevServer: helpers.isWebpackDevServer() | ||
19 | } | 22 | } |
20 | 23 | ||
21 | /* | 24 | /* |
@@ -23,247 +26,241 @@ const METADATA = { | |||
23 | * | 26 | * |
24 | * See: http://webpack.github.io/docs/configuration.html#cli | 27 | * See: http://webpack.github.io/docs/configuration.html#cli |
25 | */ | 28 | */ |
26 | module.exports = { | 29 | module.exports = function (options) { |
27 | /* | 30 | var isProd = options.env === 'production' |
28 | * Static metadata for index.html | ||
29 | * | ||
30 | * See: (custom attribute) | ||
31 | */ | ||
32 | metadata: METADATA, | ||
33 | 31 | ||
34 | /* | 32 | return { |
35 | * Cache generated modules and chunks to improve performance for multiple incremental builds. | 33 | /* |
36 | * This is enabled by default in watch mode. | 34 | * Static metadata for index.html |
37 | * You can pass false to disable it. | 35 | * |
38 | * | 36 | * See: (custom attribute) |
39 | * See: http://webpack.github.io/docs/configuration.html#cache | 37 | */ |
40 | */ | 38 | metadata: METADATA, |
41 | // cache: false, | ||
42 | |||
43 | /* | ||
44 | * The entry point for the bundle | ||
45 | * Our Angular.js app | ||
46 | * | ||
47 | * See: http://webpack.github.io/docs/configuration.html#entry | ||
48 | */ | ||
49 | entry: { | ||
50 | 'polyfills': './src/polyfills.ts', | ||
51 | 'vendor': './src/vendor.ts', | ||
52 | 'main': './src/main.ts' | ||
53 | }, | ||
54 | 39 | ||
55 | /* | ||
56 | * Options affecting the resolving of modules. | ||
57 | * | ||
58 | * See: http://webpack.github.io/docs/configuration.html#resolve | ||
59 | */ | ||
60 | resolve: { | ||
61 | /* | 40 | /* |
62 | * An array of extensions that should be used to resolve modules. | 41 | * Cache generated modules and chunks to improve performance for multiple incremental builds. |
42 | * This is enabled by default in watch mode. | ||
43 | * You can pass false to disable it. | ||
63 | * | 44 | * |
64 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions | 45 | * See: http://webpack.github.io/docs/configuration.html#cache |
65 | */ | 46 | */ |
66 | extensions: [ '', '.ts', '.js', '.scss' ], | 47 | // cache: false, |
67 | 48 | ||
68 | // Make sure root is src | 49 | /* |
69 | root: helpers.root('src'), | 50 | * The entry point for the bundle |
51 | * Our Angular.js app | ||
52 | * | ||
53 | * See: http://webpack.github.io/docs/configuration.html#entry | ||
54 | */ | ||
55 | entry: { | ||
56 | 'polyfills': './src/polyfills.ts', | ||
57 | 'vendor': './src/vendor.ts', | ||
58 | 'main': './src/main.ts' | ||
59 | }, | ||
70 | 60 | ||
71 | // remove other default values | 61 | /* |
72 | modulesDirectories: [ 'node_modules' ], | 62 | * Options affecting the resolving of modules. |
63 | * | ||
64 | * See: http://webpack.github.io/docs/configuration.html#resolve | ||
65 | */ | ||
66 | resolve: { | ||
67 | /* | ||
68 | * An array of extensions that should be used to resolve modules. | ||
69 | * | ||
70 | * See: http://webpack.github.io/docs/configuration.html#resolve-extensions | ||
71 | */ | ||
72 | extensions: [ '', '.ts', '.js', '.scss' ], | ||
73 | 73 | ||
74 | packageAlias: 'browser' | 74 | // Make sure root is src |
75 | root: helpers.root('src'), | ||
75 | 76 | ||
76 | }, | 77 | // remove other default values |
78 | modulesDirectories: [ 'node_modules' ] | ||
79 | }, | ||
77 | 80 | ||
78 | output: { | 81 | output: { |
79 | publicPath: '/client/' | 82 | publicPath: '/client/' |
80 | }, | 83 | }, |
81 | 84 | ||
82 | /* | ||
83 | * Options affecting the normal modules. | ||
84 | * | ||
85 | * See: http://webpack.github.io/docs/configuration.html#module | ||
86 | */ | ||
87 | module: { | ||
88 | /* | 85 | /* |
89 | * An array of applied pre and post loaders. | 86 | * Options affecting the normal modules. |
90 | * | 87 | * |
91 | * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders | 88 | * See: http://webpack.github.io/docs/configuration.html#module |
92 | */ | 89 | */ |
93 | preLoaders: [ | 90 | module: { |
94 | |||
95 | /* | 91 | /* |
96 | * Tslint loader support for *.ts files | 92 | * An array of applied pre and post loaders. |
97 | * | 93 | * |
98 | * See: https://github.com/wbuchwalter/tslint-loader | 94 | * See: http://webpack.github.io/docs/configuration.html#module-preloaders-module-postloaders |
99 | */ | 95 | */ |
100 | // { test: /\.ts$/, loader: 'tslint-loader', exclude: [ helpers.root('node_modules') ] }, | 96 | preLoaders: [ |
97 | { | ||
98 | test: /\.ts$/, | ||
99 | loader: 'string-replace-loader', | ||
100 | query: { | ||
101 | search: '(System|SystemJS)(.*[\\n\\r]\\s*\\.|\\.)import\\((.+)\\)', | ||
102 | replace: '$1.import($3).then(mod => (mod.__esModule && mod.default) ? mod.default : mod)', | ||
103 | flags: 'g' | ||
104 | }, | ||
105 | include: [helpers.root('src')] | ||
106 | } | ||
107 | ], | ||
101 | 108 | ||
102 | /* | 109 | /* |
103 | * Source map loader support for *.js files | 110 | * An array of automatically applied loaders. |
104 | * Extracts SourceMaps for source files that as added as sourceMappingURL comment. | 111 | * |
112 | * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. | ||
113 | * This means they are not resolved relative to the configuration file. | ||
105 | * | 114 | * |
106 | * See: https://github.com/webpack/source-map-loader | 115 | * See: http://webpack.github.io/docs/configuration.html#module-loaders |
107 | */ | 116 | */ |
108 | { | 117 | loaders: [ |
109 | test: /\.js$/, | 118 | |
110 | loader: 'source-map-loader', | 119 | /* |
111 | exclude: [ | 120 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts |
112 | // these packages have problems with their sourcemaps | 121 | * |
113 | helpers.root('node_modules/rxjs'), | 122 | * See: https://github.com/s-panferov/awesome-typescript-loader |
114 | helpers.root('node_modules/@angular') | 123 | */ |
115 | ] | 124 | { |
116 | } | 125 | test: /\.ts$/, |
117 | 126 | loaders: [ | |
118 | ], | 127 | '@angularclass/hmr-loader?pretty=' + !isProd + '&prod=' + isProd, |
128 | 'awesome-typescript-loader', | ||
129 | 'angular2-template-loader' | ||
130 | ], | ||
131 | exclude: [/\.(spec|e2e)\.ts$/] | ||
132 | }, | ||
133 | |||
134 | /* | ||
135 | * Json loader support for *.json files. | ||
136 | * | ||
137 | * See: https://github.com/webpack/json-loader | ||
138 | */ | ||
139 | { | ||
140 | test: /\.json$/, | ||
141 | loader: 'json-loader' | ||
142 | }, | ||
143 | |||
144 | { | ||
145 | test: /\.(sass|scss)$/, | ||
146 | loaders: ['css-to-string-loader', 'css-loader?sourceMap', 'resolve-url', 'sass-loader?sourceMap'] | ||
147 | }, | ||
148 | { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url?limit=10000&minetype=application/font-woff' }, | ||
149 | { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file' }, | ||
150 | |||
151 | /* Raw loader support for *.html | ||
152 | * Returns file content as string | ||
153 | * | ||
154 | * See: https://github.com/webpack/raw-loader | ||
155 | */ | ||
156 | { | ||
157 | test: /\.html$/, | ||
158 | loader: 'raw-loader', | ||
159 | exclude: [ helpers.root('src/index.html') ] | ||
160 | } | ||
161 | |||
162 | ] | ||
163 | |||
164 | }, | ||
165 | |||
166 | sassLoader: { | ||
167 | precision: 10 | ||
168 | }, | ||
119 | 169 | ||
120 | /* | 170 | /* |
121 | * An array of automatically applied loaders. | 171 | * Add additional plugins to the compiler. |
122 | * | ||
123 | * IMPORTANT: The loaders here are resolved relative to the resource which they are applied to. | ||
124 | * This means they are not resolved relative to the configuration file. | ||
125 | * | 172 | * |
126 | * See: http://webpack.github.io/docs/configuration.html#module-loaders | 173 | * See: http://webpack.github.io/docs/configuration.html#plugins |
127 | */ | 174 | */ |
128 | loaders: [ | 175 | plugins: [ |
176 | new AssetsPlugin({ | ||
177 | path: helpers.root('dist'), | ||
178 | filename: 'webpack-assets.json', | ||
179 | prettyPrint: true | ||
180 | }), | ||
129 | 181 | ||
130 | /* | 182 | /* |
131 | * Typescript loader support for .ts and Angular 2 async routes via .async.ts | 183 | * Plugin: ForkCheckerPlugin |
184 | * Description: Do type checking in a separate process, so webpack don't need to wait. | ||
132 | * | 185 | * |
133 | * See: https://github.com/s-panferov/awesome-typescript-loader | 186 | * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse |
134 | */ | 187 | */ |
135 | { | 188 | new ForkCheckerPlugin(), |
136 | test: /\.ts$/, | ||
137 | loader: 'awesome-typescript-loader', | ||
138 | exclude: [/\.(spec|e2e)\.ts$/] | ||
139 | }, | ||
140 | 189 | ||
141 | /* | 190 | /* |
142 | * Json loader support for *.json files. | 191 | * Plugin: CommonsChunkPlugin |
192 | * Description: Shares common code between the pages. | ||
193 | * It identifies common modules and put them into a commons chunk. | ||
143 | * | 194 | * |
144 | * See: https://github.com/webpack/json-loader | 195 | * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin |
196 | * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app | ||
145 | */ | 197 | */ |
146 | { | 198 | new webpack.optimize.CommonsChunkPlugin({ |
147 | test: /\.json$/, | 199 | name: [ 'polyfills', 'vendor' ].reverse() |
148 | loader: 'json-loader' | 200 | }), |
149 | }, | ||
150 | 201 | ||
151 | { | 202 | /** |
152 | test: /\.scss$/, | 203 | * Plugin: ContextReplacementPlugin |
153 | exclude: /node_modules/, | 204 | * Description: Provides context to Angular's use of System.import |
154 | loaders: [ 'raw-loader', 'sass-loader' ] | 205 | * |
155 | }, | 206 | * See: https://webpack.github.io/docs/list-of-plugins.html#contextreplacementplugin |
156 | 207 | * See: https://github.com/angular/angular/issues/11580 | |
157 | { | 208 | */ |
158 | test: /\.(woff2?|ttf|eot|svg)$/, | 209 | new ContextReplacementPlugin( |
159 | loader: 'url?limit=10000&name=assets/fonts/[hash].[ext]' | 210 | // The (\\|\/) piece accounts for path separators in *nix and Windows |
160 | }, | 211 | /angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/, |
212 | helpers.root('src') // location of your src | ||
213 | ), | ||
161 | 214 | ||
162 | /* Raw loader support for *.html | 215 | /* |
163 | * Returns file content as string | 216 | * Plugin: CopyWebpackPlugin |
217 | * Description: Copy files and directories in webpack. | ||
164 | * | 218 | * |
165 | * See: https://github.com/webpack/raw-loader | 219 | * Copies project static assets. |
220 | * | ||
221 | * See: https://www.npmjs.com/package/copy-webpack-plugin | ||
166 | */ | 222 | */ |
167 | { | 223 | new CopyWebpackPlugin([ |
168 | test: /\.html$/, | 224 | { |
169 | loader: 'raw-loader', | 225 | from: 'src/assets', |
170 | exclude: [ helpers.root('src/index.html') ] | 226 | to: 'assets' |
171 | } | 227 | }, |
172 | 228 | { | |
173 | ] | 229 | from: 'node_modules/webtorrent/webtorrent.min.js', |
174 | 230 | to: 'assets/webtorrent' | |
175 | }, | 231 | } |
232 | ]), | ||
176 | 233 | ||
177 | sassLoader: { | 234 | /* |
178 | precision: 10 | 235 | * Plugin: HtmlWebpackPlugin |
179 | }, | 236 | * Description: Simplifies creation of HTML files to serve your webpack bundles. |
180 | 237 | * This is especially useful for webpack bundles that include a hash in the filename | |
181 | /* | 238 | * which changes every compilation. |
182 | * Add additional plugins to the compiler. | 239 | * |
183 | * | 240 | * See: https://github.com/ampedandwired/html-webpack-plugin |
184 | * See: http://webpack.github.io/docs/configuration.html#plugins | 241 | */ |
185 | */ | 242 | new HtmlWebpackPlugin({ |
186 | plugins: [ | 243 | template: 'src/index.html', |
187 | 244 | chunksSortMode: 'dependency' | |
188 | /* | 245 | }), |
189 | * Plugin: ForkCheckerPlugin | ||
190 | * Description: Do type checking in a separate process, so webpack don't need to wait. | ||
191 | * | ||
192 | * See: https://github.com/s-panferov/awesome-typescript-loader#forkchecker-boolean-defaultfalse | ||
193 | */ | ||
194 | new ForkCheckerPlugin(), | ||
195 | |||
196 | /* | ||
197 | * Plugin: OccurenceOrderPlugin | ||
198 | * Description: Varies the distribution of the ids to get the smallest id length | ||
199 | * for often used ids. | ||
200 | * | ||
201 | * See: https://webpack.github.io/docs/list-of-plugins.html#occurrenceorderplugin | ||
202 | * See: https://github.com/webpack/docs/wiki/optimization#minimize | ||
203 | */ | ||
204 | new webpack.optimize.OccurenceOrderPlugin(true), | ||
205 | |||
206 | /* | ||
207 | * Plugin: CommonsChunkPlugin | ||
208 | * Description: Shares common code between the pages. | ||
209 | * It identifies common modules and put them into a commons chunk. | ||
210 | * | ||
211 | * See: https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin | ||
212 | * See: https://github.com/webpack/docs/wiki/optimization#multi-page-app | ||
213 | */ | ||
214 | new webpack.optimize.CommonsChunkPlugin({ | ||
215 | name: [ 'polyfills', 'vendor' ].reverse() | ||
216 | }), | ||
217 | 246 | ||
218 | /* | 247 | new WebpackNotifierPlugin({ alwaysNotify: true }) |
219 | * Plugin: CopyWebpackPlugin | 248 | ], |
220 | * Description: Copy files and directories in webpack. | ||
221 | * | ||
222 | * Copies project static assets. | ||
223 | * | ||
224 | * See: https://www.npmjs.com/package/copy-webpack-plugin | ||
225 | */ | ||
226 | new CopyWebpackPlugin([ | ||
227 | { | ||
228 | from: 'src/assets', | ||
229 | to: 'assets' | ||
230 | }, | ||
231 | { | ||
232 | from: 'node_modules/webtorrent/webtorrent.min.js', | ||
233 | to: 'assets/webtorrent' | ||
234 | } | ||
235 | ]), | ||
236 | 249 | ||
237 | /* | 250 | /* |
238 | * Plugin: HtmlWebpackPlugin | 251 | * Include polyfills or mocks for various node stuff |
239 | * Description: Simplifies creation of HTML files to serve your webpack bundles. | 252 | * Description: Node configuration |
240 | * This is especially useful for webpack bundles that include a hash in the filename | ||
241 | * which changes every compilation. | ||
242 | * | 253 | * |
243 | * See: https://github.com/ampedandwired/html-webpack-plugin | 254 | * See: https://webpack.github.io/docs/configuration.html#node |
244 | */ | 255 | */ |
245 | new HtmlWebpackPlugin({ | 256 | node: { |
246 | template: 'src/index.html', | 257 | global: 'window', |
247 | chunksSortMode: 'dependency' | 258 | crypto: 'empty', |
248 | }), | 259 | fs: 'empty', |
249 | 260 | events: true, | |
250 | new WebpackNotifierPlugin({ alwaysNotify: true }) | 261 | module: false, |
251 | ], | 262 | clearImmediate: false, |
252 | 263 | setImmediate: false | |
253 | /* | 264 | } |
254 | * Include polyfills or mocks for various node stuff | ||
255 | * Description: Node configuration | ||
256 | * | ||
257 | * See: https://webpack.github.io/docs/configuration.html#node | ||
258 | */ | ||
259 | node: { | ||
260 | global: 'window', | ||
261 | crypto: 'empty', | ||
262 | fs: 'empty', | ||
263 | events: true, | ||
264 | module: false, | ||
265 | clearImmediate: false, | ||
266 | setImmediate: false | ||
267 | } | 265 | } |
268 | |||
269 | } | 266 | } |
diff --git a/client/config/webpack.dev.js b/client/config/webpack.dev.js index 50193bf58..0b6c00cbd 100644 --- a/client/config/webpack.dev.js +++ b/client/config/webpack.dev.js | |||
@@ -6,15 +6,18 @@ const commonConfig = require('./webpack.common.js') // the settings that are com | |||
6 | * Webpack Plugins | 6 | * Webpack Plugins |
7 | */ | 7 | */ |
8 | const DefinePlugin = require('webpack/lib/DefinePlugin') | 8 | const DefinePlugin = require('webpack/lib/DefinePlugin') |
9 | const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin') | ||
9 | 10 | ||
10 | /** | 11 | /** |
11 | * Webpack Constants | 12 | * Webpack Constants |
12 | */ | 13 | */ |
13 | const ENV = process.env.ENV = process.env.NODE_ENV = 'development' | 14 | const ENV = process.env.ENV = process.env.NODE_ENV = 'development' |
15 | const HOST = process.env.HOST || 'localhost' | ||
16 | const PORT = process.env.PORT || 3000 | ||
14 | const HMR = helpers.hasProcessFlag('hot') | 17 | const HMR = helpers.hasProcessFlag('hot') |
15 | const METADATA = webpackMerge(commonConfig.metadata, { | 18 | const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, { |
16 | host: 'localhost', | 19 | host: HOST, |
17 | port: 3000, | 20 | port: PORT, |
18 | ENV: ENV, | 21 | ENV: ENV, |
19 | HMR: HMR | 22 | HMR: HMR |
20 | }) | 23 | }) |
@@ -24,119 +27,136 @@ const METADATA = webpackMerge(commonConfig.metadata, { | |||
24 | * | 27 | * |
25 | * See: http://webpack.github.io/docs/configuration.html#cli | 28 | * See: http://webpack.github.io/docs/configuration.html#cli |
26 | */ | 29 | */ |
27 | module.exports = webpackMerge(commonConfig, { | 30 | module.exports = function (env) { |
28 | /** | 31 | return webpackMerge(commonConfig({env: ENV}), { |
29 | * Merged metadata from webpack.common.js for index.html | ||
30 | * | ||
31 | * See: (custom attribute) | ||
32 | */ | ||
33 | metadata: METADATA, | ||
34 | |||
35 | /** | ||
36 | * Switch loaders to debug mode. | ||
37 | * | ||
38 | * See: http://webpack.github.io/docs/configuration.html#debug | ||
39 | */ | ||
40 | debug: true, | ||
41 | |||
42 | /** | ||
43 | * Developer tool to enhance debugging | ||
44 | * | ||
45 | * See: http://webpack.github.io/docs/configuration.html#devtool | ||
46 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps | ||
47 | */ | ||
48 | devtool: 'cheap-module-source-map', | ||
49 | |||
50 | /** | ||
51 | * Options affecting the output of the compilation. | ||
52 | * | ||
53 | * See: http://webpack.github.io/docs/configuration.html#output | ||
54 | */ | ||
55 | output: { | ||
56 | /** | 32 | /** |
57 | * The output directory as absolute path (required). | 33 | * Merged metadata from webpack.common.js for index.html |
58 | * | 34 | * |
59 | * See: http://webpack.github.io/docs/configuration.html#output-path | 35 | * See: (custom attribute) |
60 | */ | 36 | */ |
61 | path: helpers.root('dist'), | 37 | metadata: METADATA, |
62 | 38 | ||
63 | /** | 39 | /** |
64 | * Specifies the name of each output file on disk. | 40 | * Switch loaders to debug mode. |
65 | * IMPORTANT: You must not specify an absolute path here! | ||
66 | * | 41 | * |
67 | * See: http://webpack.github.io/docs/configuration.html#output-filename | 42 | * See: http://webpack.github.io/docs/configuration.html#debug |
68 | */ | 43 | */ |
69 | filename: '[name].bundle.js', | 44 | debug: true, |
70 | 45 | ||
71 | /** | 46 | /** |
72 | * The filename of the SourceMaps for the JavaScript files. | 47 | * Developer tool to enhance debugging |
73 | * They are inside the output.path directory. | ||
74 | * | 48 | * |
75 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename | 49 | * See: http://webpack.github.io/docs/configuration.html#devtool |
50 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps | ||
76 | */ | 51 | */ |
77 | sourceMapFilename: '[name].map', | 52 | devtool: 'cheap-module-source-map', |
78 | 53 | ||
79 | /** The filename of non-entry chunks as relative path | 54 | /** |
80 | * inside the output.path directory. | 55 | * Options affecting the output of the compilation. |
81 | * | 56 | * |
82 | * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename | 57 | * See: http://webpack.github.io/docs/configuration.html#output |
83 | */ | 58 | */ |
84 | chunkFilename: '[id].chunk.js' | 59 | output: { |
85 | 60 | /** | |
86 | }, | 61 | * The output directory as absolute path (required). |
87 | 62 | * | |
88 | externals: { | 63 | * See: http://webpack.github.io/docs/configuration.html#output-path |
89 | webtorrent: 'WebTorrent' | 64 | */ |
90 | }, | 65 | path: helpers.root('dist'), |
66 | |||
67 | /** | ||
68 | * Specifies the name of each output file on disk. | ||
69 | * IMPORTANT: You must not specify an absolute path here! | ||
70 | * | ||
71 | * See: http://webpack.github.io/docs/configuration.html#output-filename | ||
72 | */ | ||
73 | filename: '[name].bundle.js', | ||
74 | |||
75 | /** | ||
76 | * The filename of the SourceMaps for the JavaScript files. | ||
77 | * They are inside the output.path directory. | ||
78 | * | ||
79 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename | ||
80 | */ | ||
81 | sourceMapFilename: '[name].map', | ||
82 | |||
83 | /** The filename of non-entry chunks as relative path | ||
84 | * inside the output.path directory. | ||
85 | * | ||
86 | * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename | ||
87 | */ | ||
88 | chunkFilename: '[id].chunk.js', | ||
89 | |||
90 | library: 'ac_[name]', | ||
91 | libraryTarget: 'var' | ||
92 | |||
93 | }, | ||
94 | |||
95 | externals: { | ||
96 | webtorrent: 'WebTorrent' | ||
97 | }, | ||
98 | |||
99 | plugins: [ | ||
100 | |||
101 | /** | ||
102 | * Plugin: DefinePlugin | ||
103 | * Description: Define free variables. | ||
104 | * Useful for having development builds with debug logging or adding global constants. | ||
105 | * | ||
106 | * Environment helpers | ||
107 | * | ||
108 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin | ||
109 | */ | ||
110 | // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts | ||
111 | new DefinePlugin({ | ||
112 | 'ENV': JSON.stringify(METADATA.ENV), | ||
113 | 'HMR': METADATA.HMR, | ||
114 | 'process.env': { | ||
115 | 'ENV': JSON.stringify(METADATA.ENV), | ||
116 | 'NODE_ENV': JSON.stringify(METADATA.ENV), | ||
117 | 'HMR': METADATA.HMR | ||
118 | } | ||
119 | }), | ||
91 | 120 | ||
92 | plugins: [ | 121 | new NamedModulesPlugin() |
122 | ], | ||
93 | 123 | ||
94 | /** | 124 | /** |
95 | * Plugin: DefinePlugin | 125 | * Static analysis linter for TypeScript advanced options configuration |
96 | * Description: Define free variables. | 126 | * Description: An extensible linter for the TypeScript language. |
97 | * Useful for having development builds with debug logging or adding global constants. | ||
98 | * | 127 | * |
99 | * Environment helpers | 128 | * See: https://github.com/wbuchwalter/tslint-loader |
129 | */ | ||
130 | tslint: { | ||
131 | emitErrors: false, | ||
132 | failOnHint: false, | ||
133 | resourcePath: 'src' | ||
134 | }, | ||
135 | |||
136 | devServer: { | ||
137 | port: METADATA.port, | ||
138 | host: METADATA.host, | ||
139 | historyApiFallback: true, | ||
140 | watchOptions: { | ||
141 | aggregateTimeout: 300, | ||
142 | poll: 1000 | ||
143 | }, | ||
144 | outputPath: helpers.root('dist') | ||
145 | }, | ||
146 | |||
147 | /* | ||
148 | * Include polyfills or mocks for various node stuff | ||
149 | * Description: Node configuration | ||
100 | * | 150 | * |
101 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin | 151 | * See: https://webpack.github.io/docs/configuration.html#node |
102 | */ | 152 | */ |
103 | // NOTE: when adding more properties, make sure you include them in custom-typings.d.ts | 153 | node: { |
104 | new DefinePlugin({ | 154 | global: 'window', |
105 | 'ENV': JSON.stringify(METADATA.ENV), | 155 | crypto: 'empty', |
106 | 'HMR': METADATA.HMR, | 156 | process: true, |
107 | 'process.env': { | 157 | module: false, |
108 | 'ENV': JSON.stringify(METADATA.ENV), | 158 | clearImmediate: false, |
109 | 'NODE_ENV': JSON.stringify(METADATA.ENV), | 159 | setImmediate: false |
110 | 'HMR': METADATA.HMR | 160 | } |
111 | } | 161 | }) |
112 | }) | 162 | } |
113 | ], | ||
114 | |||
115 | /** | ||
116 | * Static analysis linter for TypeScript advanced options configuration | ||
117 | * Description: An extensible linter for the TypeScript language. | ||
118 | * | ||
119 | * See: https://github.com/wbuchwalter/tslint-loader | ||
120 | */ | ||
121 | tslint: { | ||
122 | emitErrors: false, | ||
123 | failOnHint: false, | ||
124 | resourcePath: 'src' | ||
125 | }, | ||
126 | |||
127 | /* | ||
128 | * Include polyfills or mocks for various node stuff | ||
129 | * Description: Node configuration | ||
130 | * | ||
131 | * See: https://webpack.github.io/docs/configuration.html#node | ||
132 | */ | ||
133 | node: { | ||
134 | global: 'window', | ||
135 | crypto: 'empty', | ||
136 | process: true, | ||
137 | module: false, | ||
138 | clearImmediate: false, | ||
139 | setImmediate: false | ||
140 | } | ||
141 | |||
142 | }) | ||
diff --git a/client/config/webpack.prod.js b/client/config/webpack.prod.js index 7ce5727d3..46db54482 100644 --- a/client/config/webpack.prod.js +++ b/client/config/webpack.prod.js | |||
@@ -9,10 +9,12 @@ const commonConfig = require('./webpack.common.js') // the settings that are com | |||
9 | /** | 9 | /** |
10 | * Webpack Plugins | 10 | * Webpack Plugins |
11 | */ | 11 | */ |
12 | // const ProvidePlugin = require('webpack/lib/ProvidePlugin') | ||
12 | const DefinePlugin = require('webpack/lib/DefinePlugin') | 13 | const DefinePlugin = require('webpack/lib/DefinePlugin') |
13 | const DedupePlugin = require('webpack/lib/optimize/DedupePlugin') | 14 | const NormalModuleReplacementPlugin = require('webpack/lib/NormalModuleReplacementPlugin') |
15 | // const IgnorePlugin = require('webpack/lib/IgnorePlugin') | ||
16 | // const DedupePlugin = require('webpack/lib/optimize/DedupePlugin') | ||
14 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin') | 17 | const UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin') |
15 | const CompressionPlugin = require('compression-webpack-plugin') | ||
16 | const WebpackMd5Hash = require('webpack-md5-hash') | 18 | const WebpackMd5Hash = require('webpack-md5-hash') |
17 | 19 | ||
18 | /** | 20 | /** |
@@ -21,211 +23,210 @@ const WebpackMd5Hash = require('webpack-md5-hash') | |||
21 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production' | 23 | const ENV = process.env.NODE_ENV = process.env.ENV = 'production' |
22 | const HOST = process.env.HOST || 'localhost' | 24 | const HOST = process.env.HOST || 'localhost' |
23 | const PORT = process.env.PORT || 8080 | 25 | const PORT = process.env.PORT || 8080 |
24 | const METADATA = webpackMerge(commonConfig.metadata, { | 26 | const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, { |
25 | host: HOST, | 27 | host: HOST, |
26 | port: PORT, | 28 | port: PORT, |
27 | ENV: ENV, | 29 | ENV: ENV, |
28 | HMR: false | 30 | HMR: false |
29 | }) | 31 | }) |
30 | 32 | ||
31 | module.exports = webpackMerge(commonConfig, { | 33 | module.exports = function (env) { |
32 | /** | 34 | return webpackMerge(commonConfig({env: ENV}), { |
33 | * Switch loaders to debug mode. | ||
34 | * | ||
35 | * See: http://webpack.github.io/docs/configuration.html#debug | ||
36 | */ | ||
37 | debug: false, | ||
38 | |||
39 | /** | ||
40 | * Developer tool to enhance debugging | ||
41 | * | ||
42 | * See: http://webpack.github.io/docs/configuration.html#devtool | ||
43 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps | ||
44 | */ | ||
45 | devtool: 'source-map', | ||
46 | |||
47 | /** | ||
48 | * Options affecting the output of the compilation. | ||
49 | * | ||
50 | * See: http://webpack.github.io/docs/configuration.html#output | ||
51 | */ | ||
52 | output: { | ||
53 | /** | 35 | /** |
54 | * The output directory as absolute path (required). | 36 | * Switch loaders to debug mode. |
55 | * | 37 | * |
56 | * See: http://webpack.github.io/docs/configuration.html#output-path | 38 | * See: http://webpack.github.io/docs/configuration.html#debug |
57 | */ | 39 | */ |
58 | path: helpers.root('dist'), | 40 | debug: false, |
59 | 41 | ||
60 | /** | 42 | /** |
61 | * Specifies the name of each output file on disk. | 43 | * Developer tool to enhance debugging |
62 | * IMPORTANT: You must not specify an absolute path here! | ||
63 | * | 44 | * |
64 | * See: http://webpack.github.io/docs/configuration.html#output-filename | 45 | * See: http://webpack.github.io/docs/configuration.html#devtool |
46 | * See: https://github.com/webpack/docs/wiki/build-performance#sourcemaps | ||
65 | */ | 47 | */ |
66 | filename: '[name].[chunkhash].bundle.js', | 48 | devtool: 'source-map', |
67 | 49 | ||
68 | /** | 50 | /** |
69 | * The filename of the SourceMaps for the JavaScript files. | 51 | * Options affecting the output of the compilation. |
70 | * They are inside the output.path directory. | ||
71 | * | 52 | * |
72 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename | 53 | * See: http://webpack.github.io/docs/configuration.html#output |
73 | */ | 54 | */ |
74 | sourceMapFilename: '[name].[chunkhash].bundle.map', | 55 | output: { |
56 | /** | ||
57 | * The output directory as absolute path (required). | ||
58 | * | ||
59 | * See: http://webpack.github.io/docs/configuration.html#output-path | ||
60 | */ | ||
61 | path: helpers.root('dist'), | ||
62 | |||
63 | /** | ||
64 | * Specifies the name of each output file on disk. | ||
65 | * IMPORTANT: You must not specify an absolute path here! | ||
66 | * | ||
67 | * See: http://webpack.github.io/docs/configuration.html#output-filename | ||
68 | */ | ||
69 | filename: '[name].[chunkhash].bundle.js', | ||
70 | |||
71 | /** | ||
72 | * The filename of the SourceMaps for the JavaScript files. | ||
73 | * They are inside the output.path directory. | ||
74 | * | ||
75 | * See: http://webpack.github.io/docs/configuration.html#output-sourcemapfilename | ||
76 | */ | ||
77 | sourceMapFilename: '[name].[chunkhash].bundle.map', | ||
78 | |||
79 | /** | ||
80 | * The filename of non-entry chunks as relative path | ||
81 | * inside the output.path directory. | ||
82 | * | ||
83 | * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename | ||
84 | */ | ||
85 | chunkFilename: '[id].[chunkhash].chunk.js' | ||
86 | |||
87 | }, | ||
88 | |||
89 | externals: { | ||
90 | webtorrent: 'WebTorrent' | ||
91 | }, | ||
75 | 92 | ||
76 | /** | 93 | /** |
77 | * The filename of non-entry chunks as relative path | 94 | * Add additional plugins to the compiler. |
78 | * inside the output.path directory. | ||
79 | * | 95 | * |
80 | * See: http://webpack.github.io/docs/configuration.html#output-chunkfilename | 96 | * See: http://webpack.github.io/docs/configuration.html#plugins |
81 | */ | 97 | */ |
82 | chunkFilename: '[id].[chunkhash].chunk.js' | 98 | plugins: [ |
83 | 99 | ||
84 | }, | 100 | /** |
85 | 101 | * Plugin: WebpackMd5Hash | |
86 | externals: { | 102 | * Description: Plugin to replace a standard webpack chunkhash with md5. |
87 | webtorrent: 'WebTorrent' | 103 | * |
88 | }, | 104 | * See: https://www.npmjs.com/package/webpack-md5-hash |
89 | 105 | */ | |
90 | /** | 106 | new WebpackMd5Hash(), |
91 | * Add additional plugins to the compiler. | 107 | |
92 | * | 108 | /** |
93 | * See: http://webpack.github.io/docs/configuration.html#plugins | 109 | * Plugin: DedupePlugin |
94 | */ | 110 | * Description: Prevents the inclusion of duplicate code into your bundle |
95 | plugins: [ | 111 | * and instead applies a copy of the function at runtime. |
96 | 112 | * | |
97 | /** | 113 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin |
98 | * Plugin: WebpackMd5Hash | 114 | * See: https://github.com/webpack/docs/wiki/optimization#deduplication |
99 | * Description: Plugin to replace a standard webpack chunkhash with md5. | 115 | */ |
100 | * | 116 | // new DedupePlugin(), |
101 | * See: https://www.npmjs.com/package/webpack-md5-hash | 117 | |
102 | */ | 118 | /** |
103 | new WebpackMd5Hash(), | 119 | * Plugin: DefinePlugin |
120 | * Description: Define free variables. | ||
121 | * Useful for having development builds with debug logging or adding global constants. | ||
122 | * | ||
123 | * Environment helpers | ||
124 | * | ||
125 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin | ||
126 | */ | ||
127 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts | ||
128 | new DefinePlugin({ | ||
129 | 'ENV': JSON.stringify(METADATA.ENV), | ||
130 | 'HMR': METADATA.HMR, | ||
131 | 'process.env': { | ||
132 | 'ENV': JSON.stringify(METADATA.ENV), | ||
133 | 'NODE_ENV': JSON.stringify(METADATA.ENV), | ||
134 | 'HMR': METADATA.HMR | ||
135 | } | ||
136 | }), | ||
137 | |||
138 | /** | ||
139 | * Plugin: UglifyJsPlugin | ||
140 | * Description: Minimize all JavaScript output of chunks. | ||
141 | * Loaders are switched into minimizing mode. | ||
142 | * | ||
143 | * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin | ||
144 | */ | ||
145 | // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines | ||
146 | new UglifyJsPlugin({ | ||
147 | // beautify: true, //debug | ||
148 | // mangle: false, //debug | ||
149 | // dead_code: false, //debug | ||
150 | // unused: false, //debug | ||
151 | // deadCode: false, //debug | ||
152 | // compress: { | ||
153 | // screw_ie8: true, | ||
154 | // keep_fnames: true, | ||
155 | // drop_debugger: false, | ||
156 | // dead_code: false, | ||
157 | // unused: false | ||
158 | // }, // debug | ||
159 | // comments: true, //debug | ||
160 | |||
161 | beautify: false, // prod | ||
162 | mangle: { screw_ie8: true, keep_fnames: true }, // prod | ||
163 | compress: { screw_ie8: true }, // prod | ||
164 | comments: false // prod | ||
165 | }), | ||
166 | |||
167 | new NormalModuleReplacementPlugin( | ||
168 | /angular2-hmr/, | ||
169 | helpers.root('config/modules/angular2-hmr-prod.js') | ||
170 | ) | ||
171 | |||
172 | /** | ||
173 | * Plugin: CompressionPlugin | ||
174 | * Description: Prepares compressed versions of assets to serve | ||
175 | * them with Content-Encoding | ||
176 | * | ||
177 | * See: https://github.com/webpack/compression-webpack-plugin | ||
178 | */ | ||
179 | // new CompressionPlugin({ | ||
180 | // regExp: /\.css$|\.html$|\.js$|\.map$/, | ||
181 | // threshold: 2 * 1024 | ||
182 | // }) | ||
104 | 183 | ||
105 | /** | 184 | ], |
106 | * Plugin: DedupePlugin | ||
107 | * Description: Prevents the inclusion of duplicate code into your bundle | ||
108 | * and instead applies a copy of the function at runtime. | ||
109 | * | ||
110 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin | ||
111 | * See: https://github.com/webpack/docs/wiki/optimization#deduplication | ||
112 | */ | ||
113 | new DedupePlugin(), | ||
114 | 185 | ||
115 | /** | 186 | /** |
116 | * Plugin: DefinePlugin | 187 | * Static analysis linter for TypeScript advanced options configuration |
117 | * Description: Define free variables. | 188 | * Description: An extensible linter for the TypeScript language. |
118 | * Useful for having development builds with debug logging or adding global constants. | ||
119 | * | ||
120 | * Environment helpers | ||
121 | * | 189 | * |
122 | * See: https://webpack.github.io/docs/list-of-plugins.html#defineplugin | 190 | * See: https://github.com/wbuchwalter/tslint-loader |
123 | */ | 191 | */ |
124 | // NOTE: when adding more properties make sure you include them in custom-typings.d.ts | 192 | tslint: { |
125 | new DefinePlugin({ | 193 | emitErrors: true, |
126 | 'ENV': JSON.stringify(METADATA.ENV), | 194 | failOnHint: true, |
127 | 'HMR': METADATA.HMR, | 195 | resourcePath: 'src' |
128 | 'process.env': { | 196 | }, |
129 | 'ENV': JSON.stringify(METADATA.ENV), | ||
130 | 'NODE_ENV': JSON.stringify(METADATA.ENV), | ||
131 | 'HMR': METADATA.HMR | ||
132 | } | ||
133 | }), | ||
134 | 197 | ||
135 | /** | 198 | /** |
136 | * Plugin: UglifyJsPlugin | 199 | * Html loader advanced options |
137 | * Description: Minimize all JavaScript output of chunks. | ||
138 | * Loaders are switched into minimizing mode. | ||
139 | * | 200 | * |
140 | * See: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin | 201 | * See: https://github.com/webpack/html-loader#advanced-options |
141 | */ | 202 | */ |
142 | // NOTE: To debug prod builds uncomment //debug lines and comment //prod lines | 203 | // TODO: Need to workaround Angular 2's html syntax => #id [bind] (event) *ngFor |
143 | new UglifyJsPlugin({ | 204 | htmlLoader: { |
144 | // beautify: true, //debug | 205 | minimize: true, |
145 | // mangle: false, //debug | 206 | removeAttributeQuotes: false, |
146 | // dead_code: false, //debug | 207 | caseSensitive: true, |
147 | // unused: false, //debug | 208 | customAttrSurround: [ |
148 | // deadCode: false, //debug | 209 | [/#/, /(?:)/], |
149 | // compress: { | 210 | [/\*/, /(?:)/], |
150 | // screw_ie8: true, | 211 | [/\[?\(?/, /(?:)/] |
151 | // keep_fnames: true, | 212 | ], |
152 | // drop_debugger: false, | 213 | customAttrAssign: [/\)?\]?=/] |
153 | // dead_code: false, | 214 | }, |
154 | // unused: false | 215 | |
155 | // }, // debug | 216 | /* |
156 | // comments: true, //debug | 217 | * Include polyfills or mocks for various node stuff |
157 | 218 | * Description: Node configuration | |
158 | beautify: false, // prod | ||
159 | |||
160 | mangle: { | ||
161 | screw_ie8: true, | ||
162 | keep_fnames: true | ||
163 | }, // prod | ||
164 | |||
165 | compress: { | ||
166 | screw_ie8: true | ||
167 | }, // prod | ||
168 | |||
169 | comments: false // prod | ||
170 | }), | ||
171 | |||
172 | /** | ||
173 | * Plugin: CompressionPlugin | ||
174 | * Description: Prepares compressed versions of assets to serve | ||
175 | * them with Content-Encoding | ||
176 | * | 219 | * |
177 | * See: https://github.com/webpack/compression-webpack-plugin | 220 | * See: https://webpack.github.io/docs/configuration.html#node |
178 | */ | 221 | */ |
179 | new CompressionPlugin({ | 222 | node: { |
180 | regExp: /\.css$|\.html$|\.js$|\.map$/, | 223 | global: 'window', |
181 | threshold: 2 * 1024 | 224 | crypto: 'empty', |
182 | }) | 225 | process: false, |
183 | 226 | module: false, | |
184 | ], | 227 | clearImmediate: false, |
185 | 228 | setImmediate: false | |
186 | /** | 229 | } |
187 | * Static analysis linter for TypeScript advanced options configuration | 230 | |
188 | * Description: An extensible linter for the TypeScript language. | 231 | }) |
189 | * | 232 | } |
190 | * See: https://github.com/wbuchwalter/tslint-loader | ||
191 | */ | ||
192 | tslint: { | ||
193 | emitErrors: true, | ||
194 | failOnHint: true, | ||
195 | resourcePath: 'src' | ||
196 | }, | ||
197 | |||
198 | /** | ||
199 | * Html loader advanced options | ||
200 | * | ||
201 | * See: https://github.com/webpack/html-loader#advanced-options | ||
202 | */ | ||
203 | // TODO: Need to workaround Angular 2's html syntax => #id [bind] (event) *ngFor | ||
204 | htmlLoader: { | ||
205 | minimize: true, | ||
206 | removeAttributeQuotes: false, | ||
207 | caseSensitive: true, | ||
208 | customAttrSurround: [ | ||
209 | [/#/, /(?:)/], | ||
210 | [/\*/, /(?:)/], | ||
211 | [/\[?\(?/, /(?:)/] | ||
212 | ], | ||
213 | customAttrAssign: [/\)?\]?=/] | ||
214 | }, | ||
215 | |||
216 | /* | ||
217 | * Include polyfills or mocks for various node stuff | ||
218 | * Description: Node configuration | ||
219 | * | ||
220 | * See: https://webpack.github.io/docs/configuration.html#node | ||
221 | */ | ||
222 | node: { | ||
223 | global: 'window', | ||
224 | crypto: 'empty', | ||
225 | process: false, | ||
226 | module: false, | ||
227 | clearImmediate: false, | ||
228 | setImmediate: false | ||
229 | } | ||
230 | |||
231 | }) | ||
diff --git a/client/package.json b/client/package.json index a5c5d092b..cc116f3e5 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -13,61 +13,72 @@ | |||
13 | "url": "git://github.com/Chocobozzz/PeerTube.git" | 13 | "url": "git://github.com/Chocobozzz/PeerTube.git" |
14 | }, | 14 | }, |
15 | "scripts": { | 15 | "scripts": { |
16 | "postinstall": "typings install", | ||
17 | "test": "standard && tslint -c ./tslint.json src/**/*.ts", | 16 | "test": "standard && tslint -c ./tslint.json src/**/*.ts", |
18 | "webpack": "webpack" | 17 | "webpack": "webpack" |
19 | }, | 18 | }, |
20 | "license": "GPLv3", | 19 | "license": "GPLv3", |
21 | "dependencies": { | 20 | "dependencies": { |
22 | "@angular/common": "2.0.0-rc.4", | 21 | "@angular/common": "^2.0.0", |
23 | "@angular/compiler": "2.0.0-rc.4", | 22 | "@angular/compiler": "^2.0.0", |
24 | "@angular/core": "2.0.0-rc.4", | 23 | "@angular/core": "^2.0.0", |
25 | "@angular/http": "2.0.0-rc.4", | 24 | "@angular/forms": "^2.0.0", |
26 | "@angular/platform-browser": "2.0.0-rc.4", | 25 | "@angular/http": "^2.0.0", |
27 | "@angular/platform-browser-dynamic": "2.0.0-rc.4", | 26 | "@angular/platform-browser": "^2.0.0", |
28 | "@angular/router": "3.0.0-beta.2", | 27 | "@angular/platform-browser-dynamic": "^2.0.0", |
29 | "angular-pipes": "^2.0.0", | 28 | "@angular/router": "^3.0.0", |
30 | "awesome-typescript-loader": "^0.17.0", | 29 | "@angularclass/hmr": "^1.2.0", |
31 | "bootstrap-loader": "^1.0.8", | 30 | "@angularclass/hmr-loader": "^3.0.2", |
31 | "@types/core-js": "^0.9.28", | ||
32 | "@types/node": "^6.0.38", | ||
33 | "@types/source-map": "^0.1.26", | ||
34 | "@types/uglify-js": "^2.0.27", | ||
35 | "@types/webpack": "^1.12.29", | ||
36 | "angular-pipes": "^3.0.0", | ||
37 | "angular2-template-loader": "^0.5.0", | ||
38 | "assets-webpack-plugin": "^3.4.0", | ||
39 | "awesome-typescript-loader": "^2.2.1", | ||
40 | "bootstrap-loader": "^2.0.0-beta.11", | ||
32 | "bootstrap-sass": "^3.3.6", | 41 | "bootstrap-sass": "^3.3.6", |
33 | "compression-webpack-plugin": "^0.3.1", | 42 | "compression-webpack-plugin": "^0.3.1", |
34 | "copy-webpack-plugin": "^3.0.1", | 43 | "copy-webpack-plugin": "^3.0.1", |
35 | "core-js": "^2.4.0", | 44 | "core-js": "^2.4.1", |
36 | "css-loader": "^0.23.1", | 45 | "css-loader": "^0.25.0", |
46 | "css-to-string-loader": "https://github.com/Chocobozzz/css-to-string-loader#patch-1", | ||
37 | "es6-promise": "^3.0.2", | 47 | "es6-promise": "^3.0.2", |
38 | "es6-promise-loader": "^1.0.1", | 48 | "es6-promise-loader": "^1.0.1", |
39 | "es6-shim": "^0.35.0", | 49 | "es6-shim": "^0.35.0", |
40 | "file-loader": "^0.8.5", | 50 | "extract-text-webpack-plugin": "^2.0.0-beta.4", |
51 | "file-loader": "^0.9.0", | ||
41 | "html-webpack-plugin": "^2.19.0", | 52 | "html-webpack-plugin": "^2.19.0", |
42 | "ie-shim": "^0.1.0", | 53 | "ie-shim": "^0.1.0", |
43 | "intl": "^1.2.4", | 54 | "intl": "^1.2.4", |
44 | "json-loader": "^0.5.4", | 55 | "json-loader": "^0.5.4", |
45 | "ng2-bootstrap": "1.0.16", | 56 | "ng2-bootstrap": "^1.1.5", |
46 | "ng2-file-upload": "^1.0.3", | 57 | "ng2-file-upload": "^1.0.3", |
47 | "node-sass": "^3.7.0", | 58 | "node-sass": "^3.10.0", |
48 | "normalize.css": "^4.1.1", | 59 | "normalize.css": "^4.1.1", |
49 | "raw-loader": "^0.5.1", | 60 | "raw-loader": "^0.5.1", |
50 | "reflect-metadata": "0.1.3", | 61 | "reflect-metadata": "0.1.3", |
51 | "resolve-url-loader": "^1.4.3", | 62 | "resolve-url-loader": "^1.6.0", |
52 | "rxjs": "5.0.0-beta.6", | 63 | "rxjs": "5.0.0-beta.12", |
53 | "sass-loader": "^3.2.0", | 64 | "sass-loader": "^4.0.2", |
54 | "source-map-loader": "^0.1.5", | 65 | "source-map-loader": "^0.1.5", |
66 | "string-replace-loader": "^1.0.3", | ||
55 | "style-loader": "^0.13.1", | 67 | "style-loader": "^0.13.1", |
56 | "ts-helpers": "^1.1.1", | 68 | "ts-helpers": "^1.1.1", |
57 | "tslint": "^3.7.4", | 69 | "tslint": "3.15.1", |
58 | "tslint-loader": "^2.1.4", | 70 | "tslint-loader": "^2.1.4", |
59 | "typescript": "^1.8.10", | 71 | "typescript": "^2.0.0", |
60 | "typings": "^1.0.4", | ||
61 | "url-loader": "^0.5.7", | 72 | "url-loader": "^0.5.7", |
62 | "webpack": "^1.13.1", | 73 | "webpack": "2.1.0-beta.22", |
63 | "webpack-md5-hash": "0.0.5", | 74 | "webpack-md5-hash": "0.0.5", |
64 | "webpack-merge": "^0.13.0", | 75 | "webpack-merge": "^0.14.1", |
65 | "webpack-notifier": "^1.3.0", | 76 | "webpack-notifier": "^1.3.0", |
66 | "webtorrent": "^0.95.2", | 77 | "webtorrent": "^0.96.0", |
67 | "zone.js": "0.6.12" | 78 | "zone.js": "0.6.23" |
68 | }, | 79 | }, |
69 | "devDependencies": { | 80 | "devDependencies": { |
70 | "codelyzer": "0.0.19", | 81 | "codelyzer": "0.0.28", |
71 | "standard": "^7.0.1" | 82 | "standard": "^8.0.0" |
72 | } | 83 | } |
73 | } | 84 | } |
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html new file mode 100644 index 000000000..5a8847acd --- /dev/null +++ b/client/src/app/account/account.component.html | |||
@@ -0,0 +1,27 @@ | |||
1 | <h3>Account</h3> | ||
2 | |||
3 | <div *ngIf="information" class="alert alert-success">{{ information }}</div> | ||
4 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
5 | |||
6 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> | ||
7 | <div class="form-group"> | ||
8 | <label for="new-password">New password</label> | ||
9 | <input | ||
10 | type="password" class="form-control" id="new-password" | ||
11 | formControlName="new-password" | ||
12 | > | ||
13 | <div *ngIf="formErrors['new-password']" class="alert alert-danger"> | ||
14 | {{ formErrors['new-password'] }} | ||
15 | </div> | ||
16 | </div> | ||
17 | |||
18 | <div class="form-group"> | ||
19 | <label for="name">Confirm new password</label> | ||
20 | <input | ||
21 | type="password" class="form-control" id="new-confirmed-password" | ||
22 | formControlName="new-confirmed-password" | ||
23 | > | ||
24 | </div> | ||
25 | |||
26 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid"> | ||
27 | </form> | ||
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts new file mode 100644 index 000000000..851eaf198 --- /dev/null +++ b/client/src/app/account/account.component.ts | |||
@@ -0,0 +1,67 @@ | |||
1 | import { } from '@angular/common'; | ||
2 | import { Component, OnInit } from '@angular/core'; | ||
3 | import { FormBuilder, FormGroup } from '@angular/forms'; | ||
4 | import { Router } from '@angular/router'; | ||
5 | |||
6 | import { AccountService } from './account.service'; | ||
7 | import { FormReactive, USER_PASSWORD } from '../shared'; | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-account', | ||
11 | templateUrl: './account.component.html' | ||
12 | }) | ||
13 | |||
14 | export class AccountComponent extends FormReactive implements OnInit { | ||
15 | information: string = null; | ||
16 | error: string = null; | ||
17 | |||
18 | form: FormGroup; | ||
19 | formErrors = { | ||
20 | 'new-password': '', | ||
21 | 'new-confirmed-password': '' | ||
22 | }; | ||
23 | validationMessages = { | ||
24 | 'new-password': USER_PASSWORD.MESSAGES, | ||
25 | 'new-confirmed-password': USER_PASSWORD.MESSAGES | ||
26 | }; | ||
27 | |||
28 | constructor( | ||
29 | private accountService: AccountService, | ||
30 | private formBuilder: FormBuilder, | ||
31 | private router: Router | ||
32 | ) { | ||
33 | super(); | ||
34 | } | ||
35 | |||
36 | buildForm() { | ||
37 | this.form = this.formBuilder.group({ | ||
38 | 'new-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
39 | 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], | ||
40 | }); | ||
41 | |||
42 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
43 | } | ||
44 | |||
45 | ngOnInit() { | ||
46 | this.buildForm(); | ||
47 | } | ||
48 | |||
49 | changePassword() { | ||
50 | const newPassword = this.form.value['new-password']; | ||
51 | const newConfirmedPassword = this.form.value['new-confirmed-password']; | ||
52 | |||
53 | this.information = null; | ||
54 | this.error = null; | ||
55 | |||
56 | if (newPassword !== newConfirmedPassword) { | ||
57 | this.error = 'The new password and the confirmed password do not correspond.'; | ||
58 | return; | ||
59 | } | ||
60 | |||
61 | this.accountService.changePassword(newPassword).subscribe( | ||
62 | ok => this.information = 'Password updated.', | ||
63 | |||
64 | err => this.error = err | ||
65 | ); | ||
66 | } | ||
67 | } | ||
diff --git a/client/src/app/account/account.routes.ts b/client/src/app/account/account.routes.ts new file mode 100644 index 000000000..e348c6ebe --- /dev/null +++ b/client/src/app/account/account.routes.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | import { AccountComponent } from './account.component'; | ||
2 | |||
3 | export const AccountRoutes = [ | ||
4 | { path: 'account', component: AccountComponent } | ||
5 | ]; | ||
diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts new file mode 100644 index 000000000..355bcef74 --- /dev/null +++ b/client/src/app/account/account.service.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | |||
3 | import { AuthHttp, AuthService, RestExtractor } from '../shared'; | ||
4 | |||
5 | @Injectable() | ||
6 | export class AccountService { | ||
7 | private static BASE_USERS_URL = '/api/v1/users/'; | ||
8 | |||
9 | constructor( | ||
10 | private authHttp: AuthHttp, | ||
11 | private authService: AuthService, | ||
12 | private restExtractor: RestExtractor | ||
13 | ) {} | ||
14 | |||
15 | changePassword(newPassword: string) { | ||
16 | const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; | ||
17 | const body = { | ||
18 | password: newPassword | ||
19 | }; | ||
20 | |||
21 | return this.authHttp.put(url, body) | ||
22 | .map(this.restExtractor.extractDataBool) | ||
23 | .catch((res) => this.restExtractor.handleError(res)); | ||
24 | } | ||
25 | } | ||
diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts new file mode 100644 index 000000000..823d9fe5f --- /dev/null +++ b/client/src/app/account/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './account.component'; | ||
2 | export * from './account.routes'; | ||
3 | export * from './account.service'; | ||
diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts new file mode 100644 index 000000000..64a7400e7 --- /dev/null +++ b/client/src/app/admin/admin.component.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | import { Component } from '@angular/core'; | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | |||
7 | export class AdminComponent { | ||
8 | } | ||
diff --git a/client/src/app/admin/admin.routes.ts b/client/src/app/admin/admin.routes.ts new file mode 100644 index 000000000..edb8ba49f --- /dev/null +++ b/client/src/app/admin/admin.routes.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { Routes } from '@angular/router'; | ||
2 | |||
3 | import { AdminComponent } from './admin.component'; | ||
4 | import { FriendsRoutes } from './friends'; | ||
5 | import { RequestsRoutes } from './requests'; | ||
6 | import { UsersRoutes } from './users'; | ||
7 | |||
8 | export const AdminRoutes: Routes = [ | ||
9 | { | ||
10 | path: 'admin', | ||
11 | component: AdminComponent, | ||
12 | children: [ | ||
13 | { | ||
14 | path: '', | ||
15 | redirectTo: 'users', | ||
16 | pathMatch: 'full' | ||
17 | }, | ||
18 | ...FriendsRoutes, | ||
19 | ...RequestsRoutes, | ||
20 | ...UsersRoutes | ||
21 | ] | ||
22 | } | ||
23 | ]; | ||
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html new file mode 100644 index 000000000..788f3b44d --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.html | |||
@@ -0,0 +1,26 @@ | |||
1 | <h3>Make friends</h3> | ||
2 | |||
3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
4 | |||
5 | <form (ngSubmit)="makeFriends()" [formGroup]="form"> | ||
6 | <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy"> | ||
7 | <label for="username">Url</label> | ||
8 | |||
9 | <div class="input-group"> | ||
10 | <input | ||
11 | type="text" class="form-control" placeholder="http://domain.com" | ||
12 | [id]="'url-' + id" [formControlName]="'url-' + id" | ||
13 | /> | ||
14 | <span class="input-group-btn"> | ||
15 | <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button> | ||
16 | <button *ngIf="displayRemoveField(id)" (click)="removeField(id)" class="btn btn-default" type="button">-</button> | ||
17 | </span> | ||
18 | </div> | ||
19 | |||
20 | <div [hidden]="form.controls['url-' + id].valid || form.controls['url-' + id].pristine" class="alert alert-warning"> | ||
21 | It should be a valid url. | ||
22 | </div> | ||
23 | </div> | ||
24 | |||
25 | <input type="submit" value="Make friends" class="btn btn-default" [disabled]="!isFormValid()"> | ||
26 | </form> | ||
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.scss b/client/src/app/admin/friends/friend-add/friend-add.component.scss new file mode 100644 index 000000000..5fde51636 --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.scss | |||
@@ -0,0 +1,7 @@ | |||
1 | table { | ||
2 | margin-bottom: 40px; | ||
3 | } | ||
4 | |||
5 | .input-group-btn button { | ||
6 | width: 35px; | ||
7 | } | ||
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts new file mode 100644 index 000000000..64165a9a5 --- /dev/null +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts | |||
@@ -0,0 +1,108 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | import { FormControl, FormGroup } from '@angular/forms'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | |||
5 | import { validateUrl } from '../../../shared'; | ||
6 | import { FriendService } from '../shared'; | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-friend-add', | ||
10 | templateUrl: './friend-add.component.html', | ||
11 | styleUrls: [ './friend-add.component.scss' ] | ||
12 | }) | ||
13 | export class FriendAddComponent implements OnInit { | ||
14 | form: FormGroup; | ||
15 | urls = [ ]; | ||
16 | error: string = null; | ||
17 | |||
18 | constructor(private router: Router, private friendService: FriendService) {} | ||
19 | |||
20 | ngOnInit() { | ||
21 | this.form = new FormGroup({}); | ||
22 | this.addField(); | ||
23 | } | ||
24 | |||
25 | addField() { | ||
26 | this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); | ||
27 | this.urls.push(''); | ||
28 | } | ||
29 | |||
30 | customTrackBy(index: number, obj: any): any { | ||
31 | return index; | ||
32 | } | ||
33 | |||
34 | displayAddField(index: number) { | ||
35 | return index === (this.urls.length - 1); | ||
36 | } | ||
37 | |||
38 | displayRemoveField(index: number) { | ||
39 | return (index !== 0 || this.urls.length > 1) && index !== (this.urls.length - 1); | ||
40 | } | ||
41 | |||
42 | isFormValid() { | ||
43 | // Do not check the last input | ||
44 | for (let i = 0; i < this.urls.length - 1; i++) { | ||
45 | if (!this.form.controls[`url-${i}`].valid) return false; | ||
46 | } | ||
47 | |||
48 | const lastIndex = this.urls.length - 1; | ||
49 | // If the last input (which is not the first) is empty, it's ok | ||
50 | if (this.urls[lastIndex] === '' && lastIndex !== 0) { | ||
51 | return true; | ||
52 | } else { | ||
53 | return this.form.controls[`url-${lastIndex}`].valid; | ||
54 | } | ||
55 | } | ||
56 | |||
57 | removeField(index: number) { | ||
58 | // Remove the last control | ||
59 | this.form.removeControl(`url-${this.urls.length - 1}`); | ||
60 | this.urls.splice(index, 1); | ||
61 | } | ||
62 | |||
63 | makeFriends() { | ||
64 | this.error = ''; | ||
65 | |||
66 | const notEmptyUrls = this.getNotEmptyUrls(); | ||
67 | if (notEmptyUrls.length === 0) { | ||
68 | this.error = 'You need to specify at less 1 url.'; | ||
69 | return; | ||
70 | } | ||
71 | |||
72 | if (!this.isUrlsUnique(notEmptyUrls)) { | ||
73 | this.error = 'Urls need to be unique.'; | ||
74 | return; | ||
75 | } | ||
76 | |||
77 | const confirmMessage = 'Are you sure to make friends with:\n - ' + notEmptyUrls.join('\n - '); | ||
78 | if (!confirm(confirmMessage)) return; | ||
79 | |||
80 | this.friendService.makeFriends(notEmptyUrls).subscribe( | ||
81 | status => { | ||
82 | // TODO: extractdatastatus | ||
83 | // if (status === 409) { | ||
84 | // alert('Already made friends!'); | ||
85 | // } else { | ||
86 | alert('Make friends request sent!'); | ||
87 | this.router.navigate([ '/admin/friends/list' ]); | ||
88 | // } | ||
89 | }, | ||
90 | error => alert(error.text) | ||
91 | ); | ||
92 | } | ||
93 | |||
94 | private getNotEmptyUrls() { | ||
95 | const notEmptyUrls = []; | ||
96 | |||
97 | Object.keys(this.form.value).forEach((urlKey) => { | ||
98 | const url = this.form.value[urlKey]; | ||
99 | if (url !== '') notEmptyUrls.push(url); | ||
100 | }); | ||
101 | |||
102 | return notEmptyUrls; | ||
103 | } | ||
104 | |||
105 | private isUrlsUnique(urls: string[]) { | ||
106 | return urls.every(url => urls.indexOf(url) === urls.lastIndexOf(url)); | ||
107 | } | ||
108 | } | ||
diff --git a/client/src/app/admin/friends/friend-add/index.ts b/client/src/app/admin/friends/friend-add/index.ts new file mode 100644 index 000000000..a101b3be5 --- /dev/null +++ b/client/src/app/admin/friends/friend-add/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './friend-add.component'; | |||
diff --git a/client/src/app/admin/friends/friend-list/friend-list.component.html b/client/src/app/admin/friends/friend-list/friend-list.component.html new file mode 100644 index 000000000..d786a7846 --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.html | |||
@@ -0,0 +1,29 @@ | |||
1 | <h3>Friends list</h3> | ||
2 | |||
3 | <table class="table table-hover"> | ||
4 | <thead> | ||
5 | <tr> | ||
6 | <th class="table-column-id">ID</th> | ||
7 | <th>Url</th> | ||
8 | <th>Score</th> | ||
9 | <th>Created Date</th> | ||
10 | </tr> | ||
11 | </thead> | ||
12 | |||
13 | <tbody> | ||
14 | <tr *ngFor="let friend of friends"> | ||
15 | <td>{{ friend.id }}</td> | ||
16 | <td>{{ friend.url }}</td> | ||
17 | <td>{{ friend.score }}</td> | ||
18 | <td>{{ friend.createdDate | date: 'medium' }}</td> | ||
19 | </tr> | ||
20 | </tbody> | ||
21 | </table> | ||
22 | |||
23 | <a *ngIf="friends?.length !== 0" class="add-user btn btn-danger pull-left" (click)="quitFriends()"> | ||
24 | Quit friends | ||
25 | </a> | ||
26 | |||
27 | <a *ngIf="friends?.length === 0" class="add-user btn btn-success pull-right" [routerLink]="['/admin/friends/add']"> | ||
28 | Make friends | ||
29 | </a> | ||
diff --git a/client/src/app/admin/friends/friend-list/friend-list.component.scss b/client/src/app/admin/friends/friend-list/friend-list.component.scss new file mode 100644 index 000000000..cb597e12b --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.scss | |||
@@ -0,0 +1,3 @@ | |||
1 | table { | ||
2 | margin-bottom: 40px; | ||
3 | } | ||
diff --git a/client/src/app/admin/friends/friend-list/friend-list.component.ts b/client/src/app/admin/friends/friend-list/friend-list.component.ts new file mode 100644 index 000000000..88c4800ee --- /dev/null +++ b/client/src/app/admin/friends/friend-list/friend-list.component.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | |||
3 | import { Friend, FriendService } from '../shared'; | ||
4 | |||
5 | @Component({ | ||
6 | selector: 'my-friend-list', | ||
7 | templateUrl: './friend-list.component.html', | ||
8 | styleUrls: [ './friend-list.component.scss' ] | ||
9 | }) | ||
10 | export class FriendListComponent implements OnInit { | ||
11 | friends: Friend[]; | ||
12 | |||
13 | constructor(private friendService: FriendService) { } | ||
14 | |||
15 | ngOnInit() { | ||
16 | this.getFriends(); | ||
17 | } | ||
18 | |||
19 | quitFriends() { | ||
20 | if (!confirm('Are you sure?')) return; | ||
21 | |||
22 | this.friendService.quitFriends().subscribe( | ||
23 | status => { | ||
24 | alert('Quit friends!'); | ||
25 | this.getFriends(); | ||
26 | }, | ||
27 | error => alert(error.text) | ||
28 | ); | ||
29 | } | ||
30 | |||
31 | private getFriends() { | ||
32 | this.friendService.getFriends().subscribe( | ||
33 | friends => this.friends = friends, | ||
34 | |||
35 | err => alert(err.text) | ||
36 | ); | ||
37 | } | ||
38 | } | ||
diff --git a/client/src/app/admin/friends/friend-list/index.ts b/client/src/app/admin/friends/friend-list/index.ts new file mode 100644 index 000000000..354c978a4 --- /dev/null +++ b/client/src/app/admin/friends/friend-list/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './friend-list.component'; | |||
diff --git a/client/src/app/admin/friends/friends.component.ts b/client/src/app/admin/friends/friends.component.ts new file mode 100644 index 000000000..bc3f54158 --- /dev/null +++ b/client/src/app/admin/friends/friends.component.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | import { Component } from '@angular/core'; | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | |||
7 | export class FriendsComponent { | ||
8 | } | ||
diff --git a/client/src/app/admin/friends/friends.routes.ts b/client/src/app/admin/friends/friends.routes.ts new file mode 100644 index 000000000..7fdef68f9 --- /dev/null +++ b/client/src/app/admin/friends/friends.routes.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { Routes } from '@angular/router'; | ||
2 | |||
3 | import { FriendsComponent } from './friends.component'; | ||
4 | import { FriendAddComponent } from './friend-add'; | ||
5 | import { FriendListComponent } from './friend-list'; | ||
6 | |||
7 | export const FriendsRoutes: Routes = [ | ||
8 | { | ||
9 | path: 'friends', | ||
10 | component: FriendsComponent, | ||
11 | children: [ | ||
12 | { | ||
13 | path: '', | ||
14 | redirectTo: 'list', | ||
15 | pathMatch: 'full' | ||
16 | }, | ||
17 | { | ||
18 | path: 'list', | ||
19 | component: FriendListComponent | ||
20 | }, | ||
21 | { | ||
22 | path: 'add', | ||
23 | component: FriendAddComponent | ||
24 | } | ||
25 | ] | ||
26 | } | ||
27 | ]; | ||
diff --git a/client/src/app/admin/friends/index.ts b/client/src/app/admin/friends/index.ts new file mode 100644 index 000000000..dd4df2538 --- /dev/null +++ b/client/src/app/admin/friends/index.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './friend-add'; | ||
2 | export * from './friend-list'; | ||
3 | export * from './shared'; | ||
4 | export * from './friends.component'; | ||
5 | export * from './friends.routes'; | ||
diff --git a/client/src/app/admin/friends/shared/friend.model.ts b/client/src/app/admin/friends/shared/friend.model.ts new file mode 100644 index 000000000..7cb28f440 --- /dev/null +++ b/client/src/app/admin/friends/shared/friend.model.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | export interface Friend { | ||
2 | id: string; | ||
3 | url: string; | ||
4 | score: number; | ||
5 | createdDate: Date; | ||
6 | } | ||
diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts new file mode 100644 index 000000000..75826fc17 --- /dev/null +++ b/client/src/app/admin/friends/shared/friend.service.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { Observable } from 'rxjs/Observable'; | ||
3 | |||
4 | import { Friend } from './friend.model'; | ||
5 | import { AuthHttp, RestExtractor } from '../../../shared'; | ||
6 | |||
7 | @Injectable() | ||
8 | export class FriendService { | ||
9 | private static BASE_FRIEND_URL: string = '/api/v1/pods/'; | ||
10 | |||
11 | constructor ( | ||
12 | private authHttp: AuthHttp, | ||
13 | private restExtractor: RestExtractor | ||
14 | ) {} | ||
15 | |||
16 | getFriends(): Observable<Friend[]> { | ||
17 | return this.authHttp.get(FriendService.BASE_FRIEND_URL) | ||
18 | // Not implemented as a data list by the server yet | ||
19 | // .map(this.restExtractor.extractDataList) | ||
20 | .map((res) => res.json()) | ||
21 | .catch((res) => this.restExtractor.handleError(res)); | ||
22 | } | ||
23 | |||
24 | makeFriends(notEmptyUrls) { | ||
25 | const body = { | ||
26 | urls: notEmptyUrls | ||
27 | }; | ||
28 | |||
29 | return this.authHttp.post(FriendService.BASE_FRIEND_URL + 'makefriends', body) | ||
30 | .map(this.restExtractor.extractDataBool) | ||
31 | .catch((res) => this.restExtractor.handleError(res)); | ||
32 | } | ||
33 | |||
34 | quitFriends() { | ||
35 | return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') | ||
36 | .map(res => res.status) | ||
37 | .catch((res) => this.restExtractor.handleError(res)); | ||
38 | } | ||
39 | } | ||
diff --git a/client/src/app/friends/index.ts b/client/src/app/admin/friends/shared/index.ts index 0adc256c4..0d671637d 100644 --- a/client/src/app/friends/index.ts +++ b/client/src/app/admin/friends/shared/index.ts | |||
@@ -1 +1,2 @@ | |||
1 | export * from './friend.model'; | ||
1 | export * from './friend.service'; | 2 | export * from './friend.service'; |
diff --git a/client/src/app/admin/index.ts b/client/src/app/admin/index.ts new file mode 100644 index 000000000..493caed15 --- /dev/null +++ b/client/src/app/admin/index.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | export * from './friends'; | ||
2 | export * from './requests'; | ||
3 | export * from './users'; | ||
4 | export * from './admin.component'; | ||
5 | export * from './admin.routes'; | ||
6 | export * from './menu-admin.component'; | ||
diff --git a/client/src/app/admin/menu-admin.component.html b/client/src/app/admin/menu-admin.component.html new file mode 100644 index 000000000..e250615aa --- /dev/null +++ b/client/src/app/admin/menu-admin.component.html | |||
@@ -0,0 +1,26 @@ | |||
1 | <menu class="col-md-2 col-sm-3 col-xs-3"> | ||
2 | |||
3 | <div class="panel-block"> | ||
4 | <div id="panel-users" class="panel-button"> | ||
5 | <span class="hidden-xs glyphicon glyphicon-user"></span> | ||
6 | <a [routerLink]="['/admin/users/list']">List users</a> | ||
7 | </div> | ||
8 | |||
9 | <div id="panel-friends" class="panel-button"> | ||
10 | <span class="hidden-xs glyphicon glyphicon-cloud"></span> | ||
11 | <a [routerLink]="['/admin/friends/list']">List friends</a> | ||
12 | </div> | ||
13 | |||
14 | <div id="panel-request-stats" class="panel-button"> | ||
15 | <span class="hidden-xs glyphicon glyphicon-stats"></span> | ||
16 | <a [routerLink]="['/admin/requests/stats']">Request stats</a> | ||
17 | </div> | ||
18 | </div> | ||
19 | |||
20 | <div class="panel-block"> | ||
21 | <div id="panel-quit-administration" class="panel-button"> | ||
22 | <span class="hidden-xs glyphicon glyphicon-cog"></span> | ||
23 | <a [routerLink]="['/videos/list']">Quit admin.</a> | ||
24 | </div> | ||
25 | </div> | ||
26 | </menu> | ||
diff --git a/client/src/app/admin/menu-admin.component.ts b/client/src/app/admin/menu-admin.component.ts new file mode 100644 index 000000000..59ffccf9f --- /dev/null +++ b/client/src/app/admin/menu-admin.component.ts | |||
@@ -0,0 +1,7 @@ | |||
1 | import { Component } from '@angular/core'; | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-menu-admin', | ||
5 | templateUrl: './menu-admin.component.html' | ||
6 | }) | ||
7 | export class MenuAdminComponent { } | ||
diff --git a/client/src/app/admin/requests/index.ts b/client/src/app/admin/requests/index.ts new file mode 100644 index 000000000..236a9ee8f --- /dev/null +++ b/client/src/app/admin/requests/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './request-stats'; | ||
2 | export * from './shared'; | ||
3 | export * from './requests.component'; | ||
4 | export * from './requests.routes'; | ||
diff --git a/client/src/app/admin/requests/request-stats/index.ts b/client/src/app/admin/requests/request-stats/index.ts new file mode 100644 index 000000000..be3a66f77 --- /dev/null +++ b/client/src/app/admin/requests/request-stats/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './request-stats.component'; | |||
diff --git a/client/src/app/admin/requests/request-stats/request-stats.component.html b/client/src/app/admin/requests/request-stats/request-stats.component.html new file mode 100644 index 000000000..b5ac59a9a --- /dev/null +++ b/client/src/app/admin/requests/request-stats/request-stats.component.html | |||
@@ -0,0 +1,23 @@ | |||
1 | <h3>Requests stats</h3> | ||
2 | |||
3 | <div *ngIf="stats !== null"> | ||
4 | <div> | ||
5 | <span class="label-description">Interval seconds between requests:</span> | ||
6 | {{ stats.secondsInterval }} | ||
7 | </div> | ||
8 | |||
9 | <div> | ||
10 | <span class="label-description">Remaining time before the scheduled request:</span> | ||
11 | {{ stats.remainingSeconds }} | ||
12 | </div> | ||
13 | |||
14 | <div> | ||
15 | <span class="label-description">Maximum number of requests per interval:</span> | ||
16 | {{ stats.maxRequestsInParallel }} | ||
17 | </div> | ||
18 | |||
19 | <div> | ||
20 | <span class="label-description">Remaining requests:</span> | ||
21 | {{ stats.requests.length }} | ||
22 | </div> | ||
23 | </div> | ||
diff --git a/client/src/app/admin/requests/request-stats/request-stats.component.scss b/client/src/app/admin/requests/request-stats/request-stats.component.scss new file mode 100644 index 000000000..92c28dc99 --- /dev/null +++ b/client/src/app/admin/requests/request-stats/request-stats.component.scss | |||
@@ -0,0 +1,6 @@ | |||
1 | .label-description { | ||
2 | display: inline-block; | ||
3 | width: 350px; | ||
4 | font-weight: bold; | ||
5 | color: black; | ||
6 | } | ||
diff --git a/client/src/app/admin/requests/request-stats/request-stats.component.ts b/client/src/app/admin/requests/request-stats/request-stats.component.ts new file mode 100644 index 000000000..4b0844574 --- /dev/null +++ b/client/src/app/admin/requests/request-stats/request-stats.component.ts | |||
@@ -0,0 +1,51 @@ | |||
1 | import { Component, OnInit, OnDestroy } from '@angular/core'; | ||
2 | |||
3 | import { RequestService, RequestStats } from '../shared'; | ||
4 | |||
5 | @Component({ | ||
6 | selector: 'my-request-stats', | ||
7 | templateUrl: './request-stats.component.html', | ||
8 | styleUrls: [ './request-stats.component.scss' ] | ||
9 | }) | ||
10 | export class RequestStatsComponent implements OnInit, OnDestroy { | ||
11 | stats: RequestStats = null; | ||
12 | |||
13 | private interval: NodeJS.Timer = null; | ||
14 | |||
15 | constructor(private requestService: RequestService) { } | ||
16 | |||
17 | ngOnInit() { | ||
18 | this.getStats(); | ||
19 | } | ||
20 | |||
21 | ngOnDestroy() { | ||
22 | if (this.stats.secondsInterval !== null) { | ||
23 | clearInterval(this.interval); | ||
24 | } | ||
25 | } | ||
26 | |||
27 | getStats() { | ||
28 | this.requestService.getStats().subscribe( | ||
29 | stats => { | ||
30 | console.log(stats); | ||
31 | this.stats = stats; | ||
32 | this.runInterval(); | ||
33 | }, | ||
34 | |||
35 | err => alert(err.text) | ||
36 | ); | ||
37 | } | ||
38 | |||
39 | private runInterval() { | ||
40 | this.interval = setInterval(() => { | ||
41 | this.stats.remainingMilliSeconds -= 1000; | ||
42 | |||
43 | if (this.stats.remainingMilliSeconds <= 0) { | ||
44 | setTimeout(() => this.getStats(), this.stats.remainingMilliSeconds + 100); | ||
45 | clearInterval(this.interval); | ||
46 | } | ||
47 | }, 1000); | ||
48 | } | ||
49 | |||
50 | |||
51 | } | ||
diff --git a/client/src/app/admin/requests/requests.component.ts b/client/src/app/admin/requests/requests.component.ts new file mode 100644 index 000000000..471112b45 --- /dev/null +++ b/client/src/app/admin/requests/requests.component.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | import { Component } from '@angular/core'; | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | |||
7 | export class RequestsComponent { | ||
8 | } | ||
diff --git a/client/src/app/admin/requests/requests.routes.ts b/client/src/app/admin/requests/requests.routes.ts new file mode 100644 index 000000000..78221a9ff --- /dev/null +++ b/client/src/app/admin/requests/requests.routes.ts | |||
@@ -0,0 +1,22 @@ | |||
1 | import { Routes } from '@angular/router'; | ||
2 | |||
3 | import { RequestsComponent } from './requests.component'; | ||
4 | import { RequestStatsComponent } from './request-stats'; | ||
5 | |||
6 | export const RequestsRoutes: Routes = [ | ||
7 | { | ||
8 | path: 'requests', | ||
9 | component: RequestsComponent, | ||
10 | children: [ | ||
11 | { | ||
12 | path: '', | ||
13 | redirectTo: 'stats', | ||
14 | pathMatch: 'full' | ||
15 | }, | ||
16 | { | ||
17 | path: 'stats', | ||
18 | component: RequestStatsComponent | ||
19 | } | ||
20 | ] | ||
21 | } | ||
22 | ]; | ||
diff --git a/client/src/app/admin/requests/shared/index.ts b/client/src/app/admin/requests/shared/index.ts new file mode 100644 index 000000000..32ab5767b --- /dev/null +++ b/client/src/app/admin/requests/shared/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './request-stats.model'; | ||
2 | export * from './request.service'; | ||
diff --git a/client/src/app/admin/requests/shared/request-stats.model.ts b/client/src/app/admin/requests/shared/request-stats.model.ts new file mode 100644 index 000000000..766e80836 --- /dev/null +++ b/client/src/app/admin/requests/shared/request-stats.model.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | export interface Request { | ||
2 | request: any; | ||
3 | to: any; | ||
4 | } | ||
5 | |||
6 | export class RequestStats { | ||
7 | maxRequestsInParallel: number; | ||
8 | milliSecondsInterval: number; | ||
9 | remainingMilliSeconds: number; | ||
10 | requests: Request[]; | ||
11 | |||
12 | constructor(hash: { | ||
13 | maxRequestsInParallel: number, | ||
14 | milliSecondsInterval: number, | ||
15 | remainingMilliSeconds: number, | ||
16 | requests: Request[]; | ||
17 | }) { | ||
18 | this.maxRequestsInParallel = hash.maxRequestsInParallel; | ||
19 | this.milliSecondsInterval = hash.milliSecondsInterval; | ||
20 | this.remainingMilliSeconds = hash.remainingMilliSeconds; | ||
21 | this.requests = hash.requests; | ||
22 | } | ||
23 | |||
24 | get remainingSeconds() { | ||
25 | return Math.floor(this.remainingMilliSeconds / 1000); | ||
26 | } | ||
27 | |||
28 | get secondsInterval() { | ||
29 | return Math.floor(this.milliSecondsInterval / 1000); | ||
30 | } | ||
31 | |||
32 | } | ||
diff --git a/client/src/app/admin/requests/shared/request.service.ts b/client/src/app/admin/requests/shared/request.service.ts new file mode 100644 index 000000000..aeec37448 --- /dev/null +++ b/client/src/app/admin/requests/shared/request.service.ts | |||
@@ -0,0 +1,22 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { Observable } from 'rxjs/Observable'; | ||
3 | |||
4 | import { RequestStats } from './request-stats.model'; | ||
5 | import { AuthHttp, RestExtractor } from '../../../shared'; | ||
6 | |||
7 | @Injectable() | ||
8 | export class RequestService { | ||
9 | private static BASE_REQUEST_URL: string = '/api/v1/requests/'; | ||
10 | |||
11 | constructor ( | ||
12 | private authHttp: AuthHttp, | ||
13 | private restExtractor: RestExtractor | ||
14 | ) {} | ||
15 | |||
16 | getStats(): Observable<RequestStats> { | ||
17 | return this.authHttp.get(RequestService.BASE_REQUEST_URL + 'stats') | ||
18 | .map(this.restExtractor.extractDataGet) | ||
19 | .map((data) => new RequestStats(data)) | ||
20 | .catch((res) => this.restExtractor.handleError(res)); | ||
21 | } | ||
22 | } | ||
diff --git a/client/src/app/admin/users/index.ts b/client/src/app/admin/users/index.ts new file mode 100644 index 000000000..e98a81f62 --- /dev/null +++ b/client/src/app/admin/users/index.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './shared'; | ||
2 | export * from './user-add'; | ||
3 | export * from './user-list'; | ||
4 | export * from './users.component'; | ||
5 | export * from './users.routes'; | ||
diff --git a/client/src/app/admin/users/shared/index.ts b/client/src/app/admin/users/shared/index.ts new file mode 100644 index 000000000..e17ee5c7a --- /dev/null +++ b/client/src/app/admin/users/shared/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './user.service'; | |||
diff --git a/client/src/app/admin/users/shared/user.service.ts b/client/src/app/admin/users/shared/user.service.ts new file mode 100644 index 000000000..13be553c0 --- /dev/null +++ b/client/src/app/admin/users/shared/user.service.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | |||
3 | import { AuthHttp, RestExtractor, ResultList, User } from '../../../shared'; | ||
4 | |||
5 | @Injectable() | ||
6 | export class UserService { | ||
7 | // TODO: merge this constant with account | ||
8 | private static BASE_USERS_URL = '/api/v1/users/'; | ||
9 | |||
10 | constructor( | ||
11 | private authHttp: AuthHttp, | ||
12 | private restExtractor: RestExtractor | ||
13 | ) {} | ||
14 | |||
15 | addUser(username: string, password: string) { | ||
16 | const body = { | ||
17 | username, | ||
18 | password | ||
19 | }; | ||
20 | |||
21 | return this.authHttp.post(UserService.BASE_USERS_URL, body) | ||
22 | .map(this.restExtractor.extractDataBool) | ||
23 | .catch(this.restExtractor.handleError); | ||
24 | } | ||
25 | |||
26 | getUsers() { | ||
27 | return this.authHttp.get(UserService.BASE_USERS_URL) | ||
28 | .map(this.restExtractor.extractDataList) | ||
29 | .map(this.extractUsers) | ||
30 | .catch((res) => this.restExtractor.handleError(res)); | ||
31 | } | ||
32 | |||
33 | removeUser(user: User) { | ||
34 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id); | ||
35 | } | ||
36 | |||
37 | private extractUsers(result: ResultList) { | ||
38 | const usersJson = result.data; | ||
39 | const totalUsers = result.total; | ||
40 | const users = []; | ||
41 | for (const userJson of usersJson) { | ||
42 | users.push(new User(userJson)); | ||
43 | } | ||
44 | |||
45 | return { users, totalUsers }; | ||
46 | } | ||
47 | } | ||
diff --git a/client/src/app/admin/users/user-add/index.ts b/client/src/app/admin/users/user-add/index.ts new file mode 100644 index 000000000..66d5ca04f --- /dev/null +++ b/client/src/app/admin/users/user-add/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './user-add.component'; | |||
diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html new file mode 100644 index 000000000..9b76c7c1b --- /dev/null +++ b/client/src/app/admin/users/user-add/user-add.component.html | |||
@@ -0,0 +1,29 @@ | |||
1 | <h3>Add user</h3> | ||
2 | |||
3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
4 | |||
5 | <form role="form" (ngSubmit)="addUser()" [formGroup]="form"> | ||
6 | <div class="form-group"> | ||
7 | <label for="username">Username</label> | ||
8 | <input | ||
9 | type="text" class="form-control" id="username" placeholder="Username" | ||
10 | formControlName="username" | ||
11 | > | ||
12 | <div *ngIf="formErrors.username" class="alert alert-danger"> | ||
13 | {{ formErrors.username }} | ||
14 | </div> | ||
15 | </div> | ||
16 | |||
17 | <div class="form-group"> | ||
18 | <label for="password">Password</label> | ||
19 | <input | ||
20 | type="password" class="form-control" id="password" placeholder="Password" | ||
21 | formControlName="password" | ||
22 | > | ||
23 | <div *ngIf="formErrors.password" class="alert alert-danger"> | ||
24 | {{ formErrors.password }} | ||
25 | </div> | ||
26 | </div> | ||
27 | |||
28 | <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid"> | ||
29 | </form> | ||
diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts new file mode 100644 index 000000000..ab96fb01d --- /dev/null +++ b/client/src/app/admin/users/user-add/user-add.component.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | import { FormBuilder, FormGroup } from '@angular/forms'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | |||
5 | import { UserService } from '../shared'; | ||
6 | import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared'; | ||
7 | |||
8 | @Component({ | ||
9 | selector: 'my-user-add', | ||
10 | templateUrl: './user-add.component.html' | ||
11 | }) | ||
12 | export class UserAddComponent extends FormReactive implements OnInit { | ||
13 | error: string = null; | ||
14 | |||
15 | form: FormGroup; | ||
16 | formErrors = { | ||
17 | 'username': '', | ||
18 | 'password': '' | ||
19 | }; | ||
20 | validationMessages = { | ||
21 | 'username': USER_USERNAME.MESSAGES, | ||
22 | 'password': USER_PASSWORD.MESSAGES, | ||
23 | }; | ||
24 | |||
25 | constructor( | ||
26 | private formBuilder: FormBuilder, | ||
27 | private router: Router, | ||
28 | private userService: UserService | ||
29 | ) { | ||
30 | super(); | ||
31 | } | ||
32 | |||
33 | buildForm() { | ||
34 | this.form = this.formBuilder.group({ | ||
35 | username: [ '', USER_USERNAME.VALIDATORS ], | ||
36 | password: [ '', USER_PASSWORD.VALIDATORS ], | ||
37 | }); | ||
38 | |||
39 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
40 | } | ||
41 | |||
42 | ngOnInit() { | ||
43 | this.buildForm(); | ||
44 | } | ||
45 | |||
46 | addUser() { | ||
47 | this.error = null; | ||
48 | |||
49 | const { username, password } = this.form.value; | ||
50 | |||
51 | this.userService.addUser(username, password).subscribe( | ||
52 | ok => this.router.navigate([ '/admin/users/list' ]), | ||
53 | |||
54 | err => this.error = err.text | ||
55 | ); | ||
56 | } | ||
57 | } | ||
diff --git a/client/src/app/admin/users/user-list/index.ts b/client/src/app/admin/users/user-list/index.ts new file mode 100644 index 000000000..51fbefa80 --- /dev/null +++ b/client/src/app/admin/users/user-list/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './user-list.component'; | |||
diff --git a/client/src/app/admin/users/user-list/user-list.component.html b/client/src/app/admin/users/user-list/user-list.component.html new file mode 100644 index 000000000..328b1be77 --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.html | |||
@@ -0,0 +1,28 @@ | |||
1 | <h3>Users list</h3> | ||
2 | |||
3 | <table class="table table-hover"> | ||
4 | <thead> | ||
5 | <tr> | ||
6 | <th class="table-column-id">ID</th> | ||
7 | <th>Username</th> | ||
8 | <th>Created Date</th> | ||
9 | <th class="text-right">Remove</th> | ||
10 | </tr> | ||
11 | </thead> | ||
12 | |||
13 | <tbody> | ||
14 | <tr *ngFor="let user of users"> | ||
15 | <td>{{ user.id }}</td> | ||
16 | <td>{{ user.username }}</td> | ||
17 | <td>{{ user.createdDate | date: 'medium' }}</td> | ||
18 | <td class="text-right"> | ||
19 | <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span> | ||
20 | </td> | ||
21 | </tr> | ||
22 | </tbody> | ||
23 | </table> | ||
24 | |||
25 | <a class="add-user btn btn-success pull-right" [routerLink]="['/admin/users/add']"> | ||
26 | <span class="glyphicon glyphicon-plus"></span> | ||
27 | Add user | ||
28 | </a> | ||
diff --git a/client/src/app/admin/users/user-list/user-list.component.scss b/client/src/app/admin/users/user-list/user-list.component.scss new file mode 100644 index 000000000..e9f61e900 --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.scss | |||
@@ -0,0 +1,7 @@ | |||
1 | .glyphicon-remove { | ||
2 | cursor: pointer; | ||
3 | } | ||
4 | |||
5 | .add-user { | ||
6 | margin-top: 10px; | ||
7 | } | ||
diff --git a/client/src/app/admin/users/user-list/user-list.component.ts b/client/src/app/admin/users/user-list/user-list.component.ts new file mode 100644 index 000000000..03f4e5c0a --- /dev/null +++ b/client/src/app/admin/users/user-list/user-list.component.ts | |||
@@ -0,0 +1,42 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | |||
3 | import { User } from '../../../shared'; | ||
4 | import { UserService } from '../shared'; | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-user-list', | ||
8 | templateUrl: './user-list.component.html', | ||
9 | styleUrls: [ './user-list.component.scss' ] | ||
10 | }) | ||
11 | export class UserListComponent implements OnInit { | ||
12 | totalUsers: number; | ||
13 | users: User[]; | ||
14 | |||
15 | constructor(private userService: UserService) {} | ||
16 | |||
17 | ngOnInit() { | ||
18 | this.getUsers(); | ||
19 | } | ||
20 | |||
21 | getUsers() { | ||
22 | this.userService.getUsers().subscribe( | ||
23 | ({ users, totalUsers }) => { | ||
24 | this.users = users; | ||
25 | this.totalUsers = totalUsers; | ||
26 | }, | ||
27 | |||
28 | err => alert(err.text) | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | |||
33 | removeUser(user: User) { | ||
34 | if (confirm('Are you sure?')) { | ||
35 | this.userService.removeUser(user).subscribe( | ||
36 | () => this.getUsers(), | ||
37 | |||
38 | err => alert(err.text) | ||
39 | ); | ||
40 | } | ||
41 | } | ||
42 | } | ||
diff --git a/client/src/app/admin/users/users.component.ts b/client/src/app/admin/users/users.component.ts new file mode 100644 index 000000000..37e3b158d --- /dev/null +++ b/client/src/app/admin/users/users.component.ts | |||
@@ -0,0 +1,8 @@ | |||
1 | import { Component } from '@angular/core'; | ||
2 | |||
3 | @Component({ | ||
4 | template: '<router-outlet></router-outlet>' | ||
5 | }) | ||
6 | |||
7 | export class UsersComponent { | ||
8 | } | ||
diff --git a/client/src/app/admin/users/users.routes.ts b/client/src/app/admin/users/users.routes.ts new file mode 100644 index 000000000..eb71bd0ae --- /dev/null +++ b/client/src/app/admin/users/users.routes.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { Routes } from '@angular/router'; | ||
2 | |||
3 | import { UsersComponent } from './users.component'; | ||
4 | import { UserAddComponent } from './user-add'; | ||
5 | import { UserListComponent } from './user-list'; | ||
6 | |||
7 | export const UsersRoutes: Routes = [ | ||
8 | { | ||
9 | path: 'users', | ||
10 | component: UsersComponent, | ||
11 | children: [ | ||
12 | { | ||
13 | path: '', | ||
14 | redirectTo: 'list', | ||
15 | pathMatch: 'full' | ||
16 | }, | ||
17 | { | ||
18 | path: 'list', | ||
19 | component: UserListComponent | ||
20 | }, | ||
21 | { | ||
22 | path: 'add', | ||
23 | component: UserAddComponent | ||
24 | } | ||
25 | ] | ||
26 | } | ||
27 | ]; | ||
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index f2acffea4..04c32f596 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -14,48 +14,14 @@ | |||
14 | 14 | ||
15 | 15 | ||
16 | <div class="row"> | 16 | <div class="row"> |
17 | 17 | <my-menu *ngIf="isInAdmin() === false"></my-menu> | |
18 | <menu class="col-md-2 col-sm-3 col-xs-3"> | 18 | <my-menu-admin *ngIf="isInAdmin() === true"></my-menu-admin> |
19 | <div class="panel-block"> | ||
20 | <div id="panel-user-login" class="panel-button"> | ||
21 | <span class="hidden-xs glyphicon glyphicon-user"></span> | ||
22 | <a *ngIf="!isLoggedIn" [routerLink]="['/login']">Login</a> | ||
23 | <a *ngIf="isLoggedIn" (click)="logout()">Logout</a> | ||
24 | </div> | ||
25 | </div> | ||
26 | |||
27 | <div class="panel-block"> | ||
28 | <div id="panel-get-videos" class="panel-button"> | ||
29 | <span class="hidden-xs glyphicon glyphicon-list"></span> | ||
30 | <a [routerLink]="['/videos/list']">Get videos</a> | ||
31 | </div> | ||
32 | |||
33 | <div id="panel-upload-video" class="panel-button" *ngIf="isLoggedIn"> | ||
34 | <span class="hidden-xs glyphicon glyphicon-cloud-upload"></span> | ||
35 | <a [routerLink]="['/videos/add']">Upload a video</a> | ||
36 | </div> | ||
37 | </div> | ||
38 | |||
39 | <div class="panel-block" *ngIf="isLoggedIn"> | ||
40 | <div id="panel-make-friends" class="panel-button"> | ||
41 | <span class="hidden-xs glyphicon glyphicon-cloud"></span> | ||
42 | <a (click)='makeFriends()'>Make friends</a> | ||
43 | </div> | ||
44 | |||
45 | <div id="panel-quit-friends" class="panel-button"> | ||
46 | <span class="hidden-xs glyphicon glyphicon-plane"></span> | ||
47 | <a (click)='quitFriends()'>Quit friends</a> | ||
48 | </div> | ||
49 | </div> | ||
50 | </menu> | ||
51 | 19 | ||
52 | <div class="col-md-9 col-sm-8 col-xs-8 router-outlet-container"> | 20 | <div class="col-md-9 col-sm-8 col-xs-8 router-outlet-container"> |
53 | <router-outlet></router-outlet> | 21 | <router-outlet></router-outlet> |
54 | </div> | 22 | </div> |
55 | |||
56 | </div> | 23 | </div> |
57 | 24 | ||
58 | |||
59 | <footer> | 25 | <footer> |
60 | PeerTube, CopyLeft 2015-2016 | 26 | PeerTube, CopyLeft 2015-2016 |
61 | </footer> | 27 | </footer> |
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 1b02b2f57..95f306d75 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -12,40 +12,6 @@ header div { | |||
12 | margin-bottom: 30px; | 12 | margin-bottom: 30px; |
13 | } | 13 | } |
14 | 14 | ||
15 | menu { | ||
16 | @media screen and (max-width: 600px) { | ||
17 | margin-right: 3px !important; | ||
18 | padding: 3px !important; | ||
19 | min-height: 400px !important; | ||
20 | } | ||
21 | |||
22 | min-height: 600px; | ||
23 | margin-right: 20px; | ||
24 | border-right: 1px solid rgba(0, 0, 0, 0.2); | ||
25 | |||
26 | .panel-button { | ||
27 | margin: 8px; | ||
28 | cursor: pointer; | ||
29 | transition: margin 0.2s; | ||
30 | |||
31 | &:hover { | ||
32 | margin-left: 15px; | ||
33 | } | ||
34 | |||
35 | a { | ||
36 | color: #333333; | ||
37 | } | ||
38 | } | ||
39 | |||
40 | .glyphicon { | ||
41 | margin: 5px; | ||
42 | } | ||
43 | } | ||
44 | |||
45 | .panel-block:not(:last-child) { | ||
46 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); | ||
47 | } | ||
48 | |||
49 | .router-outlet-container { | 15 | .router-outlet-container { |
50 | @media screen and (max-width: 400px) { | 16 | @media screen and (max-width: 400px) { |
51 | padding: 0 3px 0 3px; | 17 | padding: 0 3px 0 3px; |
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index b7a3d7c58..d6b83c684 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,73 +1,16 @@ | |||
1 | import { Component } from '@angular/core'; | 1 | import { Component } from '@angular/core'; |
2 | import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router'; | 2 | import { Router } from '@angular/router'; |
3 | |||
4 | import { FriendService } from './friends'; | ||
5 | import { | ||
6 | AuthService, | ||
7 | AuthStatus, | ||
8 | SearchComponent, | ||
9 | SearchService | ||
10 | } from './shared'; | ||
11 | import { VideoService } from './videos'; | ||
12 | 3 | ||
13 | @Component({ | 4 | @Component({ |
14 | selector: 'my-app', | 5 | selector: 'my-app', |
15 | template: require('./app.component.html'), | 6 | templateUrl: './app.component.html', |
16 | styles: [ require('./app.component.scss') ], | 7 | styleUrls: [ './app.component.scss' ] |
17 | directives: [ ROUTER_DIRECTIVES, SearchComponent ], | ||
18 | providers: [ FriendService, VideoService, SearchService ] | ||
19 | }) | 8 | }) |
20 | 9 | ||
21 | export class AppComponent { | 10 | export class AppComponent { |
22 | choices = []; | 11 | constructor(private router: Router) {} |
23 | isLoggedIn: boolean; | ||
24 | |||
25 | constructor( | ||
26 | private authService: AuthService, | ||
27 | private friendService: FriendService, | ||
28 | private route: ActivatedRoute, | ||
29 | private router: Router | ||
30 | ) { | ||
31 | this.isLoggedIn = this.authService.isLoggedIn(); | ||
32 | |||
33 | this.authService.loginChangedSource.subscribe( | ||
34 | status => { | ||
35 | if (status === AuthStatus.LoggedIn) { | ||
36 | this.isLoggedIn = true; | ||
37 | console.log('Logged in.'); | ||
38 | } else if (status === AuthStatus.LoggedOut) { | ||
39 | this.isLoggedIn = false; | ||
40 | console.log('Logged out.'); | ||
41 | } else { | ||
42 | console.error('Unknown auth status: ' + status); | ||
43 | } | ||
44 | } | ||
45 | ); | ||
46 | } | ||
47 | |||
48 | logout() { | ||
49 | this.authService.logout(); | ||
50 | } | ||
51 | |||
52 | makeFriends() { | ||
53 | this.friendService.makeFriends().subscribe( | ||
54 | status => { | ||
55 | if (status === 409) { | ||
56 | alert('Already made friends!'); | ||
57 | } else { | ||
58 | alert('Made friends!'); | ||
59 | } | ||
60 | }, | ||
61 | error => alert(error) | ||
62 | ); | ||
63 | } | ||
64 | 12 | ||
65 | quitFriends() { | 13 | isInAdmin() { |
66 | this.friendService.quitFriends().subscribe( | 14 | return this.router.url.indexOf('/admin/') !== -1; |
67 | status => { | ||
68 | alert('Quit friends!'); | ||
69 | }, | ||
70 | error => alert(error) | ||
71 | ); | ||
72 | } | 15 | } |
73 | } | 16 | } |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts new file mode 100644 index 000000000..980625f13 --- /dev/null +++ b/client/src/app/app.module.ts | |||
@@ -0,0 +1,146 @@ | |||
1 | import { ApplicationRef, NgModule } from '@angular/core'; | ||
2 | import { BrowserModule } from '@angular/platform-browser'; | ||
3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
4 | import { HttpModule, RequestOptions, XHRBackend } from '@angular/http'; | ||
5 | import { RouterModule } from '@angular/router'; | ||
6 | import { removeNgStyles, createNewHosts } from '@angularclass/hmr'; | ||
7 | |||
8 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; | ||
9 | import { ProgressbarModule } from 'ng2-bootstrap/components/progressbar'; | ||
10 | import { PaginationModule } from 'ng2-bootstrap/components/pagination'; | ||
11 | import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; | ||
12 | |||
13 | /* | ||
14 | * Platform and Environment providers/directives/pipes | ||
15 | */ | ||
16 | import { ENV_PROVIDERS } from './environment'; | ||
17 | import { routes } from './app.routes'; | ||
18 | // App is our top level component | ||
19 | import { AppComponent } from './app.component'; | ||
20 | import { AppState } from './app.service'; | ||
21 | |||
22 | import { | ||
23 | AdminComponent, | ||
24 | FriendsComponent, | ||
25 | FriendAddComponent, | ||
26 | FriendListComponent, | ||
27 | FriendService, | ||
28 | MenuAdminComponent, | ||
29 | RequestsComponent, | ||
30 | RequestStatsComponent, | ||
31 | RequestService, | ||
32 | UsersComponent, | ||
33 | UserAddComponent, | ||
34 | UserListComponent, | ||
35 | UserService | ||
36 | } from './admin'; | ||
37 | import { AccountComponent, AccountService } from './account'; | ||
38 | import { LoginComponent } from './login'; | ||
39 | import { MenuComponent } from './menu.component'; | ||
40 | import { AuthService, AuthHttp, RestExtractor, RestService, SearchComponent, SearchService } from './shared'; | ||
41 | import { | ||
42 | LoaderComponent, | ||
43 | VideosComponent, | ||
44 | VideoAddComponent, | ||
45 | VideoListComponent, | ||
46 | VideoMiniatureComponent, | ||
47 | VideoSortComponent, | ||
48 | VideoWatchComponent, | ||
49 | VideoService, | ||
50 | WebTorrentService | ||
51 | } from './videos'; | ||
52 | |||
53 | // Application wide providers | ||
54 | const APP_PROVIDERS = [ | ||
55 | AppState, | ||
56 | |||
57 | { | ||
58 | provide: AuthHttp, | ||
59 | useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) => { | ||
60 | return new AuthHttp(backend, defaultOptions, authService); | ||
61 | }, | ||
62 | deps: [ XHRBackend, RequestOptions, AuthService ] | ||
63 | }, | ||
64 | |||
65 | AuthService, | ||
66 | RestExtractor, | ||
67 | RestService, | ||
68 | |||
69 | VideoService, | ||
70 | SearchService, | ||
71 | FriendService, | ||
72 | RequestService, | ||
73 | UserService, | ||
74 | AccountService, | ||
75 | WebTorrentService | ||
76 | ]; | ||
77 | /** | ||
78 | * `AppModule` is the main entry point into Angular2's bootstraping process | ||
79 | */ | ||
80 | @NgModule({ | ||
81 | bootstrap: [ AppComponent ], | ||
82 | declarations: [ | ||
83 | AccountComponent, | ||
84 | AdminComponent, | ||
85 | AppComponent, | ||
86 | BytesPipe, | ||
87 | FriendAddComponent, | ||
88 | FriendListComponent, | ||
89 | FriendsComponent, | ||
90 | LoaderComponent, | ||
91 | LoginComponent, | ||
92 | MenuAdminComponent, | ||
93 | MenuComponent, | ||
94 | RequestsComponent, | ||
95 | RequestStatsComponent, | ||
96 | SearchComponent, | ||
97 | UserAddComponent, | ||
98 | UserListComponent, | ||
99 | UsersComponent, | ||
100 | VideoAddComponent, | ||
101 | VideoListComponent, | ||
102 | VideoMiniatureComponent, | ||
103 | VideosComponent, | ||
104 | VideoSortComponent, | ||
105 | VideoWatchComponent, | ||
106 | ], | ||
107 | imports: [ // import Angular's modules | ||
108 | BrowserModule, | ||
109 | FormsModule, | ||
110 | ReactiveFormsModule, | ||
111 | HttpModule, | ||
112 | RouterModule.forRoot(routes), | ||
113 | |||
114 | ProgressbarModule, | ||
115 | PaginationModule, | ||
116 | FileUploadModule | ||
117 | ], | ||
118 | providers: [ // expose our Services and Providers into Angular's dependency injection | ||
119 | ENV_PROVIDERS, | ||
120 | APP_PROVIDERS | ||
121 | ] | ||
122 | }) | ||
123 | export class AppModule { | ||
124 | constructor(public appRef: ApplicationRef, public appState: AppState) {} | ||
125 | hmrOnInit(store) { | ||
126 | if (!store || !store.state) return; | ||
127 | console.log('HMR store', store); | ||
128 | this.appState._state = store.state; | ||
129 | this.appRef.tick(); | ||
130 | delete store.state; | ||
131 | } | ||
132 | hmrOnDestroy(store) { | ||
133 | const cmpLocation = this.appRef.components.map(cmp => cmp.location.nativeElement); | ||
134 | // recreate elements | ||
135 | const state = this.appState._state; | ||
136 | store.state = state; | ||
137 | store.disposeOldHosts = createNewHosts(cmpLocation); | ||
138 | // remove styles | ||
139 | removeNgStyles(); | ||
140 | } | ||
141 | hmrAfterDestroy(store) { | ||
142 | // display new elements | ||
143 | store.disposeOldHosts(); | ||
144 | delete store.disposeOldHosts; | ||
145 | } | ||
146 | } | ||
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 59ef4ce55..03e2bce51 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts | |||
@@ -1,15 +1,18 @@ | |||
1 | import { RouterConfig } from '@angular/router'; | 1 | import { Routes } from '@angular/router'; |
2 | 2 | ||
3 | import { AccountRoutes } from './account'; | ||
3 | import { LoginRoutes } from './login'; | 4 | import { LoginRoutes } from './login'; |
5 | import { AdminRoutes } from './admin'; | ||
4 | import { VideosRoutes } from './videos'; | 6 | import { VideosRoutes } from './videos'; |
5 | 7 | ||
6 | export const routes: RouterConfig = [ | 8 | export const routes: Routes = [ |
7 | { | 9 | { |
8 | path: '', | 10 | path: '', |
9 | redirectTo: '/videos/list', | 11 | redirectTo: '/videos/list', |
10 | pathMatch: 'full' | 12 | pathMatch: 'full' |
11 | }, | 13 | }, |
12 | 14 | ...AdminRoutes, | |
15 | ...AccountRoutes, | ||
13 | ...LoginRoutes, | 16 | ...LoginRoutes, |
14 | ...VideosRoutes | 17 | ...VideosRoutes |
15 | ]; | 18 | ]; |
diff --git a/client/src/app/app.service.ts b/client/src/app/app.service.ts new file mode 100644 index 000000000..033c21900 --- /dev/null +++ b/client/src/app/app.service.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | |||
2 | import { Injectable } from '@angular/core'; | ||
3 | |||
4 | @Injectable() | ||
5 | export class AppState { | ||
6 | _state = { }; | ||
7 | |||
8 | constructor() { ; } | ||
9 | |||
10 | // already return a clone of the current state | ||
11 | get state() { | ||
12 | return this._state = this._clone(this._state); | ||
13 | } | ||
14 | // never allow mutation | ||
15 | set state(value) { | ||
16 | throw new Error('do not mutate the `.state` directly'); | ||
17 | } | ||
18 | |||
19 | |||
20 | get(prop?: any) { | ||
21 | // use our state getter for the clone | ||
22 | const state = this.state; | ||
23 | return state.hasOwnProperty(prop) ? state[prop] : state; | ||
24 | } | ||
25 | |||
26 | set(prop: string, value: any) { | ||
27 | // internally mutate our state | ||
28 | return this._state[prop] = value; | ||
29 | } | ||
30 | |||
31 | |||
32 | _clone(object) { | ||
33 | // simple object clone | ||
34 | return JSON.parse(JSON.stringify( object )); | ||
35 | } | ||
36 | } | ||
diff --git a/client/src/app/environment.ts b/client/src/app/environment.ts new file mode 100644 index 000000000..8bba89c4e --- /dev/null +++ b/client/src/app/environment.ts | |||
@@ -0,0 +1,50 @@ | |||
1 | |||
2 | // Angular 2 | ||
3 | // rc2 workaround | ||
4 | import { enableDebugTools, disableDebugTools } from '@angular/platform-browser'; | ||
5 | import { enableProdMode, ApplicationRef } from '@angular/core'; | ||
6 | // Environment Providers | ||
7 | let PROVIDERS = [ | ||
8 | // common env directives | ||
9 | ]; | ||
10 | |||
11 | // Angular debug tools in the dev console | ||
12 | // https://github.com/angular/angular/blob/86405345b781a9dc2438c0fbe3e9409245647019/TOOLS_JS.md | ||
13 | let _decorateModuleRef = function identity(value) { return value; }; | ||
14 | |||
15 | if ('production' === ENV) { | ||
16 | // Production | ||
17 | disableDebugTools(); | ||
18 | enableProdMode(); | ||
19 | |||
20 | PROVIDERS = [ | ||
21 | ...PROVIDERS, | ||
22 | // custom providers in production | ||
23 | ]; | ||
24 | |||
25 | } else { | ||
26 | |||
27 | _decorateModuleRef = (modRef: any) => { | ||
28 | const appRef = modRef.injector.get(ApplicationRef); | ||
29 | const cmpRef = appRef.components[0]; | ||
30 | |||
31 | let _ng = (<any>window).ng; | ||
32 | enableDebugTools(cmpRef); | ||
33 | (<any>window).ng.probe = _ng.probe; | ||
34 | (<any>window).ng.coreTokens = _ng.coreTokens; | ||
35 | return modRef; | ||
36 | }; | ||
37 | |||
38 | // Development | ||
39 | PROVIDERS = [ | ||
40 | ...PROVIDERS, | ||
41 | // custom providers in development | ||
42 | ]; | ||
43 | |||
44 | } | ||
45 | |||
46 | export const decorateModuleRef = _decorateModuleRef; | ||
47 | |||
48 | export const ENV_PROVIDERS = [ | ||
49 | ...PROVIDERS | ||
50 | ]; | ||
diff --git a/client/src/app/friends/friend.service.ts b/client/src/app/friends/friend.service.ts deleted file mode 100644 index 771046484..000000000 --- a/client/src/app/friends/friend.service.ts +++ /dev/null | |||
@@ -1,29 +0,0 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { Response } from '@angular/http'; | ||
3 | import { Observable } from 'rxjs/Observable'; | ||
4 | |||
5 | import { AuthHttp, AuthService } from '../shared'; | ||
6 | |||
7 | @Injectable() | ||
8 | export class FriendService { | ||
9 | private static BASE_FRIEND_URL: string = '/api/v1/pods/'; | ||
10 | |||
11 | constructor (private authHttp: AuthHttp, private authService: AuthService) {} | ||
12 | |||
13 | makeFriends() { | ||
14 | return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'makefriends') | ||
15 | .map(res => res.status) | ||
16 | .catch(this.handleError); | ||
17 | } | ||
18 | |||
19 | quitFriends() { | ||
20 | return this.authHttp.get(FriendService.BASE_FRIEND_URL + 'quitfriends') | ||
21 | .map(res => res.status) | ||
22 | .catch(this.handleError); | ||
23 | } | ||
24 | |||
25 | private handleError (error: Response): Observable<number> { | ||
26 | console.error(error); | ||
27 | return Observable.throw(error.json().error || 'Server error'); | ||
28 | } | ||
29 | } | ||
diff --git a/client/src/app/index.ts b/client/src/app/index.ts new file mode 100644 index 000000000..da53f6aef --- /dev/null +++ b/client/src/app/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './app.module'; | |||
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 5848fcba3..94a405405 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -1,17 +1,16 @@ | |||
1 | <h3>Login</h3> | 1 | <h3>Login</h3> |
2 | 2 | ||
3 | |||
4 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
5 | 4 | ||
6 | <form role="form" (ngSubmit)="login(username.value, password.value)" #loginForm="ngForm"> | 5 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> |
7 | <div class="form-group"> | 6 | <div class="form-group"> |
8 | <label for="username">Username</label> | 7 | <label for="username">Username</label> |
9 | <input | 8 | <input |
10 | type="text" class="form-control" name="username" id="username" placeholder="Username" required | 9 | type="text" class="form-control" id="username" placeholder="Username" required |
11 | ngControl="username" #username="ngForm" | 10 | formControlName="username" |
12 | > | 11 | > |
13 | <div [hidden]="username.valid || username.pristine" class="alert alert-danger"> | 12 | <div *ngIf="formErrors.username" class="alert alert-danger"> |
14 | Username is required | 13 | {{ formErrors.username }} |
15 | </div> | 14 | </div> |
16 | </div> | 15 | </div> |
17 | 16 | ||
@@ -19,12 +18,12 @@ | |||
19 | <label for="password">Password</label> | 18 | <label for="password">Password</label> |
20 | <input | 19 | <input |
21 | type="password" class="form-control" name="password" id="password" placeholder="Password" required | 20 | type="password" class="form-control" name="password" id="password" placeholder="Password" required |
22 | ngControl="password" #password="ngForm" | 21 | formControlName="password" |
23 | > | 22 | > |
24 | <div [hidden]="password.valid || password.pristine" class="alert alert-danger"> | 23 | <div *ngIf="formErrors.password" class="alert alert-danger"> |
25 | Password is required | 24 | {{ formErrors.password }} |
26 | </div> | 25 | </div> |
27 | </div> | 26 | </div> |
28 | 27 | ||
29 | <input type="submit" value="Login" class="btn btn-default" [disabled]="!loginForm.form.valid"> | 28 | <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid"> |
30 | </form> | 29 | </form> |
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index ddd62462e..c4ff7050b 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts | |||
@@ -1,35 +1,67 @@ | |||
1 | import { Component } from '@angular/core'; | 1 | import { Component, OnInit } from '@angular/core'; |
2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||
2 | import { Router } from '@angular/router'; | 3 | import { Router } from '@angular/router'; |
3 | 4 | ||
4 | import { AuthService } from '../shared'; | 5 | import { AuthService, FormReactive } from '../shared'; |
5 | 6 | ||
6 | @Component({ | 7 | @Component({ |
7 | selector: 'my-login', | 8 | selector: 'my-login', |
8 | template: require('./login.component.html') | 9 | templateUrl: './login.component.html' |
9 | }) | 10 | }) |
10 | 11 | ||
11 | export class LoginComponent { | 12 | export class LoginComponent extends FormReactive implements OnInit { |
12 | error: string = null; | 13 | error: string = null; |
13 | 14 | ||
15 | form: FormGroup; | ||
16 | formErrors = { | ||
17 | 'username': '', | ||
18 | 'password': '' | ||
19 | }; | ||
20 | validationMessages = { | ||
21 | 'username': { | ||
22 | 'required': 'Username is required.', | ||
23 | }, | ||
24 | 'password': { | ||
25 | 'required': 'Password is required.' | ||
26 | } | ||
27 | }; | ||
28 | |||
14 | constructor( | 29 | constructor( |
15 | private authService: AuthService, | 30 | private authService: AuthService, |
31 | private formBuilder: FormBuilder, | ||
16 | private router: Router | 32 | private router: Router |
17 | ) {} | 33 | ) { |
34 | super(); | ||
35 | } | ||
36 | |||
37 | buildForm() { | ||
38 | this.form = this.formBuilder.group({ | ||
39 | username: [ '', Validators.required ], | ||
40 | password: [ '', Validators.required ], | ||
41 | }); | ||
42 | |||
43 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
44 | } | ||
45 | |||
46 | ngOnInit() { | ||
47 | this.buildForm(); | ||
48 | } | ||
49 | |||
50 | login() { | ||
51 | this.error = null; | ||
52 | |||
53 | const { username, password } = this.form.value; | ||
18 | 54 | ||
19 | login(username: string, password: string) { | ||
20 | this.authService.login(username, password).subscribe( | 55 | this.authService.login(username, password).subscribe( |
21 | result => { | 56 | result => this.router.navigate(['/videos/list']), |
22 | this.error = null; | ||
23 | 57 | ||
24 | this.router.navigate(['/videos/list']); | ||
25 | }, | ||
26 | error => { | 58 | error => { |
27 | console.error(error); | 59 | console.error(error.json); |
28 | 60 | ||
29 | if (error.error === 'invalid_grant') { | 61 | if (error.json.error === 'invalid_grant') { |
30 | this.error = 'Credentials are invalid.'; | 62 | this.error = 'Credentials are invalid.'; |
31 | } else { | 63 | } else { |
32 | this.error = `${error.error}: ${error.error_description}`; | 64 | this.error = `${error.json.error}: ${error.json.error_description}`; |
33 | } | 65 | } |
34 | } | 66 | } |
35 | ); | 67 | ); |
diff --git a/client/src/app/menu.component.html b/client/src/app/menu.component.html new file mode 100644 index 000000000..29ef7f9cf --- /dev/null +++ b/client/src/app/menu.component.html | |||
@@ -0,0 +1,39 @@ | |||
1 | <menu class="col-md-2 col-sm-3 col-xs-3"> | ||
2 | <div class="panel-block"> | ||
3 | <div id="panel-user-login" class="panel-button"> | ||
4 | <span *ngIf="!isLoggedIn" > | ||
5 | <span class="hidden-xs glyphicon glyphicon-log-in"></span> | ||
6 | <a [routerLink]="['/login']">Login</a> | ||
7 | </span> | ||
8 | |||
9 | <span *ngIf="isLoggedIn"> | ||
10 | <span class="hidden-xs glyphicon glyphicon-log-out"></span> | ||
11 | <a *ngIf="isLoggedIn" (click)="logout()">Logout</a> | ||
12 | </span> | ||
13 | </div> | ||
14 | |||
15 | <div *ngIf="isLoggedIn" id="panel-user-account" class="panel-button"> | ||
16 | <span class="hidden-xs glyphicon glyphicon-user"></span> | ||
17 | <a [routerLink]="['/account']">My account</a> | ||
18 | </div> | ||
19 | </div> | ||
20 | |||
21 | <div class="panel-block"> | ||
22 | <div id="panel-get-videos" class="panel-button"> | ||
23 | <span class="hidden-xs glyphicon glyphicon-list"></span> | ||
24 | <a [routerLink]="['/videos/list']">Get videos</a> | ||
25 | </div> | ||
26 | |||
27 | <div id="panel-upload-video" class="panel-button" *ngIf="isLoggedIn"> | ||
28 | <span class="hidden-xs glyphicon glyphicon-cloud-upload"></span> | ||
29 | <a [routerLink]="['/videos/add']">Upload a video</a> | ||
30 | </div> | ||
31 | </div> | ||
32 | |||
33 | <div class="panel-block" *ngIf="isUserAdmin()"> | ||
34 | <div id="panel-get-videos" class="panel-button"> | ||
35 | <span class="hidden-xs glyphicon glyphicon-cog"></span> | ||
36 | <a [routerLink]="['/admin']">Administration</a> | ||
37 | </div> | ||
38 | </div> | ||
39 | </menu> | ||
diff --git a/client/src/app/menu.component.ts b/client/src/app/menu.component.ts new file mode 100644 index 000000000..6cfc854dd --- /dev/null +++ b/client/src/app/menu.component.ts | |||
@@ -0,0 +1,45 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | ||
2 | import { Router } from '@angular/router'; | ||
3 | |||
4 | import { AuthService, AuthStatus } from './shared'; | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-menu', | ||
8 | templateUrl: './menu.component.html' | ||
9 | }) | ||
10 | export class MenuComponent implements OnInit { | ||
11 | isLoggedIn: boolean; | ||
12 | |||
13 | constructor ( | ||
14 | private authService: AuthService, | ||
15 | private router: Router | ||
16 | ) {} | ||
17 | |||
18 | ngOnInit() { | ||
19 | this.isLoggedIn = this.authService.isLoggedIn(); | ||
20 | |||
21 | this.authService.loginChangedSource.subscribe( | ||
22 | status => { | ||
23 | if (status === AuthStatus.LoggedIn) { | ||
24 | this.isLoggedIn = true; | ||
25 | console.log('Logged in.'); | ||
26 | } else if (status === AuthStatus.LoggedOut) { | ||
27 | this.isLoggedIn = false; | ||
28 | console.log('Logged out.'); | ||
29 | } else { | ||
30 | console.error('Unknown auth status: ' + status); | ||
31 | } | ||
32 | } | ||
33 | ); | ||
34 | } | ||
35 | |||
36 | isUserAdmin() { | ||
37 | return this.authService.isAdmin(); | ||
38 | } | ||
39 | |||
40 | logout() { | ||
41 | this.authService.logout(); | ||
42 | // Redirect to home page | ||
43 | this.router.navigate(['/videos/list']); | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts index 9c7ef4389..2392898ca 100644 --- a/client/src/app/shared/auth/auth-http.service.ts +++ b/client/src/app/shared/auth/auth-http.service.ts | |||
@@ -28,7 +28,7 @@ export class AuthHttp extends Http { | |||
28 | return super.request(url, options) | 28 | return super.request(url, options) |
29 | .catch((err) => { | 29 | .catch((err) => { |
30 | if (err.status === 401) { | 30 | if (err.status === 401) { |
31 | return this.handleTokenExpired(err, url, options); | 31 | return this.handleTokenExpired(url, options); |
32 | } | 32 | } |
33 | 33 | ||
34 | return Observable.throw(err); | 34 | return Observable.throw(err); |
@@ -49,26 +49,29 @@ export class AuthHttp extends Http { | |||
49 | return this.request(url, options); | 49 | return this.request(url, options); |
50 | } | 50 | } |
51 | 51 | ||
52 | post(url: string, options?: RequestOptionsArgs): Observable<Response> { | 52 | post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
53 | if (!options) options = {}; | 53 | if (!options) options = {}; |
54 | options.method = RequestMethod.Post; | 54 | options.method = RequestMethod.Post; |
55 | options.body = body; | ||
55 | 56 | ||
56 | return this.request(url, options); | 57 | return this.request(url, options); |
57 | } | 58 | } |
58 | 59 | ||
59 | put(url: string, options?: RequestOptionsArgs): Observable<Response> { | 60 | put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
60 | if (!options) options = {}; | 61 | if (!options) options = {}; |
61 | options.method = RequestMethod.Put; | 62 | options.method = RequestMethod.Put; |
63 | options.body = body; | ||
62 | 64 | ||
63 | return this.request(url, options); | 65 | return this.request(url, options); |
64 | } | 66 | } |
65 | 67 | ||
66 | private handleTokenExpired(err: Response, url: string | Request, options: RequestOptionsArgs) { | 68 | private handleTokenExpired(url: string | Request, options: RequestOptionsArgs) { |
67 | return this.authService.refreshAccessToken().flatMap(() => { | 69 | return this.authService.refreshAccessToken() |
68 | this.setAuthorizationHeader(options.headers); | 70 | .flatMap(() => { |
71 | this.setAuthorizationHeader(options.headers); | ||
69 | 72 | ||
70 | return super.request(url, options); | 73 | return super.request(url, options); |
71 | }); | 74 | }); |
72 | } | 75 | } |
73 | 76 | ||
74 | private setAuthorizationHeader(headers: Headers) { | 77 | private setAuthorizationHeader(headers: Headers) { |
diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/auth-user.model.ts index 98852f835..bdd5ea5a9 100644 --- a/client/src/app/shared/auth/user.model.ts +++ b/client/src/app/shared/auth/auth-user.model.ts | |||
@@ -1,15 +1,28 @@ | |||
1 | export class User { | 1 | import { User } from '../users'; |
2 | |||
3 | export class AuthUser extends User { | ||
2 | private static KEYS = { | 4 | private static KEYS = { |
5 | ID: 'id', | ||
6 | ROLE: 'role', | ||
3 | USERNAME: 'username' | 7 | USERNAME: 'username' |
4 | }; | 8 | }; |
5 | 9 | ||
10 | id: string; | ||
11 | role: string; | ||
6 | username: string; | 12 | username: string; |
7 | tokens: Tokens; | 13 | tokens: Tokens; |
8 | 14 | ||
9 | static load() { | 15 | static load() { |
10 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); | 16 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); |
11 | if (usernameLocalStorage) { | 17 | if (usernameLocalStorage) { |
12 | return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load()); | 18 | return new AuthUser( |
19 | { | ||
20 | id: localStorage.getItem(this.KEYS.ID), | ||
21 | username: localStorage.getItem(this.KEYS.USERNAME), | ||
22 | role: localStorage.getItem(this.KEYS.ROLE) | ||
23 | }, | ||
24 | Tokens.load() | ||
25 | ); | ||
13 | } | 26 | } |
14 | 27 | ||
15 | return null; | 28 | return null; |
@@ -17,12 +30,14 @@ export class User { | |||
17 | 30 | ||
18 | static flush() { | 31 | static flush() { |
19 | localStorage.removeItem(this.KEYS.USERNAME); | 32 | localStorage.removeItem(this.KEYS.USERNAME); |
33 | localStorage.removeItem(this.KEYS.ID); | ||
34 | localStorage.removeItem(this.KEYS.ROLE); | ||
20 | Tokens.flush(); | 35 | Tokens.flush(); |
21 | } | 36 | } |
22 | 37 | ||
23 | constructor(username: string, hash_tokens: any) { | 38 | constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) { |
24 | this.username = username; | 39 | super(userHash); |
25 | this.tokens = new Tokens(hash_tokens); | 40 | this.tokens = new Tokens(hashTokens); |
26 | } | 41 | } |
27 | 42 | ||
28 | getAccessToken() { | 43 | getAccessToken() { |
@@ -43,12 +58,14 @@ export class User { | |||
43 | } | 58 | } |
44 | 59 | ||
45 | save() { | 60 | save() { |
46 | localStorage.setItem('username', this.username); | 61 | localStorage.setItem(AuthUser.KEYS.ID, this.id); |
62 | localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); | ||
63 | localStorage.setItem(AuthUser.KEYS.ROLE, this.role); | ||
47 | this.tokens.save(); | 64 | this.tokens.save(); |
48 | } | 65 | } |
49 | } | 66 | } |
50 | 67 | ||
51 | // Private class used only by User | 68 | // Private class only used by User |
52 | class Tokens { | 69 | class Tokens { |
53 | private static KEYS = { | 70 | private static KEYS = { |
54 | ACCESS_TOKEN: 'access_token', | 71 | ACCESS_TOKEN: 'access_token', |
diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 584298fff..a30c79c86 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts | |||
@@ -1,32 +1,39 @@ | |||
1 | import { Injectable } from '@angular/core'; | 1 | import { Injectable } from '@angular/core'; |
2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http'; | 2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http'; |
3 | import { Router } from '@angular/router'; | ||
3 | import { Observable } from 'rxjs/Observable'; | 4 | import { Observable } from 'rxjs/Observable'; |
4 | import { Subject } from 'rxjs/Subject'; | 5 | import { Subject } from 'rxjs/Subject'; |
5 | 6 | ||
6 | import { AuthStatus } from './auth-status.model'; | 7 | import { AuthStatus } from './auth-status.model'; |
7 | import { User } from './user.model'; | 8 | import { AuthUser } from './auth-user.model'; |
9 | import { RestExtractor } from '../rest'; | ||
8 | 10 | ||
9 | @Injectable() | 11 | @Injectable() |
10 | export class AuthService { | 12 | export class AuthService { |
11 | private static BASE_CLIENT_URL = '/api/v1/users/client'; | 13 | private static BASE_CLIENT_URL = '/api/v1/clients/local'; |
12 | private static BASE_TOKEN_URL = '/api/v1/users/token'; | 14 | private static BASE_TOKEN_URL = '/api/v1/users/token'; |
15 | private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; | ||
13 | 16 | ||
14 | loginChangedSource: Observable<AuthStatus>; | 17 | loginChangedSource: Observable<AuthStatus>; |
15 | 18 | ||
16 | private clientId: string; | 19 | private clientId: string; |
17 | private clientSecret: string; | 20 | private clientSecret: string; |
18 | private loginChanged: Subject<AuthStatus>; | 21 | private loginChanged: Subject<AuthStatus>; |
19 | private user: User = null; | 22 | private user: AuthUser = null; |
20 | 23 | ||
21 | constructor(private http: Http) { | 24 | constructor( |
25 | private http: Http, | ||
26 | private restExtractor: RestExtractor, | ||
27 | private router: Router | ||
28 | ) { | ||
22 | this.loginChanged = new Subject<AuthStatus>(); | 29 | this.loginChanged = new Subject<AuthStatus>(); |
23 | this.loginChangedSource = this.loginChanged.asObservable(); | 30 | this.loginChangedSource = this.loginChanged.asObservable(); |
24 | 31 | ||
25 | // Fetch the client_id/client_secret | 32 | // Fetch the client_id/client_secret |
26 | // FIXME: save in local storage? | 33 | // FIXME: save in local storage? |
27 | this.http.get(AuthService.BASE_CLIENT_URL) | 34 | this.http.get(AuthService.BASE_CLIENT_URL) |
28 | .map(res => res.json()) | 35 | .map(this.restExtractor.extractDataGet) |
29 | .catch(this.handleError) | 36 | .catch((res) => this.restExtractor.handleError(res)) |
30 | .subscribe( | 37 | .subscribe( |
31 | result => { | 38 | result => { |
32 | this.clientId = result.client_id; | 39 | this.clientId = result.client_id; |
@@ -34,12 +41,15 @@ export class AuthService { | |||
34 | console.log('Client credentials loaded.'); | 41 | console.log('Client credentials loaded.'); |
35 | }, | 42 | }, |
36 | error => { | 43 | error => { |
37 | alert(error); | 44 | alert( |
45 | `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + | ||
46 | 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | ||
47 | ); | ||
38 | } | 48 | } |
39 | ); | 49 | ); |
40 | 50 | ||
41 | // Return null if there is nothing to load | 51 | // Return null if there is nothing to load |
42 | this.user = User.load(); | 52 | this.user = AuthUser.load(); |
43 | } | 53 | } |
44 | 54 | ||
45 | getRefreshToken() { | 55 | getRefreshToken() { |
@@ -64,10 +74,16 @@ export class AuthService { | |||
64 | return this.user.getTokenType(); | 74 | return this.user.getTokenType(); |
65 | } | 75 | } |
66 | 76 | ||
67 | getUser(): User { | 77 | getUser(): AuthUser { |
68 | return this.user; | 78 | return this.user; |
69 | } | 79 | } |
70 | 80 | ||
81 | isAdmin() { | ||
82 | if (this.user === null) return false; | ||
83 | |||
84 | return this.user.isAdmin(); | ||
85 | } | ||
86 | |||
71 | isLoggedIn() { | 87 | isLoggedIn() { |
72 | if (this.getAccessToken()) { | 88 | if (this.getAccessToken()) { |
73 | return true; | 89 | return true; |
@@ -94,21 +110,23 @@ export class AuthService { | |||
94 | }; | 110 | }; |
95 | 111 | ||
96 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | 112 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) |
97 | .map(res => res.json()) | 113 | .map(this.restExtractor.extractDataGet) |
98 | .map(res => { | 114 | .map(res => { |
99 | res.username = username; | 115 | res.username = username; |
100 | return res; | 116 | return res; |
101 | }) | 117 | }) |
118 | .flatMap(res => this.fetchUserInformations(res)) | ||
102 | .map(res => this.handleLogin(res)) | 119 | .map(res => this.handleLogin(res)) |
103 | .catch(this.handleError); | 120 | .catch((res) => this.restExtractor.handleError(res)); |
104 | } | 121 | } |
105 | 122 | ||
106 | logout() { | 123 | logout() { |
107 | // TODO: make an HTTP request to revoke the tokens | 124 | // TODO: make an HTTP request to revoke the tokens |
108 | this.user = null; | 125 | this.user = null; |
109 | User.flush(); | ||
110 | 126 | ||
111 | this.setStatus(AuthStatus.LoggedIn); | 127 | AuthUser.flush(); |
128 | |||
129 | this.setStatus(AuthStatus.LoggedOut); | ||
112 | } | 130 | } |
113 | 131 | ||
114 | refreshAccessToken() { | 132 | refreshAccessToken() { |
@@ -131,36 +149,64 @@ export class AuthService { | |||
131 | }; | 149 | }; |
132 | 150 | ||
133 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | 151 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) |
134 | .map(res => res.json()) | 152 | .map(this.restExtractor.extractDataGet) |
135 | .map(res => this.handleRefreshToken(res)) | 153 | .map(res => this.handleRefreshToken(res)) |
136 | .catch(this.handleError); | 154 | .catch((res: Response) => { |
155 | // The refresh token is invalid? | ||
156 | if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { | ||
157 | console.error('Cannot refresh token -> logout...'); | ||
158 | this.logout(); | ||
159 | this.router.navigate(['/login']); | ||
160 | |||
161 | return Observable.throw({ | ||
162 | json: '', | ||
163 | text: 'You need to reconnect.' | ||
164 | }); | ||
165 | } | ||
166 | |||
167 | return this.restExtractor.handleError(res); | ||
168 | }); | ||
137 | } | 169 | } |
138 | 170 | ||
139 | private setStatus(status: AuthStatus) { | 171 | private fetchUserInformations (obj: any) { |
140 | this.loginChanged.next(status); | 172 | // Do not call authHttp here to avoid circular dependencies headaches |
173 | |||
174 | const headers = new Headers(); | ||
175 | headers.set('Authorization', `Bearer ${obj.access_token}`); | ||
176 | |||
177 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | ||
178 | .map(res => res.json()) | ||
179 | .map(res => { | ||
180 | obj.id = res.id; | ||
181 | obj.role = res.role; | ||
182 | return obj; | ||
183 | } | ||
184 | ); | ||
141 | } | 185 | } |
142 | 186 | ||
143 | private handleLogin (obj: any) { | 187 | private handleLogin (obj: any) { |
188 | const id = obj.id; | ||
144 | const username = obj.username; | 189 | const username = obj.username; |
145 | const hash_tokens = { | 190 | const role = obj.role; |
191 | const hashTokens = { | ||
146 | access_token: obj.access_token, | 192 | access_token: obj.access_token, |
147 | token_type: obj.token_type, | 193 | token_type: obj.token_type, |
148 | refresh_token: obj.refresh_token | 194 | refresh_token: obj.refresh_token |
149 | }; | 195 | }; |
150 | 196 | ||
151 | this.user = new User(username, hash_tokens); | 197 | this.user = new AuthUser({ id, username, role }, hashTokens); |
152 | this.user.save(); | 198 | this.user.save(); |
153 | 199 | ||
154 | this.setStatus(AuthStatus.LoggedIn); | 200 | this.setStatus(AuthStatus.LoggedIn); |
155 | } | 201 | } |
156 | 202 | ||
157 | private handleError (error: Response) { | ||
158 | console.error(error); | ||
159 | return Observable.throw(error.json() || { error: 'Server error' }); | ||
160 | } | ||
161 | |||
162 | private handleRefreshToken (obj: any) { | 203 | private handleRefreshToken (obj: any) { |
163 | this.user.refreshTokens(obj.access_token, obj.refresh_token); | 204 | this.user.refreshTokens(obj.access_token, obj.refresh_token); |
164 | this.user.save(); | 205 | this.user.save(); |
165 | } | 206 | } |
207 | |||
208 | private setStatus(status: AuthStatus) { | ||
209 | this.loginChanged.next(status); | ||
210 | } | ||
211 | |||
166 | } | 212 | } |
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts index aafaacbf1..ebd9e14cd 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/auth/index.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export * from './auth-http.service'; | 1 | export * from './auth-http.service'; |
2 | export * from './auth-status.model'; | 2 | export * from './auth-status.model'; |
3 | export * from './auth.service'; | 3 | export * from './auth.service'; |
4 | export * from './user.model'; | 4 | export * from './auth-user.model'; |
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts new file mode 100644 index 000000000..1e8a69771 --- /dev/null +++ b/client/src/app/shared/forms/form-reactive.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { FormGroup } from '@angular/forms'; | ||
2 | |||
3 | export abstract class FormReactive { | ||
4 | abstract form: FormGroup; | ||
5 | abstract formErrors: Object; | ||
6 | abstract validationMessages: Object; | ||
7 | |||
8 | abstract buildForm(): void; | ||
9 | |||
10 | protected onValueChanged(data?: any) { | ||
11 | for (const field in this.formErrors) { | ||
12 | // clear previous error message (if any) | ||
13 | this.formErrors[field] = ''; | ||
14 | const control = this.form.get(field); | ||
15 | |||
16 | if (control && control.dirty && !control.valid) { | ||
17 | const messages = this.validationMessages[field]; | ||
18 | for (const key in control.errors) { | ||
19 | this.formErrors[field] += messages[key] + ' '; | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | } | ||
24 | } | ||
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts new file mode 100644 index 000000000..1d2ae6f68 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './url.validator'; | ||
2 | export * from './user'; | ||
3 | export * from './video'; | ||
diff --git a/client/src/app/shared/forms/form-validators/url.validator.ts b/client/src/app/shared/forms/form-validators/url.validator.ts new file mode 100644 index 000000000..67163b4e9 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/url.validator.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { FormControl } from '@angular/forms'; | ||
2 | |||
3 | export function validateUrl(c: FormControl) { | ||
4 | let URL_REGEXP = new RegExp('^https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$'); | ||
5 | |||
6 | return URL_REGEXP.test(c.value) ? null : { | ||
7 | validateUrl: { | ||
8 | valid: false | ||
9 | } | ||
10 | }; | ||
11 | } | ||
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts new file mode 100644 index 000000000..5b11ff265 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/user.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import { Validators } from '@angular/forms'; | ||
2 | |||
3 | export const USER_USERNAME = { | ||
4 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Username is required.', | ||
7 | 'minlength': 'Username must be at least 3 characters long.', | ||
8 | 'maxlength': 'Username cannot be more than 20 characters long.' | ||
9 | } | ||
10 | }; | ||
11 | export const USER_PASSWORD = { | ||
12 | VALIDATORS: [ Validators.required, Validators.minLength(6) ], | ||
13 | MESSAGES: { | ||
14 | 'required': 'Password is required.', | ||
15 | 'minlength': 'Password must be at least 6 characters long.', | ||
16 | } | ||
17 | }; | ||
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts new file mode 100644 index 000000000..3766d4018 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/video.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { Validators } from '@angular/forms'; | ||
2 | |||
3 | export const VIDEO_NAME = { | ||
4 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Video name is required.', | ||
7 | 'minlength': 'Video name must be at least 3 characters long.', | ||
8 | 'maxlength': 'Video name cannot be more than 50 characters long.' | ||
9 | } | ||
10 | }; | ||
11 | export const VIDEO_DESCRIPTION = { | ||
12 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], | ||
13 | MESSAGES: { | ||
14 | 'required': 'Video description is required.', | ||
15 | 'minlength': 'Video description must be at least 3 characters long.', | ||
16 | 'maxlength': 'Video description cannot be more than 250 characters long.' | ||
17 | } | ||
18 | }; | ||
19 | |||
20 | export const VIDEO_TAGS = { | ||
21 | VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ], | ||
22 | MESSAGES: { | ||
23 | 'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.' | ||
24 | } | ||
25 | }; | ||
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts new file mode 100644 index 000000000..588ebb4be --- /dev/null +++ b/client/src/app/shared/forms/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './form-validators'; | ||
2 | export * from './form-reactive'; | ||
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index dfea4c67c..af34b4b64 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts | |||
@@ -1,2 +1,5 @@ | |||
1 | export * from './auth'; | 1 | export * from './auth'; |
2 | export * from './forms'; | ||
3 | export * from './rest'; | ||
2 | export * from './search'; | 4 | export * from './search'; |
5 | export * from './users'; | ||
diff --git a/client/src/app/shared/rest/index.ts b/client/src/app/shared/rest/index.ts new file mode 100644 index 000000000..3c9509dc7 --- /dev/null +++ b/client/src/app/shared/rest/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './rest-extractor.service'; | ||
2 | export * from './rest-pagination'; | ||
3 | export * from './rest.service'; | ||
diff --git a/client/src/app/shared/rest/rest-extractor.service.ts b/client/src/app/shared/rest/rest-extractor.service.ts new file mode 100644 index 000000000..fcb1598f4 --- /dev/null +++ b/client/src/app/shared/rest/rest-extractor.service.ts | |||
@@ -0,0 +1,52 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { Response } from '@angular/http'; | ||
3 | import { Observable } from 'rxjs/Observable'; | ||
4 | |||
5 | export interface ResultList { | ||
6 | data: any[]; | ||
7 | total: number; | ||
8 | } | ||
9 | |||
10 | @Injectable() | ||
11 | export class RestExtractor { | ||
12 | |||
13 | constructor () { ; } | ||
14 | |||
15 | extractDataBool(res: Response) { | ||
16 | return true; | ||
17 | } | ||
18 | |||
19 | extractDataList(res: Response) { | ||
20 | const body = res.json(); | ||
21 | |||
22 | const ret: ResultList = { | ||
23 | data: body.data, | ||
24 | total: body.total | ||
25 | }; | ||
26 | |||
27 | return ret; | ||
28 | } | ||
29 | |||
30 | extractDataGet(res: Response) { | ||
31 | return res.json(); | ||
32 | } | ||
33 | |||
34 | handleError(res: Response) { | ||
35 | let text = 'Server error: '; | ||
36 | text += res.text(); | ||
37 | let json = ''; | ||
38 | |||
39 | try { | ||
40 | json = res.json(); | ||
41 | } catch (err) { ; } | ||
42 | |||
43 | const error = { | ||
44 | json, | ||
45 | text | ||
46 | }; | ||
47 | |||
48 | console.error(error); | ||
49 | |||
50 | return Observable.throw(error); | ||
51 | } | ||
52 | } | ||
diff --git a/client/src/app/videos/shared/pagination.model.ts b/client/src/app/shared/rest/rest-pagination.ts index eda44ebfb..0cfa4f468 100644 --- a/client/src/app/videos/shared/pagination.model.ts +++ b/client/src/app/shared/rest/rest-pagination.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export interface Pagination { | 1 | export interface RestPagination { |
2 | currentPage: number; | 2 | currentPage: number; |
3 | itemsPerPage: number; | 3 | itemsPerPage: number; |
4 | totalItems: number; | 4 | totalItems: number; |
5 | } | 5 | }; |
diff --git a/client/src/app/shared/rest/rest.service.ts b/client/src/app/shared/rest/rest.service.ts new file mode 100644 index 000000000..16b47e957 --- /dev/null +++ b/client/src/app/shared/rest/rest.service.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { URLSearchParams } from '@angular/http'; | ||
3 | |||
4 | import { RestPagination } from './rest-pagination'; | ||
5 | |||
6 | @Injectable() | ||
7 | export class RestService { | ||
8 | |||
9 | buildRestGetParams(pagination?: RestPagination, sort?: string) { | ||
10 | const params = new URLSearchParams(); | ||
11 | |||
12 | if (pagination) { | ||
13 | const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage; | ||
14 | const count: number = pagination.itemsPerPage; | ||
15 | |||
16 | params.set('start', start.toString()); | ||
17 | params.set('count', count.toString()); | ||
18 | } | ||
19 | |||
20 | if (sort) { | ||
21 | params.set('sort', sort); | ||
22 | } | ||
23 | |||
24 | return params; | ||
25 | } | ||
26 | |||
27 | } | ||
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts index e864fbc17..b6237469b 100644 --- a/client/src/app/shared/search/search.component.ts +++ b/client/src/app/shared/search/search.component.ts | |||
@@ -1,15 +1,13 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | 1 | import { Component, OnInit } from '@angular/core'; |
2 | 2 | import { Router } from '@angular/router'; | |
3 | import { DROPDOWN_DIRECTIVES} from 'ng2-bootstrap/components/dropdown'; | ||
4 | 3 | ||
5 | import { Search } from './search.model'; | 4 | import { Search } from './search.model'; |
6 | import { SearchField } from './search-field.type'; | 5 | import { SearchField } from './search-field.type'; |
7 | import { SearchService } from './search.service'; | 6 | import { SearchService } from './search.service'; |
8 | 7 | ||
9 | @Component({ | 8 | @Component({ |
10 | selector: 'my-search', | 9 | selector: 'my-search', |
11 | template: require('./search.component.html'), | 10 | templateUrl: './search.component.html' |
12 | directives: [ DROPDOWN_DIRECTIVES ] | ||
13 | }) | 11 | }) |
14 | 12 | ||
15 | export class SearchComponent implements OnInit { | 13 | export class SearchComponent implements OnInit { |
@@ -25,10 +23,10 @@ export class SearchComponent implements OnInit { | |||
25 | value: '' | 23 | value: '' |
26 | }; | 24 | }; |
27 | 25 | ||
28 | constructor(private searchService: SearchService) {} | 26 | constructor(private searchService: SearchService, private router: Router) {} |
29 | 27 | ||
30 | ngOnInit() { | 28 | ngOnInit() { |
31 | // Subscribe is the search changed | 29 | // Subscribe if the search changed |
32 | // Usually changed by videos list component | 30 | // Usually changed by videos list component |
33 | this.searchService.updateSearch.subscribe( | 31 | this.searchService.updateSearch.subscribe( |
34 | newSearchCriterias => { | 32 | newSearchCriterias => { |
@@ -58,6 +56,10 @@ export class SearchComponent implements OnInit { | |||
58 | } | 56 | } |
59 | 57 | ||
60 | doSearch() { | 58 | doSearch() { |
59 | if (this.router.url.indexOf('/videos/list') === -1) { | ||
60 | this.router.navigate([ '/videos/list' ]); | ||
61 | } | ||
62 | |||
61 | this.searchService.searchUpdated.next(this.searchCriterias); | 63 | this.searchService.searchUpdated.next(this.searchCriterias); |
62 | } | 64 | } |
63 | 65 | ||
diff --git a/client/src/app/shared/search/search.service.ts b/client/src/app/shared/search/search.service.ts index c7993db3d..717a7fa50 100644 --- a/client/src/app/shared/search/search.service.ts +++ b/client/src/app/shared/search/search.service.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Injectable } from '@angular/core'; | 1 | import { Injectable } from '@angular/core'; |
2 | import { Subject } from 'rxjs/Subject'; | 2 | import { Subject } from 'rxjs/Subject'; |
3 | import { ReplaySubject } from 'rxjs/ReplaySubject'; | ||
3 | 4 | ||
4 | import { Search } from './search.model'; | 5 | import { Search } from './search.model'; |
5 | 6 | ||
@@ -12,6 +13,6 @@ export class SearchService { | |||
12 | 13 | ||
13 | constructor() { | 14 | constructor() { |
14 | this.updateSearch = new Subject<Search>(); | 15 | this.updateSearch = new Subject<Search>(); |
15 | this.searchUpdated = new Subject<Search>(); | 16 | this.searchUpdated = new ReplaySubject<Search>(1); |
16 | } | 17 | } |
17 | } | 18 | } |
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts new file mode 100644 index 000000000..5a670ce8f --- /dev/null +++ b/client/src/app/shared/users/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './user.model'; | |||
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts new file mode 100644 index 000000000..726495d11 --- /dev/null +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -0,0 +1,20 @@ | |||
1 | export class User { | ||
2 | id: string; | ||
3 | username: string; | ||
4 | role: string; | ||
5 | createdDate: Date; | ||
6 | |||
7 | constructor(hash: { id: string, username: string, role: string, createdDate?: Date }) { | ||
8 | this.id = hash.id; | ||
9 | this.username = hash.username; | ||
10 | this.role = hash.role; | ||
11 | |||
12 | if (hash.createdDate) { | ||
13 | this.createdDate = hash.createdDate; | ||
14 | } | ||
15 | } | ||
16 | |||
17 | isAdmin() { | ||
18 | return this.role === 'admin'; | ||
19 | } | ||
20 | } | ||
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index a54120f5d..67d16ead1 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | export * from './loader'; | 1 | export * from './loader'; |
2 | export * from './pagination.model'; | ||
3 | export * from './sort-field.type'; | 2 | export * from './sort-field.type'; |
4 | export * from './video.model'; | 3 | export * from './video.model'; |
5 | export * from './video.service'; | 4 | export * from './video.service'; |
diff --git a/client/src/app/videos/shared/loader/loader.component.ts b/client/src/app/videos/shared/loader/loader.component.ts index cdd07d1b4..e72d2f3f3 100644 --- a/client/src/app/videos/shared/loader/loader.component.ts +++ b/client/src/app/videos/shared/loader/loader.component.ts | |||
@@ -2,8 +2,8 @@ import { Component, Input } from '@angular/core'; | |||
2 | 2 | ||
3 | @Component({ | 3 | @Component({ |
4 | selector: 'my-loader', | 4 | selector: 'my-loader', |
5 | styles: [ require('./loader.component.scss') ], | 5 | styleUrls: [ './loader.component.scss' ], |
6 | template: require('./loader.component.html') | 6 | templateUrl: './loader.component.html' |
7 | }) | 7 | }) |
8 | 8 | ||
9 | export class LoaderComponent { | 9 | export class LoaderComponent { |
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index b4396f767..ad8557533 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import { Injectable } from '@angular/core'; | 1 | import { Injectable } from '@angular/core'; |
2 | import { Http, Response, URLSearchParams } from '@angular/http'; | 2 | import { Http } from '@angular/http'; |
3 | import { Observable } from 'rxjs/Observable'; | 3 | import { Observable } from 'rxjs/Observable'; |
4 | 4 | ||
5 | import { Pagination } from './pagination.model'; | ||
6 | import { Search } from '../../shared'; | 5 | import { Search } from '../../shared'; |
7 | import { SortField } from './sort-field.type'; | 6 | import { SortField } from './sort-field.type'; |
8 | import { AuthHttp, AuthService } from '../../shared'; | 7 | import { AuthHttp, AuthService, RestExtractor, RestPagination, RestService, ResultList } from '../../shared'; |
9 | import { Video } from './video.model'; | 8 | import { Video } from './video.model'; |
10 | 9 | ||
11 | @Injectable() | 10 | @Injectable() |
@@ -15,68 +14,51 @@ export class VideoService { | |||
15 | constructor( | 14 | constructor( |
16 | private authService: AuthService, | 15 | private authService: AuthService, |
17 | private authHttp: AuthHttp, | 16 | private authHttp: AuthHttp, |
18 | private http: Http | 17 | private http: Http, |
18 | private restExtractor: RestExtractor, | ||
19 | private restService: RestService | ||
19 | ) {} | 20 | ) {} |
20 | 21 | ||
21 | getVideo(id: string) { | 22 | getVideo(id: string): Observable<Video> { |
22 | return this.http.get(VideoService.BASE_VIDEO_URL + id) | 23 | return this.http.get(VideoService.BASE_VIDEO_URL + id) |
23 | .map(res => <Video> res.json()) | 24 | .map(this.restExtractor.extractDataGet) |
24 | .catch(this.handleError); | 25 | .catch((res) => this.restExtractor.handleError(res)); |
25 | } | 26 | } |
26 | 27 | ||
27 | getVideos(pagination: Pagination, sort: SortField) { | 28 | getVideos(pagination: RestPagination, sort: SortField) { |
28 | const params = this.createPaginationParams(pagination); | 29 | const params = this.restService.buildRestGetParams(pagination, sort); |
29 | |||
30 | if (sort) params.set('sort', sort); | ||
31 | 30 | ||
32 | return this.http.get(VideoService.BASE_VIDEO_URL, { search: params }) | 31 | return this.http.get(VideoService.BASE_VIDEO_URL, { search: params }) |
33 | .map(res => res.json()) | 32 | .map(res => res.json()) |
34 | .map(this.extractVideos) | 33 | .map(this.extractVideos) |
35 | .catch(this.handleError); | 34 | .catch((res) => this.restExtractor.handleError(res)); |
36 | } | 35 | } |
37 | 36 | ||
38 | removeVideo(id: string) { | 37 | removeVideo(id: string) { |
39 | return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) | 38 | return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) |
40 | .map(res => <number> res.status) | 39 | .map(this.restExtractor.extractDataBool) |
41 | .catch(this.handleError); | 40 | .catch((res) => this.restExtractor.handleError(res)); |
42 | } | 41 | } |
43 | 42 | ||
44 | searchVideos(search: Search, pagination: Pagination, sort: SortField) { | 43 | searchVideos(search: Search, pagination: RestPagination, sort: SortField) { |
45 | const params = this.createPaginationParams(pagination); | 44 | const params = this.restService.buildRestGetParams(pagination, sort); |
46 | 45 | ||
47 | if (search.field) params.set('field', search.field); | 46 | if (search.field) params.set('field', search.field); |
48 | if (sort) params.set('sort', sort); | ||
49 | 47 | ||
50 | return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params }) | 48 | return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params }) |
51 | .map(res => res.json()) | 49 | .map(this.restExtractor.extractDataList) |
52 | .map(this.extractVideos) | 50 | .map(this.extractVideos) |
53 | .catch(this.handleError); | 51 | .catch((res) => this.restExtractor.handleError(res)); |
54 | } | ||
55 | |||
56 | private createPaginationParams(pagination: Pagination) { | ||
57 | const params = new URLSearchParams(); | ||
58 | const start: number = (pagination.currentPage - 1) * pagination.itemsPerPage; | ||
59 | const count: number = pagination.itemsPerPage; | ||
60 | |||
61 | params.set('start', start.toString()); | ||
62 | params.set('count', count.toString()); | ||
63 | |||
64 | return params; | ||
65 | } | 52 | } |
66 | 53 | ||
67 | private extractVideos(body: any) { | 54 | private extractVideos(result: ResultList) { |
68 | const videos_json = body.data; | 55 | const videosJson = result.data; |
69 | const totalVideos = body.total; | 56 | const totalVideos = result.total; |
70 | const videos = []; | 57 | const videos = []; |
71 | for (const video_json of videos_json) { | 58 | for (const videoJson of videosJson) { |
72 | videos.push(new Video(video_json)); | 59 | videos.push(new Video(videoJson)); |
73 | } | 60 | } |
74 | 61 | ||
75 | return { videos, totalVideos }; | 62 | return { videos, totalVideos }; |
76 | } | 63 | } |
77 | |||
78 | private handleError(error: Response) { | ||
79 | console.error(error); | ||
80 | return Observable.throw(error.json().error || 'Server error'); | ||
81 | } | ||
82 | } | 64 | } |
diff --git a/client/src/app/videos/video-add/video-add.component.html b/client/src/app/videos/video-add/video-add.component.html index bcd78c7cb..64320cae7 100644 --- a/client/src/app/videos/video-add/video-add.component.html +++ b/client/src/app/videos/video-add/video-add.component.html | |||
@@ -2,31 +2,31 @@ | |||
2 | 2 | ||
3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 3 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
4 | 4 | ||
5 | <form novalidate (ngSubmit)="upload()" [ngFormModel]="videoForm"> | 5 | <form novalidate (ngSubmit)="upload()" [formGroup]="form"> |
6 | <div class="form-group"> | 6 | <div class="form-group"> |
7 | <label for="name">Name</label> | 7 | <label for="name">Name</label> |
8 | <input | 8 | <input |
9 | type="text" class="form-control" name="name" id="name" | 9 | type="text" class="form-control" id="name" |
10 | ngControl="name" #name="ngForm" [(ngModel)]="video.name" | 10 | formControlName="name" |
11 | > | 11 | > |
12 | <div [hidden]="name.valid || name.pristine" class="alert alert-warning"> | 12 | <div *ngIf="formErrors.name" class="alert alert-danger"> |
13 | A name is required and should be between 3 and 50 characters long | 13 | {{ formErrors.name }} |
14 | </div> | 14 | </div> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div class="form-group"> | 17 | <div class="form-group"> |
18 | <label for="tags">Tags</label> | 18 | <label for="tags">Tags</label> |
19 | <input | 19 | <input |
20 | type="text" class="form-control" name="tags" id="tags" | 20 | type="text" class="form-control" id="currentTag" |
21 | ngControl="tags" #tags="ngForm" [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag" | 21 | formControlName="currentTag" (keyup)="onTagKeyPress($event)" |
22 | > | 22 | > |
23 | <div [hidden]="tags.valid || tags.pristine" class="alert alert-warning"> | 23 | <div *ngIf="formErrors.currentTag" class="alert alert-danger"> |
24 | A tag should be between 2 and 10 characters (alphanumeric) long | 24 | {{ formErrors.currentTag }} |
25 | </div> | 25 | </div> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div class="tags"> | 28 | <div class="tags"> |
29 | <div class="label label-primary tag" *ngFor="let tag of video.tags"> | 29 | <div class="label label-primary tag" *ngFor="let tag of tags"> |
30 | {{ tag }} | 30 | {{ tag }} |
31 | <span class="remove" (click)="removeTag(tag)">x</span> | 31 | <span class="remove" (click)="removeTag(tag)">x</span> |
32 | </div> | 32 | </div> |
@@ -53,12 +53,12 @@ | |||
53 | <div class="form-group"> | 53 | <div class="form-group"> |
54 | <label for="description">Description</label> | 54 | <label for="description">Description</label> |
55 | <textarea | 55 | <textarea |
56 | name="description" id="description" class="form-control" placeholder="Description..." | 56 | id="description" class="form-control" placeholder="Description..." |
57 | ngControl="description" #description="ngForm" [(ngModel)]="video.description" | 57 | formControlName="description" |
58 | > | 58 | > |
59 | </textarea> | 59 | </textarea> |
60 | <div [hidden]="description.valid || description.pristine" class="alert alert-warning"> | 60 | <div *ngIf="formErrors.description" class="alert alert-danger"> |
61 | A description is required and should be between 3 and 250 characters long | 61 | {{ formErrors.description }} |
62 | </div> | 62 | </div> |
63 | </div> | 63 | </div> |
64 | 64 | ||
@@ -69,7 +69,7 @@ | |||
69 | <div class="form-group"> | 69 | <div class="form-group"> |
70 | <input | 70 | <input |
71 | type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()" | 71 | type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()" |
72 | [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null" | 72 | [disabled]="!form.valid || tags.length === 0 || filename === null" |
73 | > | 73 | > |
74 | </div> | 74 | </div> |
75 | </form> | 75 | </form> |
diff --git a/client/src/app/videos/video-add/video-add.component.ts b/client/src/app/videos/video-add/video-add.component.ts index c0f8cb9c4..d12a7d572 100644 --- a/client/src/app/videos/video-add/video-add.component.ts +++ b/client/src/app/videos/video-add/video-add.component.ts | |||
@@ -1,37 +1,42 @@ | |||
1 | import { Control, ControlGroup, Validators } from '@angular/common'; | ||
2 | import { Component, ElementRef, OnInit } from '@angular/core'; | 1 | import { Component, ElementRef, OnInit } from '@angular/core'; |
2 | import { FormBuilder, FormGroup } from '@angular/forms'; | ||
3 | import { Router } from '@angular/router'; | 3 | import { Router } from '@angular/router'; |
4 | 4 | ||
5 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; | 5 | import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; |
6 | import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar'; | ||
7 | import { FileSelectDirective, FileUploader } from 'ng2-file-upload/ng2-file-upload'; | ||
8 | 6 | ||
9 | import { AuthService } from '../../shared'; | 7 | import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared'; |
10 | 8 | ||
11 | @Component({ | 9 | @Component({ |
12 | selector: 'my-videos-add', | 10 | selector: 'my-videos-add', |
13 | styles: [ require('./video-add.component.scss') ], | 11 | styleUrls: [ './video-add.component.scss' ], |
14 | template: require('./video-add.component.html'), | 12 | templateUrl: './video-add.component.html' |
15 | directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES ], | ||
16 | pipes: [ BytesPipe ] | ||
17 | }) | 13 | }) |
18 | 14 | ||
19 | export class VideoAddComponent implements OnInit { | 15 | export class VideoAddComponent extends FormReactive implements OnInit { |
20 | currentTag: string; // Tag the user is writing in the input | 16 | tags: string[] = []; |
21 | error: string = null; | ||
22 | videoForm: ControlGroup; | ||
23 | uploader: FileUploader; | 17 | uploader: FileUploader; |
24 | video = { | 18 | |
19 | error: string = null; | ||
20 | form: FormGroup; | ||
21 | formErrors = { | ||
25 | name: '', | 22 | name: '', |
26 | tags: [], | 23 | description: '', |
27 | description: '' | 24 | currentTag: '' |
25 | }; | ||
26 | validationMessages = { | ||
27 | name: VIDEO_NAME.MESSAGES, | ||
28 | description: VIDEO_DESCRIPTION.MESSAGES, | ||
29 | currentTag: VIDEO_TAGS.MESSAGES | ||
28 | }; | 30 | }; |
29 | 31 | ||
30 | constructor( | 32 | constructor( |
31 | private authService: AuthService, | 33 | private authService: AuthService, |
32 | private elementRef: ElementRef, | 34 | private elementRef: ElementRef, |
35 | private formBuilder: FormBuilder, | ||
33 | private router: Router | 36 | private router: Router |
34 | ) {} | 37 | ) { |
38 | super(); | ||
39 | } | ||
35 | 40 | ||
36 | get filename() { | 41 | get filename() { |
37 | if (this.uploader.queue.length === 0) { | 42 | if (this.uploader.queue.length === 0) { |
@@ -41,20 +46,26 @@ export class VideoAddComponent implements OnInit { | |||
41 | return this.uploader.queue[0].file.name; | 46 | return this.uploader.queue[0].file.name; |
42 | } | 47 | } |
43 | 48 | ||
44 | get isTagsInputDisabled () { | 49 | buildForm() { |
45 | return this.video.tags.length >= 3; | 50 | this.form = this.formBuilder.group({ |
51 | name: [ '', VIDEO_NAME.VALIDATORS ], | ||
52 | description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], | ||
53 | currentTag: [ '', VIDEO_TAGS.VALIDATORS ] | ||
54 | }); | ||
55 | |||
56 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
46 | } | 57 | } |
47 | 58 | ||
48 | getInvalidFieldsTitle() { | 59 | getInvalidFieldsTitle() { |
49 | let title = ''; | 60 | let title = ''; |
50 | const nameControl = this.videoForm.controls['name']; | 61 | const nameControl = this.form.controls['name']; |
51 | const descriptionControl = this.videoForm.controls['description']; | 62 | const descriptionControl = this.form.controls['description']; |
52 | 63 | ||
53 | if (!nameControl.valid) { | 64 | if (!nameControl.valid) { |
54 | title += 'A name is required\n'; | 65 | title += 'A name is required\n'; |
55 | } | 66 | } |
56 | 67 | ||
57 | if (this.video.tags.length === 0) { | 68 | if (this.tags.length === 0) { |
58 | title += 'At least one tag is required\n'; | 69 | title += 'At least one tag is required\n'; |
59 | } | 70 | } |
60 | 71 | ||
@@ -70,13 +81,6 @@ export class VideoAddComponent implements OnInit { | |||
70 | } | 81 | } |
71 | 82 | ||
72 | ngOnInit() { | 83 | ngOnInit() { |
73 | this.videoForm = new ControlGroup({ | ||
74 | name: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(50) ])), | ||
75 | description: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(250) ])), | ||
76 | tags: new Control('', Validators.pattern('^[a-zA-Z0-9]{2,10}$')) | ||
77 | }); | ||
78 | |||
79 | |||
80 | this.uploader = new FileUploader({ | 84 | this.uploader = new FileUploader({ |
81 | authToken: this.authService.getRequestHeaderValue(), | 85 | authToken: this.authService.getRequestHeaderValue(), |
82 | queueLimit: 1, | 86 | queueLimit: 1, |
@@ -85,26 +89,37 @@ export class VideoAddComponent implements OnInit { | |||
85 | }); | 89 | }); |
86 | 90 | ||
87 | this.uploader.onBuildItemForm = (item, form) => { | 91 | this.uploader.onBuildItemForm = (item, form) => { |
88 | form.append('name', this.video.name); | 92 | const name = this.form.value['name']; |
89 | form.append('description', this.video.description); | 93 | const description = this.form.value['description']; |
94 | |||
95 | form.append('name', name); | ||
96 | form.append('description', description); | ||
90 | 97 | ||
91 | for (let i = 0; i < this.video.tags.length; i++) { | 98 | for (let i = 0; i < this.tags.length; i++) { |
92 | form.append(`tags[${i}]`, this.video.tags[i]); | 99 | form.append(`tags[${i}]`, this.tags[i]); |
93 | } | 100 | } |
94 | }; | 101 | }; |
102 | |||
103 | this.buildForm(); | ||
95 | } | 104 | } |
96 | 105 | ||
97 | onTagKeyPress(event: KeyboardEvent) { | 106 | onTagKeyPress(event: KeyboardEvent) { |
107 | const currentTag = this.form.value['currentTag']; | ||
108 | |||
98 | // Enter press | 109 | // Enter press |
99 | if (event.keyCode === 13) { | 110 | if (event.keyCode === 13) { |
100 | // Check if the tag is valid and does not already exist | 111 | // Check if the tag is valid and does not already exist |
101 | if ( | 112 | if ( |
102 | this.currentTag !== '' && | 113 | currentTag !== '' && |
103 | this.videoForm.controls['tags'].valid && | 114 | this.form.controls['currentTag'].valid && |
104 | this.video.tags.indexOf(this.currentTag) === -1 | 115 | this.tags.indexOf(currentTag) === -1 |
105 | ) { | 116 | ) { |
106 | this.video.tags.push(this.currentTag); | 117 | this.tags.push(currentTag); |
107 | this.currentTag = ''; | 118 | this.form.patchValue({ currentTag: '' }); |
119 | |||
120 | if (this.tags.length >= 3) { | ||
121 | this.form.get('currentTag').disable(); | ||
122 | } | ||
108 | } | 123 | } |
109 | } | 124 | } |
110 | } | 125 | } |
@@ -114,7 +129,7 @@ export class VideoAddComponent implements OnInit { | |||
114 | } | 129 | } |
115 | 130 | ||
116 | removeTag(tag: string) { | 131 | removeTag(tag: string) { |
117 | this.video.tags.splice(this.video.tags.indexOf(tag), 1); | 132 | this.tags.splice(this.tags.indexOf(tag), 1); |
118 | } | 133 | } |
119 | 134 | ||
120 | upload() { | 135 | upload() { |
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index 5691d684e..6b086e938 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts | |||
@@ -1,39 +1,30 @@ | |||
1 | import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; | 1 | import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; |
2 | import { AsyncPipe } from '@angular/common'; | 2 | import { ActivatedRoute, Router } from '@angular/router'; |
3 | import { ActivatedRoute, Router, ROUTER_DIRECTIVES } from '@angular/router'; | ||
4 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; | 3 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; |
5 | 4 | ||
6 | import { PAGINATION_DIRECTIVES } from 'ng2-bootstrap/components/pagination'; | ||
7 | |||
8 | import { | 5 | import { |
9 | LoaderComponent, | ||
10 | Pagination, | ||
11 | SortField, | 6 | SortField, |
12 | Video, | 7 | Video, |
13 | VideoService | 8 | VideoService |
14 | } from '../shared'; | 9 | } from '../shared'; |
15 | import { AuthService, Search, SearchField, User } from '../../shared'; | 10 | import { AuthService, AuthUser, RestPagination, Search, SearchField } from '../../shared'; |
16 | import { VideoMiniatureComponent } from './video-miniature.component'; | ||
17 | import { VideoSortComponent } from './video-sort.component'; | ||
18 | import { SearchService } from '../../shared'; | 11 | import { SearchService } from '../../shared'; |
19 | 12 | ||
20 | @Component({ | 13 | @Component({ |
21 | selector: 'my-videos-list', | 14 | selector: 'my-videos-list', |
22 | styles: [ require('./video-list.component.scss') ], | 15 | styleUrls: [ './video-list.component.scss' ], |
23 | pipes: [ AsyncPipe ], | 16 | templateUrl: './video-list.component.html' |
24 | template: require('./video-list.component.html'), | ||
25 | directives: [ LoaderComponent, PAGINATION_DIRECTIVES, ROUTER_DIRECTIVES, VideoMiniatureComponent, VideoSortComponent ] | ||
26 | }) | 17 | }) |
27 | 18 | ||
28 | export class VideoListComponent implements OnInit, OnDestroy { | 19 | export class VideoListComponent implements OnInit, OnDestroy { |
29 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false); | 20 | loading: BehaviorSubject<boolean> = new BehaviorSubject(false); |
30 | pagination: Pagination = { | 21 | pagination: RestPagination = { |
31 | currentPage: 1, | 22 | currentPage: 1, |
32 | itemsPerPage: 9, | 23 | itemsPerPage: 9, |
33 | totalItems: null | 24 | totalItems: null |
34 | }; | 25 | }; |
35 | sort: SortField; | 26 | sort: SortField; |
36 | user: User = null; | 27 | user: AuthUser = null; |
37 | videos: Video[] = []; | 28 | videos: Video[] = []; |
38 | 29 | ||
39 | private search: Search; | 30 | private search: Search; |
@@ -51,7 +42,7 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
51 | 42 | ||
52 | ngOnInit() { | 43 | ngOnInit() { |
53 | if (this.authService.isLoggedIn()) { | 44 | if (this.authService.isLoggedIn()) { |
54 | this.user = User.load(); | 45 | this.user = AuthUser.load(); |
55 | } | 46 | } |
56 | 47 | ||
57 | // Subscribe to route changes | 48 | // Subscribe to route changes |
@@ -66,6 +57,8 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
66 | // Subscribe to search changes | 57 | // Subscribe to search changes |
67 | this.subSearch = this.searchService.searchUpdated.subscribe(search => { | 58 | this.subSearch = this.searchService.searchUpdated.subscribe(search => { |
68 | this.search = search; | 59 | this.search = search; |
60 | // Reset pagination | ||
61 | this.pagination.currentPage = 1; | ||
69 | 62 | ||
70 | this.navigateToNewParams(); | 63 | this.navigateToNewParams(); |
71 | }); | 64 | }); |
@@ -76,7 +69,7 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
76 | this.subSearch.unsubscribe(); | 69 | this.subSearch.unsubscribe(); |
77 | } | 70 | } |
78 | 71 | ||
79 | getVideos(detectChanges = true) { | 72 | getVideos() { |
80 | this.loading.next(true); | 73 | this.loading.next(true); |
81 | this.videos = []; | 74 | this.videos = []; |
82 | 75 | ||
@@ -97,7 +90,7 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
97 | 90 | ||
98 | this.loading.next(false); | 91 | this.loading.next(false); |
99 | }, | 92 | }, |
100 | error => alert(error) | 93 | error => alert(error.text) |
101 | ); | 94 | ); |
102 | } | 95 | } |
103 | 96 | ||
@@ -153,7 +146,11 @@ export class VideoListComponent implements OnInit, OnDestroy { | |||
153 | 146 | ||
154 | this.sort = <SortField>routeParams['sort'] || '-createdDate'; | 147 | this.sort = <SortField>routeParams['sort'] || '-createdDate'; |
155 | 148 | ||
156 | this.pagination.currentPage = parseInt(routeParams['page']) || 1; | 149 | if (routeParams['page'] !== undefined) { |
150 | this.pagination.currentPage = parseInt(routeParams['page']); | ||
151 | } else { | ||
152 | this.pagination.currentPage = 1; | ||
153 | } | ||
157 | 154 | ||
158 | this.changeDetector.detectChanges(); | 155 | this.changeDetector.detectChanges(); |
159 | } | 156 | } |
diff --git a/client/src/app/videos/video-list/video-miniature.component.ts b/client/src/app/videos/video-list/video-miniature.component.ts index 84bab950e..398d2db75 100644 --- a/client/src/app/videos/video-list/video-miniature.component.ts +++ b/client/src/app/videos/video-list/video-miniature.component.ts | |||
@@ -1,16 +1,12 @@ | |||
1 | import { DatePipe } from '@angular/common'; | ||
2 | import { Component, Input, Output, EventEmitter } from '@angular/core'; | 1 | import { Component, Input, Output, EventEmitter } from '@angular/core'; |
3 | import { ROUTER_DIRECTIVES } from '@angular/router'; | ||
4 | 2 | ||
5 | import { SortField, Video, VideoService } from '../shared'; | 3 | import { SortField, Video, VideoService } from '../shared'; |
6 | import { User } from '../../shared'; | 4 | import { User } from '../../shared'; |
7 | 5 | ||
8 | @Component({ | 6 | @Component({ |
9 | selector: 'my-video-miniature', | 7 | selector: 'my-video-miniature', |
10 | styles: [ require('./video-miniature.component.scss') ], | 8 | styleUrls: [ './video-miniature.component.scss' ], |
11 | template: require('./video-miniature.component.html'), | 9 | templateUrl: './video-miniature.component.html' |
12 | directives: [ ROUTER_DIRECTIVES ], | ||
13 | pipes: [ DatePipe ] | ||
14 | }) | 10 | }) |
15 | 11 | ||
16 | export class VideoMiniatureComponent { | 12 | export class VideoMiniatureComponent { |
@@ -40,7 +36,7 @@ export class VideoMiniatureComponent { | |||
40 | if (confirm('Do you really want to remove this video?')) { | 36 | if (confirm('Do you really want to remove this video?')) { |
41 | this.videoService.removeVideo(id).subscribe( | 37 | this.videoService.removeVideo(id).subscribe( |
42 | status => this.removed.emit(true), | 38 | status => this.removed.emit(true), |
43 | error => alert(error) | 39 | error => alert(error.text) |
44 | ); | 40 | ); |
45 | } | 41 | } |
46 | } | 42 | } |
diff --git a/client/src/app/videos/video-list/video-sort.component.ts b/client/src/app/videos/video-list/video-sort.component.ts index 0d76b54b7..ca94b07c2 100644 --- a/client/src/app/videos/video-list/video-sort.component.ts +++ b/client/src/app/videos/video-list/video-sort.component.ts | |||
@@ -4,7 +4,7 @@ import { SortField } from '../shared'; | |||
4 | 4 | ||
5 | @Component({ | 5 | @Component({ |
6 | selector: 'my-video-sort', | 6 | selector: 'my-video-sort', |
7 | template: require('./video-sort.component.html') | 7 | templateUrl: './video-sort.component.html' |
8 | }) | 8 | }) |
9 | 9 | ||
10 | export class VideoSortComponent { | 10 | export class VideoSortComponent { |
diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts index 3aaed0487..239e24c99 100644 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ b/client/src/app/videos/video-watch/video-watch.component.ts | |||
@@ -1,18 +1,13 @@ | |||
1 | import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; | 1 | import { Component, ElementRef, NgZone, OnDestroy, OnInit } from '@angular/core'; |
2 | import { ActivatedRoute } from '@angular/router'; | 2 | import { ActivatedRoute } from '@angular/router'; |
3 | 3 | ||
4 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; | 4 | import { Video, VideoService } from '../shared'; |
5 | |||
6 | import { LoaderComponent, Video, VideoService } from '../shared'; | ||
7 | import { WebTorrentService } from './webtorrent.service'; | 5 | import { WebTorrentService } from './webtorrent.service'; |
8 | 6 | ||
9 | @Component({ | 7 | @Component({ |
10 | selector: 'my-video-watch', | 8 | selector: 'my-video-watch', |
11 | template: require('./video-watch.component.html'), | 9 | templateUrl: './video-watch.component.html', |
12 | styles: [ require('./video-watch.component.scss') ], | 10 | styleUrls: [ './video-watch.component.scss' ] |
13 | providers: [ WebTorrentService ], | ||
14 | directives: [ LoaderComponent ], | ||
15 | pipes: [ BytesPipe ] | ||
16 | }) | 11 | }) |
17 | 12 | ||
18 | export class VideoWatchComponent implements OnInit, OnDestroy { | 13 | export class VideoWatchComponent implements OnInit, OnDestroy { |
@@ -31,6 +26,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
31 | 26 | ||
32 | constructor( | 27 | constructor( |
33 | private elementRef: ElementRef, | 28 | private elementRef: ElementRef, |
29 | private ngZone: NgZone, | ||
34 | private route: ActivatedRoute, | 30 | private route: ActivatedRoute, |
35 | private videoService: VideoService, | 31 | private videoService: VideoService, |
36 | private webTorrentService: WebTorrentService | 32 | private webTorrentService: WebTorrentService |
@@ -65,12 +61,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
65 | } | 61 | } |
66 | }); | 62 | }); |
67 | 63 | ||
68 | // Refresh each second | 64 | this.runInProgress(torrent); |
69 | this.torrentInfosInterval = setInterval(() => { | ||
70 | this.downloadSpeed = torrent.downloadSpeed; | ||
71 | this.numPeers = torrent.numPeers; | ||
72 | this.uploadSpeed = torrent.uploadSpeed; | ||
73 | }, 1000); | ||
74 | }); | 65 | }); |
75 | } | 66 | } |
76 | 67 | ||
@@ -91,7 +82,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
91 | this.video = video; | 82 | this.video = video; |
92 | this.loadVideo(); | 83 | this.loadVideo(); |
93 | }, | 84 | }, |
94 | error => alert(error) | 85 | error => alert(error.text) |
95 | ); | 86 | ); |
96 | }); | 87 | }); |
97 | } | 88 | } |
@@ -100,4 +91,15 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
100 | this.error = true; | 91 | this.error = true; |
101 | console.error('The video load seems to be abnormally long.'); | 92 | console.error('The video load seems to be abnormally long.'); |
102 | } | 93 | } |
94 | |||
95 | private runInProgress(torrent: any) { | ||
96 | // Refresh each second | ||
97 | this.torrentInfosInterval = setInterval(() => { | ||
98 | this.ngZone.run(() => { | ||
99 | this.downloadSpeed = torrent.downloadSpeed; | ||
100 | this.numPeers = torrent.numPeers; | ||
101 | this.uploadSpeed = torrent.uploadSpeed; | ||
102 | }); | ||
103 | }, 1000); | ||
104 | } | ||
103 | } | 105 | } |
diff --git a/client/src/app/videos/videos.component.ts b/client/src/app/videos/videos.component.ts index 76252afbb..591e7523d 100644 --- a/client/src/app/videos/videos.component.ts +++ b/client/src/app/videos/videos.component.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { Component } from '@angular/core'; | 1 | import { Component } from '@angular/core'; |
2 | import { ROUTER_DIRECTIVES } from '@angular/router'; | ||
3 | 2 | ||
4 | @Component({ | 3 | @Component({ |
5 | template: '<router-outlet></router-outlet>', | 4 | template: '<router-outlet></router-outlet>' |
6 | directives: [ ROUTER_DIRECTIVES ] | ||
7 | }) | 5 | }) |
8 | 6 | ||
9 | export class VideosComponent { | 7 | export class VideosComponent { |
diff --git a/client/src/app/videos/videos.routes.ts b/client/src/app/videos/videos.routes.ts index 1f088b376..074f96596 100644 --- a/client/src/app/videos/videos.routes.ts +++ b/client/src/app/videos/videos.routes.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import { RouterConfig } from '@angular/router'; | 1 | import { Routes } from '@angular/router'; |
2 | 2 | ||
3 | import { VideoAddComponent } from './video-add'; | 3 | import { VideoAddComponent } from './video-add'; |
4 | import { VideoListComponent } from './video-list'; | 4 | import { VideoListComponent } from './video-list'; |
5 | import { VideosComponent } from './videos.component'; | 5 | import { VideosComponent } from './videos.component'; |
6 | import { VideoWatchComponent } from './video-watch'; | 6 | import { VideoWatchComponent } from './video-watch'; |
7 | 7 | ||
8 | export const VideosRoutes: RouterConfig = [ | 8 | export const VideosRoutes: Routes = [ |
9 | { | 9 | { |
10 | path: 'videos', | 10 | path: 'videos', |
11 | component: VideosComponent, | 11 | component: VideosComponent, |
diff --git a/client/src/custom-typings.d.ts b/client/src/custom-typings.d.ts index 14c7d8ade..95787181f 100644 --- a/client/src/custom-typings.d.ts +++ b/client/src/custom-typings.d.ts | |||
@@ -1,15 +1,27 @@ | |||
1 | /* | 1 | /* |
2 | * Custom Type Definitions | 2 | * Custom Type Definitions |
3 | * When including 3rd party modules you also need to include the type definition for the module | 3 | * When including 3rd party modules you also need to include the type definition for the module |
4 | * if they don't provide one within the module. You can try to install it with typings | 4 | * if they don't provide one within the module. You can try to install it with @types |
5 | 5 | ||
6 | typings install node --save | 6 | npm install @types/node |
7 | npm install @types/lodash | ||
7 | 8 | ||
8 | * If you can't find the type definition in the registry we can make an ambient definition in | 9 | * If you can't find the type definition in the registry we can make an ambient/global definition in |
9 | * this file for now. For example | 10 | * this file for now. For example |
10 | 11 | ||
11 | declare module "my-module" { | 12 | declare module 'my-module' { |
12 | export function doesSomething(value: string): string; | 13 | export function doesSomething(value: string): string; |
14 | } | ||
15 | |||
16 | * If you are using a CommonJS module that is using module.exports then you will have to write your | ||
17 | * types using export = yourObjectOrFunction with a namespace above it | ||
18 | * notice how we have to create a namespace that is equal to the function we're | ||
19 | * assigning the export to | ||
20 | |||
21 | declare module 'jwt-decode' { | ||
22 | function jwtDecode(token: string): any; | ||
23 | namespace jwtDecode {} | ||
24 | export = jwtDecode; | ||
13 | } | 25 | } |
14 | 26 | ||
15 | * | 27 | * |
@@ -17,33 +29,65 @@ declare module "my-module" { | |||
17 | * | 29 | * |
18 | 30 | ||
19 | declare var assert: any; | 31 | declare var assert: any; |
32 | declare var _: any; | ||
33 | declare var $: any; | ||
20 | 34 | ||
21 | * | 35 | * |
22 | * If you're importing a module that uses Node.js modules which are CommonJS you need to import as | 36 | * If you're importing a module that uses Node.js modules which are CommonJS you need to import as |
37 | * in the files such as main.browser.ts or any file within app/ | ||
23 | * | 38 | * |
24 | 39 | ||
25 | import * as _ from 'lodash' | 40 | import * as _ from 'lodash' |
26 | 41 | ||
27 | * You can include your type definitions in this file until you create one for the typings registry | 42 | * You can include your type definitions in this file until you create one for the @types |
28 | * see https://github.com/typings/registry | ||
29 | * | 43 | * |
30 | */ | 44 | */ |
31 | 45 | ||
46 | // support NodeJS modules without type definitions | ||
47 | declare module '*'; | ||
32 | 48 | ||
33 | // Extra variables that live on Global that will be replaced by webpack DefinePlugin | 49 | // Extra variables that live on Global that will be replaced by webpack DefinePlugin |
34 | declare var ENV: string; | 50 | declare var ENV: string; |
35 | declare var HMR: boolean; | 51 | declare var HMR: boolean; |
52 | declare var System: SystemJS; | ||
53 | |||
54 | interface SystemJS { | ||
55 | import: (path?: string) => Promise<any>; | ||
56 | } | ||
57 | |||
36 | interface GlobalEnvironment { | 58 | interface GlobalEnvironment { |
37 | ENV; | 59 | ENV; |
38 | HMR; | 60 | HMR; |
61 | SystemJS: SystemJS; | ||
62 | System: SystemJS; | ||
39 | } | 63 | } |
40 | 64 | ||
65 | interface Es6PromiseLoader { | ||
66 | (id: string): (exportName?: string) => Promise<any>; | ||
67 | } | ||
68 | |||
69 | type FactoryEs6PromiseLoader = () => Es6PromiseLoader; | ||
70 | type FactoryPromise = () => Promise<any>; | ||
71 | |||
72 | type AsyncRoutes = { | ||
73 | [component: string]: Es6PromiseLoader | | ||
74 | Function | | ||
75 | FactoryEs6PromiseLoader | | ||
76 | FactoryPromise | ||
77 | }; | ||
78 | |||
79 | |||
80 | type IdleCallbacks = Es6PromiseLoader | | ||
81 | Function | | ||
82 | FactoryEs6PromiseLoader | | ||
83 | FactoryPromise ; | ||
84 | |||
41 | interface WebpackModule { | 85 | interface WebpackModule { |
42 | hot: { | 86 | hot: { |
43 | data?: any, | 87 | data?: any, |
44 | idle: any, | 88 | idle: any, |
45 | accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void; | 89 | accept(dependencies?: string | string[], callback?: (updatedDependencies?: any) => void): void; |
46 | decline(dependencies?: string | string[]): void; | 90 | decline(deps?: any | string | string[]): void; |
47 | dispose(callback?: (data?: any) => void): void; | 91 | dispose(callback?: (data?: any) => void): void; |
48 | addDisposeHandler(callback?: (data?: any) => void): void; | 92 | addDisposeHandler(callback?: (data?: any) => void): void; |
49 | removeDisposeHandler(callback?: (data?: any) => void): void; | 93 | removeDisposeHandler(callback?: (data?: any) => void): void; |
@@ -54,66 +98,26 @@ interface WebpackModule { | |||
54 | }; | 98 | }; |
55 | } | 99 | } |
56 | 100 | ||
101 | |||
57 | interface WebpackRequire { | 102 | interface WebpackRequire { |
58 | context(file: string, flag?: boolean, exp?: RegExp): any; | 103 | (id: string): any; |
104 | (paths: string[], callback: (...modules: any[]) => void): void; | ||
105 | ensure(ids: string[], callback: (req: WebpackRequire) => void, chunkName?: string): void; | ||
106 | context(directory: string, useSubDirectories?: boolean, regExp?: RegExp): WebpackContext; | ||
59 | } | 107 | } |
60 | 108 | ||
109 | interface WebpackContext extends WebpackRequire { | ||
110 | keys(): string[]; | ||
111 | } | ||
61 | 112 | ||
62 | interface ErrorStackTraceLimit { | 113 | interface ErrorStackTraceLimit { |
63 | stackTraceLimit: number; | 114 | stackTraceLimit: number; |
64 | } | 115 | } |
65 | 116 | ||
66 | 117 | ||
67 | |||
68 | // Extend typings | 118 | // Extend typings |
69 | interface NodeRequire extends WebpackRequire {} | 119 | interface NodeRequire extends WebpackRequire {} |
70 | interface ErrorConstructor extends ErrorStackTraceLimit {} | 120 | interface ErrorConstructor extends ErrorStackTraceLimit {} |
121 | interface NodeRequireFunction extends Es6PromiseLoader {} | ||
71 | interface NodeModule extends WebpackModule {} | 122 | interface NodeModule extends WebpackModule {} |
72 | interface Global extends GlobalEnvironment {} | 123 | interface Global extends GlobalEnvironment {} |
73 | |||
74 | |||
75 | declare namespace Reflect { | ||
76 | function decorate(decorators: ClassDecorator[], target: Function): Function; | ||
77 | function decorate( | ||
78 | decorators: (PropertyDecorator | MethodDecorator)[], | ||
79 | target: Object, | ||
80 | targetKey: string | symbol, | ||
81 | descriptor?: PropertyDescriptor): PropertyDescriptor; | ||
82 | |||
83 | function metadata(metadataKey: any, metadataValue: any): { | ||
84 | (target: Function): void; | ||
85 | (target: Object, propertyKey: string | symbol): void; | ||
86 | }; | ||
87 | function defineMetadata(metadataKey: any, metadataValue: any, target: Object): void; | ||
88 | function defineMetadata( | ||
89 | metadataKey: any, | ||
90 | metadataValue: any, | ||
91 | target: Object, | ||
92 | targetKey: string | symbol): void; | ||
93 | function hasMetadata(metadataKey: any, target: Object): boolean; | ||
94 | function hasMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; | ||
95 | function hasOwnMetadata(metadataKey: any, target: Object): boolean; | ||
96 | function hasOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; | ||
97 | function getMetadata(metadataKey: any, target: Object): any; | ||
98 | function getMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any; | ||
99 | function getOwnMetadata(metadataKey: any, target: Object): any; | ||
100 | function getOwnMetadata(metadataKey: any, target: Object, targetKey: string | symbol): any; | ||
101 | function getMetadataKeys(target: Object): any[]; | ||
102 | function getMetadataKeys(target: Object, targetKey: string | symbol): any[]; | ||
103 | function getOwnMetadataKeys(target: Object): any[]; | ||
104 | function getOwnMetadataKeys(target: Object, targetKey: string | symbol): any[]; | ||
105 | function deleteMetadata(metadataKey: any, target: Object): boolean; | ||
106 | function deleteMetadata(metadataKey: any, target: Object, targetKey: string | symbol): boolean; | ||
107 | } | ||
108 | |||
109 | |||
110 | // We need this here since there is a problem with Zone.js typings | ||
111 | interface Thenable<T> { | ||
112 | then<U>( | ||
113 | onFulfilled?: (value: T) => U | Thenable<U>, | ||
114 | onRejected?: (error: any) => U | Thenable<U>): Thenable<U>; | ||
115 | then<U>( | ||
116 | onFulfilled?: (value: T) => U | Thenable<U>, | ||
117 | onRejected?: (error: any) => void): Thenable<U>; | ||
118 | catch<U>(onRejected?: (error: any) => U | Thenable<U>): Thenable<U>; | ||
119 | } | ||
diff --git a/client/src/index.html b/client/src/index.html index 5cf491221..f39d8d2cf 100644 --- a/client/src/index.html +++ b/client/src/index.html | |||
@@ -1,3 +1,4 @@ | |||
1 | <!DOCTYPE html> | ||
1 | <html> | 2 | <html> |
2 | <head> | 3 | <head> |
3 | <base href="/"> | 4 | <base href="/"> |
diff --git a/client/src/main.ts b/client/src/main.ts index a78d275ad..70bf48537 100644 --- a/client/src/main.ts +++ b/client/src/main.ts | |||
@@ -1,28 +1,20 @@ | |||
1 | import { enableProdMode, provide } from '@angular/core'; | 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; |
2 | import { | 2 | import { decorateModuleRef } from './app/environment'; |
3 | HTTP_PROVIDERS, | 3 | import { bootloader } from '@angularclass/hmr'; |
4 | RequestOptions, | 4 | /* |
5 | XHRBackend | 5 | * App Module |
6 | } from '@angular/http'; | 6 | * our top level module that holds all of our components |
7 | import { bootstrap } from '@angular/platform-browser-dynamic'; | 7 | */ |
8 | import { provideRouter } from '@angular/router'; | 8 | import { AppModule } from './app'; |
9 | 9 | ||
10 | import { AppComponent } from './app/app.component'; | 10 | /* |
11 | import { routes } from './app/app.routes'; | 11 | * Bootstrap our Angular app with a top level NgModule |
12 | import { AuthHttp, AuthService } from './app/shared'; | 12 | */ |
13 | 13 | export function main(): Promise<any> { | |
14 | if (process.env.ENV === 'production') { | 14 | return platformBrowserDynamic() |
15 | enableProdMode(); | 15 | .bootstrapModule(AppModule) |
16 | .then(decorateModuleRef) | ||
17 | .catch(err => console.error(err)); | ||
16 | } | 18 | } |
17 | 19 | ||
18 | bootstrap(AppComponent, [ | 20 | bootloader(main); |
19 | HTTP_PROVIDERS, | ||
20 | provide(AuthHttp, { | ||
21 | useFactory: (backend: XHRBackend, defaultOptions: RequestOptions, authService: AuthService) => { | ||
22 | return new AuthHttp(backend, defaultOptions, authService); | ||
23 | }, | ||
24 | deps: [ XHRBackend, RequestOptions, AuthService ] | ||
25 | }), | ||
26 | AuthService, | ||
27 | provideRouter(routes) | ||
28 | ]); | ||
diff --git a/client/src/polyfills.ts b/client/src/polyfills.ts index 740a563bb..65e211459 100644 --- a/client/src/polyfills.ts +++ b/client/src/polyfills.ts | |||
@@ -6,9 +6,28 @@ require('intl/locale-data/jsonp/en.js'); | |||
6 | import 'ie-shim'; // Internet Explorer | 6 | import 'ie-shim'; // Internet Explorer |
7 | 7 | ||
8 | // Prefer CoreJS over the polyfills above | 8 | // Prefer CoreJS over the polyfills above |
9 | import 'core-js/es6'; | 9 | import 'core-js/es6/symbol'; |
10 | import 'core-js/es6/object'; | ||
11 | import 'core-js/es6/function'; | ||
12 | import 'core-js/es6/parse-int'; | ||
13 | import 'core-js/es6/parse-float'; | ||
14 | import 'core-js/es6/number'; | ||
15 | import 'core-js/es6/math'; | ||
16 | import 'core-js/es6/string'; | ||
17 | import 'core-js/es6/date'; | ||
18 | import 'core-js/es6/array'; | ||
19 | import 'core-js/es6/regexp'; | ||
20 | import 'core-js/es6/map'; | ||
21 | import 'core-js/es6/set'; | ||
22 | import 'core-js/es6/weak-map'; | ||
23 | import 'core-js/es6/weak-set'; | ||
24 | import 'core-js/es6/typed'; | ||
25 | import 'core-js/es6/reflect'; | ||
26 | // see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 | ||
27 | // import 'core-js/es6/promise'; | ||
28 | |||
10 | import 'core-js/es7/reflect'; | 29 | import 'core-js/es7/reflect'; |
11 | require('zone.js/dist/zone'); | 30 | import 'zone.js/dist/zone'; |
12 | 31 | ||
13 | // Typescript emit helpers polyfill | 32 | // Typescript emit helpers polyfill |
14 | import 'ts-helpers'; | 33 | import 'ts-helpers'; |
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index 9c48b4627..b3bdffe50 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -6,6 +6,45 @@ body { | |||
6 | } | 6 | } |
7 | } | 7 | } |
8 | 8 | ||
9 | menu { | ||
10 | @media screen and (max-width: 600px) { | ||
11 | margin-right: 3px !important; | ||
12 | padding: 3px !important; | ||
13 | min-height: 400px !important; | ||
14 | } | ||
15 | |||
16 | min-height: 600px; | ||
17 | margin-right: 20px; | ||
18 | border-right: 1px solid rgba(0, 0, 0, 0.2); | ||
19 | |||
20 | .panel-block:not(:last-child) { | ||
21 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); | ||
22 | } | ||
23 | |||
24 | .panel-button { | ||
25 | margin: 8px; | ||
26 | cursor: pointer; | ||
27 | transition: margin 0.2s; | ||
28 | |||
29 | &:hover { | ||
30 | margin-left: 15px; | ||
31 | } | ||
32 | |||
33 | a { | ||
34 | color: #333333; | ||
35 | } | ||
36 | } | ||
37 | |||
38 | .glyphicon { | ||
39 | margin: 5px; | ||
40 | } | ||
41 | } | ||
42 | |||
43 | .table-column-id { | ||
44 | width: 200px; | ||
45 | } | ||
46 | |||
47 | |||
9 | footer { | 48 | footer { |
10 | border-top: 1px solid rgba(0, 0, 0, 0.2); | 49 | border-top: 1px solid rgba(0, 0, 0, 0.2); |
11 | padding-top: 10px; | 50 | padding-top: 10px; |
diff --git a/client/src/vendor.ts b/client/src/vendor.ts index 8f029191a..95356d9d0 100644 --- a/client/src/vendor.ts +++ b/client/src/vendor.ts | |||
@@ -8,13 +8,17 @@ import '@angular/platform-browser'; | |||
8 | import '@angular/platform-browser-dynamic'; | 8 | import '@angular/platform-browser-dynamic'; |
9 | import '@angular/core'; | 9 | import '@angular/core'; |
10 | import '@angular/common'; | 10 | import '@angular/common'; |
11 | import '@angular/forms'; | ||
11 | import '@angular/http'; | 12 | import '@angular/http'; |
12 | import '@angular/router'; | 13 | import '@angular/router'; |
13 | 14 | ||
15 | import '@angularclass/hmr'; | ||
16 | |||
14 | // RxJS | 17 | // RxJS |
15 | import 'rxjs/Observable'; | 18 | import 'rxjs/Observable'; |
16 | import 'rxjs/Subject'; | 19 | import 'rxjs/Subject'; |
17 | import 'rxjs/add/operator/catch'; | 20 | import 'rxjs/add/operator/catch'; |
21 | import 'rxjs/add/operator/mergeMap'; | ||
18 | import 'rxjs/add/operator/map'; | 22 | import 'rxjs/add/operator/map'; |
19 | import 'rxjs/add/observable/throw'; | 23 | import 'rxjs/add/observable/throw'; |
20 | 24 | ||
diff --git a/client/tsconfig.json b/client/tsconfig.json index 67d1fb4f1..10573b8ee 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json | |||
@@ -3,74 +3,35 @@ | |||
3 | "target": "es5", | 3 | "target": "es5", |
4 | "module": "commonjs", | 4 | "module": "commonjs", |
5 | "moduleResolution": "node", | 5 | "moduleResolution": "node", |
6 | "sourceMap": true, | ||
7 | "emitDecoratorMetadata": true, | 6 | "emitDecoratorMetadata": true, |
8 | "experimentalDecorators": true, | 7 | "experimentalDecorators": true, |
9 | "noImplicitAny": false, | 8 | "allowSyntheticDefaultImports": true, |
10 | "noEmitHelpers": true | 9 | "sourceMap": true, |
10 | "noEmitHelpers": true, | ||
11 | "strictNullChecks": false, | ||
12 | "baseUrl": "./src", | ||
13 | "paths": [ | ||
14 | ], | ||
15 | "lib": [ | ||
16 | "dom", | ||
17 | "es6" | ||
18 | ], | ||
19 | "types": [ | ||
20 | "node", | ||
21 | "source-map", | ||
22 | "uglify-js", | ||
23 | "webpack" | ||
24 | ] | ||
11 | }, | 25 | }, |
26 | "exclude": [ | ||
27 | "node_modules", | ||
28 | "dist" | ||
29 | ], | ||
12 | "awesomeTypescriptLoaderOptions": { | 30 | "awesomeTypescriptLoaderOptions": { |
13 | "forkChecker": true | 31 | "forkChecker": true, |
32 | "useWebpackText": true | ||
14 | }, | 33 | }, |
15 | "compileOnSave": false, | 34 | "compileOnSave": false, |
16 | "buildOnSave": false, | 35 | "buildOnSave": false, |
17 | "atom": { | 36 | "atom": { "rewriteTsconfig": false } |
18 | "rewriteTsconfig": true | ||
19 | }, | ||
20 | "filesGlob": [ | ||
21 | "**/*.ts", | ||
22 | "!node_modules/**" | ||
23 | ], | ||
24 | "exclude": [ | ||
25 | "node_modules", | ||
26 | "typings/main", | ||
27 | "typings/main.d.ts" | ||
28 | ], | ||
29 | "files": [ | ||
30 | "src/app/app.component.ts", | ||
31 | "src/app/app.routes.ts", | ||
32 | "src/app/friends/friend.service.ts", | ||
33 | "src/app/friends/index.ts", | ||
34 | "src/app/login/index.ts", | ||
35 | "src/app/login/login.component.ts", | ||
36 | "src/app/login/login.routes.ts", | ||
37 | "src/app/shared/auth/auth-http.service.ts", | ||
38 | "src/app/shared/auth/auth-status.model.ts", | ||
39 | "src/app/shared/auth/auth.service.ts", | ||
40 | "src/app/shared/auth/index.ts", | ||
41 | "src/app/shared/auth/user.model.ts", | ||
42 | "src/app/shared/index.ts", | ||
43 | "src/app/shared/search/index.ts", | ||
44 | "src/app/shared/search/search-field.type.ts", | ||
45 | "src/app/shared/search/search.component.ts", | ||
46 | "src/app/shared/search/search.model.ts", | ||
47 | "src/app/shared/search/search.service.ts", | ||
48 | "src/app/videos/index.ts", | ||
49 | "src/app/videos/shared/index.ts", | ||
50 | "src/app/videos/shared/loader/index.ts", | ||
51 | "src/app/videos/shared/loader/loader.component.ts", | ||
52 | "src/app/videos/shared/pagination.model.ts", | ||
53 | "src/app/videos/shared/sort-field.type.ts", | ||
54 | "src/app/videos/shared/video.model.ts", | ||
55 | "src/app/videos/shared/video.service.ts", | ||
56 | "src/app/videos/video-add/index.ts", | ||
57 | "src/app/videos/video-add/video-add.component.ts", | ||
58 | "src/app/videos/video-list/index.ts", | ||
59 | "src/app/videos/video-list/video-list.component.ts", | ||
60 | "src/app/videos/video-list/video-miniature.component.ts", | ||
61 | "src/app/videos/video-list/video-sort.component.ts", | ||
62 | "src/app/videos/video-watch/index.ts", | ||
63 | "src/app/videos/video-watch/video-watch.component.ts", | ||
64 | "src/app/videos/video-watch/webtorrent.service.ts", | ||
65 | "src/app/videos/videos.component.ts", | ||
66 | "src/app/videos/videos.routes.ts", | ||
67 | "src/custom-typings.d.ts", | ||
68 | "src/main.ts", | ||
69 | "src/polyfills.ts", | ||
70 | "src/vendor.ts", | ||
71 | "typings/globals/es6-shim/index.d.ts", | ||
72 | "typings/globals/jasmine/index.d.ts", | ||
73 | "typings/globals/node/index.d.ts", | ||
74 | "typings/index.d.ts" | ||
75 | ] | ||
76 | } | 37 | } |
diff --git a/client/typings.json b/client/typings.json deleted file mode 100644 index 9a8891f25..000000000 --- a/client/typings.json +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | { | ||
2 | "globalDependencies": { | ||
3 | "es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654", | ||
4 | "jasmine": "registry:dt/jasmine#2.2.0+20160412134438", | ||
5 | "node": "registry:dt/node#4.0.0+20160509154515" | ||
6 | } | ||
7 | } | ||
diff --git a/client/webpack.config.js b/client/webpack.config.js index 8f54d88e5..3d3af91ad 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js | |||
@@ -1,16 +1,16 @@ | |||
1 | switch (process.env.NODE_ENV) { | 1 | switch (process.env.NODE_ENV) { |
2 | case 'prod': | 2 | case 'prod': |
3 | case 'production': | 3 | case 'production': |
4 | module.exports = require('./config/webpack.prod') | 4 | module.exports = require('./config/webpack.prod')({env: 'production'}) |
5 | break | 5 | break |
6 | 6 | ||
7 | case 'test': | 7 | case 'test': |
8 | case 'testing': | 8 | case 'testing': |
9 | module.exports = require('./config/webpack.test') | 9 | module.exports = require('./config/webpack.test')({env: 'test'}) |
10 | break | 10 | break |
11 | 11 | ||
12 | case 'dev': | 12 | case 'dev': |
13 | case 'development': | 13 | case 'development': |
14 | default: | 14 | default: |
15 | module.exports = require('./config/webpack.dev') | 15 | module.exports = require('./config/webpack.dev')({env: 'development'}) |
16 | } | 16 | } |
diff --git a/config/default.yaml b/config/default.yaml index 9a8a57879..b44be31b0 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -19,8 +19,5 @@ storage: | |||
19 | thumbnails: 'thumbnails/' | 19 | thumbnails: 'thumbnails/' |
20 | torrents: 'torrents/' | 20 | torrents: 'torrents/' |
21 | 21 | ||
22 | network: | ||
23 | friends: [] | ||
24 | |||
25 | electron: | 22 | electron: |
26 | debug: false | 23 | debug: false |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 6dc79d73f..8bc63ee92 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -1,4 +1,8 @@ | |||
1 | # Correspond to your reverse proxy "listen" configuration | ||
1 | webserver: | 2 | webserver: |
2 | https: false | 3 | https: false |
3 | host: 'example.com' | 4 | host: 'example.com' |
4 | port: 80 | 5 | port: 80 |
6 | |||
7 | database: | ||
8 | suffix: '-prod' | ||
diff --git a/config/test-1.yaml b/config/test-1.yaml index 0998eaea1..a59566cc4 100644 --- a/config/test-1.yaml +++ b/config/test-1.yaml | |||
@@ -15,7 +15,3 @@ storage: | |||
15 | logs: 'test1/logs/' | 15 | logs: 'test1/logs/' |
16 | thumbnails: 'test1/thumbnails/' | 16 | thumbnails: 'test1/thumbnails/' |
17 | torrents: 'test1/torrents/' | 17 | torrents: 'test1/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9002' | ||
diff --git a/config/test-2.yaml b/config/test-2.yaml index ec2cff811..1b937898f 100644 --- a/config/test-2.yaml +++ b/config/test-2.yaml | |||
@@ -15,7 +15,3 @@ storage: | |||
15 | logs: 'test2/logs/' | 15 | logs: 'test2/logs/' |
16 | thumbnails: 'test2/thumbnails/' | 16 | thumbnails: 'test2/thumbnails/' |
17 | torrents: 'test2/torrents/' | 17 | torrents: 'test2/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9003' | ||
diff --git a/config/test-3.yaml b/config/test-3.yaml index 24f5533e0..e522c13e7 100644 --- a/config/test-3.yaml +++ b/config/test-3.yaml | |||
@@ -15,7 +15,3 @@ storage: | |||
15 | logs: 'test3/logs/' | 15 | logs: 'test3/logs/' |
16 | thumbnails: 'test3/thumbnails/' | 16 | thumbnails: 'test3/thumbnails/' |
17 | torrents: 'test3/torrents/' | 17 | torrents: 'test3/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9001' | ||
diff --git a/config/test-4.yaml b/config/test-4.yaml index 1f884dbf2..e30cd7978 100644 --- a/config/test-4.yaml +++ b/config/test-4.yaml | |||
@@ -15,7 +15,3 @@ storage: | |||
15 | logs: 'test4/logs/' | 15 | logs: 'test4/logs/' |
16 | thumbnails: 'test4/thumbnails/' | 16 | thumbnails: 'test4/thumbnails/' |
17 | torrents: 'test4/torrents/' | 17 | torrents: 'test4/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9002' | ||
diff --git a/config/test-5.yaml b/config/test-5.yaml index 08ed9f068..3a54599f5 100644 --- a/config/test-5.yaml +++ b/config/test-5.yaml | |||
@@ -15,8 +15,3 @@ storage: | |||
15 | logs: 'test5/logs/' | 15 | logs: 'test5/logs/' |
16 | thumbnails: 'test5/thumbnails/' | 16 | thumbnails: 'test5/thumbnails/' |
17 | torrents: 'test5/torrents/' | 17 | torrents: 'test5/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9001' | ||
22 | - 'http://localhost:9004' | ||
diff --git a/config/test-6.yaml b/config/test-6.yaml index a57784cca..31608add2 100644 --- a/config/test-6.yaml +++ b/config/test-6.yaml | |||
@@ -15,9 +15,3 @@ storage: | |||
15 | logs: 'test6/logs/' | 15 | logs: 'test6/logs/' |
16 | thumbnails: 'test6/thumbnails/' | 16 | thumbnails: 'test6/thumbnails/' |
17 | torrents: 'test6/torrents/' | 17 | torrents: 'test6/torrents/' |
18 | |||
19 | network: | ||
20 | friends: | ||
21 | - 'http://localhost:9001' | ||
22 | - 'http://localhost:9002' | ||
23 | - 'http://localhost:9003' | ||
diff --git a/package.json b/package.json index 63d014376..59c7a4332 100644 --- a/package.json +++ b/package.json | |||
@@ -35,6 +35,7 @@ | |||
35 | }, | 35 | }, |
36 | "dependencies": { | 36 | "dependencies": { |
37 | "async": "^2.0.0", | 37 | "async": "^2.0.0", |
38 | "bcrypt": "^0.8.7", | ||
38 | "bittorrent-tracker": "^8.0.0", | 39 | "bittorrent-tracker": "^8.0.0", |
39 | "body-parser": "^1.12.4", | 40 | "body-parser": "^1.12.4", |
40 | "concurrently": "^2.0.0", | 41 | "concurrently": "^2.0.0", |
@@ -59,7 +60,6 @@ | |||
59 | "request": "^2.57.0", | 60 | "request": "^2.57.0", |
60 | "request-replay": "^1.0.2", | 61 | "request-replay": "^1.0.2", |
61 | "scripty": "^1.5.0", | 62 | "scripty": "^1.5.0", |
62 | "segfault-handler": "^1.0.0", | ||
63 | "ursa": "^0.9.1", | 63 | "ursa": "^0.9.1", |
64 | "winston": "^2.1.1", | 64 | "winston": "^2.1.1", |
65 | "ws": "^1.1.1" | 65 | "ws": "^1.1.1" |
@@ -67,9 +67,9 @@ | |||
67 | "devDependencies": { | 67 | "devDependencies": { |
68 | "chai": "^3.3.0", | 68 | "chai": "^3.3.0", |
69 | "commander": "^2.9.0", | 69 | "commander": "^2.9.0", |
70 | "mocha": "^2.3.3", | 70 | "mocha": "^3.0.1", |
71 | "standard": "^7.0.1", | 71 | "standard": "^8.0.0", |
72 | "supertest": "^1.1.0" | 72 | "supertest": "^2.0.0" |
73 | }, | 73 | }, |
74 | "standard": { | 74 | "standard": { |
75 | "ignore": [ | 75 | "ignore": [ |
diff --git a/scripts/build/client/prod.sh b/scripts/build/client/prod.sh index 478cae99e..e090e8082 100755 --- a/scripts/build/client/prod.sh +++ b/scripts/build/client/prod.sh | |||
@@ -2,4 +2,4 @@ | |||
2 | 2 | ||
3 | cd client || exit -1 | 3 | cd client || exit -1 |
4 | 4 | ||
5 | npm run webpack -- --config config/webpack.prod.js --progress --profile --colors --display-error-details --display-cached --bail | 5 | npm run webpack -- --config config/webpack.prod.js --progress --profile --bail |
@@ -32,6 +32,7 @@ if (miss.length !== 0) { | |||
32 | // ----------- PeerTube modules ----------- | 32 | // ----------- PeerTube modules ----------- |
33 | const customValidators = require('./server/helpers/custom-validators') | 33 | const customValidators = require('./server/helpers/custom-validators') |
34 | const installer = require('./server/initializers/installer') | 34 | const installer = require('./server/initializers/installer') |
35 | const migrator = require('./server/initializers/migrator') | ||
35 | const mongoose = require('mongoose') | 36 | const mongoose = require('mongoose') |
36 | const routes = require('./server/controllers') | 37 | const routes = require('./server/controllers') |
37 | const Request = mongoose.model('Request') | 38 | const Request = mongoose.model('Request') |
@@ -46,18 +47,21 @@ const port = config.get('listen.port') | |||
46 | // For the logger | 47 | // For the logger |
47 | app.use(morgan('combined', { stream: logger.stream })) | 48 | app.use(morgan('combined', { stream: logger.stream })) |
48 | // For body requests | 49 | // For body requests |
49 | app.use(bodyParser.json()) | 50 | app.use(bodyParser.json({ limit: '500kb' })) |
50 | app.use(bodyParser.urlencoded({ extended: false })) | 51 | app.use(bodyParser.urlencoded({ extended: false })) |
51 | // Validate some params for the API | 52 | // Validate some params for the API |
52 | app.use(expressValidator({ | 53 | app.use(expressValidator({ |
53 | customValidators: customValidators | 54 | customValidators: Object.assign( |
55 | {}, | ||
56 | customValidators.misc, | ||
57 | customValidators.pods, | ||
58 | customValidators.users, | ||
59 | customValidators.videos | ||
60 | ) | ||
54 | })) | 61 | })) |
55 | 62 | ||
56 | // ----------- Views, routes and static files ----------- | 63 | // ----------- Views, routes and static files ----------- |
57 | 64 | ||
58 | // Catch sefaults | ||
59 | require('segfault-handler').registerHandler() | ||
60 | |||
61 | // API routes | 65 | // API routes |
62 | const apiRoute = '/api/' + constants.API_VERSION | 66 | const apiRoute = '/api/' + constants.API_VERSION |
63 | app.use(apiRoute, routes.api) | 67 | app.use(apiRoute, routes.api) |
@@ -125,14 +129,19 @@ app.use(function (err, req, res, next) { | |||
125 | installer.installApplication(function (err) { | 129 | installer.installApplication(function (err) { |
126 | if (err) throw err | 130 | if (err) throw err |
127 | 131 | ||
128 | // ----------- Make the server listening ----------- | 132 | // Run the migration scripts if needed |
129 | server.listen(port, function () { | 133 | migrator.migrate(function (err) { |
130 | // Activate the pool requests | 134 | if (err) throw err |
131 | Request.activate() | 135 | |
136 | // ----------- Make the server listening ----------- | ||
137 | server.listen(port, function () { | ||
138 | // Activate the pool requests | ||
139 | Request.activate() | ||
132 | 140 | ||
133 | logger.info('Seeded all the videos') | 141 | logger.info('Seeded all the videos') |
134 | logger.info('Server listening on port %d', port) | 142 | logger.info('Server listening on port %d', port) |
135 | app.emit('ready') | 143 | app.emit('ready') |
144 | }) | ||
136 | }) | 145 | }) |
137 | }) | 146 | }) |
138 | 147 | ||
diff --git a/server/controllers/api/v1/clients.js b/server/controllers/api/v1/clients.js new file mode 100644 index 000000000..5b460db2e --- /dev/null +++ b/server/controllers/api/v1/clients.js | |||
@@ -0,0 +1,41 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const express = require('express') | ||
4 | const mongoose = require('mongoose') | ||
5 | |||
6 | const constants = require('../../../initializers/constants') | ||
7 | |||
8 | const Client = mongoose.model('OAuthClient') | ||
9 | |||
10 | const router = express.Router() | ||
11 | |||
12 | router.get('/local', getLocalClient) | ||
13 | |||
14 | // Get the client credentials for the PeerTube front end | ||
15 | function getLocalClient (req, res, next) { | ||
16 | const serverHost = constants.CONFIG.WEBSERVER.HOST | ||
17 | const serverPort = constants.CONFIG.WEBSERVER.PORT | ||
18 | let headerHostShouldBe = serverHost | ||
19 | if (serverPort !== 80 && serverPort !== 443) { | ||
20 | headerHostShouldBe += ':' + serverPort | ||
21 | } | ||
22 | |||
23 | // Don't make this check if this is a test instance | ||
24 | if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) { | ||
25 | return res.type('json').status(403).end() | ||
26 | } | ||
27 | |||
28 | Client.loadFirstClient(function (err, client) { | ||
29 | if (err) return next(err) | ||
30 | if (!client) return next(new Error('No client available.')) | ||
31 | |||
32 | res.json({ | ||
33 | client_id: client._id, | ||
34 | client_secret: client.clientSecret | ||
35 | }) | ||
36 | }) | ||
37 | } | ||
38 | |||
39 | // --------------------------------------------------------------------------- | ||
40 | |||
41 | module.exports = router | ||
diff --git a/server/controllers/api/v1/index.js b/server/controllers/api/v1/index.js index e0c29a8a2..2e4fb2dab 100644 --- a/server/controllers/api/v1/index.js +++ b/server/controllers/api/v1/index.js | |||
@@ -4,13 +4,17 @@ const express = require('express') | |||
4 | 4 | ||
5 | const router = express.Router() | 5 | const router = express.Router() |
6 | 6 | ||
7 | const clientsController = require('./clients') | ||
7 | const podsController = require('./pods') | 8 | const podsController = require('./pods') |
8 | const remoteController = require('./remote') | 9 | const remoteController = require('./remote') |
10 | const requestsController = require('./requests') | ||
9 | const usersController = require('./users') | 11 | const usersController = require('./users') |
10 | const videosController = require('./videos') | 12 | const videosController = require('./videos') |
11 | 13 | ||
14 | router.use('/clients', clientsController) | ||
12 | router.use('/pods', podsController) | 15 | router.use('/pods', podsController) |
13 | router.use('/remote', remoteController) | 16 | router.use('/remote', remoteController) |
17 | router.use('/requests', requestsController) | ||
14 | router.use('/users', usersController) | 18 | router.use('/users', usersController) |
15 | router.use('/videos', videosController) | 19 | router.use('/videos', videosController) |
16 | router.use('/*', badRequest) | 20 | router.use('/*', badRequest) |
diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js index 2bc761fef..8ffade578 100644 --- a/server/controllers/api/v1/pods.js +++ b/server/controllers/api/v1/pods.js | |||
@@ -8,7 +8,10 @@ const waterfall = require('async/waterfall') | |||
8 | const logger = require('../../../helpers/logger') | 8 | const logger = require('../../../helpers/logger') |
9 | const friends = require('../../../lib/friends') | 9 | const friends = require('../../../lib/friends') |
10 | const middlewares = require('../../../middlewares') | 10 | const middlewares = require('../../../middlewares') |
11 | const admin = middlewares.admin | ||
11 | const oAuth = middlewares.oauth | 12 | const oAuth = middlewares.oauth |
13 | const podsMiddleware = middlewares.pods | ||
14 | const checkSignature = middlewares.secure.checkSignature | ||
12 | const validators = middlewares.validators.pods | 15 | const validators = middlewares.validators.pods |
13 | const signatureValidator = middlewares.validators.remote.signature | 16 | const signatureValidator = middlewares.validators.remote.signature |
14 | 17 | ||
@@ -16,12 +19,30 @@ const router = express.Router() | |||
16 | const Pod = mongoose.model('Pod') | 19 | const Pod = mongoose.model('Pod') |
17 | const Video = mongoose.model('Video') | 20 | const Video = mongoose.model('Video') |
18 | 21 | ||
19 | router.get('/', listPodsUrl) | 22 | router.get('/', listPods) |
20 | router.post('/', validators.podsAdd, addPods) | 23 | router.post('/', |
21 | router.get('/makefriends', oAuth.authenticate, validators.makeFriends, makeFriends) | 24 | validators.podsAdd, |
22 | router.get('/quitfriends', oAuth.authenticate, quitFriends) | 25 | podsMiddleware.setBodyUrlPort, |
26 | addPods | ||
27 | ) | ||
28 | router.post('/makefriends', | ||
29 | oAuth.authenticate, | ||
30 | admin.ensureIsAdmin, | ||
31 | validators.makeFriends, | ||
32 | podsMiddleware.setBodyUrlsPort, | ||
33 | makeFriends | ||
34 | ) | ||
35 | router.get('/quitfriends', | ||
36 | oAuth.authenticate, | ||
37 | admin.ensureIsAdmin, | ||
38 | quitFriends | ||
39 | ) | ||
23 | // Post because this is a secured request | 40 | // Post because this is a secured request |
24 | router.post('/remove', signatureValidator, removePods) | 41 | router.post('/remove', |
42 | signatureValidator, | ||
43 | checkSignature, | ||
44 | removePods | ||
45 | ) | ||
25 | 46 | ||
26 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
27 | 48 | ||
@@ -64,20 +85,27 @@ function addPods (req, res, next) { | |||
64 | }) | 85 | }) |
65 | } | 86 | } |
66 | 87 | ||
67 | function listPodsUrl (req, res, next) { | 88 | function listPods (req, res, next) { |
68 | Pod.listOnlyUrls(function (err, podsUrlList) { | 89 | Pod.list(function (err, podsUrlList) { |
69 | if (err) return next(err) | 90 | if (err) return next(err) |
70 | 91 | ||
71 | res.json(podsUrlList) | 92 | res.json(getFormatedPods(podsUrlList)) |
72 | }) | 93 | }) |
73 | } | 94 | } |
74 | 95 | ||
75 | function makeFriends (req, res, next) { | 96 | function makeFriends (req, res, next) { |
76 | friends.makeFriends(function (err) { | 97 | const urls = req.body.urls |
77 | if (err) return next(err) | ||
78 | 98 | ||
79 | res.type('json').status(204).end() | 99 | friends.makeFriends(urls, function (err) { |
100 | if (err) { | ||
101 | logger.error('Could not make friends.', { error: err }) | ||
102 | return | ||
103 | } | ||
104 | |||
105 | logger.info('Made friends!') | ||
80 | }) | 106 | }) |
107 | |||
108 | res.type('json').status(204).end() | ||
81 | } | 109 | } |
82 | 110 | ||
83 | function removePods (req, res, next) { | 111 | function removePods (req, res, next) { |
@@ -125,3 +153,15 @@ function quitFriends (req, res, next) { | |||
125 | res.type('json').status(204).end() | 153 | res.type('json').status(204).end() |
126 | }) | 154 | }) |
127 | } | 155 | } |
156 | |||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
159 | function getFormatedPods (pods) { | ||
160 | const formatedPods = [] | ||
161 | |||
162 | pods.forEach(function (pod) { | ||
163 | formatedPods.push(pod.toFormatedJSON()) | ||
164 | }) | ||
165 | |||
166 | return formatedPods | ||
167 | } | ||
diff --git a/server/controllers/api/v1/remote.js b/server/controllers/api/v1/remote.js index f452986b8..a22c5d151 100644 --- a/server/controllers/api/v1/remote.js +++ b/server/controllers/api/v1/remote.js | |||
@@ -16,6 +16,7 @@ const Video = mongoose.model('Video') | |||
16 | router.post('/videos', | 16 | router.post('/videos', |
17 | validators.signature, | 17 | validators.signature, |
18 | validators.dataToDecrypt, | 18 | validators.dataToDecrypt, |
19 | secureMiddleware.checkSignature, | ||
19 | secureMiddleware.decryptBody, | 20 | secureMiddleware.decryptBody, |
20 | validators.remoteVideos, | 21 | validators.remoteVideos, |
21 | remoteVideos | 22 | remoteVideos |
diff --git a/server/controllers/api/v1/requests.js b/server/controllers/api/v1/requests.js new file mode 100644 index 000000000..97616424d --- /dev/null +++ b/server/controllers/api/v1/requests.js | |||
@@ -0,0 +1,38 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const express = require('express') | ||
4 | const mongoose = require('mongoose') | ||
5 | |||
6 | const constants = require('../../../initializers/constants') | ||
7 | const middlewares = require('../../../middlewares') | ||
8 | const admin = middlewares.admin | ||
9 | const oAuth = middlewares.oauth | ||
10 | |||
11 | const Request = mongoose.model('Request') | ||
12 | |||
13 | const router = express.Router() | ||
14 | |||
15 | router.get('/stats', | ||
16 | oAuth.authenticate, | ||
17 | admin.ensureIsAdmin, | ||
18 | getStatsRequests | ||
19 | ) | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | module.exports = router | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | function getStatsRequests (req, res, next) { | ||
28 | Request.list(function (err, requests) { | ||
29 | if (err) return next(err) | ||
30 | |||
31 | return res.json({ | ||
32 | requests: requests, | ||
33 | maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, | ||
34 | remainingMilliSeconds: Request.remainingMilliSeconds(), | ||
35 | milliSecondsInterval: constants.REQUESTS_INTERVAL | ||
36 | }) | ||
37 | }) | ||
38 | } | ||
diff --git a/server/controllers/api/v1/users.js b/server/controllers/api/v1/users.js index fbbe6e472..975e25e68 100644 --- a/server/controllers/api/v1/users.js +++ b/server/controllers/api/v1/users.js | |||
@@ -1,18 +1,59 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | 3 | const each = require('async/each') |
4 | const mongoose = require('mongoose') | ||
5 | const express = require('express') | 4 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
6 | const waterfall = require('async/waterfall') | ||
6 | 7 | ||
7 | const oAuth = require('../../../middlewares').oauth | 8 | const constants = require('../../../initializers/constants') |
9 | const friends = require('../../../lib/friends') | ||
10 | const logger = require('../../../helpers/logger') | ||
11 | const middlewares = require('../../../middlewares') | ||
12 | const admin = middlewares.admin | ||
13 | const oAuth = middlewares.oauth | ||
14 | const pagination = middlewares.pagination | ||
15 | const sort = middlewares.sort | ||
16 | const validatorsPagination = middlewares.validators.pagination | ||
17 | const validatorsSort = middlewares.validators.sort | ||
18 | const validatorsUsers = middlewares.validators.users | ||
8 | 19 | ||
9 | const Client = mongoose.model('OAuthClient') | 20 | const User = mongoose.model('User') |
21 | const Video = mongoose.model('Video') | ||
10 | 22 | ||
11 | const router = express.Router() | 23 | const router = express.Router() |
12 | 24 | ||
13 | router.get('/client', getAngularClient) | 25 | router.get('/me', oAuth.authenticate, getUserInformation) |
26 | |||
27 | router.get('/', | ||
28 | validatorsPagination.pagination, | ||
29 | validatorsSort.usersSort, | ||
30 | sort.setUsersSort, | ||
31 | pagination.setPagination, | ||
32 | listUsers | ||
33 | ) | ||
34 | |||
35 | router.post('/', | ||
36 | oAuth.authenticate, | ||
37 | admin.ensureIsAdmin, | ||
38 | validatorsUsers.usersAdd, | ||
39 | createUser | ||
40 | ) | ||
41 | |||
42 | router.put('/:id', | ||
43 | oAuth.authenticate, | ||
44 | validatorsUsers.usersUpdate, | ||
45 | updateUser | ||
46 | ) | ||
47 | |||
48 | router.delete('/:id', | ||
49 | oAuth.authenticate, | ||
50 | admin.ensureIsAdmin, | ||
51 | validatorsUsers.usersRemove, | ||
52 | removeUser | ||
53 | ) | ||
54 | |||
14 | router.post('/token', oAuth.token, success) | 55 | router.post('/token', oAuth.token, success) |
15 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged,, implement revoke token route | 56 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route |
16 | 57 | ||
17 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
18 | 59 | ||
@@ -20,26 +61,91 @@ module.exports = router | |||
20 | 61 | ||
21 | // --------------------------------------------------------------------------- | 62 | // --------------------------------------------------------------------------- |
22 | 63 | ||
23 | function getAngularClient (req, res, next) { | 64 | function createUser (req, res, next) { |
24 | const serverHost = config.get('webserver.host') | 65 | const user = new User({ |
25 | const serverPort = config.get('webserver.port') | 66 | username: req.body.username, |
26 | let headerHostShouldBe = serverHost | 67 | password: req.body.password, |
27 | if (serverPort !== 80 && serverPort !== 443) { | 68 | role: constants.USER_ROLES.USER |
28 | headerHostShouldBe += ':' + serverPort | 69 | }) |
29 | } | ||
30 | 70 | ||
31 | // Don't make this check if this is a test instance | 71 | user.save(function (err, createdUser) { |
32 | if (process.env.NODE_ENV !== 'test' && req.get('host') !== headerHostShouldBe) { | 72 | if (err) return next(err) |
33 | return res.type('json').status(403).end() | ||
34 | } | ||
35 | 73 | ||
36 | Client.loadFirstClient(function (err, client) { | 74 | return res.type('json').status(204).end() |
75 | }) | ||
76 | } | ||
77 | |||
78 | function getUserInformation (req, res, next) { | ||
79 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | ||
37 | if (err) return next(err) | 80 | if (err) return next(err) |
38 | if (!client) return next(new Error('No client available.')) | ||
39 | 81 | ||
40 | res.json({ | 82 | return res.json(user.toFormatedJSON()) |
41 | client_id: client._id, | 83 | }) |
42 | client_secret: client.clientSecret | 84 | } |
85 | |||
86 | function listUsers (req, res, next) { | ||
87 | User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { | ||
88 | if (err) return next(err) | ||
89 | |||
90 | res.json(getFormatedUsers(usersList, usersTotal)) | ||
91 | }) | ||
92 | } | ||
93 | |||
94 | function removeUser (req, res, next) { | ||
95 | waterfall([ | ||
96 | function getUser (callback) { | ||
97 | User.loadById(req.params.id, callback) | ||
98 | }, | ||
99 | |||
100 | function getVideos (user, callback) { | ||
101 | Video.listOwnedByAuthor(user.username, function (err, videos) { | ||
102 | return callback(err, user, videos) | ||
103 | }) | ||
104 | }, | ||
105 | |||
106 | function removeVideosFromDB (user, videos, callback) { | ||
107 | each(videos, function (video, callbackEach) { | ||
108 | video.remove(callbackEach) | ||
109 | }, function (err) { | ||
110 | return callback(err, user, videos) | ||
111 | }) | ||
112 | }, | ||
113 | |||
114 | function sendInformationToFriends (user, videos, callback) { | ||
115 | videos.forEach(function (video) { | ||
116 | const params = { | ||
117 | name: video.name, | ||
118 | magnetUri: video.magnetUri | ||
119 | } | ||
120 | |||
121 | friends.removeVideoToFriends(params) | ||
122 | }) | ||
123 | |||
124 | return callback(null, user) | ||
125 | }, | ||
126 | |||
127 | function removeUserFromDB (user, callback) { | ||
128 | user.remove(callback) | ||
129 | } | ||
130 | ], function andFinally (err) { | ||
131 | if (err) { | ||
132 | logger.error('Errors when removed the user.', { error: err }) | ||
133 | return next(err) | ||
134 | } | ||
135 | |||
136 | return res.sendStatus(204) | ||
137 | }) | ||
138 | } | ||
139 | |||
140 | function updateUser (req, res, next) { | ||
141 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | ||
142 | if (err) return next(err) | ||
143 | |||
144 | user.password = req.body.password | ||
145 | user.save(function (err) { | ||
146 | if (err) return next(err) | ||
147 | |||
148 | return res.sendStatus(204) | ||
43 | }) | 149 | }) |
44 | }) | 150 | }) |
45 | } | 151 | } |
@@ -47,3 +153,18 @@ function getAngularClient (req, res, next) { | |||
47 | function success (req, res, next) { | 153 | function success (req, res, next) { |
48 | res.end() | 154 | res.end() |
49 | } | 155 | } |
156 | |||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
159 | function getFormatedUsers (users, usersTotal) { | ||
160 | const formatedUsers = [] | ||
161 | |||
162 | users.forEach(function (user) { | ||
163 | formatedUsers.push(user.toFormatedJSON()) | ||
164 | }) | ||
165 | |||
166 | return { | ||
167 | total: usersTotal, | ||
168 | data: formatedUsers | ||
169 | } | ||
170 | } | ||
diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js index 1f939b077..70d22f139 100644 --- a/server/controllers/api/v1/videos.js +++ b/server/controllers/api/v1/videos.js | |||
@@ -1,11 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | ||
4 | const express = require('express') | 3 | const express = require('express') |
5 | const mongoose = require('mongoose') | 4 | const mongoose = require('mongoose') |
6 | const multer = require('multer') | 5 | const multer = require('multer') |
7 | const waterfall = require('async/waterfall') | 6 | const waterfall = require('async/waterfall') |
8 | 7 | ||
8 | const constants = require('../../../initializers/constants') | ||
9 | const logger = require('../../../helpers/logger') | 9 | const logger = require('../../../helpers/logger') |
10 | const friends = require('../../../lib/friends') | 10 | const friends = require('../../../lib/friends') |
11 | const middlewares = require('../../../middlewares') | 11 | const middlewares = require('../../../middlewares') |
@@ -20,13 +20,12 @@ const sort = middlewares.sort | |||
20 | const utils = require('../../../helpers/utils') | 20 | const utils = require('../../../helpers/utils') |
21 | 21 | ||
22 | const router = express.Router() | 22 | const router = express.Router() |
23 | const uploads = config.get('storage.uploads') | ||
24 | const Video = mongoose.model('Video') | 23 | const Video = mongoose.model('Video') |
25 | 24 | ||
26 | // multer configuration | 25 | // multer configuration |
27 | const storage = multer.diskStorage({ | 26 | const storage = multer.diskStorage({ |
28 | destination: function (req, file, cb) { | 27 | destination: function (req, file, cb) { |
29 | cb(null, uploads) | 28 | cb(null, constants.CONFIG.STORAGE.UPLOAD_DIR) |
30 | }, | 29 | }, |
31 | 30 | ||
32 | filename: function (req, file, cb) { | 31 | filename: function (req, file, cb) { |
@@ -142,7 +141,7 @@ function getVideo (req, res, next) { | |||
142 | } | 141 | } |
143 | 142 | ||
144 | function listVideos (req, res, next) { | 143 | function listVideos (req, res, next) { |
145 | Video.list(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | 144 | Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { |
146 | if (err) return next(err) | 145 | if (err) return next(err) |
147 | 146 | ||
148 | res.json(getFormatedVideos(videosList, videosTotal)) | 147 | res.json(getFormatedVideos(videosList, videosTotal)) |
diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js new file mode 100644 index 000000000..96b5b20b9 --- /dev/null +++ b/server/helpers/custom-validators/index.js | |||
@@ -0,0 +1,17 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const miscValidators = require('./misc') | ||
4 | const podsValidators = require('./pods') | ||
5 | const usersValidators = require('./users') | ||
6 | const videosValidators = require('./videos') | ||
7 | |||
8 | const validators = { | ||
9 | misc: miscValidators, | ||
10 | pods: podsValidators, | ||
11 | users: usersValidators, | ||
12 | videos: videosValidators | ||
13 | } | ||
14 | |||
15 | // --------------------------------------------------------------------------- | ||
16 | |||
17 | module.exports = validators | ||
diff --git a/server/helpers/custom-validators/misc.js b/server/helpers/custom-validators/misc.js new file mode 100644 index 000000000..052726241 --- /dev/null +++ b/server/helpers/custom-validators/misc.js | |||
@@ -0,0 +1,18 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const miscValidators = { | ||
4 | exists, | ||
5 | isArray | ||
6 | } | ||
7 | |||
8 | function exists (value) { | ||
9 | return value !== undefined && value !== null | ||
10 | } | ||
11 | |||
12 | function isArray (value) { | ||
13 | return Array.isArray(value) | ||
14 | } | ||
15 | |||
16 | // --------------------------------------------------------------------------- | ||
17 | |||
18 | module.exports = miscValidators | ||
diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js new file mode 100644 index 000000000..40f8b5d0b --- /dev/null +++ b/server/helpers/custom-validators/pods.js | |||
@@ -0,0 +1,21 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const validator = require('express-validator').validator | ||
4 | |||
5 | const miscValidators = require('./misc') | ||
6 | |||
7 | const podsValidators = { | ||
8 | isEachUniqueUrlValid | ||
9 | } | ||
10 | |||
11 | function isEachUniqueUrlValid (urls) { | ||
12 | return miscValidators.isArray(urls) && | ||
13 | urls.length !== 0 && | ||
14 | urls.every(function (url) { | ||
15 | return validator.isURL(url) && urls.indexOf(url) === urls.lastIndexOf(url) | ||
16 | }) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | module.exports = podsValidators | ||
diff --git a/server/helpers/custom-validators/users.js b/server/helpers/custom-validators/users.js new file mode 100644 index 000000000..88fa1592e --- /dev/null +++ b/server/helpers/custom-validators/users.js | |||
@@ -0,0 +1,31 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const validator = require('express-validator').validator | ||
4 | const values = require('lodash/values') | ||
5 | |||
6 | const constants = require('../../initializers/constants') | ||
7 | const USERS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.USERS | ||
8 | |||
9 | const usersValidators = { | ||
10 | isUserPasswordValid, | ||
11 | isUserRoleValid, | ||
12 | isUserUsernameValid | ||
13 | } | ||
14 | |||
15 | function isUserPasswordValid (value) { | ||
16 | return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) | ||
17 | } | ||
18 | |||
19 | function isUserRoleValid (value) { | ||
20 | return values(constants.USER_ROLES).indexOf(value) !== -1 | ||
21 | } | ||
22 | |||
23 | function isUserUsernameValid (value) { | ||
24 | const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max | ||
25 | const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min | ||
26 | return validator.matches(value, new RegExp(`^[a-zA-Z0-9._]{${min},${max}}$`)) | ||
27 | } | ||
28 | |||
29 | // --------------------------------------------------------------------------- | ||
30 | |||
31 | module.exports = usersValidators | ||
diff --git a/server/helpers/custom-validators.js b/server/helpers/custom-validators/videos.js index b666644c0..a507ff686 100644 --- a/server/helpers/custom-validators.js +++ b/server/helpers/custom-validators/videos.js | |||
@@ -2,66 +2,51 @@ | |||
2 | 2 | ||
3 | const validator = require('express-validator').validator | 3 | const validator = require('express-validator').validator |
4 | 4 | ||
5 | const constants = require('../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const VIDEOS_CONSTRAINTS_FIELDS = constants.VIDEOS_CONSTRAINTS_FIELDS | 6 | const usersValidators = require('./users') |
7 | 7 | const miscValidators = require('./misc') | |
8 | const customValidators = { | 8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS |
9 | exists: exists, | 9 | |
10 | isEachRemoteVideosValid: isEachRemoteVideosValid, | 10 | const videosValidators = { |
11 | isArray: isArray, | 11 | isEachRemoteVideosValid, |
12 | isVideoAuthorValid: isVideoAuthorValid, | 12 | isVideoAuthorValid, |
13 | isVideoDateValid: isVideoDateValid, | 13 | isVideoDateValid, |
14 | isVideoDescriptionValid: isVideoDescriptionValid, | 14 | isVideoDescriptionValid, |
15 | isVideoDurationValid: isVideoDurationValid, | 15 | isVideoDurationValid, |
16 | isVideoMagnetUriValid: isVideoMagnetUriValid, | 16 | isVideoMagnetUriValid, |
17 | isVideoNameValid: isVideoNameValid, | 17 | isVideoNameValid, |
18 | isVideoPodUrlValid: isVideoPodUrlValid, | 18 | isVideoPodUrlValid, |
19 | isVideoTagsValid: isVideoTagsValid, | 19 | isVideoTagsValid, |
20 | isVideoThumbnailValid: isVideoThumbnailValid, | 20 | isVideoThumbnailValid, |
21 | isVideoThumbnail64Valid: isVideoThumbnail64Valid | 21 | isVideoThumbnail64Valid |
22 | } | ||
23 | |||
24 | function exists (value) { | ||
25 | return value !== undefined && value !== null | ||
26 | } | 22 | } |
27 | 23 | ||
28 | function isEachRemoteVideosValid (requests) { | 24 | function isEachRemoteVideosValid (requests) { |
29 | return requests.every(function (request) { | 25 | return miscValidators.isArray(requests) && |
30 | const video = request.data | 26 | requests.every(function (request) { |
31 | return ( | 27 | const video = request.data |
32 | isRequestTypeAddValid(request.type) && | 28 | return ( |
33 | isVideoAuthorValid(video.author) && | 29 | isRequestTypeAddValid(request.type) && |
34 | isVideoDateValid(video.createdDate) && | 30 | isVideoAuthorValid(video.author) && |
35 | isVideoDescriptionValid(video.description) && | 31 | isVideoDateValid(video.createdDate) && |
36 | isVideoDurationValid(video.duration) && | 32 | isVideoDescriptionValid(video.description) && |
37 | isVideoMagnetUriValid(video.magnetUri) && | 33 | isVideoDurationValid(video.duration) && |
38 | isVideoNameValid(video.name) && | 34 | isVideoMagnetUriValid(video.magnetUri) && |
39 | isVideoPodUrlValid(video.podUrl) && | 35 | isVideoNameValid(video.name) && |
40 | isVideoTagsValid(video.tags) && | 36 | isVideoPodUrlValid(video.podUrl) && |
41 | isVideoThumbnail64Valid(video.thumbnailBase64) | 37 | isVideoTagsValid(video.tags) && |
42 | ) || | 38 | isVideoThumbnail64Valid(video.thumbnailBase64) |
43 | ( | 39 | ) || |
44 | isRequestTypeRemoveValid(request.type) && | 40 | ( |
45 | isVideoNameValid(video.name) && | 41 | isRequestTypeRemoveValid(request.type) && |
46 | isVideoMagnetUriValid(video.magnetUri) | 42 | isVideoNameValid(video.name) && |
47 | ) | 43 | isVideoMagnetUriValid(video.magnetUri) |
48 | }) | 44 | ) |
49 | } | 45 | }) |
50 | |||
51 | function isArray (value) { | ||
52 | return Array.isArray(value) | ||
53 | } | ||
54 | |||
55 | function isRequestTypeAddValid (value) { | ||
56 | return value === 'add' | ||
57 | } | ||
58 | |||
59 | function isRequestTypeRemoveValid (value) { | ||
60 | return value === 'remove' | ||
61 | } | 46 | } |
62 | 47 | ||
63 | function isVideoAuthorValid (value) { | 48 | function isVideoAuthorValid (value) { |
64 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.AUTHOR) | 49 | return usersValidators.isUserUsernameValid(value) |
65 | } | 50 | } |
66 | 51 | ||
67 | function isVideoDateValid (value) { | 52 | function isVideoDateValid (value) { |
@@ -90,7 +75,7 @@ function isVideoPodUrlValid (value) { | |||
90 | } | 75 | } |
91 | 76 | ||
92 | function isVideoTagsValid (tags) { | 77 | function isVideoTagsValid (tags) { |
93 | return isArray(tags) && | 78 | return miscValidators.isArray(tags) && |
94 | validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && | 79 | validator.isInt(tags.length, VIDEOS_CONSTRAINTS_FIELDS.TAGS) && |
95 | tags.every(function (tag) { | 80 | tags.every(function (tag) { |
96 | return validator.isAlphanumeric(tag) && | 81 | return validator.isAlphanumeric(tag) && |
@@ -109,6 +94,14 @@ function isVideoThumbnail64Valid (value) { | |||
109 | 94 | ||
110 | // --------------------------------------------------------------------------- | 95 | // --------------------------------------------------------------------------- |
111 | 96 | ||
112 | module.exports = customValidators | 97 | module.exports = videosValidators |
113 | 98 | ||
114 | // --------------------------------------------------------------------------- | 99 | // --------------------------------------------------------------------------- |
100 | |||
101 | function isRequestTypeAddValid (value) { | ||
102 | return value === 'add' | ||
103 | } | ||
104 | |||
105 | function isRequestTypeRemoveValid (value) { | ||
106 | return value === 'remove' | ||
107 | } | ||
diff --git a/server/helpers/logger.js b/server/helpers/logger.js index 8ae90a4b2..590ceaeb6 100644 --- a/server/helpers/logger.js +++ b/server/helpers/logger.js | |||
@@ -1,23 +1,23 @@ | |||
1 | // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ | 1 | // Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/ |
2 | 'use strict' | 2 | 'use strict' |
3 | 3 | ||
4 | const config = require('config') | ||
5 | const mkdirp = require('mkdirp') | 4 | const mkdirp = require('mkdirp') |
6 | const path = require('path') | 5 | const path = require('path') |
7 | const winston = require('winston') | 6 | const winston = require('winston') |
8 | winston.emitErrs = true | 7 | winston.emitErrs = true |
9 | 8 | ||
10 | const logDir = path.join(__dirname, '..', '..', config.get('storage.logs')) | 9 | const constants = require('../initializers/constants') |
11 | const label = config.get('webserver.host') + ':' + config.get('webserver.port') | 10 | |
11 | const label = constants.CONFIG.WEBSERVER.HOST + ':' + constants.CONFIG.WEBSERVER.PORT | ||
12 | 12 | ||
13 | // Create the directory if it does not exist | 13 | // Create the directory if it does not exist |
14 | mkdirp.sync(logDir) | 14 | mkdirp.sync(constants.CONFIG.STORAGE.LOG_DIR) |
15 | 15 | ||
16 | const logger = new winston.Logger({ | 16 | const logger = new winston.Logger({ |
17 | transports: [ | 17 | transports: [ |
18 | new winston.transports.File({ | 18 | new winston.transports.File({ |
19 | level: 'debug', | 19 | level: 'debug', |
20 | filename: path.join(logDir, 'all-logs.log'), | 20 | filename: path.join(constants.CONFIG.STORAGE.LOG_DIR, 'all-logs.log'), |
21 | handleExceptions: true, | 21 | handleExceptions: true, |
22 | json: true, | 22 | json: true, |
23 | maxsize: 5242880, | 23 | maxsize: 5242880, |
diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js index 46dff8d03..1ff638b04 100644 --- a/server/helpers/peertube-crypto.js +++ b/server/helpers/peertube-crypto.js | |||
@@ -1,24 +1,24 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | 3 | const bcrypt = require('bcrypt') |
4 | const crypto = require('crypto') | 4 | const crypto = require('crypto') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const openssl = require('openssl-wrapper') | 6 | const openssl = require('openssl-wrapper') |
7 | const path = require('path') | ||
8 | const ursa = require('ursa') | 7 | const ursa = require('ursa') |
9 | 8 | ||
9 | const constants = require('../initializers/constants') | ||
10 | const logger = require('./logger') | 10 | const logger = require('./logger') |
11 | 11 | ||
12 | const certDir = path.join(__dirname, '..', '..', config.get('storage.certs')) | ||
13 | const algorithm = 'aes-256-ctr' | 12 | const algorithm = 'aes-256-ctr' |
14 | 13 | ||
15 | const peertubeCrypto = { | 14 | const peertubeCrypto = { |
16 | checkSignature: checkSignature, | 15 | checkSignature, |
17 | createCertsIfNotExist: createCertsIfNotExist, | 16 | comparePassword, |
18 | decrypt: decrypt, | 17 | createCertsIfNotExist, |
19 | encrypt: encrypt, | 18 | cryptPassword, |
20 | getCertDir: getCertDir, | 19 | decrypt, |
21 | sign: sign | 20 | encrypt, |
21 | sign | ||
22 | } | 22 | } |
23 | 23 | ||
24 | function checkSignature (publicKey, rawData, hexSignature) { | 24 | function checkSignature (publicKey, rawData, hexSignature) { |
@@ -27,6 +27,14 @@ function checkSignature (publicKey, rawData, hexSignature) { | |||
27 | return isValid | 27 | return isValid |
28 | } | 28 | } |
29 | 29 | ||
30 | function comparePassword (plainPassword, hashPassword, callback) { | ||
31 | bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { | ||
32 | if (err) return callback(err) | ||
33 | |||
34 | return callback(null, isPasswordMatch) | ||
35 | }) | ||
36 | } | ||
37 | |||
30 | function createCertsIfNotExist (callback) { | 38 | function createCertsIfNotExist (callback) { |
31 | certsExist(function (exist) { | 39 | certsExist(function (exist) { |
32 | if (exist === true) { | 40 | if (exist === true) { |
@@ -39,8 +47,18 @@ function createCertsIfNotExist (callback) { | |||
39 | }) | 47 | }) |
40 | } | 48 | } |
41 | 49 | ||
50 | function cryptPassword (password, callback) { | ||
51 | bcrypt.genSalt(constants.BCRYPT_SALT_SIZE, function (err, salt) { | ||
52 | if (err) return callback(err) | ||
53 | |||
54 | bcrypt.hash(password, salt, function (err, hash) { | ||
55 | return callback(err, hash) | ||
56 | }) | ||
57 | }) | ||
58 | } | ||
59 | |||
42 | function decrypt (key, data, callback) { | 60 | function decrypt (key, data, callback) { |
43 | fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) { | 61 | fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (err, file) { |
44 | if (err) return callback(err) | 62 | if (err) return callback(err) |
45 | 63 | ||
46 | const myPrivateKey = ursa.createPrivateKey(file) | 64 | const myPrivateKey = ursa.createPrivateKey(file) |
@@ -67,12 +85,8 @@ function encrypt (publicKey, data, callback) { | |||
67 | }) | 85 | }) |
68 | } | 86 | } |
69 | 87 | ||
70 | function getCertDir () { | ||
71 | return certDir | ||
72 | } | ||
73 | |||
74 | function sign (data) { | 88 | function sign (data) { |
75 | const myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem')) | 89 | const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem')) |
76 | const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') | 90 | const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') |
77 | 91 | ||
78 | return signature | 92 | return signature |
@@ -85,7 +99,7 @@ module.exports = peertubeCrypto | |||
85 | // --------------------------------------------------------------------------- | 99 | // --------------------------------------------------------------------------- |
86 | 100 | ||
87 | function certsExist (callback) { | 101 | function certsExist (callback) { |
88 | fs.exists(certDir + 'peertube.key.pem', function (exists) { | 102 | fs.exists(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', function (exists) { |
89 | return callback(exists) | 103 | return callback(exists) |
90 | }) | 104 | }) |
91 | } | 105 | } |
@@ -99,15 +113,25 @@ function createCerts (callback) { | |||
99 | } | 113 | } |
100 | 114 | ||
101 | logger.info('Generating a RSA key...') | 115 | logger.info('Generating a RSA key...') |
102 | openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) { | 116 | |
117 | let options = { | ||
118 | 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', | ||
119 | '2048': false | ||
120 | } | ||
121 | openssl.exec('genrsa', options, function (err) { | ||
103 | if (err) { | 122 | if (err) { |
104 | logger.error('Cannot create private key on this pod.') | 123 | logger.error('Cannot create private key on this pod.') |
105 | return callback(err) | 124 | return callback(err) |
106 | } | 125 | } |
107 | logger.info('RSA key generated.') | 126 | logger.info('RSA key generated.') |
108 | 127 | ||
128 | options = { | ||
129 | 'in': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem', | ||
130 | 'pubout': true, | ||
131 | 'out': constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub' | ||
132 | } | ||
109 | logger.info('Manage public key...') | 133 | logger.info('Manage public key...') |
110 | openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) { | 134 | openssl.exec('rsa', options, function (err) { |
111 | if (err) { | 135 | if (err) { |
112 | logger.error('Cannot create public key on this pod.') | 136 | logger.error('Cannot create public key on this pod.') |
113 | return callback(err) | 137 | return callback(err) |
diff --git a/server/helpers/requests.js b/server/helpers/requests.js index 547230adc..95775c981 100644 --- a/server/helpers/requests.js +++ b/server/helpers/requests.js | |||
@@ -1,19 +1,14 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | ||
4 | const replay = require('request-replay') | 3 | const replay = require('request-replay') |
5 | const request = require('request') | 4 | const request = require('request') |
6 | 5 | ||
7 | const constants = require('../initializers/constants') | 6 | const constants = require('../initializers/constants') |
8 | const peertubeCrypto = require('./peertube-crypto') | 7 | const peertubeCrypto = require('./peertube-crypto') |
9 | 8 | ||
10 | const http = config.get('webserver.https') ? 'https' : 'http' | ||
11 | const host = config.get('webserver.host') | ||
12 | const port = config.get('webserver.port') | ||
13 | |||
14 | const requests = { | 9 | const requests = { |
15 | makeRetryRequest: makeRetryRequest, | 10 | makeRetryRequest, |
16 | makeSecureRequest: makeSecureRequest | 11 | makeSecureRequest |
17 | } | 12 | } |
18 | 13 | ||
19 | function makeRetryRequest (params, callback) { | 14 | function makeRetryRequest (params, callback) { |
@@ -29,8 +24,6 @@ function makeRetryRequest (params, callback) { | |||
29 | } | 24 | } |
30 | 25 | ||
31 | function makeSecureRequest (params, callback) { | 26 | function makeSecureRequest (params, callback) { |
32 | const myUrl = http + '://' + host + ':' + port | ||
33 | |||
34 | const requestParams = { | 27 | const requestParams = { |
35 | url: params.toPod.url + params.path | 28 | url: params.toPod.url + params.path |
36 | } | 29 | } |
@@ -42,8 +35,8 @@ function makeSecureRequest (params, callback) { | |||
42 | // Add signature if it is specified in the params | 35 | // Add signature if it is specified in the params |
43 | if (params.sign === true) { | 36 | if (params.sign === true) { |
44 | requestParams.json.signature = { | 37 | requestParams.json.signature = { |
45 | url: myUrl, | 38 | url: constants.CONFIG.WEBSERVER.URL, |
46 | signature: peertubeCrypto.sign(myUrl) | 39 | signature: peertubeCrypto.sign(constants.CONFIG.WEBSERVER.URL) |
47 | } | 40 | } |
48 | } | 41 | } |
49 | 42 | ||
diff --git a/server/helpers/utils.js b/server/helpers/utils.js index a77116e08..9c2d402e3 100644 --- a/server/helpers/utils.js +++ b/server/helpers/utils.js | |||
@@ -5,8 +5,8 @@ const crypto = require('crypto') | |||
5 | const logger = require('./logger') | 5 | const logger = require('./logger') |
6 | 6 | ||
7 | const utils = { | 7 | const utils = { |
8 | cleanForExit: cleanForExit, | 8 | cleanForExit, |
9 | generateRandomString: generateRandomString | 9 | generateRandomString |
10 | } | 10 | } |
11 | 11 | ||
12 | function generateRandomString (size, callback) { | 12 | function generateRandomString (size, callback) { |
diff --git a/server/initializers/checker.js b/server/initializers/checker.js index 3831efb8d..91fbcfaf9 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js | |||
@@ -7,9 +7,9 @@ const Client = mongoose.model('OAuthClient') | |||
7 | const User = mongoose.model('User') | 7 | const User = mongoose.model('User') |
8 | 8 | ||
9 | const checker = { | 9 | const checker = { |
10 | checkConfig: checkConfig, | 10 | checkConfig, |
11 | clientsExist: clientsExist, | 11 | clientsExist, |
12 | usersExist: usersExist | 12 | usersExist |
13 | } | 13 | } |
14 | 14 | ||
15 | // Check the config files | 15 | // Check the config files |
@@ -17,8 +17,8 @@ function checkConfig () { | |||
17 | const required = [ 'listen.port', | 17 | const required = [ 'listen.port', |
18 | 'webserver.https', 'webserver.host', 'webserver.port', | 18 | 'webserver.https', 'webserver.host', 'webserver.port', |
19 | 'database.host', 'database.port', 'database.suffix', | 19 | 'database.host', 'database.port', 'database.suffix', |
20 | 'storage.certs', 'storage.uploads', 'storage.logs', | 20 | 'storage.certs', 'storage.uploads', 'storage.logs', 'storage.thumbnails', |
21 | 'network.friends', 'electron.debug' ] | 21 | 'electron.debug' ] |
22 | const miss = [] | 22 | const miss = [] |
23 | 23 | ||
24 | for (const key of required) { | 24 | for (const key of required) { |
@@ -39,10 +39,10 @@ function clientsExist (callback) { | |||
39 | } | 39 | } |
40 | 40 | ||
41 | function usersExist (callback) { | 41 | function usersExist (callback) { |
42 | User.list(function (err, users) { | 42 | User.countTotal(function (err, totalUsers) { |
43 | if (err) return callback(err) | 43 | if (err) return callback(err) |
44 | 44 | ||
45 | return callback(null, users.length !== 0) | 45 | return callback(null, totalUsers !== 0) |
46 | }) | 46 | }) |
47 | } | 47 | } |
48 | 48 | ||
diff --git a/server/initializers/constants.js b/server/initializers/constants.js index e0ea188af..be2e3e943 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js | |||
@@ -1,24 +1,103 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | // API version of our pod | 3 | const config = require('config') |
4 | const path = require('path') | ||
5 | |||
6 | // --------------------------------------------------------------------------- | ||
7 | |||
8 | // API version | ||
4 | const API_VERSION = 'v1' | 9 | const API_VERSION = 'v1' |
5 | 10 | ||
6 | // Score a pod has when we create it as a friend | 11 | // Number of results by default for the pagination |
7 | const FRIEND_SCORE = { | 12 | const PAGINATION_COUNT_DEFAULT = 15 |
8 | BASE: 100, | 13 | |
9 | MAX: 1000 | 14 | // Sortable columns per schema |
15 | const SEARCHABLE_COLUMNS = { | ||
16 | VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ] | ||
10 | } | 17 | } |
11 | 18 | ||
12 | // Time to wait between requests to the friends (10 min) | 19 | // Sortable columns per schema |
13 | let INTERVAL = 600000 | 20 | const SORTABLE_COLUMNS = { |
21 | USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], | ||
22 | VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] | ||
23 | } | ||
14 | 24 | ||
15 | const OAUTH_LIFETIME = { | 25 | const OAUTH_LIFETIME = { |
16 | ACCESS_TOKEN: 3600 * 4, // 4 hours | 26 | ACCESS_TOKEN: 3600 * 4, // 4 hours |
17 | REFRESH_TOKEN: 1209600 // 2 weeks | 27 | REFRESH_TOKEN: 1209600 // 2 weeks |
18 | } | 28 | } |
19 | 29 | ||
20 | // Number of results by default for the pagination | 30 | // --------------------------------------------------------------------------- |
21 | const PAGINATION_COUNT_DEFAULT = 15 | 31 | |
32 | const CONFIG = { | ||
33 | DATABASE: { | ||
34 | DBNAME: 'peertube' + config.get('database.suffix'), | ||
35 | HOST: config.get('database.host'), | ||
36 | PORT: config.get('database.port') | ||
37 | }, | ||
38 | ELECTRON: { | ||
39 | DEBUG: config.get('electron.debug') | ||
40 | }, | ||
41 | STORAGE: { | ||
42 | CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), | ||
43 | LOG_DIR: path.join(__dirname, '..', '..', config.get('storage.logs')), | ||
44 | UPLOAD_DIR: path.join(__dirname, '..', '..', config.get('storage.uploads')), | ||
45 | THUMBNAILS_DIR: path.join(__dirname, '..', '..', config.get('storage.thumbnails')) | ||
46 | }, | ||
47 | WEBSERVER: { | ||
48 | SCHEME: config.get('webserver.https') === true ? 'https' : 'http', | ||
49 | HOST: config.get('webserver.host'), | ||
50 | PORT: config.get('webserver.port') | ||
51 | } | ||
52 | } | ||
53 | CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOST + ':' + CONFIG.WEBSERVER.PORT | ||
54 | |||
55 | // --------------------------------------------------------------------------- | ||
56 | |||
57 | const CONSTRAINTS_FIELDS = { | ||
58 | USERS: { | ||
59 | USERNAME: { min: 3, max: 20 }, // Length | ||
60 | PASSWORD: { min: 6, max: 255 } // Length | ||
61 | }, | ||
62 | VIDEOS: { | ||
63 | NAME: { min: 3, max: 50 }, // Length | ||
64 | DESCRIPTION: { min: 3, max: 250 }, // Length | ||
65 | MAGNET_URI: { min: 10 }, // Length | ||
66 | DURATION: { min: 1, max: 7200 }, // Number | ||
67 | TAGS: { min: 1, max: 3 }, // Number of total tags | ||
68 | TAG: { min: 2, max: 10 }, // Length | ||
69 | THUMBNAIL: { min: 2, max: 30 }, | ||
70 | THUMBNAIL64: { min: 0, max: 20000 } // Bytes | ||
71 | } | ||
72 | } | ||
73 | |||
74 | // --------------------------------------------------------------------------- | ||
75 | |||
76 | // Score a pod has when we create it as a friend | ||
77 | const FRIEND_SCORE = { | ||
78 | BASE: 100, | ||
79 | MAX: 1000 | ||
80 | } | ||
81 | |||
82 | // --------------------------------------------------------------------------- | ||
83 | |||
84 | const MONGO_MIGRATION_SCRIPTS = [ | ||
85 | { | ||
86 | script: '0005-create-application', | ||
87 | version: 5 | ||
88 | }, | ||
89 | { | ||
90 | script: '0010-users-password', | ||
91 | version: 10 | ||
92 | }, | ||
93 | { | ||
94 | script: '0015-admin-role', | ||
95 | version: 15 | ||
96 | } | ||
97 | ] | ||
98 | const LAST_MONGO_SCHEMA_VERSION = 15 | ||
99 | |||
100 | // --------------------------------------------------------------------------- | ||
22 | 101 | ||
23 | // Number of points we add/remove from a friend after a successful/bad request | 102 | // Number of points we add/remove from a friend after a successful/bad request |
24 | const PODS_SCORE = { | 103 | const PODS_SCORE = { |
@@ -26,28 +105,22 @@ const PODS_SCORE = { | |||
26 | BONUS: 10 | 105 | BONUS: 10 |
27 | } | 106 | } |
28 | 107 | ||
108 | // Time to wait between requests to the friends (10 min) | ||
109 | let REQUESTS_INTERVAL = 600000 | ||
110 | |||
29 | // Number of requests in parallel we can make | 111 | // Number of requests in parallel we can make |
30 | const REQUESTS_IN_PARALLEL = 10 | 112 | const REQUESTS_IN_PARALLEL = 10 |
31 | 113 | ||
32 | // How many requests we put in request (request scheduler) | 114 | // How many requests we put in request |
33 | const REQUESTS_LIMIT = 10 | 115 | const REQUESTS_LIMIT = 10 |
34 | 116 | ||
35 | // Number of requests to retry for replay requests module | 117 | // Number of requests to retry for replay requests module |
36 | const RETRY_REQUESTS = 5 | 118 | const RETRY_REQUESTS = 5 |
37 | 119 | ||
38 | // Sortable columns per schema | 120 | // --------------------------------------------------------------------------- |
39 | const SEARCHABLE_COLUMNS = { | ||
40 | VIDEOS: [ 'name', 'magnetUri', 'podUrl', 'author', 'tags' ] | ||
41 | } | ||
42 | |||
43 | // Seeds in parallel we send to electron when "seed all" | ||
44 | // Once a video is in seeding state we seed another video etc | ||
45 | const SEEDS_IN_PARALLEL = 3 | ||
46 | 121 | ||
47 | // Sortable columns per schema | 122 | // Password encryption |
48 | const SORTABLE_COLUMNS = { | 123 | const BCRYPT_SALT_SIZE = 10 |
49 | VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] | ||
50 | } | ||
51 | 124 | ||
52 | // Express static paths (router) | 125 | // Express static paths (router) |
53 | const STATIC_PATHS = { | 126 | const STATIC_PATHS = { |
@@ -59,43 +132,47 @@ const STATIC_PATHS = { | |||
59 | // Videos thumbnail size | 132 | // Videos thumbnail size |
60 | const THUMBNAILS_SIZE = '200x110' | 133 | const THUMBNAILS_SIZE = '200x110' |
61 | 134 | ||
62 | const VIDEOS_CONSTRAINTS_FIELDS = { | 135 | const USER_ROLES = { |
63 | NAME: { min: 3, max: 50 }, // Length | 136 | ADMIN: 'admin', |
64 | DESCRIPTION: { min: 3, max: 250 }, // Length | 137 | USER: 'user' |
65 | MAGNET_URI: { min: 10 }, // Length | ||
66 | DURATION: { min: 1, max: 7200 }, // Number | ||
67 | AUTHOR: { min: 3, max: 20 }, // Length | ||
68 | TAGS: { min: 1, max: 3 }, // Number of total tags | ||
69 | TAG: { min: 2, max: 10 }, // Length | ||
70 | THUMBNAIL: { min: 2, max: 30 }, | ||
71 | THUMBNAIL64: { min: 0, max: 20000 } // Bytes | ||
72 | } | 138 | } |
73 | 139 | ||
140 | // Seeds in parallel we send to electron when "seed all" | ||
141 | // Once a video is in seeding state we seed another video etc | ||
142 | const SEEDS_IN_PARALLEL = 3 | ||
143 | |||
144 | // --------------------------------------------------------------------------- | ||
145 | |||
74 | // Special constants for a test instance | 146 | // Special constants for a test instance |
75 | if (isTestInstance() === true) { | 147 | if (isTestInstance() === true) { |
148 | CONSTRAINTS_FIELDS.VIDEOS.DURATION.max = 14 | ||
76 | FRIEND_SCORE.BASE = 20 | 149 | FRIEND_SCORE.BASE = 20 |
77 | INTERVAL = 10000 | 150 | REQUESTS_INTERVAL = 10000 |
78 | VIDEOS_CONSTRAINTS_FIELDS.DURATION.max = 14 | ||
79 | } | 151 | } |
80 | 152 | ||
81 | // --------------------------------------------------------------------------- | 153 | // --------------------------------------------------------------------------- |
82 | 154 | ||
83 | module.exports = { | 155 | module.exports = { |
84 | API_VERSION: API_VERSION, | 156 | API_VERSION, |
85 | FRIEND_SCORE: FRIEND_SCORE, | 157 | BCRYPT_SALT_SIZE, |
86 | INTERVAL: INTERVAL, | 158 | CONFIG, |
87 | OAUTH_LIFETIME: OAUTH_LIFETIME, | 159 | CONSTRAINTS_FIELDS, |
88 | PAGINATION_COUNT_DEFAULT: PAGINATION_COUNT_DEFAULT, | 160 | FRIEND_SCORE, |
89 | PODS_SCORE: PODS_SCORE, | 161 | LAST_MONGO_SCHEMA_VERSION, |
90 | REQUESTS_IN_PARALLEL: REQUESTS_IN_PARALLEL, | 162 | MONGO_MIGRATION_SCRIPTS, |
91 | REQUESTS_LIMIT: REQUESTS_LIMIT, | 163 | OAUTH_LIFETIME, |
92 | RETRY_REQUESTS: RETRY_REQUESTS, | 164 | PAGINATION_COUNT_DEFAULT, |
93 | SEARCHABLE_COLUMNS: SEARCHABLE_COLUMNS, | 165 | PODS_SCORE, |
94 | SEEDS_IN_PARALLEL: SEEDS_IN_PARALLEL, | 166 | REQUESTS_IN_PARALLEL, |
95 | SORTABLE_COLUMNS: SORTABLE_COLUMNS, | 167 | REQUESTS_INTERVAL, |
96 | STATIC_PATHS: STATIC_PATHS, | 168 | REQUESTS_LIMIT, |
97 | THUMBNAILS_SIZE: THUMBNAILS_SIZE, | 169 | RETRY_REQUESTS, |
98 | VIDEOS_CONSTRAINTS_FIELDS: VIDEOS_CONSTRAINTS_FIELDS | 170 | SEARCHABLE_COLUMNS, |
171 | SEEDS_IN_PARALLEL, | ||
172 | SORTABLE_COLUMNS, | ||
173 | STATIC_PATHS, | ||
174 | THUMBNAILS_SIZE, | ||
175 | USER_ROLES | ||
99 | } | 176 | } |
100 | 177 | ||
101 | // --------------------------------------------------------------------------- | 178 | // --------------------------------------------------------------------------- |
diff --git a/server/initializers/database.js b/server/initializers/database.js index 8626895ee..45c8a240d 100644 --- a/server/initializers/database.js +++ b/server/initializers/database.js | |||
@@ -1,30 +1,27 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | ||
4 | const mongoose = require('mongoose') | 3 | const mongoose = require('mongoose') |
5 | 4 | ||
5 | const constants = require('../initializers/constants') | ||
6 | const logger = require('../helpers/logger') | 6 | const logger = require('../helpers/logger') |
7 | 7 | ||
8 | // Bootstrap models | 8 | // Bootstrap models |
9 | require('../models/application') | ||
10 | require('../models/oauth-token') | ||
9 | require('../models/user') | 11 | require('../models/user') |
10 | require('../models/oauth-client') | 12 | require('../models/oauth-client') |
11 | require('../models/oauth-token') | ||
12 | require('../models/pods') | 13 | require('../models/pods') |
13 | require('../models/video') | 14 | require('../models/video') |
14 | // Request model needs Video model | 15 | // Request model needs Video model |
15 | require('../models/request') | 16 | require('../models/request') |
16 | 17 | ||
17 | const dbname = 'peertube' + config.get('database.suffix') | ||
18 | const host = config.get('database.host') | ||
19 | const port = config.get('database.port') | ||
20 | |||
21 | const database = { | 18 | const database = { |
22 | connect: connect | 19 | connect: connect |
23 | } | 20 | } |
24 | 21 | ||
25 | function connect () { | 22 | function connect () { |
26 | mongoose.Promise = global.Promise | 23 | mongoose.Promise = global.Promise |
27 | mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname) | 24 | mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOST + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME) |
28 | mongoose.connection.on('error', function () { | 25 | mongoose.connection.on('error', function () { |
29 | throw new Error('Mongodb connection error.') | 26 | throw new Error('Mongodb connection error.') |
30 | }) | 27 | }) |
diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 32830d4da..1df300ba8 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js | |||
@@ -9,14 +9,16 @@ const path = require('path') | |||
9 | const series = require('async/series') | 9 | const series = require('async/series') |
10 | 10 | ||
11 | const checker = require('./checker') | 11 | const checker = require('./checker') |
12 | const constants = require('./constants') | ||
12 | const logger = require('../helpers/logger') | 13 | const logger = require('../helpers/logger') |
13 | const peertubeCrypto = require('../helpers/peertube-crypto') | 14 | const peertubeCrypto = require('../helpers/peertube-crypto') |
14 | 15 | ||
16 | const Application = mongoose.model('Application') | ||
15 | const Client = mongoose.model('OAuthClient') | 17 | const Client = mongoose.model('OAuthClient') |
16 | const User = mongoose.model('User') | 18 | const User = mongoose.model('User') |
17 | 19 | ||
18 | const installer = { | 20 | const installer = { |
19 | installApplication: installApplication | 21 | installApplication |
20 | } | 22 | } |
21 | 23 | ||
22 | function installApplication (callback) { | 24 | function installApplication (callback) { |
@@ -34,7 +36,7 @@ function installApplication (callback) { | |||
34 | }, | 36 | }, |
35 | 37 | ||
36 | function createOAuthUser (callbackAsync) { | 38 | function createOAuthUser (callbackAsync) { |
37 | createOAuthUserIfNotExist(callbackAsync) | 39 | createOAuthAdminIfNotExist(callbackAsync) |
38 | } | 40 | } |
39 | ], callback) | 41 | ], callback) |
40 | } | 42 | } |
@@ -80,7 +82,7 @@ function createOAuthClientIfNotExist (callback) { | |||
80 | }) | 82 | }) |
81 | } | 83 | } |
82 | 84 | ||
83 | function createOAuthUserIfNotExist (callback) { | 85 | function createOAuthAdminIfNotExist (callback) { |
84 | checker.usersExist(function (err, exist) { | 86 | checker.usersExist(function (err, exist) { |
85 | if (err) return callback(err) | 87 | if (err) return callback(err) |
86 | 88 | ||
@@ -90,6 +92,7 @@ function createOAuthUserIfNotExist (callback) { | |||
90 | logger.info('Creating the administrator.') | 92 | logger.info('Creating the administrator.') |
91 | 93 | ||
92 | const username = 'root' | 94 | const username = 'root' |
95 | const role = constants.USER_ROLES.ADMIN | ||
93 | let password = '' | 96 | let password = '' |
94 | 97 | ||
95 | // Do not generate a random password for tests | 98 | // Do not generate a random password for tests |
@@ -104,17 +107,20 @@ function createOAuthUserIfNotExist (callback) { | |||
104 | } | 107 | } |
105 | 108 | ||
106 | const user = new User({ | 109 | const user = new User({ |
107 | username: username, | 110 | username, |
108 | password: password | 111 | password, |
112 | role | ||
109 | }) | 113 | }) |
110 | 114 | ||
111 | user.save(function (err, createdUser) { | 115 | user.save(function (err, createdUser) { |
112 | if (err) return callback(err) | 116 | if (err) return callback(err) |
113 | 117 | ||
114 | logger.info('Username: ' + createdUser.username) | 118 | logger.info('Username: ' + username) |
115 | logger.info('User password: ' + createdUser.password) | 119 | logger.info('User password: ' + password) |
116 | 120 | ||
117 | return callback(null) | 121 | logger.info('Creating Application collection.') |
122 | const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION }) | ||
123 | application.save(callback) | ||
118 | }) | 124 | }) |
119 | }) | 125 | }) |
120 | } | 126 | } |
diff --git a/server/initializers/migrations/0005-create-application.js b/server/initializers/migrations/0005-create-application.js new file mode 100644 index 000000000..e99dec019 --- /dev/null +++ b/server/initializers/migrations/0005-create-application.js | |||
@@ -0,0 +1,17 @@ | |||
1 | /* | ||
2 | Create the application collection in MongoDB. | ||
3 | Used to store the actual MongoDB scheme version. | ||
4 | */ | ||
5 | |||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const Application = mongoose.model('Application') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | const application = new Application() | ||
12 | application.save(callback) | ||
13 | } | ||
14 | |||
15 | exports.down = function (callback) { | ||
16 | throw new Error('Not implemented.') | ||
17 | } | ||
diff --git a/server/initializers/migrations/0010-users-password.js b/server/initializers/migrations/0010-users-password.js new file mode 100644 index 000000000..a0616a269 --- /dev/null +++ b/server/initializers/migrations/0010-users-password.js | |||
@@ -0,0 +1,22 @@ | |||
1 | /* | ||
2 | Convert plain user password to encrypted user password. | ||
3 | */ | ||
4 | |||
5 | const eachSeries = require('async/eachSeries') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.list(function (err, users) { | ||
12 | if (err) return callback(err) | ||
13 | |||
14 | eachSeries(users, function (user, callbackEach) { | ||
15 | user.save(callbackEach) | ||
16 | }, callback) | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | exports.down = function (callback) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
diff --git a/server/initializers/migrations/0015-admin-role.js b/server/initializers/migrations/0015-admin-role.js new file mode 100644 index 000000000..af06dca9e --- /dev/null +++ b/server/initializers/migrations/0015-admin-role.js | |||
@@ -0,0 +1,16 @@ | |||
1 | /* | ||
2 | Set the admin role to the root user. | ||
3 | */ | ||
4 | |||
5 | const constants = require('../constants') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.update({ username: 'root' }, { role: constants.USER_ROLES.ADMIN }, callback) | ||
12 | } | ||
13 | |||
14 | exports.down = function (callback) { | ||
15 | throw new Error('Not implemented.') | ||
16 | } | ||
diff --git a/server/initializers/migrator.js b/server/initializers/migrator.js new file mode 100644 index 000000000..6b31d994f --- /dev/null +++ b/server/initializers/migrator.js | |||
@@ -0,0 +1,56 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const eachSeries = require('async/eachSeries') | ||
4 | const mongoose = require('mongoose') | ||
5 | const path = require('path') | ||
6 | |||
7 | const constants = require('./constants') | ||
8 | const logger = require('../helpers/logger') | ||
9 | |||
10 | const Application = mongoose.model('Application') | ||
11 | |||
12 | const migrator = { | ||
13 | migrate: migrate | ||
14 | } | ||
15 | |||
16 | function migrate (callback) { | ||
17 | Application.loadMongoSchemaVersion(function (err, actualVersion) { | ||
18 | if (err) return callback(err) | ||
19 | |||
20 | // If there are a new mongo schemas | ||
21 | if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) { | ||
22 | logger.info('Begin migrations.') | ||
23 | |||
24 | eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) { | ||
25 | const versionScript = entity.version | ||
26 | |||
27 | // Do not execute old migration scripts | ||
28 | if (versionScript <= actualVersion) return callbackEach(null) | ||
29 | |||
30 | // Load the migration module and run it | ||
31 | const migrationScriptName = entity.script | ||
32 | logger.info('Executing %s migration script.', migrationScriptName) | ||
33 | |||
34 | const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) | ||
35 | migrationScript.up(function (err) { | ||
36 | if (err) return callbackEach(err) | ||
37 | |||
38 | // Update the new mongo version schema | ||
39 | Application.updateMongoSchemaVersion(versionScript, callbackEach) | ||
40 | }) | ||
41 | }, function (err) { | ||
42 | if (err) return callback(err) | ||
43 | |||
44 | logger.info('Migrations finished. New mongo version schema: %s', constants.LAST_MONGO_SCHEMA_VERSION) | ||
45 | return callback(null) | ||
46 | }) | ||
47 | } else { | ||
48 | return callback(null) | ||
49 | } | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | // --------------------------------------------------------------------------- | ||
54 | |||
55 | module.exports = migrator | ||
56 | |||
diff --git a/server/lib/friends.js b/server/lib/friends.js index 6e1516b94..556d2e773 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js | |||
@@ -1,6 +1,5 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | ||
4 | const each = require('async/each') | 3 | const each = require('async/each') |
5 | const eachLimit = require('async/eachLimit') | 4 | const eachLimit = require('async/eachLimit') |
6 | const eachSeries = require('async/eachSeries') | 5 | const eachSeries = require('async/eachSeries') |
@@ -11,24 +10,20 @@ const waterfall = require('async/waterfall') | |||
11 | 10 | ||
12 | const constants = require('../initializers/constants') | 11 | const constants = require('../initializers/constants') |
13 | const logger = require('../helpers/logger') | 12 | const logger = require('../helpers/logger') |
14 | const peertubeCrypto = require('../helpers/peertube-crypto') | ||
15 | const requests = require('../helpers/requests') | 13 | const requests = require('../helpers/requests') |
16 | 14 | ||
17 | const http = config.get('webserver.https') ? 'https' : 'http' | ||
18 | const host = config.get('webserver.host') | ||
19 | const port = config.get('webserver.port') | ||
20 | const Pod = mongoose.model('Pod') | 15 | const Pod = mongoose.model('Pod') |
21 | const Request = mongoose.model('Request') | 16 | const Request = mongoose.model('Request') |
22 | const Video = mongoose.model('Video') | 17 | const Video = mongoose.model('Video') |
23 | 18 | ||
24 | const friends = { | 19 | const friends = { |
25 | addVideoToFriends: addVideoToFriends, | 20 | addVideoToFriends, |
26 | hasFriends: hasFriends, | 21 | hasFriends, |
27 | getMyCertificate: getMyCertificate, | 22 | getMyCertificate, |
28 | makeFriends: makeFriends, | 23 | makeFriends, |
29 | quitFriends: quitFriends, | 24 | quitFriends, |
30 | removeVideoToFriends: removeVideoToFriends, | 25 | removeVideoToFriends, |
31 | sendOwnedVideosToPod: sendOwnedVideosToPod | 26 | sendOwnedVideosToPod |
32 | } | 27 | } |
33 | 28 | ||
34 | function addVideoToFriends (video) { | 29 | function addVideoToFriends (video) { |
@@ -45,10 +40,10 @@ function hasFriends (callback) { | |||
45 | } | 40 | } |
46 | 41 | ||
47 | function getMyCertificate (callback) { | 42 | function getMyCertificate (callback) { |
48 | fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', callback) | 43 | fs.readFile(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.pub', 'utf8', callback) |
49 | } | 44 | } |
50 | 45 | ||
51 | function makeFriends (callback) { | 46 | function makeFriends (urls, callback) { |
52 | const podsScore = {} | 47 | const podsScore = {} |
53 | 48 | ||
54 | logger.info('Make friends!') | 49 | logger.info('Make friends!') |
@@ -58,8 +53,6 @@ function makeFriends (callback) { | |||
58 | return callback(err) | 53 | return callback(err) |
59 | } | 54 | } |
60 | 55 | ||
61 | const urls = config.get('network.friends') | ||
62 | |||
63 | eachSeries(urls, function (url, callbackEach) { | 56 | eachSeries(urls, function (url, callbackEach) { |
64 | computeForeignPodsList(url, podsScore, callbackEach) | 57 | computeForeignPodsList(url, podsScore, callbackEach) |
65 | }, function (err) { | 58 | }, function (err) { |
@@ -205,7 +198,12 @@ function getForeignPodsList (url, callback) { | |||
205 | request.get(url + path, function (err, response, body) { | 198 | request.get(url + path, function (err, response, body) { |
206 | if (err) return callback(err) | 199 | if (err) return callback(err) |
207 | 200 | ||
208 | callback(null, JSON.parse(body)) | 201 | try { |
202 | const json = JSON.parse(body) | ||
203 | return callback(null, json) | ||
204 | } catch (err) { | ||
205 | return callback(err) | ||
206 | } | ||
209 | }) | 207 | }) |
210 | } | 208 | } |
211 | 209 | ||
@@ -220,7 +218,7 @@ function makeRequestsToWinningPods (cert, podsList, callback) { | |||
220 | url: pod.url + '/api/' + constants.API_VERSION + '/pods/', | 218 | url: pod.url + '/api/' + constants.API_VERSION + '/pods/', |
221 | method: 'POST', | 219 | method: 'POST', |
222 | json: { | 220 | json: { |
223 | url: http + '://' + host + ':' + port, | 221 | url: constants.CONFIG.WEBSERVER.URL, |
224 | publicKey: cert | 222 | publicKey: cert |
225 | } | 223 | } |
226 | } | 224 | } |
diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js index d9f8b175a..45f796796 100644 --- a/server/lib/oauth-model.js +++ b/server/lib/oauth-model.js | |||
@@ -8,12 +8,12 @@ const User = mongoose.model('User') | |||
8 | 8 | ||
9 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications | 9 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications |
10 | const OAuthModel = { | 10 | const OAuthModel = { |
11 | getAccessToken: getAccessToken, | 11 | getAccessToken, |
12 | getClient: getClient, | 12 | getClient, |
13 | getRefreshToken: getRefreshToken, | 13 | getRefreshToken, |
14 | getUser: getUser, | 14 | getUser, |
15 | revokeToken: revokeToken, | 15 | revokeToken, |
16 | saveToken: saveToken | 16 | saveToken |
17 | } | 17 | } |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
@@ -41,7 +41,22 @@ function getRefreshToken (refreshToken, callback) { | |||
41 | function getUser (username, password) { | 41 | function getUser (username, password) { |
42 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') | 42 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') |
43 | 43 | ||
44 | return User.getByUsernameAndPassword(username, password) | 44 | return User.getByUsername(username).then(function (user) { |
45 | if (!user) return null | ||
46 | |||
47 | // We need to return a promise | ||
48 | return new Promise(function (resolve, reject) { | ||
49 | return user.isPasswordMatch(password, function (err, isPasswordMatch) { | ||
50 | if (err) return reject(err) | ||
51 | |||
52 | if (isPasswordMatch === true) { | ||
53 | return resolve(user) | ||
54 | } | ||
55 | |||
56 | return resolve(null) | ||
57 | }) | ||
58 | }) | ||
59 | }) | ||
45 | } | 60 | } |
46 | 61 | ||
47 | function revokeToken (token) { | 62 | function revokeToken (token) { |
diff --git a/server/middlewares/admin.js b/server/middlewares/admin.js new file mode 100644 index 000000000..e6d9dc887 --- /dev/null +++ b/server/middlewares/admin.js | |||
@@ -0,0 +1,22 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const constants = require('../initializers/constants') | ||
4 | const logger = require('../helpers/logger') | ||
5 | |||
6 | const adminMiddleware = { | ||
7 | ensureIsAdmin | ||
8 | } | ||
9 | |||
10 | function ensureIsAdmin (req, res, next) { | ||
11 | const user = res.locals.oauth.token.user | ||
12 | if (user.role !== constants.USER_ROLES.ADMIN) { | ||
13 | logger.info('A non admin user is trying to access to an admin content.') | ||
14 | return res.sendStatus(403) | ||
15 | } | ||
16 | |||
17 | return next() | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | module.exports = adminMiddleware | ||
diff --git a/server/middlewares/index.js b/server/middlewares/index.js index 0a233e701..3f253e31b 100644 --- a/server/middlewares/index.js +++ b/server/middlewares/index.js | |||
@@ -1,19 +1,23 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const oauth = require('./oauth') | 3 | const adminMiddleware = require('./admin') |
4 | const pagination = require('./pagination') | 4 | const oauthMiddleware = require('./oauth') |
5 | const paginationMiddleware = require('./pagination') | ||
6 | const podsMiddleware = require('./pods') | ||
5 | const validatorsMiddleware = require('./validators') | 7 | const validatorsMiddleware = require('./validators') |
6 | const search = require('./search') | 8 | const searchMiddleware = require('./search') |
7 | const sort = require('./sort') | 9 | const sortMiddleware = require('./sort') |
8 | const secureMiddleware = require('./secure') | 10 | const secureMiddleware = require('./secure') |
9 | 11 | ||
10 | const middlewares = { | 12 | const middlewares = { |
11 | oauth: oauth, | 13 | admin: adminMiddleware, |
12 | pagination: pagination, | 14 | oauth: oauthMiddleware, |
13 | validators: validatorsMiddleware, | 15 | pagination: paginationMiddleware, |
14 | search: search, | 16 | pods: podsMiddleware, |
15 | sort: sort, | 17 | search: searchMiddleware, |
16 | secure: secureMiddleware | 18 | secure: secureMiddleware, |
19 | sort: sortMiddleware, | ||
20 | validators: validatorsMiddleware | ||
17 | } | 21 | } |
18 | 22 | ||
19 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/oauth.js b/server/middlewares/oauth.js index 91a990509..3a02b9b48 100644 --- a/server/middlewares/oauth.js +++ b/server/middlewares/oauth.js | |||
@@ -12,8 +12,8 @@ const oAuthServer = new OAuthServer({ | |||
12 | }) | 12 | }) |
13 | 13 | ||
14 | const oAuth = { | 14 | const oAuth = { |
15 | authenticate: authenticate, | 15 | authenticate, |
16 | token: token | 16 | token |
17 | } | 17 | } |
18 | 18 | ||
19 | function authenticate (req, res, next) { | 19 | function authenticate (req, res, next) { |
@@ -23,7 +23,7 @@ function authenticate (req, res, next) { | |||
23 | return res.sendStatus(500) | 23 | return res.sendStatus(500) |
24 | } | 24 | } |
25 | 25 | ||
26 | if (res.statusCode === 401 || res.statusCode === 400) return res.end() | 26 | if (res.statusCode === 401 || res.statusCode === 400 || res.statusCode === 503) return res.end() |
27 | 27 | ||
28 | return next() | 28 | return next() |
29 | }) | 29 | }) |
diff --git a/server/middlewares/pagination.js b/server/middlewares/pagination.js index a571e51f6..a90f60aab 100644 --- a/server/middlewares/pagination.js +++ b/server/middlewares/pagination.js | |||
@@ -3,7 +3,7 @@ | |||
3 | const constants = require('../initializers/constants') | 3 | const constants = require('../initializers/constants') |
4 | 4 | ||
5 | const paginationMiddleware = { | 5 | const paginationMiddleware = { |
6 | setPagination: setPagination | 6 | setPagination |
7 | } | 7 | } |
8 | 8 | ||
9 | function setPagination (req, res, next) { | 9 | function setPagination (req, res, next) { |
diff --git a/server/middlewares/pods.js b/server/middlewares/pods.js new file mode 100644 index 000000000..6e0874a76 --- /dev/null +++ b/server/middlewares/pods.js | |||
@@ -0,0 +1,62 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const urlModule = require('url') | ||
4 | |||
5 | const logger = require('../helpers/logger') | ||
6 | |||
7 | const podsMiddleware = { | ||
8 | setBodyUrlsPort, | ||
9 | setBodyUrlPort | ||
10 | } | ||
11 | |||
12 | function setBodyUrlsPort (req, res, next) { | ||
13 | for (let i = 0; i < req.body.urls.length; i++) { | ||
14 | const urlWithPort = getUrlWithPort(req.body.urls[i]) | ||
15 | |||
16 | // Problem with the url parsing? | ||
17 | if (urlWithPort === null) { | ||
18 | return res.sendStatus(500) | ||
19 | } | ||
20 | |||
21 | req.body.urls[i] = urlWithPort | ||
22 | } | ||
23 | |||
24 | return next() | ||
25 | } | ||
26 | |||
27 | function setBodyUrlPort (req, res, next) { | ||
28 | const urlWithPort = getUrlWithPort(req.body.url) | ||
29 | |||
30 | // Problem with the url parsing? | ||
31 | if (urlWithPort === null) { | ||
32 | return res.sendStatus(500) | ||
33 | } | ||
34 | |||
35 | req.body.url = urlWithPort | ||
36 | |||
37 | return next() | ||
38 | } | ||
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | module.exports = podsMiddleware | ||
43 | |||
44 | // --------------------------------------------------------------------------- | ||
45 | |||
46 | function getUrlWithPort (url) { | ||
47 | const urlObj = urlModule.parse(url) | ||
48 | |||
49 | // Add the port if it is not specified | ||
50 | if (urlObj.port === null) { | ||
51 | if (urlObj.protocol === 'http:') { | ||
52 | return url + ':80' | ||
53 | } else if (urlObj.protocol === 'https:') { | ||
54 | return url + ':443' | ||
55 | } else { | ||
56 | logger.error('Unknown url protocol: ' + urlObj.protocol) | ||
57 | return null | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return url | ||
62 | } | ||
diff --git a/server/middlewares/search.js b/server/middlewares/search.js index 89302a564..bb88faf54 100644 --- a/server/middlewares/search.js +++ b/server/middlewares/search.js | |||
@@ -1,7 +1,7 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const searchMiddleware = { | 3 | const searchMiddleware = { |
4 | setVideosSearch: setVideosSearch | 4 | setVideosSearch |
5 | } | 5 | } |
6 | 6 | ||
7 | function setVideosSearch (req, res, next) { | 7 | function setVideosSearch (req, res, next) { |
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js index 9779c14ac..58f824d14 100644 --- a/server/middlewares/secure.js +++ b/server/middlewares/secure.js | |||
@@ -7,10 +7,11 @@ const peertubeCrypto = require('../helpers/peertube-crypto') | |||
7 | const Pod = mongoose.model('Pod') | 7 | const Pod = mongoose.model('Pod') |
8 | 8 | ||
9 | const secureMiddleware = { | 9 | const secureMiddleware = { |
10 | decryptBody: decryptBody | 10 | checkSignature, |
11 | decryptBody | ||
11 | } | 12 | } |
12 | 13 | ||
13 | function decryptBody (req, res, next) { | 14 | function checkSignature (req, res, next) { |
14 | const url = req.body.signature.url | 15 | const url = req.body.signature.url |
15 | Pod.loadByUrl(url, function (err, pod) { | 16 | Pod.loadByUrl(url, function (err, pod) { |
16 | if (err) { | 17 | if (err) { |
@@ -28,21 +29,30 @@ function decryptBody (req, res, next) { | |||
28 | const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature) | 29 | const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature) |
29 | 30 | ||
30 | if (signatureOk === true) { | 31 | if (signatureOk === true) { |
31 | peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) { | 32 | return next() |
32 | if (err) { | 33 | } |
33 | logger.error('Cannot decrypt data.', { error: err }) | 34 | |
34 | return res.sendStatus(500) | 35 | logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url) |
35 | } | 36 | return res.sendStatus(403) |
36 | 37 | }) | |
37 | req.body.data = JSON.parse(decrypted) | 38 | } |
38 | delete req.body.key | 39 | |
39 | 40 | function decryptBody (req, res, next) { | |
40 | next() | 41 | peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) { |
41 | }) | 42 | if (err) { |
42 | } else { | 43 | logger.error('Cannot decrypt data.', { error: err }) |
43 | logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url) | 44 | return res.sendStatus(500) |
44 | return res.sendStatus(403) | ||
45 | } | 45 | } |
46 | |||
47 | try { | ||
48 | req.body.data = JSON.parse(decrypted) | ||
49 | delete req.body.key | ||
50 | } catch (err) { | ||
51 | logger.error('Error in JSON.parse', { error: err }) | ||
52 | return res.sendStatus(500) | ||
53 | } | ||
54 | |||
55 | next() | ||
46 | }) | 56 | }) |
47 | } | 57 | } |
48 | 58 | ||
diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js index 9f52290a6..f0b7274eb 100644 --- a/server/middlewares/sort.js +++ b/server/middlewares/sort.js | |||
@@ -1,7 +1,14 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const sortMiddleware = { | 3 | const sortMiddleware = { |
4 | setVideosSort: setVideosSort | 4 | setUsersSort, |
5 | setVideosSort | ||
6 | } | ||
7 | |||
8 | function setUsersSort (req, res, next) { | ||
9 | if (!req.query.sort) req.query.sort = '-createdDate' | ||
10 | |||
11 | return next() | ||
5 | } | 12 | } |
6 | 13 | ||
7 | function setVideosSort (req, res, next) { | 14 | function setVideosSort (req, res, next) { |
diff --git a/server/middlewares/validators/index.js b/server/middlewares/validators/index.js index 0471b3f92..6c3a9c2b4 100644 --- a/server/middlewares/validators/index.js +++ b/server/middlewares/validators/index.js | |||
@@ -4,6 +4,7 @@ const paginationValidators = require('./pagination') | |||
4 | const podsValidators = require('./pods') | 4 | const podsValidators = require('./pods') |
5 | const remoteValidators = require('./remote') | 5 | const remoteValidators = require('./remote') |
6 | const sortValidators = require('./sort') | 6 | const sortValidators = require('./sort') |
7 | const usersValidators = require('./users') | ||
7 | const videosValidators = require('./videos') | 8 | const videosValidators = require('./videos') |
8 | 9 | ||
9 | const validators = { | 10 | const validators = { |
@@ -11,6 +12,7 @@ const validators = { | |||
11 | pods: podsValidators, | 12 | pods: podsValidators, |
12 | remote: remoteValidators, | 13 | remote: remoteValidators, |
13 | sort: sortValidators, | 14 | sort: sortValidators, |
15 | users: usersValidators, | ||
14 | videos: videosValidators | 16 | videos: videosValidators |
15 | } | 17 | } |
16 | 18 | ||
diff --git a/server/middlewares/validators/pagination.js b/server/middlewares/validators/pagination.js index 8e9a01053..16682696e 100644 --- a/server/middlewares/validators/pagination.js +++ b/server/middlewares/validators/pagination.js | |||
@@ -4,7 +4,7 @@ const checkErrors = require('./utils').checkErrors | |||
4 | const logger = require('../../helpers/logger') | 4 | const logger = require('../../helpers/logger') |
5 | 5 | ||
6 | const validatorsPagination = { | 6 | const validatorsPagination = { |
7 | pagination: pagination | 7 | pagination |
8 | } | 8 | } |
9 | 9 | ||
10 | function pagination (req, res, next) { | 10 | function pagination (req, res, next) { |
diff --git a/server/middlewares/validators/pods.js b/server/middlewares/validators/pods.js index fda2e865f..fd3d1e2f2 100644 --- a/server/middlewares/validators/pods.js +++ b/server/middlewares/validators/pods.js | |||
@@ -5,23 +5,29 @@ const friends = require('../../lib/friends') | |||
5 | const logger = require('../../helpers/logger') | 5 | const logger = require('../../helpers/logger') |
6 | 6 | ||
7 | const validatorsPod = { | 7 | const validatorsPod = { |
8 | makeFriends: makeFriends, | 8 | makeFriends, |
9 | podsAdd: podsAdd | 9 | podsAdd |
10 | } | 10 | } |
11 | 11 | ||
12 | function makeFriends (req, res, next) { | 12 | function makeFriends (req, res, next) { |
13 | friends.hasFriends(function (err, hasFriends) { | 13 | req.checkBody('urls', 'Should have an array of unique urls').isEachUniqueUrlValid() |
14 | if (err) { | 14 | |
15 | logger.error('Cannot know if we have friends.', { error: err }) | 15 | logger.debug('Checking makeFriends parameters', { parameters: req.body }) |
16 | res.sendStatus(500) | 16 | |
17 | } | 17 | checkErrors(req, res, function () { |
18 | 18 | friends.hasFriends(function (err, hasFriends) { | |
19 | if (hasFriends === true) { | 19 | if (err) { |
20 | // We need to quit our friends before make new ones | 20 | logger.error('Cannot know if we have friends.', { error: err }) |
21 | res.sendStatus(409) | 21 | res.sendStatus(500) |
22 | } else { | 22 | } |
23 | return next() | 23 | |
24 | } | 24 | if (hasFriends === true) { |
25 | // We need to quit our friends before make new ones | ||
26 | res.sendStatus(409) | ||
27 | } else { | ||
28 | return next() | ||
29 | } | ||
30 | }) | ||
25 | }) | 31 | }) |
26 | } | 32 | } |
27 | 33 | ||
diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js index 1be119458..8c29ef8ca 100644 --- a/server/middlewares/validators/remote.js +++ b/server/middlewares/validators/remote.js | |||
@@ -4,9 +4,9 @@ const checkErrors = require('./utils').checkErrors | |||
4 | const logger = require('../../helpers/logger') | 4 | const logger = require('../../helpers/logger') |
5 | 5 | ||
6 | const validatorsRemote = { | 6 | const validatorsRemote = { |
7 | dataToDecrypt: dataToDecrypt, | 7 | dataToDecrypt, |
8 | remoteVideos: remoteVideos, | 8 | remoteVideos, |
9 | signature: signature | 9 | signature |
10 | } | 10 | } |
11 | 11 | ||
12 | function dataToDecrypt (req, res, next) { | 12 | function dataToDecrypt (req, res, next) { |
@@ -19,7 +19,6 @@ function dataToDecrypt (req, res, next) { | |||
19 | } | 19 | } |
20 | 20 | ||
21 | function remoteVideos (req, res, next) { | 21 | function remoteVideos (req, res, next) { |
22 | req.checkBody('data').isArray() | ||
23 | req.checkBody('data').isEachRemoteVideosValid() | 22 | req.checkBody('data').isEachRemoteVideosValid() |
24 | 23 | ||
25 | logger.debug('Checking remoteVideos parameters', { parameters: req.body }) | 24 | logger.debug('Checking remoteVideos parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/sort.js b/server/middlewares/validators/sort.js index 56b63cc8b..431d3fffd 100644 --- a/server/middlewares/validators/sort.js +++ b/server/middlewares/validators/sort.js | |||
@@ -5,7 +5,18 @@ const constants = require('../../initializers/constants') | |||
5 | const logger = require('../../helpers/logger') | 5 | const logger = require('../../helpers/logger') |
6 | 6 | ||
7 | const validatorsSort = { | 7 | const validatorsSort = { |
8 | videosSort: videosSort | 8 | usersSort, |
9 | videosSort | ||
10 | } | ||
11 | |||
12 | function usersSort (req, res, next) { | ||
13 | const sortableColumns = constants.SORTABLE_COLUMNS.USERS | ||
14 | |||
15 | req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) | ||
16 | |||
17 | logger.debug('Checking sort parameters', { parameters: req.query }) | ||
18 | |||
19 | checkErrors(req, res, next) | ||
9 | } | 20 | } |
10 | 21 | ||
11 | function videosSort (req, res, next) { | 22 | function videosSort (req, res, next) { |
diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js new file mode 100644 index 000000000..d541e9124 --- /dev/null +++ b/server/middlewares/validators/users.js | |||
@@ -0,0 +1,67 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const mongoose = require('mongoose') | ||
4 | |||
5 | const checkErrors = require('./utils').checkErrors | ||
6 | const logger = require('../../helpers/logger') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | const validatorsUsers = { | ||
11 | usersAdd, | ||
12 | usersRemove, | ||
13 | usersUpdate | ||
14 | } | ||
15 | |||
16 | function usersAdd (req, res, next) { | ||
17 | req.checkBody('username', 'Should have a valid username').isUserUsernameValid() | ||
18 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() | ||
19 | |||
20 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) | ||
21 | |||
22 | checkErrors(req, res, function () { | ||
23 | User.loadByUsername(req.body.username, function (err, user) { | ||
24 | if (err) { | ||
25 | logger.error('Error in usersAdd request validator.', { error: err }) | ||
26 | return res.sendStatus(500) | ||
27 | } | ||
28 | |||
29 | if (user) return res.status(409).send('User already exists.') | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }) | ||
34 | } | ||
35 | |||
36 | function usersRemove (req, res, next) { | ||
37 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | ||
38 | |||
39 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) | ||
40 | |||
41 | checkErrors(req, res, function () { | ||
42 | User.loadById(req.params.id, function (err, user) { | ||
43 | if (err) { | ||
44 | logger.error('Error in usersRemove request validator.', { error: err }) | ||
45 | return res.sendStatus(500) | ||
46 | } | ||
47 | |||
48 | if (!user) return res.status(404).send('User not found') | ||
49 | |||
50 | next() | ||
51 | }) | ||
52 | }) | ||
53 | } | ||
54 | |||
55 | function usersUpdate (req, res, next) { | ||
56 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | ||
57 | // Add old password verification | ||
58 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() | ||
59 | |||
60 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) | ||
61 | |||
62 | checkErrors(req, res, next) | ||
63 | } | ||
64 | |||
65 | // --------------------------------------------------------------------------- | ||
66 | |||
67 | module.exports = validatorsUsers | ||
diff --git a/server/middlewares/validators/utils.js b/server/middlewares/validators/utils.js index f6e5b2b38..3741b84c6 100644 --- a/server/middlewares/validators/utils.js +++ b/server/middlewares/validators/utils.js | |||
@@ -5,7 +5,7 @@ const util = require('util') | |||
5 | const logger = require('../../helpers/logger') | 5 | const logger = require('../../helpers/logger') |
6 | 6 | ||
7 | const validatorsUtils = { | 7 | const validatorsUtils = { |
8 | checkErrors: checkErrors | 8 | checkErrors |
9 | } | 9 | } |
10 | 10 | ||
11 | function checkErrors (req, res, next, statusCode) { | 11 | function checkErrors (req, res, next, statusCode) { |
diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 3e2af06fb..76e943e77 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js | |||
@@ -4,20 +4,21 @@ const mongoose = require('mongoose') | |||
4 | 4 | ||
5 | const checkErrors = require('./utils').checkErrors | 5 | const checkErrors = require('./utils').checkErrors |
6 | const constants = require('../../initializers/constants') | 6 | const constants = require('../../initializers/constants') |
7 | const customValidators = require('../../helpers/custom-validators') | 7 | const customVideosValidators = require('../../helpers/custom-validators').videos |
8 | const logger = require('../../helpers/logger') | 8 | const logger = require('../../helpers/logger') |
9 | 9 | ||
10 | const Video = mongoose.model('Video') | 10 | const Video = mongoose.model('Video') |
11 | 11 | ||
12 | const validatorsVideos = { | 12 | const validatorsVideos = { |
13 | videosAdd: videosAdd, | 13 | videosAdd, |
14 | videosGet: videosGet, | 14 | videosGet, |
15 | videosRemove: videosRemove, | 15 | videosRemove, |
16 | videosSearch: videosSearch | 16 | videosSearch |
17 | } | 17 | } |
18 | 18 | ||
19 | function videosAdd (req, res, next) { | 19 | function videosAdd (req, res, next) { |
20 | req.checkFiles('videofile[0].originalname', 'Should have an input video').notEmpty() | 20 | req.checkFiles('videofile[0].originalname', 'Should have an input video').notEmpty() |
21 | // TODO: move to constants and function | ||
21 | req.checkFiles('videofile[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i) | 22 | req.checkFiles('videofile[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i) |
22 | req.checkBody('name', 'Should have a valid name').isVideoNameValid() | 23 | req.checkBody('name', 'Should have a valid name').isVideoNameValid() |
23 | req.checkBody('description', 'Should have a valid description').isVideoDescriptionValid() | 24 | req.checkBody('description', 'Should have a valid description').isVideoDescriptionValid() |
@@ -33,8 +34,8 @@ function videosAdd (req, res, next) { | |||
33 | return res.status(400).send('Cannot retrieve metadata of the file.') | 34 | return res.status(400).send('Cannot retrieve metadata of the file.') |
34 | } | 35 | } |
35 | 36 | ||
36 | if (!customValidators.isVideoDurationValid(duration)) { | 37 | if (!customVideosValidators.isVideoDurationValid(duration)) { |
37 | return res.status(400).send('Duration of the video file is too big (max: ' + constants.VIDEOS_CONSTRAINTS_FIELDS.DURATION.max + 's).') | 38 | return res.status(400).send('Duration of the video file is too big (max: ' + constants.CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') |
38 | } | 39 | } |
39 | 40 | ||
40 | videoFile.duration = duration | 41 | videoFile.duration = duration |
@@ -76,6 +77,7 @@ function videosRemove (req, res, next) { | |||
76 | 77 | ||
77 | if (!video) return res.status(404).send('Video not found') | 78 | if (!video) return res.status(404).send('Video not found') |
78 | else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') | 79 | else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') |
80 | else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user') | ||
79 | 81 | ||
80 | next() | 82 | next() |
81 | }) | 83 | }) |
diff --git a/server/models/application.js b/server/models/application.js new file mode 100644 index 000000000..452ac4283 --- /dev/null +++ b/server/models/application.js | |||
@@ -0,0 +1,31 @@ | |||
1 | const mongoose = require('mongoose') | ||
2 | |||
3 | // --------------------------------------------------------------------------- | ||
4 | |||
5 | const ApplicationSchema = mongoose.Schema({ | ||
6 | mongoSchemaVersion: { | ||
7 | type: Number, | ||
8 | default: 0 | ||
9 | } | ||
10 | }) | ||
11 | |||
12 | ApplicationSchema.statics = { | ||
13 | loadMongoSchemaVersion, | ||
14 | updateMongoSchemaVersion | ||
15 | } | ||
16 | |||
17 | mongoose.model('Application', ApplicationSchema) | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | function loadMongoSchemaVersion (callback) { | ||
22 | return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) { | ||
23 | const version = data ? data.mongoSchemaVersion : 0 | ||
24 | |||
25 | return callback(err, version) | ||
26 | }) | ||
27 | } | ||
28 | |||
29 | function updateMongoSchemaVersion (newVersion, callback) { | ||
30 | return this.update({}, { mongoSchemaVersion: newVersion }, callback) | ||
31 | } | ||
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js index 45834c5a5..a1aefa985 100644 --- a/server/models/oauth-client.js +++ b/server/models/oauth-client.js | |||
@@ -11,9 +11,9 @@ const OAuthClientSchema = mongoose.Schema({ | |||
11 | OAuthClientSchema.path('clientSecret').required(true) | 11 | OAuthClientSchema.path('clientSecret').required(true) |
12 | 12 | ||
13 | OAuthClientSchema.statics = { | 13 | OAuthClientSchema.statics = { |
14 | getByIdAndSecret: getByIdAndSecret, | 14 | getByIdAndSecret, |
15 | list: list, | 15 | list, |
16 | loadFirstClient: loadFirstClient | 16 | loadFirstClient |
17 | } | 17 | } |
18 | 18 | ||
19 | mongoose.model('OAuthClient', OAuthClientSchema) | 19 | mongoose.model('OAuthClient', OAuthClientSchema) |
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js index f6a814c36..5beb47bed 100644 --- a/server/models/oauth-token.js +++ b/server/models/oauth-token.js | |||
@@ -18,9 +18,10 @@ OAuthTokenSchema.path('client').required(true) | |||
18 | OAuthTokenSchema.path('user').required(true) | 18 | OAuthTokenSchema.path('user').required(true) |
19 | 19 | ||
20 | OAuthTokenSchema.statics = { | 20 | OAuthTokenSchema.statics = { |
21 | getByRefreshTokenAndPopulateClient: getByRefreshTokenAndPopulateClient, | 21 | getByRefreshTokenAndPopulateClient, |
22 | getByTokenAndPopulateUser: getByTokenAndPopulateUser, | 22 | getByTokenAndPopulateUser, |
23 | getByRefreshToken: getByRefreshToken | 23 | getByRefreshToken, |
24 | removeByUserId | ||
24 | } | 25 | } |
25 | 26 | ||
26 | mongoose.model('OAuthToken', OAuthTokenSchema) | 27 | mongoose.model('OAuthToken', OAuthTokenSchema) |
@@ -53,3 +54,7 @@ function getByTokenAndPopulateUser (bearerToken) { | |||
53 | function getByRefreshToken (refreshToken) { | 54 | function getByRefreshToken (refreshToken) { |
54 | return this.findOne({ refreshToken: refreshToken }).exec() | 55 | return this.findOne({ refreshToken: refreshToken }).exec() |
55 | } | 56 | } |
57 | |||
58 | function removeByUserId (userId, callback) { | ||
59 | return this.remove({ user: userId }, callback) | ||
60 | } | ||
diff --git a/server/models/pods.js b/server/models/pods.js index bf43d7b25..4020a9603 100644 --- a/server/models/pods.js +++ b/server/models/pods.js | |||
@@ -11,7 +11,11 @@ const constants = require('../initializers/constants') | |||
11 | const PodSchema = mongoose.Schema({ | 11 | const PodSchema = mongoose.Schema({ |
12 | url: String, | 12 | url: String, |
13 | publicKey: String, | 13 | publicKey: String, |
14 | score: { type: Number, max: constants.FRIEND_SCORE.MAX } | 14 | score: { type: Number, max: constants.FRIEND_SCORE.MAX }, |
15 | createdDate: { | ||
16 | type: Date, | ||
17 | default: Date.now | ||
18 | } | ||
15 | }) | 19 | }) |
16 | 20 | ||
17 | // TODO: set options (TLD...) | 21 | // TODO: set options (TLD...) |
@@ -19,16 +23,19 @@ PodSchema.path('url').validate(validator.isURL) | |||
19 | PodSchema.path('publicKey').required(true) | 23 | PodSchema.path('publicKey').required(true) |
20 | PodSchema.path('score').validate(function (value) { return !isNaN(value) }) | 24 | PodSchema.path('score').validate(function (value) { return !isNaN(value) }) |
21 | 25 | ||
26 | PodSchema.methods = { | ||
27 | toFormatedJSON | ||
28 | } | ||
29 | |||
22 | PodSchema.statics = { | 30 | PodSchema.statics = { |
23 | countAll: countAll, | 31 | countAll, |
24 | incrementScores: incrementScores, | 32 | incrementScores, |
25 | list: list, | 33 | list, |
26 | listAllIds: listAllIds, | 34 | listAllIds, |
27 | listOnlyUrls: listOnlyUrls, | 35 | listBadPods, |
28 | listBadPods: listBadPods, | 36 | load, |
29 | load: load, | 37 | loadByUrl, |
30 | loadByUrl: loadByUrl, | 38 | removeAll |
31 | removeAll: removeAll | ||
32 | } | 39 | } |
33 | 40 | ||
34 | PodSchema.pre('save', function (next) { | 41 | PodSchema.pre('save', function (next) { |
@@ -46,6 +53,19 @@ PodSchema.pre('save', function (next) { | |||
46 | 53 | ||
47 | const Pod = mongoose.model('Pod', PodSchema) | 54 | const Pod = mongoose.model('Pod', PodSchema) |
48 | 55 | ||
56 | // ------------------------------ METHODS ------------------------------ | ||
57 | |||
58 | function toFormatedJSON () { | ||
59 | const json = { | ||
60 | id: this._id, | ||
61 | url: this.url, | ||
62 | score: this.score, | ||
63 | createdDate: this.createdDate | ||
64 | } | ||
65 | |||
66 | return json | ||
67 | } | ||
68 | |||
49 | // ------------------------------ Statics ------------------------------ | 69 | // ------------------------------ Statics ------------------------------ |
50 | 70 | ||
51 | function countAll (callback) { | 71 | function countAll (callback) { |
@@ -69,10 +89,6 @@ function listAllIds (callback) { | |||
69 | }) | 89 | }) |
70 | } | 90 | } |
71 | 91 | ||
72 | function listOnlyUrls (callback) { | ||
73 | return this.find({}, { _id: 0, url: 1 }, callback) | ||
74 | } | ||
75 | |||
76 | function listBadPods (callback) { | 92 | function listBadPods (callback) { |
77 | return this.find({ score: 0 }, callback) | 93 | return this.find({ score: 0 }, callback) |
78 | } | 94 | } |
diff --git a/server/models/request.js b/server/models/request.js index 4d521919a..2d1c5af15 100644 --- a/server/models/request.js +++ b/server/models/request.js | |||
@@ -14,19 +14,22 @@ const Pod = mongoose.model('Pod') | |||
14 | const Video = mongoose.model('Video') | 14 | const Video = mongoose.model('Video') |
15 | 15 | ||
16 | let timer = null | 16 | let timer = null |
17 | let lastRequestTimestamp = 0 | ||
17 | 18 | ||
18 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
19 | 20 | ||
20 | const RequestSchema = mongoose.Schema({ | 21 | const RequestSchema = mongoose.Schema({ |
21 | request: mongoose.Schema.Types.Mixed, | 22 | request: mongoose.Schema.Types.Mixed, |
22 | to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'users' } ] | 23 | to: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Pod' } ] |
23 | }) | 24 | }) |
24 | 25 | ||
25 | RequestSchema.statics = { | 26 | RequestSchema.statics = { |
26 | activate, | 27 | activate, |
27 | deactivate, | 28 | deactivate, |
28 | flush, | 29 | flush, |
29 | forceSend | 30 | forceSend, |
31 | list, | ||
32 | remainingMilliSeconds | ||
30 | } | 33 | } |
31 | 34 | ||
32 | RequestSchema.pre('save', function (next) { | 35 | RequestSchema.pre('save', function (next) { |
@@ -53,12 +56,19 @@ mongoose.model('Request', RequestSchema) | |||
53 | 56 | ||
54 | function activate () { | 57 | function activate () { |
55 | logger.info('Requests scheduler activated.') | 58 | logger.info('Requests scheduler activated.') |
56 | timer = setInterval(makeRequests.bind(this), constants.INTERVAL) | 59 | lastRequestTimestamp = Date.now() |
60 | |||
61 | const self = this | ||
62 | timer = setInterval(function () { | ||
63 | lastRequestTimestamp = Date.now() | ||
64 | makeRequests.call(self) | ||
65 | }, constants.REQUESTS_INTERVAL) | ||
57 | } | 66 | } |
58 | 67 | ||
59 | function deactivate () { | 68 | function deactivate () { |
60 | logger.info('Requests scheduler deactivated.') | 69 | logger.info('Requests scheduler deactivated.') |
61 | clearInterval(timer) | 70 | clearInterval(timer) |
71 | timer = null | ||
62 | } | 72 | } |
63 | 73 | ||
64 | function flush () { | 74 | function flush () { |
@@ -72,6 +82,16 @@ function forceSend () { | |||
72 | makeRequests.call(this) | 82 | makeRequests.call(this) |
73 | } | 83 | } |
74 | 84 | ||
85 | function list (callback) { | ||
86 | this.find({ }, callback) | ||
87 | } | ||
88 | |||
89 | function remainingMilliSeconds () { | ||
90 | if (timer === null) return -1 | ||
91 | |||
92 | return constants.REQUESTS_INTERVAL - (Date.now() - lastRequestTimestamp) | ||
93 | } | ||
94 | |||
75 | // --------------------------------------------------------------------------- | 95 | // --------------------------------------------------------------------------- |
76 | 96 | ||
77 | // Make a requests to friends of a certain type | 97 | // Make a requests to friends of a certain type |
@@ -91,7 +111,13 @@ function makeRequest (toPod, requestsToMake, callback) { | |||
91 | // The function fire some useful callbacks | 111 | // The function fire some useful callbacks |
92 | requests.makeSecureRequest(params, function (err, res) { | 112 | requests.makeSecureRequest(params, function (err, res) { |
93 | if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) { | 113 | if (err || (res.statusCode !== 200 && res.statusCode !== 201 && res.statusCode !== 204)) { |
94 | logger.error('Error sending secure request to %s pod.', toPod.url, { error: err || new Error('Status code not 20x') }) | 114 | logger.error( |
115 | 'Error sending secure request to %s pod.', | ||
116 | toPod.url, | ||
117 | { | ||
118 | error: err || new Error('Status code not 20x : ' + res.statusCode) | ||
119 | } | ||
120 | ) | ||
95 | 121 | ||
96 | return callback(false) | 122 | return callback(false) |
97 | } | 123 | } |
@@ -148,19 +174,14 @@ function makeRequests () { | |||
148 | return callbackEach() | 174 | return callbackEach() |
149 | } | 175 | } |
150 | 176 | ||
151 | // Maybe the pod is not our friend anymore so simply remove them | 177 | // Maybe the pod is not our friend anymore so simply remove it |
152 | if (!toPod) { | 178 | if (!toPod) { |
179 | logger.info('Removing %d requests of unexisting pod %s.', requestToMake.ids.length, toPodId) | ||
153 | removePodOf.call(self, requestToMake.ids, toPodId) | 180 | removePodOf.call(self, requestToMake.ids, toPodId) |
154 | return callbackEach() | 181 | return callbackEach() |
155 | } | 182 | } |
156 | 183 | ||
157 | makeRequest(toPod, requestToMake.datas, function (success) { | 184 | makeRequest(toPod, requestToMake.datas, function (success) { |
158 | if (err) { | ||
159 | logger.error('Errors when sent request to %s.', toPod.url, { error: err }) | ||
160 | // Do not stop the process just for one error | ||
161 | return callbackEach() | ||
162 | } | ||
163 | |||
164 | if (success === true) { | 185 | if (success === true) { |
165 | logger.debug('Removing requests for %s pod.', toPodId, { requestsIds: requestToMake.ids }) | 186 | logger.debug('Removing requests for %s pod.', toPodId, { requestsIds: requestToMake.ids }) |
166 | 187 | ||
diff --git a/server/models/user.js b/server/models/user.js index 14ffecbff..a19de7072 100644 --- a/server/models/user.js +++ b/server/models/user.js | |||
@@ -1,28 +1,98 @@ | |||
1 | const mongoose = require('mongoose') | 1 | const mongoose = require('mongoose') |
2 | 2 | ||
3 | const customUsersValidators = require('../helpers/custom-validators').users | ||
4 | const modelUtils = require('./utils') | ||
5 | const peertubeCrypto = require('../helpers/peertube-crypto') | ||
6 | |||
7 | const OAuthToken = mongoose.model('OAuthToken') | ||
8 | |||
3 | // --------------------------------------------------------------------------- | 9 | // --------------------------------------------------------------------------- |
4 | 10 | ||
5 | const UserSchema = mongoose.Schema({ | 11 | const UserSchema = mongoose.Schema({ |
12 | createdDate: { | ||
13 | type: Date, | ||
14 | default: Date.now | ||
15 | }, | ||
6 | password: String, | 16 | password: String, |
7 | username: String | 17 | username: String, |
18 | role: String | ||
8 | }) | 19 | }) |
9 | 20 | ||
10 | UserSchema.path('password').required(true) | 21 | UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) |
11 | UserSchema.path('username').required(true) | 22 | UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) |
23 | UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) | ||
24 | |||
25 | UserSchema.methods = { | ||
26 | isPasswordMatch, | ||
27 | toFormatedJSON | ||
28 | } | ||
12 | 29 | ||
13 | UserSchema.statics = { | 30 | UserSchema.statics = { |
14 | getByUsernameAndPassword: getByUsernameAndPassword, | 31 | countTotal, |
15 | list: list | 32 | getByUsername, |
33 | list, | ||
34 | listForApi, | ||
35 | loadById, | ||
36 | loadByUsername | ||
16 | } | 37 | } |
17 | 38 | ||
39 | UserSchema.pre('save', function (next) { | ||
40 | const user = this | ||
41 | |||
42 | peertubeCrypto.cryptPassword(this.password, function (err, hash) { | ||
43 | if (err) return next(err) | ||
44 | |||
45 | user.password = hash | ||
46 | |||
47 | return next() | ||
48 | }) | ||
49 | }) | ||
50 | |||
51 | UserSchema.pre('remove', function (next) { | ||
52 | const user = this | ||
53 | |||
54 | OAuthToken.removeByUserId(user._id, next) | ||
55 | }) | ||
56 | |||
18 | mongoose.model('User', UserSchema) | 57 | mongoose.model('User', UserSchema) |
19 | 58 | ||
20 | // --------------------------------------------------------------------------- | 59 | // ------------------------------ METHODS ------------------------------ |
60 | |||
61 | function isPasswordMatch (password, callback) { | ||
62 | return peertubeCrypto.comparePassword(password, this.password, callback) | ||
63 | } | ||
64 | |||
65 | function toFormatedJSON () { | ||
66 | return { | ||
67 | id: this._id, | ||
68 | username: this.username, | ||
69 | role: this.role, | ||
70 | createdDate: this.createdDate | ||
71 | } | ||
72 | } | ||
73 | // ------------------------------ STATICS ------------------------------ | ||
74 | |||
75 | function countTotal (callback) { | ||
76 | return this.count(callback) | ||
77 | } | ||
78 | |||
79 | function getByUsername (username) { | ||
80 | return this.findOne({ username: username }) | ||
81 | } | ||
21 | 82 | ||
22 | function list (callback) { | 83 | function list (callback) { |
23 | return this.find(callback) | 84 | return this.find(callback) |
24 | } | 85 | } |
25 | 86 | ||
26 | function getByUsernameAndPassword (username, password) { | 87 | function listForApi (start, count, sort, callback) { |
27 | return this.findOne({ username: username, password: password }) | 88 | const query = {} |
89 | return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) | ||
90 | } | ||
91 | |||
92 | function loadById (id, callback) { | ||
93 | return this.findById(id, callback) | ||
94 | } | ||
95 | |||
96 | function loadByUsername (username, callback) { | ||
97 | return this.findOne({ username: username }, callback) | ||
28 | } | 98 | } |
diff --git a/server/models/utils.js b/server/models/utils.js new file mode 100644 index 000000000..e798aabe6 --- /dev/null +++ b/server/models/utils.js | |||
@@ -0,0 +1,30 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const parallel = require('async/parallel') | ||
4 | |||
5 | const utils = { | ||
6 | listForApiWithCount | ||
7 | } | ||
8 | |||
9 | function listForApiWithCount (query, start, count, sort, callback) { | ||
10 | const self = this | ||
11 | |||
12 | parallel([ | ||
13 | function (asyncCallback) { | ||
14 | self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) | ||
15 | }, | ||
16 | function (asyncCallback) { | ||
17 | self.count(query, asyncCallback) | ||
18 | } | ||
19 | ], function (err, results) { | ||
20 | if (err) return callback(err) | ||
21 | |||
22 | const data = results[0] | ||
23 | const total = results[1] | ||
24 | return callback(null, data, total) | ||
25 | }) | ||
26 | } | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | module.exports = utils | ||
diff --git a/server/models/video.js b/server/models/video.js index 14e0df6f2..7d073cffa 100644 --- a/server/models/video.js +++ b/server/models/video.js | |||
@@ -11,8 +11,9 @@ const magnet = require('magnet-uri') | |||
11 | const mongoose = require('mongoose') | 11 | const mongoose = require('mongoose') |
12 | 12 | ||
13 | const constants = require('../initializers/constants') | 13 | const constants = require('../initializers/constants') |
14 | const customValidators = require('../helpers/custom-validators') | 14 | const customVideosValidators = require('../helpers/custom-validators').videos |
15 | const logger = require('../helpers/logger') | 15 | const logger = require('../helpers/logger') |
16 | const modelUtils = require('./utils') | ||
16 | const utils = require('../helpers/utils') | 17 | const utils = require('../helpers/utils') |
17 | 18 | ||
18 | const http = config.get('webserver.https') === true ? 'https' : 'http' | 19 | const http = config.get('webserver.https') === true ? 'https' : 'http' |
@@ -42,34 +43,35 @@ const VideoSchema = mongoose.Schema({ | |||
42 | } | 43 | } |
43 | }) | 44 | }) |
44 | 45 | ||
45 | VideoSchema.path('name').validate(customValidators.isVideoNameValid) | 46 | VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) |
46 | VideoSchema.path('description').validate(customValidators.isVideoDescriptionValid) | 47 | VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) |
47 | VideoSchema.path('magnetUri').validate(customValidators.isVideoMagnetUriValid) | 48 | VideoSchema.path('magnetUri').validate(customVideosValidators.isVideoMagnetUriValid) |
48 | VideoSchema.path('podUrl').validate(customValidators.isVideoPodUrlValid) | 49 | VideoSchema.path('podUrl').validate(customVideosValidators.isVideoPodUrlValid) |
49 | VideoSchema.path('author').validate(customValidators.isVideoAuthorValid) | 50 | VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) |
50 | VideoSchema.path('duration').validate(customValidators.isVideoDurationValid) | 51 | VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) |
51 | // The tumbnail can be the path or the data in base 64 | 52 | // The tumbnail can be the path or the data in base 64 |
52 | // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename | 53 | // The pre save hook will convert the base 64 data in a file on disk and replace the thumbnail key by the filename |
53 | VideoSchema.path('thumbnail').validate(function (value) { | 54 | VideoSchema.path('thumbnail').validate(function (value) { |
54 | return customValidators.isVideoThumbnailValid(value) || customValidators.isVideoThumbnail64Valid(value) | 55 | return customVideosValidators.isVideoThumbnailValid(value) || customVideosValidators.isVideoThumbnail64Valid(value) |
55 | }) | 56 | }) |
56 | VideoSchema.path('tags').validate(customValidators.isVideoTagsValid) | 57 | VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) |
57 | 58 | ||
58 | VideoSchema.methods = { | 59 | VideoSchema.methods = { |
59 | isOwned: isOwned, | 60 | isOwned, |
60 | toFormatedJSON: toFormatedJSON, | 61 | toFormatedJSON, |
61 | toRemoteJSON: toRemoteJSON | 62 | toRemoteJSON |
62 | } | 63 | } |
63 | 64 | ||
64 | VideoSchema.statics = { | 65 | VideoSchema.statics = { |
65 | getDurationFromFile: getDurationFromFile, | 66 | getDurationFromFile, |
66 | list: list, | 67 | listForApi, |
67 | listByUrlAndMagnet: listByUrlAndMagnet, | 68 | listByUrlAndMagnet, |
68 | listByUrls: listByUrls, | 69 | listByUrls, |
69 | listOwned: listOwned, | 70 | listOwned, |
70 | listRemotes: listRemotes, | 71 | listOwnedByAuthor, |
71 | load: load, | 72 | listRemotes, |
72 | search: search | 73 | load, |
74 | search | ||
73 | } | 75 | } |
74 | 76 | ||
75 | VideoSchema.pre('remove', function (next) { | 77 | VideoSchema.pre('remove', function (next) { |
@@ -101,8 +103,8 @@ VideoSchema.pre('save', function (next) { | |||
101 | const tasks = [] | 103 | const tasks = [] |
102 | 104 | ||
103 | if (video.isOwned()) { | 105 | if (video.isOwned()) { |
104 | const videoPath = pathUtils.join(uploadsDir, video.filename) | 106 | const videoPath = pathUtils.join(constants.CONFIG.STORAGE.UPLOAD_DIR, video.filename) |
105 | this.podUrl = http + '://' + host + ':' + port | 107 | this.podUrl = constants.CONFIG.WEBSERVER.URL |
106 | 108 | ||
107 | tasks.push( | 109 | tasks.push( |
108 | // TODO: refractoring | 110 | // TODO: refractoring |
@@ -174,7 +176,7 @@ function toRemoteJSON (callback) { | |||
174 | const self = this | 176 | const self = this |
175 | 177 | ||
176 | // Convert thumbnail to base64 | 178 | // Convert thumbnail to base64 |
177 | fs.readFile(pathUtils.join(thumbnailsDir, this.thumbnail), function (err, thumbnailData) { | 179 | fs.readFile(pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.thumbnail), function (err, thumbnailData) { |
178 | if (err) { | 180 | if (err) { |
179 | logger.error('Cannot read the thumbnail of the video') | 181 | logger.error('Cannot read the thumbnail of the video') |
180 | return callback(err) | 182 | return callback(err) |
@@ -207,9 +209,9 @@ function getDurationFromFile (videoPath, callback) { | |||
207 | }) | 209 | }) |
208 | } | 210 | } |
209 | 211 | ||
210 | function list (start, count, sort, callback) { | 212 | function listForApi (start, count, sort, callback) { |
211 | const query = {} | 213 | const query = {} |
212 | return findWithCount.call(this, query, start, count, sort, callback) | 214 | return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) |
213 | } | 215 | } |
214 | 216 | ||
215 | function listByUrlAndMagnet (fromUrl, magnetUri, callback) { | 217 | function listByUrlAndMagnet (fromUrl, magnetUri, callback) { |
@@ -225,6 +227,10 @@ function listOwned (callback) { | |||
225 | this.find({ filename: { $ne: null } }, callback) | 227 | this.find({ filename: { $ne: null } }, callback) |
226 | } | 228 | } |
227 | 229 | ||
230 | function listOwnedByAuthor (author, callback) { | ||
231 | this.find({ filename: { $ne: null }, author: author }, callback) | ||
232 | } | ||
233 | |||
228 | function listRemotes (callback) { | 234 | function listRemotes (callback) { |
229 | this.find({ filename: null }, callback) | 235 | this.find({ filename: null }, callback) |
230 | } | 236 | } |
@@ -242,36 +248,17 @@ function search (value, field, start, count, sort, callback) { | |||
242 | query[field] = new RegExp(value) | 248 | query[field] = new RegExp(value) |
243 | } | 249 | } |
244 | 250 | ||
245 | findWithCount.call(this, query, start, count, sort, callback) | 251 | modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) |
246 | } | 252 | } |
247 | 253 | ||
248 | // --------------------------------------------------------------------------- | 254 | // --------------------------------------------------------------------------- |
249 | 255 | ||
250 | function findWithCount (query, start, count, sort, callback) { | ||
251 | const self = this | ||
252 | |||
253 | parallel([ | ||
254 | function (asyncCallback) { | ||
255 | self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) | ||
256 | }, | ||
257 | function (asyncCallback) { | ||
258 | self.count(query, asyncCallback) | ||
259 | } | ||
260 | ], function (err, results) { | ||
261 | if (err) return callback(err) | ||
262 | |||
263 | const videos = results[0] | ||
264 | const totalVideos = results[1] | ||
265 | return callback(null, videos, totalVideos) | ||
266 | }) | ||
267 | } | ||
268 | |||
269 | function removeThumbnail (video, callback) { | 256 | function removeThumbnail (video, callback) { |
270 | fs.unlink(thumbnailsDir + video.thumbnail, callback) | 257 | fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.thumbnail, callback) |
271 | } | 258 | } |
272 | 259 | ||
273 | function removeFile (video, callback) { | 260 | function removeFile (video, callback) { |
274 | fs.unlink(uploadsDir + video.filename, callback) | 261 | fs.unlink(constants.CONFIG.STORAGE.UPLOAD_DIR + video.filename, callback) |
275 | } | 262 | } |
276 | 263 | ||
277 | // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process | 264 | // Maybe the torrent is not seeded, but we catch the error to don't stop the removing process |
@@ -288,7 +275,7 @@ function createThumbnail (videoPath, callback) { | |||
288 | }) | 275 | }) |
289 | .thumbnail({ | 276 | .thumbnail({ |
290 | count: 1, | 277 | count: 1, |
291 | folder: thumbnailsDir, | 278 | folder: constants.CONFIG.STORAGE.THUMBNAILS_DIR, |
292 | size: constants.THUMBNAILS_SIZE, | 279 | size: constants.THUMBNAILS_SIZE, |
293 | filename: filename | 280 | filename: filename |
294 | }) | 281 | }) |
@@ -300,7 +287,7 @@ function generateThumbnailFromBase64 (data, callback) { | |||
300 | if (err) return callback(err) | 287 | if (err) return callback(err) |
301 | 288 | ||
302 | const thumbnailName = randomString + '.jpg' | 289 | const thumbnailName = randomString + '.jpg' |
303 | fs.writeFile(thumbnailsDir + thumbnailName, data, { encoding: 'base64' }, function (err) { | 290 | fs.writeFile(constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName, data, { encoding: 'base64' }, function (err) { |
304 | if (err) return callback(err) | 291 | if (err) return callback(err) |
305 | 292 | ||
306 | return callback(null, thumbnailName) | 293 | return callback(null, thumbnailName) |
diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js new file mode 100644 index 000000000..57b5ca024 --- /dev/null +++ b/server/tests/api/check-params.js | |||
@@ -0,0 +1,746 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const loginUtils = require('../utils/login') | ||
10 | const requestsUtils = require('../utils/requests') | ||
11 | const serversUtils = require('../utils/servers') | ||
12 | const usersUtils = require('../utils/users') | ||
13 | |||
14 | describe('Test parameters validator', function () { | ||
15 | let server = null | ||
16 | let userAccessToken = null | ||
17 | |||
18 | // --------------------------------------------------------------- | ||
19 | |||
20 | before(function (done) { | ||
21 | this.timeout(20000) | ||
22 | |||
23 | series([ | ||
24 | function (next) { | ||
25 | serversUtils.flushTests(next) | ||
26 | }, | ||
27 | function (next) { | ||
28 | serversUtils.runServer(1, function (server1) { | ||
29 | server = server1 | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }, | ||
34 | function (next) { | ||
35 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
36 | if (err) throw err | ||
37 | server.accessToken = token | ||
38 | |||
39 | next() | ||
40 | }) | ||
41 | } | ||
42 | ], done) | ||
43 | }) | ||
44 | |||
45 | describe('Of the pods API', function () { | ||
46 | const path = '/api/v1/pods/' | ||
47 | |||
48 | describe('When making friends', function () { | ||
49 | let userAccessToken = null | ||
50 | |||
51 | before(function (done) { | ||
52 | usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { | ||
53 | server.user = { | ||
54 | username: 'user1', | ||
55 | password: 'password' | ||
56 | } | ||
57 | |||
58 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
59 | if (err) throw err | ||
60 | |||
61 | userAccessToken = accessToken | ||
62 | |||
63 | done() | ||
64 | }) | ||
65 | }) | ||
66 | }) | ||
67 | |||
68 | describe('When making friends', function () { | ||
69 | const body = { | ||
70 | urls: [ 'http://localhost:9002' ] | ||
71 | } | ||
72 | |||
73 | it('Should fail without urls', function (done) { | ||
74 | request(server.url) | ||
75 | .post(path + '/makefriends') | ||
76 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
77 | .set('Accept', 'application/json') | ||
78 | .expect(400, done) | ||
79 | }) | ||
80 | |||
81 | it('Should fail with urls is not an array', function (done) { | ||
82 | request(server.url) | ||
83 | .post(path + '/makefriends') | ||
84 | .send({ urls: 'http://localhost:9002' }) | ||
85 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
86 | .set('Accept', 'application/json') | ||
87 | .expect(400, done) | ||
88 | }) | ||
89 | |||
90 | it('Should fail if the array is not composed by urls', function (done) { | ||
91 | request(server.url) | ||
92 | .post(path + '/makefriends') | ||
93 | .send({ urls: [ 'http://localhost:9002', 'localhost:coucou' ] }) | ||
94 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
95 | .set('Accept', 'application/json') | ||
96 | .expect(400, done) | ||
97 | }) | ||
98 | |||
99 | it('Should fail if urls are not unique', function (done) { | ||
100 | request(server.url) | ||
101 | .post(path + '/makefriends') | ||
102 | .send({ urls: [ 'http://localhost:9002', 'http://localhost:9002' ] }) | ||
103 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
104 | .set('Accept', 'application/json') | ||
105 | .expect(400, done) | ||
106 | }) | ||
107 | |||
108 | it('Should fail with a invalid token', function (done) { | ||
109 | request(server.url) | ||
110 | .post(path + '/makefriends') | ||
111 | .send(body) | ||
112 | .set('Authorization', 'Bearer faketoken') | ||
113 | .set('Accept', 'application/json') | ||
114 | .expect(401, done) | ||
115 | }) | ||
116 | |||
117 | it('Should fail if the user is not an administrator', function (done) { | ||
118 | request(server.url) | ||
119 | .post(path + '/makefriends') | ||
120 | .send(body) | ||
121 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
122 | .set('Accept', 'application/json') | ||
123 | .expect(403, done) | ||
124 | }) | ||
125 | }) | ||
126 | |||
127 | describe('When quitting friends', function () { | ||
128 | it('Should fail with a invalid token', function (done) { | ||
129 | request(server.url) | ||
130 | .get(path + '/quitfriends') | ||
131 | .query({ start: 'hello' }) | ||
132 | .set('Authorization', 'Bearer faketoken') | ||
133 | .set('Accept', 'application/json') | ||
134 | .expect(401, done) | ||
135 | }) | ||
136 | |||
137 | it('Should fail if the user is not an administrator', function (done) { | ||
138 | request(server.url) | ||
139 | .get(path + '/quitfriends') | ||
140 | .query({ start: 'hello' }) | ||
141 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
142 | .set('Accept', 'application/json') | ||
143 | .expect(403, done) | ||
144 | }) | ||
145 | }) | ||
146 | }) | ||
147 | |||
148 | describe('When adding a pod', function () { | ||
149 | it('Should fail with nothing', function (done) { | ||
150 | const data = {} | ||
151 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
152 | }) | ||
153 | |||
154 | it('Should fail without public key', function (done) { | ||
155 | const data = { | ||
156 | url: 'http://coucou.com' | ||
157 | } | ||
158 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
159 | }) | ||
160 | |||
161 | it('Should fail without an url', function (done) { | ||
162 | const data = { | ||
163 | publicKey: 'mysuperpublickey' | ||
164 | } | ||
165 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
166 | }) | ||
167 | |||
168 | it('Should fail with an incorrect url', function (done) { | ||
169 | const data = { | ||
170 | url: 'coucou.com', | ||
171 | publicKey: 'mysuperpublickey' | ||
172 | } | ||
173 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
174 | data.url = 'http://coucou' | ||
175 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
176 | data.url = 'coucou' | ||
177 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
178 | }) | ||
179 | }) | ||
180 | }) | ||
181 | |||
182 | it('Should succeed with the correct parameters', function (done) { | ||
183 | const data = { | ||
184 | url: 'http://coucou.com', | ||
185 | publicKey: 'mysuperpublickey' | ||
186 | } | ||
187 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200) | ||
188 | }) | ||
189 | }) | ||
190 | }) | ||
191 | |||
192 | describe('Of the videos API', function () { | ||
193 | const path = '/api/v1/videos/' | ||
194 | |||
195 | describe('When listing a video', function () { | ||
196 | it('Should fail with a bad start pagination', function (done) { | ||
197 | request(server.url) | ||
198 | .get(path) | ||
199 | .query({ start: 'hello' }) | ||
200 | .set('Accept', 'application/json') | ||
201 | .expect(400, done) | ||
202 | }) | ||
203 | |||
204 | it('Should fail with a bad count pagination', function (done) { | ||
205 | request(server.url) | ||
206 | .get(path) | ||
207 | .query({ count: 'hello' }) | ||
208 | .set('Accept', 'application/json') | ||
209 | .expect(400, done) | ||
210 | }) | ||
211 | |||
212 | it('Should fail with an incorrect sort', function (done) { | ||
213 | request(server.url) | ||
214 | .get(path) | ||
215 | .query({ sort: 'hello' }) | ||
216 | .set('Accept', 'application/json') | ||
217 | .expect(400, done) | ||
218 | }) | ||
219 | }) | ||
220 | |||
221 | describe('When searching a video', function () { | ||
222 | it('Should fail with nothing', function (done) { | ||
223 | request(server.url) | ||
224 | .get(pathUtils.join(path, 'search')) | ||
225 | .set('Accept', 'application/json') | ||
226 | .expect(400, done) | ||
227 | }) | ||
228 | |||
229 | it('Should fail with a bad start pagination', function (done) { | ||
230 | request(server.url) | ||
231 | .get(pathUtils.join(path, 'search', 'test')) | ||
232 | .query({ start: 'hello' }) | ||
233 | .set('Accept', 'application/json') | ||
234 | .expect(400, done) | ||
235 | }) | ||
236 | |||
237 | it('Should fail with a bad count pagination', function (done) { | ||
238 | request(server.url) | ||
239 | .get(pathUtils.join(path, 'search', 'test')) | ||
240 | .query({ count: 'hello' }) | ||
241 | .set('Accept', 'application/json') | ||
242 | .expect(400, done) | ||
243 | }) | ||
244 | |||
245 | it('Should fail with an incorrect sort', function (done) { | ||
246 | request(server.url) | ||
247 | .get(pathUtils.join(path, 'search', 'test')) | ||
248 | .query({ sort: 'hello' }) | ||
249 | .set('Accept', 'application/json') | ||
250 | .expect(400, done) | ||
251 | }) | ||
252 | }) | ||
253 | |||
254 | describe('When adding a video', function () { | ||
255 | it('Should fail with nothing', function (done) { | ||
256 | const data = {} | ||
257 | const attach = {} | ||
258 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
259 | }) | ||
260 | |||
261 | it('Should fail without name', function (done) { | ||
262 | const data = { | ||
263 | description: 'my super description', | ||
264 | tags: [ 'tag1', 'tag2' ] | ||
265 | } | ||
266 | const attach = { | ||
267 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
268 | } | ||
269 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
270 | }) | ||
271 | |||
272 | it('Should fail with a long name', function (done) { | ||
273 | const data = { | ||
274 | name: 'My very very very very very very very very very very very very very very very very long name', | ||
275 | description: 'my super description', | ||
276 | tags: [ 'tag1', 'tag2' ] | ||
277 | } | ||
278 | const attach = { | ||
279 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
280 | } | ||
281 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
282 | }) | ||
283 | |||
284 | it('Should fail without description', function (done) { | ||
285 | const data = { | ||
286 | name: 'my super name', | ||
287 | tags: [ 'tag1', 'tag2' ] | ||
288 | } | ||
289 | const attach = { | ||
290 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
291 | } | ||
292 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
293 | }) | ||
294 | |||
295 | it('Should fail with a long description', function (done) { | ||
296 | const data = { | ||
297 | name: 'my super name', | ||
298 | description: 'my super description which is very very very very very very very very very very very very very very' + | ||
299 | 'very very very very very very very very very very very very very very very very very very very very very' + | ||
300 | 'very very very very very very very very very very very very very very very long', | ||
301 | tags: [ 'tag1', 'tag2' ] | ||
302 | } | ||
303 | const attach = { | ||
304 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
305 | } | ||
306 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
307 | }) | ||
308 | |||
309 | it('Should fail without tags', function (done) { | ||
310 | const data = { | ||
311 | name: 'my super name', | ||
312 | description: 'my super description' | ||
313 | } | ||
314 | const attach = { | ||
315 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
316 | } | ||
317 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
318 | }) | ||
319 | |||
320 | it('Should fail with too many tags', function (done) { | ||
321 | const data = { | ||
322 | name: 'my super name', | ||
323 | description: 'my super description', | ||
324 | tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ] | ||
325 | } | ||
326 | const attach = { | ||
327 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
328 | } | ||
329 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
330 | }) | ||
331 | |||
332 | it('Should fail with not enough tags', function (done) { | ||
333 | const data = { | ||
334 | name: 'my super name', | ||
335 | description: 'my super description', | ||
336 | tags: [ ] | ||
337 | } | ||
338 | const attach = { | ||
339 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
340 | } | ||
341 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
342 | }) | ||
343 | |||
344 | it('Should fail with a tag length too low', function (done) { | ||
345 | const data = { | ||
346 | name: 'my super name', | ||
347 | description: 'my super description', | ||
348 | tags: [ 'tag1', 't' ] | ||
349 | } | ||
350 | const attach = { | ||
351 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
352 | } | ||
353 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
354 | }) | ||
355 | |||
356 | it('Should fail with a tag length too big', function (done) { | ||
357 | const data = { | ||
358 | name: 'my super name', | ||
359 | description: 'my super description', | ||
360 | tags: [ 'mysupertagtoolong', 'tag1' ] | ||
361 | } | ||
362 | const attach = { | ||
363 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
364 | } | ||
365 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
366 | }) | ||
367 | |||
368 | it('Should fail with malformed tags', function (done) { | ||
369 | const data = { | ||
370 | name: 'my super name', | ||
371 | description: 'my super description', | ||
372 | tags: [ 'my tag' ] | ||
373 | } | ||
374 | const attach = { | ||
375 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
376 | } | ||
377 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
378 | }) | ||
379 | |||
380 | it('Should fail without an input file', function (done) { | ||
381 | const data = { | ||
382 | name: 'my super name', | ||
383 | description: 'my super description', | ||
384 | tags: [ 'tag1', 'tag2' ] | ||
385 | } | ||
386 | const attach = {} | ||
387 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
388 | }) | ||
389 | |||
390 | it('Should fail without an incorrect input file', function (done) { | ||
391 | const data = { | ||
392 | name: 'my super name', | ||
393 | description: 'my super description', | ||
394 | tags: [ 'tag1', 'tag2' ] | ||
395 | } | ||
396 | const attach = { | ||
397 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm') | ||
398 | } | ||
399 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
400 | }) | ||
401 | |||
402 | it('Should fail with a too big duration', function (done) { | ||
403 | const data = { | ||
404 | name: 'my super name', | ||
405 | description: 'my super description', | ||
406 | tags: [ 'tag1', 'tag2' ] | ||
407 | } | ||
408 | const attach = { | ||
409 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm') | ||
410 | } | ||
411 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
412 | }) | ||
413 | |||
414 | it('Should succeed with the correct parameters', function (done) { | ||
415 | const data = { | ||
416 | name: 'my super name', | ||
417 | description: 'my super description', | ||
418 | tags: [ 'tag1', 'tag2' ] | ||
419 | } | ||
420 | const attach = { | ||
421 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
422 | } | ||
423 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
424 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') | ||
425 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
426 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') | ||
427 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204) | ||
428 | }, false) | ||
429 | }, false) | ||
430 | }) | ||
431 | }) | ||
432 | |||
433 | describe('When getting a video', function () { | ||
434 | it('Should return the list of the videos with nothing', function (done) { | ||
435 | request(server.url) | ||
436 | .get(path) | ||
437 | .set('Accept', 'application/json') | ||
438 | .expect(200) | ||
439 | .expect('Content-Type', /json/) | ||
440 | .end(function (err, res) { | ||
441 | if (err) throw err | ||
442 | |||
443 | expect(res.body.data).to.be.an('array') | ||
444 | expect(res.body.data.length).to.equal(3) | ||
445 | |||
446 | done() | ||
447 | }) | ||
448 | }) | ||
449 | |||
450 | it('Should fail without a mongodb id', function (done) { | ||
451 | request(server.url) | ||
452 | .get(path + 'coucou') | ||
453 | .set('Accept', 'application/json') | ||
454 | .expect(400, done) | ||
455 | }) | ||
456 | |||
457 | it('Should return 404 with an incorrect video', function (done) { | ||
458 | request(server.url) | ||
459 | .get(path + '123456789012345678901234') | ||
460 | .set('Accept', 'application/json') | ||
461 | .expect(404, done) | ||
462 | }) | ||
463 | |||
464 | it('Should succeed with the correct parameters') | ||
465 | }) | ||
466 | |||
467 | describe('When removing a video', function () { | ||
468 | it('Should have 404 with nothing', function (done) { | ||
469 | request(server.url) | ||
470 | .delete(path) | ||
471 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
472 | .expect(400, done) | ||
473 | }) | ||
474 | |||
475 | it('Should fail without a mongodb id', function (done) { | ||
476 | request(server.url) | ||
477 | .delete(path + 'hello') | ||
478 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
479 | .expect(400, done) | ||
480 | }) | ||
481 | |||
482 | it('Should fail with a video which does not exist', function (done) { | ||
483 | request(server.url) | ||
484 | .delete(path + '123456789012345678901234') | ||
485 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
486 | .expect(404, done) | ||
487 | }) | ||
488 | |||
489 | it('Should fail with a video of another user') | ||
490 | |||
491 | it('Should fail with a video of another pod') | ||
492 | |||
493 | it('Should succeed with the correct parameters') | ||
494 | }) | ||
495 | }) | ||
496 | |||
497 | describe('Of the users API', function () { | ||
498 | const path = '/api/v1/users/' | ||
499 | let userId = null | ||
500 | |||
501 | describe('When listing users', function () { | ||
502 | it('Should fail with a bad start pagination', function (done) { | ||
503 | request(server.url) | ||
504 | .get(path) | ||
505 | .query({ start: 'hello' }) | ||
506 | .set('Accept', 'application/json') | ||
507 | .expect(400, done) | ||
508 | }) | ||
509 | |||
510 | it('Should fail with a bad count pagination', function (done) { | ||
511 | request(server.url) | ||
512 | .get(path) | ||
513 | .query({ count: 'hello' }) | ||
514 | .set('Accept', 'application/json') | ||
515 | .expect(400, done) | ||
516 | }) | ||
517 | |||
518 | it('Should fail with an incorrect sort', function (done) { | ||
519 | request(server.url) | ||
520 | .get(path) | ||
521 | .query({ sort: 'hello' }) | ||
522 | .set('Accept', 'application/json') | ||
523 | .expect(400, done) | ||
524 | }) | ||
525 | }) | ||
526 | |||
527 | describe('When adding a new user', function () { | ||
528 | it('Should fail with a too small username', function (done) { | ||
529 | const data = { | ||
530 | username: 'ji', | ||
531 | password: 'mysuperpassword' | ||
532 | } | ||
533 | |||
534 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
535 | }) | ||
536 | |||
537 | it('Should fail with a too long username', function (done) { | ||
538 | const data = { | ||
539 | username: 'mysuperusernamewhichisverylong', | ||
540 | password: 'mysuperpassword' | ||
541 | } | ||
542 | |||
543 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
544 | }) | ||
545 | |||
546 | it('Should fail with an incorrect username', function (done) { | ||
547 | const data = { | ||
548 | username: 'my username', | ||
549 | password: 'mysuperpassword' | ||
550 | } | ||
551 | |||
552 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
553 | }) | ||
554 | |||
555 | it('Should fail with a too small password', function (done) { | ||
556 | const data = { | ||
557 | username: 'myusername', | ||
558 | password: 'bla' | ||
559 | } | ||
560 | |||
561 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
562 | }) | ||
563 | |||
564 | it('Should fail with a too long password', function (done) { | ||
565 | const data = { | ||
566 | username: 'myusername', | ||
567 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
568 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
569 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
570 | } | ||
571 | |||
572 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
573 | }) | ||
574 | |||
575 | it('Should fail with an non authenticated user', function (done) { | ||
576 | const data = { | ||
577 | username: 'myusername', | ||
578 | password: 'my super password' | ||
579 | } | ||
580 | |||
581 | requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401) | ||
582 | }) | ||
583 | |||
584 | it('Should fail if we add a user with the same username', function (done) { | ||
585 | const data = { | ||
586 | username: 'user1', | ||
587 | password: 'my super password' | ||
588 | } | ||
589 | |||
590 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409) | ||
591 | }) | ||
592 | |||
593 | it('Should succeed with the correct params', function (done) { | ||
594 | const data = { | ||
595 | username: 'user2', | ||
596 | password: 'my super password' | ||
597 | } | ||
598 | |||
599 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204) | ||
600 | }) | ||
601 | |||
602 | it('Should fail with a non admin user', function (done) { | ||
603 | server.user = { | ||
604 | username: 'user1', | ||
605 | password: 'password' | ||
606 | } | ||
607 | |||
608 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
609 | if (err) throw err | ||
610 | |||
611 | userAccessToken = accessToken | ||
612 | |||
613 | const data = { | ||
614 | username: 'user3', | ||
615 | password: 'my super password' | ||
616 | } | ||
617 | |||
618 | requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403) | ||
619 | }) | ||
620 | }) | ||
621 | }) | ||
622 | |||
623 | describe('When updating a user', function () { | ||
624 | before(function (done) { | ||
625 | usersUtils.getUsersList(server.url, function (err, res) { | ||
626 | if (err) throw err | ||
627 | |||
628 | userId = res.body.data[1].id | ||
629 | done() | ||
630 | }) | ||
631 | }) | ||
632 | |||
633 | it('Should fail with a too small password', function (done) { | ||
634 | const data = { | ||
635 | password: 'bla' | ||
636 | } | ||
637 | |||
638 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
639 | }) | ||
640 | |||
641 | it('Should fail with a too long password', function (done) { | ||
642 | const data = { | ||
643 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
644 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
645 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
646 | } | ||
647 | |||
648 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
649 | }) | ||
650 | |||
651 | it('Should fail with an non authenticated user', function (done) { | ||
652 | const data = { | ||
653 | password: 'my super password' | ||
654 | } | ||
655 | |||
656 | requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401) | ||
657 | }) | ||
658 | |||
659 | it('Should succeed with the correct params', function (done) { | ||
660 | const data = { | ||
661 | password: 'my super password' | ||
662 | } | ||
663 | |||
664 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204) | ||
665 | }) | ||
666 | }) | ||
667 | |||
668 | describe('When getting my information', function () { | ||
669 | it('Should fail with a non authenticated user', function (done) { | ||
670 | request(server.url) | ||
671 | .get(path + 'me') | ||
672 | .set('Authorization', 'Bearer faketoken') | ||
673 | .set('Accept', 'application/json') | ||
674 | .expect(401, done) | ||
675 | }) | ||
676 | |||
677 | it('Should success with the correct parameters', function (done) { | ||
678 | request(server.url) | ||
679 | .get(path + 'me') | ||
680 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
681 | .set('Accept', 'application/json') | ||
682 | .expect(200, done) | ||
683 | }) | ||
684 | }) | ||
685 | |||
686 | describe('When removing an user', function () { | ||
687 | it('Should fail with an incorrect id', function (done) { | ||
688 | request(server.url) | ||
689 | .delete(path + 'bla-bla') | ||
690 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
691 | .expect(400, done) | ||
692 | }) | ||
693 | |||
694 | it('Should return 404 with a non existing id', function (done) { | ||
695 | request(server.url) | ||
696 | .delete(path + '579f982228c99c221d8092b8') | ||
697 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
698 | .expect(404, done) | ||
699 | }) | ||
700 | }) | ||
701 | }) | ||
702 | |||
703 | describe('Of the remote videos API', function () { | ||
704 | describe('When making a secure request', function () { | ||
705 | it('Should check a secure request') | ||
706 | }) | ||
707 | |||
708 | describe('When adding a video', function () { | ||
709 | it('Should check when adding a video') | ||
710 | }) | ||
711 | |||
712 | describe('When removing a video', function () { | ||
713 | it('Should check when removing a video') | ||
714 | }) | ||
715 | }) | ||
716 | |||
717 | describe('Of the requests API', function () { | ||
718 | const path = '/api/v1/requests/stats' | ||
719 | |||
720 | it('Should fail with an non authenticated user', function (done) { | ||
721 | request(server.url) | ||
722 | .get(path) | ||
723 | .set('Accept', 'application/json') | ||
724 | .expect(401, done) | ||
725 | }) | ||
726 | |||
727 | it('Should fail with a non admin user', function (done) { | ||
728 | request(server.url) | ||
729 | .get(path) | ||
730 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
731 | .set('Accept', 'application/json') | ||
732 | .expect(403, done) | ||
733 | }) | ||
734 | }) | ||
735 | |||
736 | after(function (done) { | ||
737 | process.kill(-server.app.pid) | ||
738 | |||
739 | // Keep the logs if the test failed | ||
740 | if (this.ok) { | ||
741 | serversUtils.flushTests(done) | ||
742 | } else { | ||
743 | done() | ||
744 | } | ||
745 | }) | ||
746 | }) | ||
diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js deleted file mode 100644 index c1ba9c2c0..000000000 --- a/server/tests/api/checkParams.js +++ /dev/null | |||
@@ -1,456 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const utils = require('./utils') | ||
10 | |||
11 | describe('Test parameters validator', function () { | ||
12 | let server = null | ||
13 | |||
14 | function makePostRequest (path, token, fields, attaches, done, fail) { | ||
15 | let statusCode = 400 | ||
16 | if (fail !== undefined && fail === false) statusCode = 204 | ||
17 | |||
18 | const req = request(server.url) | ||
19 | .post(path) | ||
20 | .set('Accept', 'application/json') | ||
21 | |||
22 | if (token) req.set('Authorization', 'Bearer ' + token) | ||
23 | |||
24 | Object.keys(fields).forEach(function (field) { | ||
25 | const value = fields[field] | ||
26 | |||
27 | if (Array.isArray(value)) { | ||
28 | for (let i = 0; i < value.length; i++) { | ||
29 | req.field(field + '[' + i + ']', value[i]) | ||
30 | } | ||
31 | } else { | ||
32 | req.field(field, value) | ||
33 | } | ||
34 | }) | ||
35 | |||
36 | Object.keys(attaches).forEach(function (attach) { | ||
37 | const value = attaches[attach] | ||
38 | req.attach(attach, value) | ||
39 | }) | ||
40 | |||
41 | req.expect(statusCode, done) | ||
42 | } | ||
43 | |||
44 | function makePostBodyRequest (path, fields, done, fail) { | ||
45 | let statusCode = 400 | ||
46 | if (fail !== undefined && fail === false) statusCode = 200 | ||
47 | |||
48 | request(server.url) | ||
49 | .post(path) | ||
50 | .set('Accept', 'application/json') | ||
51 | .send(fields) | ||
52 | .expect(statusCode, done) | ||
53 | } | ||
54 | |||
55 | // --------------------------------------------------------------- | ||
56 | |||
57 | before(function (done) { | ||
58 | this.timeout(20000) | ||
59 | |||
60 | series([ | ||
61 | function (next) { | ||
62 | utils.flushTests(next) | ||
63 | }, | ||
64 | function (next) { | ||
65 | utils.runServer(1, function (server1) { | ||
66 | server = server1 | ||
67 | |||
68 | next() | ||
69 | }) | ||
70 | }, | ||
71 | function (next) { | ||
72 | utils.loginAndGetAccessToken(server, function (err, token) { | ||
73 | if (err) throw err | ||
74 | server.accessToken = token | ||
75 | |||
76 | next() | ||
77 | }) | ||
78 | } | ||
79 | ], done) | ||
80 | }) | ||
81 | |||
82 | describe('Of the pods API', function () { | ||
83 | const path = '/api/v1/pods/' | ||
84 | |||
85 | describe('When adding a pod', function () { | ||
86 | it('Should fail with nothing', function (done) { | ||
87 | const data = {} | ||
88 | makePostBodyRequest(path, data, done) | ||
89 | }) | ||
90 | |||
91 | it('Should fail without public key', function (done) { | ||
92 | const data = { | ||
93 | url: 'http://coucou.com' | ||
94 | } | ||
95 | makePostBodyRequest(path, data, done) | ||
96 | }) | ||
97 | |||
98 | it('Should fail without an url', function (done) { | ||
99 | const data = { | ||
100 | publicKey: 'mysuperpublickey' | ||
101 | } | ||
102 | makePostBodyRequest(path, data, done) | ||
103 | }) | ||
104 | |||
105 | it('Should fail with an incorrect url', function (done) { | ||
106 | const data = { | ||
107 | url: 'coucou.com', | ||
108 | publicKey: 'mysuperpublickey' | ||
109 | } | ||
110 | makePostBodyRequest(path, data, function () { | ||
111 | data.url = 'http://coucou' | ||
112 | makePostBodyRequest(path, data, function () { | ||
113 | data.url = 'coucou' | ||
114 | makePostBodyRequest(path, data, done) | ||
115 | }) | ||
116 | }) | ||
117 | }) | ||
118 | |||
119 | it('Should succeed with the correct parameters', function (done) { | ||
120 | const data = { | ||
121 | url: 'http://coucou.com', | ||
122 | publicKey: 'mysuperpublickey' | ||
123 | } | ||
124 | makePostBodyRequest(path, data, done, false) | ||
125 | }) | ||
126 | }) | ||
127 | }) | ||
128 | |||
129 | describe('Of the videos API', function () { | ||
130 | const path = '/api/v1/videos/' | ||
131 | |||
132 | describe('When listing a video', function () { | ||
133 | it('Should fail with a bad start pagination', function (done) { | ||
134 | request(server.url) | ||
135 | .get(path) | ||
136 | .query({ start: 'hello' }) | ||
137 | .set('Accept', 'application/json') | ||
138 | .expect(400, done) | ||
139 | }) | ||
140 | |||
141 | it('Should fail with a bad count pagination', function (done) { | ||
142 | request(server.url) | ||
143 | .get(path) | ||
144 | .query({ count: 'hello' }) | ||
145 | .set('Accept', 'application/json') | ||
146 | .expect(400, done) | ||
147 | }) | ||
148 | |||
149 | it('Should fail with an incorrect sort', function (done) { | ||
150 | request(server.url) | ||
151 | .get(path) | ||
152 | .query({ sort: 'hello' }) | ||
153 | .set('Accept', 'application/json') | ||
154 | .expect(400, done) | ||
155 | }) | ||
156 | }) | ||
157 | |||
158 | describe('When searching a video', function () { | ||
159 | it('Should fail with nothing', function (done) { | ||
160 | request(server.url) | ||
161 | .get(pathUtils.join(path, 'search')) | ||
162 | .set('Accept', 'application/json') | ||
163 | .expect(400, done) | ||
164 | }) | ||
165 | |||
166 | it('Should fail with a bad start pagination', function (done) { | ||
167 | request(server.url) | ||
168 | .get(pathUtils.join(path, 'search', 'test')) | ||
169 | .query({ start: 'hello' }) | ||
170 | .set('Accept', 'application/json') | ||
171 | .expect(400, done) | ||
172 | }) | ||
173 | |||
174 | it('Should fail with a bad count pagination', function (done) { | ||
175 | request(server.url) | ||
176 | .get(pathUtils.join(path, 'search', 'test')) | ||
177 | .query({ count: 'hello' }) | ||
178 | .set('Accept', 'application/json') | ||
179 | .expect(400, done) | ||
180 | }) | ||
181 | |||
182 | it('Should fail with an incorrect sort', function (done) { | ||
183 | request(server.url) | ||
184 | .get(pathUtils.join(path, 'search', 'test')) | ||
185 | .query({ sort: 'hello' }) | ||
186 | .set('Accept', 'application/json') | ||
187 | .expect(400, done) | ||
188 | }) | ||
189 | }) | ||
190 | |||
191 | describe('When adding a video', function () { | ||
192 | it('Should fail with nothing', function (done) { | ||
193 | const data = {} | ||
194 | const attach = {} | ||
195 | makePostRequest(path, server.accessToken, data, attach, done) | ||
196 | }) | ||
197 | |||
198 | it('Should fail without name', function (done) { | ||
199 | const data = { | ||
200 | description: 'my super description', | ||
201 | tags: [ 'tag1', 'tag2' ] | ||
202 | } | ||
203 | const attach = { | ||
204 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
205 | } | ||
206 | makePostRequest(path, server.accessToken, data, attach, done) | ||
207 | }) | ||
208 | |||
209 | it('Should fail with a long name', function (done) { | ||
210 | const data = { | ||
211 | name: 'My very very very very very very very very very very very very very very very very long name', | ||
212 | description: 'my super description', | ||
213 | tags: [ 'tag1', 'tag2' ] | ||
214 | } | ||
215 | const attach = { | ||
216 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
217 | } | ||
218 | makePostRequest(path, server.accessToken, data, attach, done) | ||
219 | }) | ||
220 | |||
221 | it('Should fail without description', function (done) { | ||
222 | const data = { | ||
223 | name: 'my super name', | ||
224 | tags: [ 'tag1', 'tag2' ] | ||
225 | } | ||
226 | const attach = { | ||
227 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
228 | } | ||
229 | makePostRequest(path, server.accessToken, data, attach, done) | ||
230 | }) | ||
231 | |||
232 | it('Should fail with a long description', function (done) { | ||
233 | const data = { | ||
234 | name: 'my super name', | ||
235 | description: 'my super description which is very very very very very very very very very very very very very very' + | ||
236 | 'very very very very very very very very very very very very very very very very very very very very very' + | ||
237 | 'very very very very very very very very very very very very very very very long', | ||
238 | tags: [ 'tag1', 'tag2' ] | ||
239 | } | ||
240 | const attach = { | ||
241 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
242 | } | ||
243 | makePostRequest(path, server.accessToken, data, attach, done) | ||
244 | }) | ||
245 | |||
246 | it('Should fail without tags', function (done) { | ||
247 | const data = { | ||
248 | name: 'my super name', | ||
249 | description: 'my super description' | ||
250 | } | ||
251 | const attach = { | ||
252 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
253 | } | ||
254 | makePostRequest(path, server.accessToken, data, attach, done) | ||
255 | }) | ||
256 | |||
257 | it('Should fail with too many tags', function (done) { | ||
258 | const data = { | ||
259 | name: 'my super name', | ||
260 | description: 'my super description', | ||
261 | tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ] | ||
262 | } | ||
263 | const attach = { | ||
264 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
265 | } | ||
266 | makePostRequest(path, server.accessToken, data, attach, done) | ||
267 | }) | ||
268 | |||
269 | it('Should fail with not enough tags', function (done) { | ||
270 | const data = { | ||
271 | name: 'my super name', | ||
272 | description: 'my super description', | ||
273 | tags: [ ] | ||
274 | } | ||
275 | const attach = { | ||
276 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
277 | } | ||
278 | makePostRequest(path, server.accessToken, data, attach, done) | ||
279 | }) | ||
280 | |||
281 | it('Should fail with a tag length too low', function (done) { | ||
282 | const data = { | ||
283 | name: 'my super name', | ||
284 | description: 'my super description', | ||
285 | tags: [ 'tag1', 't' ] | ||
286 | } | ||
287 | const attach = { | ||
288 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
289 | } | ||
290 | makePostRequest(path, server.accessToken, data, attach, done) | ||
291 | }) | ||
292 | |||
293 | it('Should fail with a tag length too big', function (done) { | ||
294 | const data = { | ||
295 | name: 'my super name', | ||
296 | description: 'my super description', | ||
297 | tags: [ 'mysupertagtoolong', 'tag1' ] | ||
298 | } | ||
299 | const attach = { | ||
300 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
301 | } | ||
302 | makePostRequest(path, server.accessToken, data, attach, done) | ||
303 | }) | ||
304 | |||
305 | it('Should fail with malformed tags', function (done) { | ||
306 | const data = { | ||
307 | name: 'my super name', | ||
308 | description: 'my super description', | ||
309 | tags: [ 'my tag' ] | ||
310 | } | ||
311 | const attach = { | ||
312 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
313 | } | ||
314 | makePostRequest(path, server.accessToken, data, attach, done) | ||
315 | }) | ||
316 | |||
317 | it('Should fail without an input file', function (done) { | ||
318 | const data = { | ||
319 | name: 'my super name', | ||
320 | description: 'my super description', | ||
321 | tags: [ 'tag1', 'tag2' ] | ||
322 | } | ||
323 | const attach = {} | ||
324 | makePostRequest(path, server.accessToken, data, attach, done) | ||
325 | }) | ||
326 | |||
327 | it('Should fail without an incorrect input file', function (done) { | ||
328 | const data = { | ||
329 | name: 'my super name', | ||
330 | description: 'my super description', | ||
331 | tags: [ 'tag1', 'tag2' ] | ||
332 | } | ||
333 | const attach = { | ||
334 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm') | ||
335 | } | ||
336 | makePostRequest(path, server.accessToken, data, attach, done) | ||
337 | }) | ||
338 | |||
339 | it('Should fail with a too big duration', function (done) { | ||
340 | const data = { | ||
341 | name: 'my super name', | ||
342 | description: 'my super description', | ||
343 | tags: [ 'tag1', 'tag2' ] | ||
344 | } | ||
345 | const attach = { | ||
346 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm') | ||
347 | } | ||
348 | makePostRequest(path, server.accessToken, data, attach, done) | ||
349 | }) | ||
350 | |||
351 | it('Should succeed with the correct parameters', function (done) { | ||
352 | const data = { | ||
353 | name: 'my super name', | ||
354 | description: 'my super description', | ||
355 | tags: [ 'tag1', 'tag2' ] | ||
356 | } | ||
357 | const attach = { | ||
358 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
359 | } | ||
360 | makePostRequest(path, server.accessToken, data, attach, function () { | ||
361 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') | ||
362 | makePostRequest(path, server.accessToken, data, attach, function () { | ||
363 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') | ||
364 | makePostRequest(path, server.accessToken, data, attach, done, false) | ||
365 | }, false) | ||
366 | }, false) | ||
367 | }) | ||
368 | }) | ||
369 | |||
370 | describe('When getting a video', function () { | ||
371 | it('Should return the list of the videos with nothing', function (done) { | ||
372 | request(server.url) | ||
373 | .get(path) | ||
374 | .set('Accept', 'application/json') | ||
375 | .expect(200) | ||
376 | .expect('Content-Type', /json/) | ||
377 | .end(function (err, res) { | ||
378 | if (err) throw err | ||
379 | |||
380 | expect(res.body.data).to.be.an('array') | ||
381 | expect(res.body.data.length).to.equal(3) | ||
382 | |||
383 | done() | ||
384 | }) | ||
385 | }) | ||
386 | |||
387 | it('Should fail without a mongodb id', function (done) { | ||
388 | request(server.url) | ||
389 | .get(path + 'coucou') | ||
390 | .set('Accept', 'application/json') | ||
391 | .expect(400, done) | ||
392 | }) | ||
393 | |||
394 | it('Should return 404 with an incorrect video', function (done) { | ||
395 | request(server.url) | ||
396 | .get(path + '123456789012345678901234') | ||
397 | .set('Accept', 'application/json') | ||
398 | .expect(404, done) | ||
399 | }) | ||
400 | |||
401 | it('Should succeed with the correct parameters') | ||
402 | }) | ||
403 | |||
404 | describe('When removing a video', function () { | ||
405 | it('Should have 404 with nothing', function (done) { | ||
406 | request(server.url) | ||
407 | .delete(path) | ||
408 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
409 | .expect(400, done) | ||
410 | }) | ||
411 | |||
412 | it('Should fail without a mongodb id', function (done) { | ||
413 | request(server.url) | ||
414 | .delete(path + 'hello') | ||
415 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
416 | .expect(400, done) | ||
417 | }) | ||
418 | |||
419 | it('Should fail with a video which does not exist', function (done) { | ||
420 | request(server.url) | ||
421 | .delete(path + '123456789012345678901234') | ||
422 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
423 | .expect(404, done) | ||
424 | }) | ||
425 | |||
426 | it('Should fail with a video of another pod') | ||
427 | |||
428 | it('Should succeed with the correct parameters') | ||
429 | }) | ||
430 | }) | ||
431 | |||
432 | describe('Of the remote videos API', function () { | ||
433 | describe('When making a secure request', function () { | ||
434 | it('Should check a secure request') | ||
435 | }) | ||
436 | |||
437 | describe('When adding a video', function () { | ||
438 | it('Should check when adding a video') | ||
439 | }) | ||
440 | |||
441 | describe('When removing a video', function () { | ||
442 | it('Should check when removing a video') | ||
443 | }) | ||
444 | }) | ||
445 | |||
446 | after(function (done) { | ||
447 | process.kill(-server.app.pid) | ||
448 | |||
449 | // Keep the logs if the test failed | ||
450 | if (this.ok) { | ||
451 | utils.flushTests(done) | ||
452 | } else { | ||
453 | done() | ||
454 | } | ||
455 | }) | ||
456 | }) | ||
diff --git a/server/tests/api/friendsAdvanced.js b/server/tests/api/friends-advanced.js index 603fbc16b..0d24481ef 100644 --- a/server/tests/api/friendsAdvanced.js +++ b/server/tests/api/friends-advanced.js | |||
@@ -5,24 +5,27 @@ const each = require('async/each') | |||
5 | const expect = chai.expect | 5 | const expect = chai.expect |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
7 | 7 | ||
8 | const utils = require('./utils') | 8 | const loginUtils = require('../utils/login') |
9 | const podsUtils = require('../utils/pods') | ||
10 | const serversUtils = require('../utils/servers') | ||
11 | const videosUtils = require('../utils/videos') | ||
9 | 12 | ||
10 | describe('Test advanced friends', function () { | 13 | describe('Test advanced friends', function () { |
11 | let servers = [] | 14 | let servers = [] |
12 | 15 | ||
13 | function makeFriends (podNumber, callback) { | 16 | function makeFriends (podNumber, callback) { |
14 | const server = servers[podNumber - 1] | 17 | const server = servers[podNumber - 1] |
15 | return utils.makeFriends(server.url, server.accessToken, callback) | 18 | return podsUtils.makeFriends(server.url, server.accessToken, callback) |
16 | } | 19 | } |
17 | 20 | ||
18 | function quitFriends (podNumber, callback) { | 21 | function quitFriends (podNumber, callback) { |
19 | const server = servers[podNumber - 1] | 22 | const server = servers[podNumber - 1] |
20 | return utils.quitFriends(server.url, server.accessToken, callback) | 23 | return podsUtils.quitFriends(server.url, server.accessToken, callback) |
21 | } | 24 | } |
22 | 25 | ||
23 | function getFriendsList (podNumber, end) { | 26 | function getFriendsList (podNumber, end) { |
24 | const server = servers[podNumber - 1] | 27 | const server = servers[podNumber - 1] |
25 | return utils.getFriendsList(server.url, end) | 28 | return podsUtils.getFriendsList(server.url, end) |
26 | } | 29 | } |
27 | 30 | ||
28 | function uploadVideo (podNumber, callback) { | 31 | function uploadVideo (podNumber, callback) { |
@@ -32,22 +35,22 @@ describe('Test advanced friends', function () { | |||
32 | const fixture = 'video_short.webm' | 35 | const fixture = 'video_short.webm' |
33 | const server = servers[podNumber - 1] | 36 | const server = servers[podNumber - 1] |
34 | 37 | ||
35 | return utils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback) | 38 | return videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback) |
36 | } | 39 | } |
37 | 40 | ||
38 | function getVideos (podNumber, callback) { | 41 | function getVideos (podNumber, callback) { |
39 | return utils.getVideosList(servers[podNumber - 1].url, callback) | 42 | return videosUtils.getVideosList(servers[podNumber - 1].url, callback) |
40 | } | 43 | } |
41 | 44 | ||
42 | // --------------------------------------------------------------- | 45 | // --------------------------------------------------------------- |
43 | 46 | ||
44 | before(function (done) { | 47 | before(function (done) { |
45 | this.timeout(30000) | 48 | this.timeout(30000) |
46 | utils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) { | 49 | serversUtils.flushAndRunMultipleServers(6, function (serversRun, urlsRun) { |
47 | servers = serversRun | 50 | servers = serversRun |
48 | 51 | ||
49 | each(servers, function (server, callbackEach) { | 52 | each(servers, function (server, callbackEach) { |
50 | utils.loginAndGetAccessToken(server, function (err, accessToken) { | 53 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { |
51 | if (err) return callbackEach(err) | 54 | if (err) return callbackEach(err) |
52 | 55 | ||
53 | server.accessToken = accessToken | 56 | server.accessToken = accessToken |
@@ -169,7 +172,7 @@ describe('Test advanced friends', function () { | |||
169 | }, | 172 | }, |
170 | // Rerun server 4 | 173 | // Rerun server 4 |
171 | function (next) { | 174 | function (next) { |
172 | utils.runServer(4, function (server) { | 175 | serversUtils.runServer(4, function (server) { |
173 | servers[3].app = server.app | 176 | servers[3].app = server.app |
174 | next() | 177 | next() |
175 | }) | 178 | }) |
@@ -273,7 +276,7 @@ describe('Test advanced friends', function () { | |||
273 | }) | 276 | }) |
274 | 277 | ||
275 | if (this.ok) { | 278 | if (this.ok) { |
276 | utils.flushTests(done) | 279 | serversUtils.flushTests(done) |
277 | } else { | 280 | } else { |
278 | done() | 281 | done() |
279 | } | 282 | } |
diff --git a/server/tests/api/friendsBasic.js b/server/tests/api/friends-basic.js index c74a7f224..f1393b5ec 100644 --- a/server/tests/api/friendsBasic.js +++ b/server/tests/api/friends-basic.js | |||
@@ -5,14 +5,17 @@ const each = require('async/each') | |||
5 | const expect = chai.expect | 5 | const expect = chai.expect |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
7 | 7 | ||
8 | const utils = require('./utils') | 8 | const loginUtils = require('../utils/login') |
9 | const miscsUtils = require('../utils/miscs') | ||
10 | const podsUtils = require('../utils/pods') | ||
11 | const serversUtils = require('../utils/servers') | ||
9 | 12 | ||
10 | describe('Test basic friends', function () { | 13 | describe('Test basic friends', function () { |
11 | let servers = [] | 14 | let servers = [] |
12 | 15 | ||
13 | function makeFriends (podNumber, callback) { | 16 | function makeFriends (podNumber, callback) { |
14 | const server = servers[podNumber - 1] | 17 | const server = servers[podNumber - 1] |
15 | return utils.makeFriends(server.url, server.accessToken, callback) | 18 | return podsUtils.makeFriends(server.url, server.accessToken, callback) |
16 | } | 19 | } |
17 | 20 | ||
18 | function testMadeFriends (servers, serverToTest, callback) { | 21 | function testMadeFriends (servers, serverToTest, callback) { |
@@ -22,7 +25,7 @@ describe('Test basic friends', function () { | |||
22 | friends.push(servers[i].url) | 25 | friends.push(servers[i].url) |
23 | } | 26 | } |
24 | 27 | ||
25 | utils.getFriendsList(serverToTest.url, function (err, res) { | 28 | podsUtils.getFriendsList(serverToTest.url, function (err, res) { |
26 | if (err) throw err | 29 | if (err) throw err |
27 | 30 | ||
28 | const result = res.body | 31 | const result = res.body |
@@ -43,11 +46,11 @@ describe('Test basic friends', function () { | |||
43 | 46 | ||
44 | before(function (done) { | 47 | before(function (done) { |
45 | this.timeout(20000) | 48 | this.timeout(20000) |
46 | utils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) { | 49 | serversUtils.flushAndRunMultipleServers(3, function (serversRun, urlsRun) { |
47 | servers = serversRun | 50 | servers = serversRun |
48 | 51 | ||
49 | each(servers, function (server, callbackEach) { | 52 | each(servers, function (server, callbackEach) { |
50 | utils.loginAndGetAccessToken(server, function (err, accessToken) { | 53 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { |
51 | if (err) return callbackEach(err) | 54 | if (err) return callbackEach(err) |
52 | 55 | ||
53 | server.accessToken = accessToken | 56 | server.accessToken = accessToken |
@@ -59,7 +62,7 @@ describe('Test basic friends', function () { | |||
59 | 62 | ||
60 | it('Should not have friends', function (done) { | 63 | it('Should not have friends', function (done) { |
61 | each(servers, function (server, callback) { | 64 | each(servers, function (server, callback) { |
62 | utils.getFriendsList(server.url, function (err, res) { | 65 | podsUtils.getFriendsList(server.url, function (err, res) { |
63 | if (err) throw err | 66 | if (err) throw err |
64 | 67 | ||
65 | const result = res.body | 68 | const result = res.body |
@@ -71,7 +74,7 @@ describe('Test basic friends', function () { | |||
71 | }) | 74 | }) |
72 | 75 | ||
73 | it('Should make friends', function (done) { | 76 | it('Should make friends', function (done) { |
74 | this.timeout(10000) | 77 | this.timeout(40000) |
75 | 78 | ||
76 | series([ | 79 | series([ |
77 | // The second pod make friend with the third | 80 | // The second pod make friend with the third |
@@ -80,30 +83,38 @@ describe('Test basic friends', function () { | |||
80 | }, | 83 | }, |
81 | // Wait for the request between pods | 84 | // Wait for the request between pods |
82 | function (next) { | 85 | function (next) { |
83 | setTimeout(next, 1000) | 86 | setTimeout(next, 11000) |
84 | }, | 87 | }, |
85 | // The second pod should have the third as a friend | 88 | // The second pod should have the third as a friend |
86 | function (next) { | 89 | function (next) { |
87 | utils.getFriendsList(servers[1].url, function (err, res) { | 90 | podsUtils.getFriendsList(servers[1].url, function (err, res) { |
88 | if (err) throw err | 91 | if (err) throw err |
89 | 92 | ||
90 | const result = res.body | 93 | const result = res.body |
91 | expect(result).to.be.an('array') | 94 | expect(result).to.be.an('array') |
92 | expect(result.length).to.equal(1) | 95 | expect(result.length).to.equal(1) |
93 | expect(result[0].url).to.be.equal(servers[2].url) | 96 | |
97 | const pod = result[0] | ||
98 | expect(pod.url).to.equal(servers[2].url) | ||
99 | expect(pod.score).to.equal(20) | ||
100 | expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true | ||
94 | 101 | ||
95 | next() | 102 | next() |
96 | }) | 103 | }) |
97 | }, | 104 | }, |
98 | // Same here, the third pod should have the second pod as a friend | 105 | // Same here, the third pod should have the second pod as a friend |
99 | function (next) { | 106 | function (next) { |
100 | utils.getFriendsList(servers[2].url, function (err, res) { | 107 | podsUtils.getFriendsList(servers[2].url, function (err, res) { |
101 | if (err) throw err | 108 | if (err) throw err |
102 | 109 | ||
103 | const result = res.body | 110 | const result = res.body |
104 | expect(result).to.be.an('array') | 111 | expect(result).to.be.an('array') |
105 | expect(result.length).to.equal(1) | 112 | expect(result.length).to.equal(1) |
106 | expect(result[0].url).to.be.equal(servers[1].url) | 113 | |
114 | const pod = result[0] | ||
115 | expect(pod.url).to.equal(servers[1].url) | ||
116 | expect(pod.score).to.equal(20) | ||
117 | expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true | ||
107 | 118 | ||
108 | next() | 119 | next() |
109 | }) | 120 | }) |
@@ -114,7 +125,7 @@ describe('Test basic friends', function () { | |||
114 | }, | 125 | }, |
115 | // Wait for the request between pods | 126 | // Wait for the request between pods |
116 | function (next) { | 127 | function (next) { |
117 | setTimeout(next, 1000) | 128 | setTimeout(next, 11000) |
118 | } | 129 | } |
119 | ], | 130 | ], |
120 | // Now each pod should be friend with the other ones | 131 | // Now each pod should be friend with the other ones |
@@ -128,7 +139,7 @@ describe('Test basic friends', function () { | |||
128 | 139 | ||
129 | it('Should not be allowed to make friend again', function (done) { | 140 | it('Should not be allowed to make friend again', function (done) { |
130 | const server = servers[1] | 141 | const server = servers[1] |
131 | utils.makeFriends(server.url, server.accessToken, 409, done) | 142 | podsUtils.makeFriends(server.url, server.accessToken, 409, done) |
132 | }) | 143 | }) |
133 | 144 | ||
134 | it('Should quit friends of pod 2', function (done) { | 145 | it('Should quit friends of pod 2', function (done) { |
@@ -136,11 +147,11 @@ describe('Test basic friends', function () { | |||
136 | // Pod 1 quit friends | 147 | // Pod 1 quit friends |
137 | function (next) { | 148 | function (next) { |
138 | const server = servers[1] | 149 | const server = servers[1] |
139 | utils.quitFriends(server.url, server.accessToken, next) | 150 | podsUtils.quitFriends(server.url, server.accessToken, next) |
140 | }, | 151 | }, |
141 | // Pod 1 should not have friends anymore | 152 | // Pod 1 should not have friends anymore |
142 | function (next) { | 153 | function (next) { |
143 | utils.getFriendsList(servers[1].url, function (err, res) { | 154 | podsUtils.getFriendsList(servers[1].url, function (err, res) { |
144 | if (err) throw err | 155 | if (err) throw err |
145 | 156 | ||
146 | const result = res.body | 157 | const result = res.body |
@@ -153,7 +164,7 @@ describe('Test basic friends', function () { | |||
153 | // Other pods shouldn't have pod 1 too | 164 | // Other pods shouldn't have pod 1 too |
154 | function (next) { | 165 | function (next) { |
155 | each([ servers[0].url, servers[2].url ], function (url, callback) { | 166 | each([ servers[0].url, servers[2].url ], function (url, callback) { |
156 | utils.getFriendsList(url, function (err, res) { | 167 | podsUtils.getFriendsList(url, function (err, res) { |
157 | if (err) throw err | 168 | if (err) throw err |
158 | 169 | ||
159 | const result = res.body | 170 | const result = res.body |
@@ -168,11 +179,15 @@ describe('Test basic friends', function () { | |||
168 | }) | 179 | }) |
169 | 180 | ||
170 | it('Should allow pod 2 to make friend again', function (done) { | 181 | it('Should allow pod 2 to make friend again', function (done) { |
182 | this.timeout(20000) | ||
183 | |||
171 | const server = servers[1] | 184 | const server = servers[1] |
172 | utils.makeFriends(server.url, server.accessToken, function () { | 185 | podsUtils.makeFriends(server.url, server.accessToken, function () { |
173 | each(servers, function (server, callback) { | 186 | setTimeout(function () { |
174 | testMadeFriends(servers, server, callback) | 187 | each(servers, function (server, callback) { |
175 | }, done) | 188 | testMadeFriends(servers, server, callback) |
189 | }, done) | ||
190 | }, 11000) | ||
176 | }) | 191 | }) |
177 | }) | 192 | }) |
178 | 193 | ||
@@ -182,7 +197,7 @@ describe('Test basic friends', function () { | |||
182 | }) | 197 | }) |
183 | 198 | ||
184 | if (this.ok) { | 199 | if (this.ok) { |
185 | utils.flushTests(done) | 200 | serversUtils.flushTests(done) |
186 | } else { | 201 | } else { |
187 | done() | 202 | done() |
188 | } | 203 | } |
diff --git a/server/tests/api/index.js b/server/tests/api/index.js index 61c9a7aca..11f49e1e2 100644 --- a/server/tests/api/index.js +++ b/server/tests/api/index.js | |||
@@ -1,9 +1,9 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | // Order of the tests we want to execute | 3 | // Order of the tests we want to execute |
4 | require('./checkParams') | 4 | require('./check-params') |
5 | require('./friendsBasic') | 5 | require('./friends-basic') |
6 | require('./users') | 6 | require('./users') |
7 | require('./singlePod') | 7 | require('./single-pod') |
8 | require('./multiplePods') | 8 | require('./multiple-pods') |
9 | require('./friendsAdvanced') | 9 | require('./friends-advanced') |
diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiple-pods.js index ac140f6bb..b86f88c22 100644 --- a/server/tests/api/multiplePods.js +++ b/server/tests/api/multiple-pods.js | |||
@@ -6,7 +6,11 @@ const expect = chai.expect | |||
6 | const pathUtils = require('path') | 6 | const pathUtils = require('path') |
7 | const series = require('async/series') | 7 | const series = require('async/series') |
8 | 8 | ||
9 | const utils = require('./utils') | 9 | const loginUtils = require('../utils/login') |
10 | const miscsUtils = require('../utils/miscs') | ||
11 | const podsUtils = require('../utils/pods') | ||
12 | const serversUtils = require('../utils/servers') | ||
13 | const videosUtils = require('../utils/videos') | ||
10 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) | 14 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) |
11 | webtorrent.silent = true | 15 | webtorrent.silent = true |
12 | 16 | ||
@@ -20,7 +24,7 @@ describe('Test multiple pods', function () { | |||
20 | series([ | 24 | series([ |
21 | // Run servers | 25 | // Run servers |
22 | function (next) { | 26 | function (next) { |
23 | utils.flushAndRunMultipleServers(3, function (serversRun) { | 27 | serversUtils.flushAndRunMultipleServers(3, function (serversRun) { |
24 | servers = serversRun | 28 | servers = serversRun |
25 | next() | 29 | next() |
26 | }) | 30 | }) |
@@ -28,7 +32,7 @@ describe('Test multiple pods', function () { | |||
28 | // Get the access tokens | 32 | // Get the access tokens |
29 | function (next) { | 33 | function (next) { |
30 | each(servers, function (server, callbackEach) { | 34 | each(servers, function (server, callbackEach) { |
31 | utils.loginAndGetAccessToken(server, function (err, accessToken) { | 35 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { |
32 | if (err) return callbackEach(err) | 36 | if (err) return callbackEach(err) |
33 | 37 | ||
34 | server.accessToken = accessToken | 38 | server.accessToken = accessToken |
@@ -39,7 +43,7 @@ describe('Test multiple pods', function () { | |||
39 | // The second pod make friend with the third | 43 | // The second pod make friend with the third |
40 | function (next) { | 44 | function (next) { |
41 | const server = servers[1] | 45 | const server = servers[1] |
42 | utils.makeFriends(server.url, server.accessToken, next) | 46 | podsUtils.makeFriends(server.url, server.accessToken, next) |
43 | }, | 47 | }, |
44 | // Wait for the request between pods | 48 | // Wait for the request between pods |
45 | function (next) { | 49 | function (next) { |
@@ -48,7 +52,7 @@ describe('Test multiple pods', function () { | |||
48 | // Pod 1 make friends too | 52 | // Pod 1 make friends too |
49 | function (next) { | 53 | function (next) { |
50 | const server = servers[0] | 54 | const server = servers[0] |
51 | utils.makeFriends(server.url, server.accessToken, next) | 55 | podsUtils.makeFriends(server.url, server.accessToken, next) |
52 | }, | 56 | }, |
53 | function (next) { | 57 | function (next) { |
54 | webtorrent.create({ host: 'client', port: '1' }, next) | 58 | webtorrent.create({ host: 'client', port: '1' }, next) |
@@ -58,7 +62,7 @@ describe('Test multiple pods', function () { | |||
58 | 62 | ||
59 | it('Should not have videos for all pods', function (done) { | 63 | it('Should not have videos for all pods', function (done) { |
60 | each(servers, function (server, callback) { | 64 | each(servers, function (server, callback) { |
61 | utils.getVideosList(server.url, function (err, res) { | 65 | videosUtils.getVideosList(server.url, function (err, res) { |
62 | if (err) throw err | 66 | if (err) throw err |
63 | 67 | ||
64 | const videos = res.body.data | 68 | const videos = res.body.data |
@@ -80,7 +84,7 @@ describe('Test multiple pods', function () { | |||
80 | const description = 'my super description for pod 1' | 84 | const description = 'my super description for pod 1' |
81 | const tags = [ 'tag1p1', 'tag2p1' ] | 85 | const tags = [ 'tag1p1', 'tag2p1' ] |
82 | const file = 'video_short1.webm' | 86 | const file = 'video_short1.webm' |
83 | utils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next) | 87 | videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next) |
84 | }, | 88 | }, |
85 | function (next) { | 89 | function (next) { |
86 | setTimeout(next, 11000) | 90 | setTimeout(next, 11000) |
@@ -92,7 +96,7 @@ describe('Test multiple pods', function () { | |||
92 | each(servers, function (server, callback) { | 96 | each(servers, function (server, callback) { |
93 | let baseMagnet = null | 97 | let baseMagnet = null |
94 | 98 | ||
95 | utils.getVideosList(server.url, function (err, res) { | 99 | videosUtils.getVideosList(server.url, function (err, res) { |
96 | if (err) throw err | 100 | if (err) throw err |
97 | 101 | ||
98 | const videos = res.body.data | 102 | const videos = res.body.data |
@@ -105,7 +109,7 @@ describe('Test multiple pods', function () { | |||
105 | expect(video.magnetUri).to.exist | 109 | expect(video.magnetUri).to.exist |
106 | expect(video.duration).to.equal(10) | 110 | expect(video.duration).to.equal(10) |
107 | expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) | 111 | expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) |
108 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 112 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
109 | expect(video.author).to.equal('root') | 113 | expect(video.author).to.equal('root') |
110 | 114 | ||
111 | if (server.url !== 'http://localhost:9001') { | 115 | if (server.url !== 'http://localhost:9001') { |
@@ -121,7 +125,7 @@ describe('Test multiple pods', function () { | |||
121 | expect(video.magnetUri).to.equal.magnetUri | 125 | expect(video.magnetUri).to.equal.magnetUri |
122 | } | 126 | } |
123 | 127 | ||
124 | utils.testImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) { | 128 | videosUtils.testVideoImage(server.url, 'video_short1.webm', video.thumbnailPath, function (err, test) { |
125 | if (err) throw err | 129 | if (err) throw err |
126 | expect(test).to.equal(true) | 130 | expect(test).to.equal(true) |
127 | 131 | ||
@@ -142,7 +146,7 @@ describe('Test multiple pods', function () { | |||
142 | const description = 'my super description for pod 2' | 146 | const description = 'my super description for pod 2' |
143 | const tags = [ 'tag1p2', 'tag2p2', 'tag3p2' ] | 147 | const tags = [ 'tag1p2', 'tag2p2', 'tag3p2' ] |
144 | const file = 'video_short2.webm' | 148 | const file = 'video_short2.webm' |
145 | utils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next) | 149 | videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next) |
146 | }, | 150 | }, |
147 | function (next) { | 151 | function (next) { |
148 | setTimeout(next, 11000) | 152 | setTimeout(next, 11000) |
@@ -154,7 +158,7 @@ describe('Test multiple pods', function () { | |||
154 | each(servers, function (server, callback) { | 158 | each(servers, function (server, callback) { |
155 | let baseMagnet = null | 159 | let baseMagnet = null |
156 | 160 | ||
157 | utils.getVideosList(server.url, function (err, res) { | 161 | videosUtils.getVideosList(server.url, function (err, res) { |
158 | if (err) throw err | 162 | if (err) throw err |
159 | 163 | ||
160 | const videos = res.body.data | 164 | const videos = res.body.data |
@@ -167,7 +171,7 @@ describe('Test multiple pods', function () { | |||
167 | expect(video.magnetUri).to.exist | 171 | expect(video.magnetUri).to.exist |
168 | expect(video.duration).to.equal(5) | 172 | expect(video.duration).to.equal(5) |
169 | expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) | 173 | expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) |
170 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 174 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
171 | expect(video.author).to.equal('root') | 175 | expect(video.author).to.equal('root') |
172 | 176 | ||
173 | if (server.url !== 'http://localhost:9002') { | 177 | if (server.url !== 'http://localhost:9002') { |
@@ -183,7 +187,7 @@ describe('Test multiple pods', function () { | |||
183 | expect(video.magnetUri).to.equal.magnetUri | 187 | expect(video.magnetUri).to.equal.magnetUri |
184 | } | 188 | } |
185 | 189 | ||
186 | utils.testImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) { | 190 | videosUtils.testVideoImage(server.url, 'video_short2.webm', video.thumbnailPath, function (err, test) { |
187 | if (err) throw err | 191 | if (err) throw err |
188 | expect(test).to.equal(true) | 192 | expect(test).to.equal(true) |
189 | 193 | ||
@@ -204,14 +208,14 @@ describe('Test multiple pods', function () { | |||
204 | const description = 'my super description for pod 3' | 208 | const description = 'my super description for pod 3' |
205 | const tags = [ 'tag1p3' ] | 209 | const tags = [ 'tag1p3' ] |
206 | const file = 'video_short3.webm' | 210 | const file = 'video_short3.webm' |
207 | utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) | 211 | videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) |
208 | }, | 212 | }, |
209 | function (next) { | 213 | function (next) { |
210 | const name = 'my super name for pod 3-2' | 214 | const name = 'my super name for pod 3-2' |
211 | const description = 'my super description for pod 3-2' | 215 | const description = 'my super description for pod 3-2' |
212 | const tags = [ 'tag2p3', 'tag3p3', 'tag4p3' ] | 216 | const tags = [ 'tag2p3', 'tag3p3', 'tag4p3' ] |
213 | const file = 'video_short.webm' | 217 | const file = 'video_short.webm' |
214 | utils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) | 218 | videosUtils.uploadVideo(servers[2].url, servers[2].accessToken, name, description, tags, file, next) |
215 | }, | 219 | }, |
216 | function (next) { | 220 | function (next) { |
217 | setTimeout(next, 22000) | 221 | setTimeout(next, 22000) |
@@ -222,7 +226,7 @@ describe('Test multiple pods', function () { | |||
222 | let baseMagnet = null | 226 | let baseMagnet = null |
223 | // All pods should have this video | 227 | // All pods should have this video |
224 | each(servers, function (server, callback) { | 228 | each(servers, function (server, callback) { |
225 | utils.getVideosList(server.url, function (err, res) { | 229 | videosUtils.getVideosList(server.url, function (err, res) { |
226 | if (err) throw err | 230 | if (err) throw err |
227 | 231 | ||
228 | const videos = res.body.data | 232 | const videos = res.body.data |
@@ -247,7 +251,7 @@ describe('Test multiple pods', function () { | |||
247 | expect(video1.duration).to.equal(5) | 251 | expect(video1.duration).to.equal(5) |
248 | expect(video1.tags).to.deep.equal([ 'tag1p3' ]) | 252 | expect(video1.tags).to.deep.equal([ 'tag1p3' ]) |
249 | expect(video1.author).to.equal('root') | 253 | expect(video1.author).to.equal('root') |
250 | expect(utils.dateIsValid(video1.createdDate)).to.be.true | 254 | expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true |
251 | 255 | ||
252 | expect(video2.name).to.equal('my super name for pod 3-2') | 256 | expect(video2.name).to.equal('my super name for pod 3-2') |
253 | expect(video2.description).to.equal('my super description for pod 3-2') | 257 | expect(video2.description).to.equal('my super description for pod 3-2') |
@@ -256,7 +260,7 @@ describe('Test multiple pods', function () { | |||
256 | expect(video2.duration).to.equal(5) | 260 | expect(video2.duration).to.equal(5) |
257 | expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) | 261 | expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) |
258 | expect(video2.author).to.equal('root') | 262 | expect(video2.author).to.equal('root') |
259 | expect(utils.dateIsValid(video2.createdDate)).to.be.true | 263 | expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true |
260 | 264 | ||
261 | if (server.url !== 'http://localhost:9003') { | 265 | if (server.url !== 'http://localhost:9003') { |
262 | expect(video1.isLocal).to.be.false | 266 | expect(video1.isLocal).to.be.false |
@@ -273,11 +277,11 @@ describe('Test multiple pods', function () { | |||
273 | expect(video2.magnetUri).to.equal.magnetUri | 277 | expect(video2.magnetUri).to.equal.magnetUri |
274 | } | 278 | } |
275 | 279 | ||
276 | utils.testImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) { | 280 | videosUtils.testVideoImage(server.url, 'video_short3.webm', video1.thumbnailPath, function (err, test) { |
277 | if (err) throw err | 281 | if (err) throw err |
278 | expect(test).to.equal(true) | 282 | expect(test).to.equal(true) |
279 | 283 | ||
280 | utils.testImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) { | 284 | videosUtils.testVideoImage(server.url, 'video_short.webm', video2.thumbnailPath, function (err, test) { |
281 | if (err) throw err | 285 | if (err) throw err |
282 | expect(test).to.equal(true) | 286 | expect(test).to.equal(true) |
283 | 287 | ||
@@ -296,7 +300,7 @@ describe('Test multiple pods', function () { | |||
296 | // Yes, this could be long | 300 | // Yes, this could be long |
297 | this.timeout(200000) | 301 | this.timeout(200000) |
298 | 302 | ||
299 | utils.getVideosList(servers[2].url, function (err, res) { | 303 | videosUtils.getVideosList(servers[2].url, function (err, res) { |
300 | if (err) throw err | 304 | if (err) throw err |
301 | 305 | ||
302 | const video = res.body.data[0] | 306 | const video = res.body.data[0] |
@@ -317,7 +321,7 @@ describe('Test multiple pods', function () { | |||
317 | // Yes, this could be long | 321 | // Yes, this could be long |
318 | this.timeout(200000) | 322 | this.timeout(200000) |
319 | 323 | ||
320 | utils.getVideosList(servers[0].url, function (err, res) { | 324 | videosUtils.getVideosList(servers[0].url, function (err, res) { |
321 | if (err) throw err | 325 | if (err) throw err |
322 | 326 | ||
323 | const video = res.body.data[1] | 327 | const video = res.body.data[1] |
@@ -336,7 +340,7 @@ describe('Test multiple pods', function () { | |||
336 | // Yes, this could be long | 340 | // Yes, this could be long |
337 | this.timeout(200000) | 341 | this.timeout(200000) |
338 | 342 | ||
339 | utils.getVideosList(servers[1].url, function (err, res) { | 343 | videosUtils.getVideosList(servers[1].url, function (err, res) { |
340 | if (err) throw err | 344 | if (err) throw err |
341 | 345 | ||
342 | const video = res.body.data[2] | 346 | const video = res.body.data[2] |
@@ -355,7 +359,7 @@ describe('Test multiple pods', function () { | |||
355 | // Yes, this could be long | 359 | // Yes, this could be long |
356 | this.timeout(200000) | 360 | this.timeout(200000) |
357 | 361 | ||
358 | utils.getVideosList(servers[0].url, function (err, res) { | 362 | videosUtils.getVideosList(servers[0].url, function (err, res) { |
359 | if (err) throw err | 363 | if (err) throw err |
360 | 364 | ||
361 | const video = res.body.data[3] | 365 | const video = res.body.data[3] |
@@ -375,10 +379,10 @@ describe('Test multiple pods', function () { | |||
375 | 379 | ||
376 | series([ | 380 | series([ |
377 | function (next) { | 381 | function (next) { |
378 | utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) | 382 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) |
379 | }, | 383 | }, |
380 | function (next) { | 384 | function (next) { |
381 | utils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) | 385 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) |
382 | }], | 386 | }], |
383 | function (err) { | 387 | function (err) { |
384 | if (err) throw err | 388 | if (err) throw err |
@@ -389,7 +393,7 @@ describe('Test multiple pods', function () { | |||
389 | 393 | ||
390 | it('Should have videos 1 and 3 on each pod', function (done) { | 394 | it('Should have videos 1 and 3 on each pod', function (done) { |
391 | each(servers, function (server, callback) { | 395 | each(servers, function (server, callback) { |
392 | utils.getVideosList(server.url, function (err, res) { | 396 | videosUtils.getVideosList(server.url, function (err, res) { |
393 | if (err) throw err | 397 | if (err) throw err |
394 | 398 | ||
395 | const videos = res.body.data | 399 | const videos = res.body.data |
@@ -415,7 +419,7 @@ describe('Test multiple pods', function () { | |||
415 | 419 | ||
416 | // Keep the logs if the test failed | 420 | // Keep the logs if the test failed |
417 | if (this.ok) { | 421 | if (this.ok) { |
418 | utils.flushTests(done) | 422 | serversUtils.flushTests(done) |
419 | } else { | 423 | } else { |
420 | done() | 424 | done() |
421 | } | 425 | } |
diff --git a/server/tests/api/requests.js b/server/tests/api/requests.js new file mode 100644 index 000000000..af36f6e34 --- /dev/null +++ b/server/tests/api/requests.js | |||
@@ -0,0 +1,128 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const each = require('async/each') | ||
5 | const expect = chai.expect | ||
6 | const request = require('supertest') | ||
7 | |||
8 | const loginUtils = require('../utils/login') | ||
9 | const podsUtils = require('../utils/pods') | ||
10 | const serversUtils = require('../utils/servers') | ||
11 | const videosUtils = require('../utils/videos') | ||
12 | |||
13 | describe('Test requests stats', function () { | ||
14 | const path = '/api/v1/requests/stats' | ||
15 | let servers = [] | ||
16 | |||
17 | function uploadVideo (server, callback) { | ||
18 | const name = 'my super video' | ||
19 | const description = 'my super description' | ||
20 | const tags = [ 'tag1', 'tag2' ] | ||
21 | const fixture = 'video_short.webm' | ||
22 | |||
23 | videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, fixture, callback) | ||
24 | } | ||
25 | |||
26 | function getRequestsStats (server, callback) { | ||
27 | request(server.url) | ||
28 | .get(path) | ||
29 | .set('Accept', 'application/json') | ||
30 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
31 | .expect(200) | ||
32 | .end(callback) | ||
33 | } | ||
34 | |||
35 | // --------------------------------------------------------------- | ||
36 | |||
37 | before(function (done) { | ||
38 | this.timeout(20000) | ||
39 | serversUtils.flushAndRunMultipleServers(2, function (serversRun, urlsRun) { | ||
40 | servers = serversRun | ||
41 | |||
42 | each(servers, function (server, callbackEach) { | ||
43 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
44 | if (err) return callbackEach(err) | ||
45 | |||
46 | server.accessToken = accessToken | ||
47 | callbackEach() | ||
48 | }) | ||
49 | }, function (err) { | ||
50 | if (err) throw err | ||
51 | |||
52 | const server1 = servers[0] | ||
53 | podsUtils.makeFriends(server1.url, server1.accessToken, done) | ||
54 | }) | ||
55 | }) | ||
56 | }) | ||
57 | |||
58 | it('Should have a correct timer', function (done) { | ||
59 | const server = servers[0] | ||
60 | |||
61 | getRequestsStats(server, function (err, res) { | ||
62 | if (err) throw err | ||
63 | |||
64 | const body = res.body | ||
65 | expect(body.remainingMilliSeconds).to.be.at.least(0) | ||
66 | expect(body.remainingMilliSeconds).to.be.at.most(10000) | ||
67 | |||
68 | done() | ||
69 | }) | ||
70 | }) | ||
71 | |||
72 | it('Should have the correct request', function (done) { | ||
73 | this.timeout(15000) | ||
74 | |||
75 | const server = servers[0] | ||
76 | // Ensure the requests of pod 1 won't be made | ||
77 | servers[1].app.kill() | ||
78 | |||
79 | uploadVideo(server, function (err) { | ||
80 | if (err) throw err | ||
81 | |||
82 | getRequestsStats(server, function (err, res) { | ||
83 | if (err) throw err | ||
84 | |||
85 | const body = res.body | ||
86 | expect(body.requests).to.have.lengthOf(1) | ||
87 | |||
88 | const request = body.requests[0] | ||
89 | expect(request.to).to.have.lengthOf(1) | ||
90 | expect(request.request.type).to.equal('add') | ||
91 | |||
92 | // Wait one cycle | ||
93 | setTimeout(done, 10000) | ||
94 | }) | ||
95 | }) | ||
96 | }) | ||
97 | |||
98 | it('Should have the correct requests', function (done) { | ||
99 | const server = servers[0] | ||
100 | |||
101 | uploadVideo(server, function (err) { | ||
102 | if (err) throw err | ||
103 | |||
104 | getRequestsStats(server, function (err, res) { | ||
105 | if (err) throw err | ||
106 | |||
107 | const body = res.body | ||
108 | expect(body.requests).to.have.lengthOf(2) | ||
109 | |||
110 | const request = body.requests[1] | ||
111 | expect(request.to).to.have.lengthOf(1) | ||
112 | expect(request.request.type).to.equal('add') | ||
113 | |||
114 | done() | ||
115 | }) | ||
116 | }) | ||
117 | }) | ||
118 | |||
119 | after(function (done) { | ||
120 | process.kill(-servers[0].app.pid) | ||
121 | |||
122 | if (this.ok) { | ||
123 | serversUtils.flushTests(done) | ||
124 | } else { | ||
125 | done() | ||
126 | } | ||
127 | }) | ||
128 | }) | ||
diff --git a/server/tests/api/singlePod.js b/server/tests/api/single-pod.js index 6ed719f87..bdaaee46c 100644 --- a/server/tests/api/singlePod.js +++ b/server/tests/api/single-pod.js | |||
@@ -8,11 +8,13 @@ const keyBy = require('lodash/keyBy') | |||
8 | const pathUtils = require('path') | 8 | const pathUtils = require('path') |
9 | const series = require('async/series') | 9 | const series = require('async/series') |
10 | 10 | ||
11 | const loginUtils = require('../utils/login') | ||
12 | const miscsUtils = require('../utils/miscs') | ||
13 | const serversUtils = require('../utils/servers') | ||
14 | const videosUtils = require('../utils/videos') | ||
11 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) | 15 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) |
12 | webtorrent.silent = true | 16 | webtorrent.silent = true |
13 | 17 | ||
14 | const utils = require('./utils') | ||
15 | |||
16 | describe('Test a single pod', function () { | 18 | describe('Test a single pod', function () { |
17 | let server = null | 19 | let server = null |
18 | let videoId = -1 | 20 | let videoId = -1 |
@@ -23,16 +25,16 @@ describe('Test a single pod', function () { | |||
23 | 25 | ||
24 | series([ | 26 | series([ |
25 | function (next) { | 27 | function (next) { |
26 | utils.flushTests(next) | 28 | serversUtils.flushTests(next) |
27 | }, | 29 | }, |
28 | function (next) { | 30 | function (next) { |
29 | utils.runServer(1, function (server1) { | 31 | serversUtils.runServer(1, function (server1) { |
30 | server = server1 | 32 | server = server1 |
31 | next() | 33 | next() |
32 | }) | 34 | }) |
33 | }, | 35 | }, |
34 | function (next) { | 36 | function (next) { |
35 | utils.loginAndGetAccessToken(server, function (err, token) { | 37 | loginUtils.loginAndGetAccessToken(server, function (err, token) { |
36 | if (err) throw err | 38 | if (err) throw err |
37 | server.accessToken = token | 39 | server.accessToken = token |
38 | next() | 40 | next() |
@@ -45,7 +47,7 @@ describe('Test a single pod', function () { | |||
45 | }) | 47 | }) |
46 | 48 | ||
47 | it('Should not have videos', function (done) { | 49 | it('Should not have videos', function (done) { |
48 | utils.getVideosList(server.url, function (err, res) { | 50 | videosUtils.getVideosList(server.url, function (err, res) { |
49 | if (err) throw err | 51 | if (err) throw err |
50 | 52 | ||
51 | expect(res.body.total).to.equal(0) | 53 | expect(res.body.total).to.equal(0) |
@@ -62,14 +64,14 @@ describe('Test a single pod', function () { | |||
62 | const description = 'my super description' | 64 | const description = 'my super description' |
63 | const tags = [ 'tag1', 'tag2', 'tag3' ] | 65 | const tags = [ 'tag1', 'tag2', 'tag3' ] |
64 | const file = 'video_short.webm' | 66 | const file = 'video_short.webm' |
65 | utils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done) | 67 | videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, done) |
66 | }) | 68 | }) |
67 | 69 | ||
68 | it('Should seed the uploaded video', function (done) { | 70 | it('Should seed the uploaded video', function (done) { |
69 | // Yes, this could be long | 71 | // Yes, this could be long |
70 | this.timeout(60000) | 72 | this.timeout(60000) |
71 | 73 | ||
72 | utils.getVideosList(server.url, function (err, res) { | 74 | videosUtils.getVideosList(server.url, function (err, res) { |
73 | if (err) throw err | 75 | if (err) throw err |
74 | 76 | ||
75 | expect(res.body.total).to.equal(1) | 77 | expect(res.body.total).to.equal(1) |
@@ -84,9 +86,9 @@ describe('Test a single pod', function () { | |||
84 | expect(video.author).to.equal('root') | 86 | expect(video.author).to.equal('root') |
85 | expect(video.isLocal).to.be.true | 87 | expect(video.isLocal).to.be.true |
86 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 88 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
87 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 89 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
88 | 90 | ||
89 | utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 91 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
90 | if (err) throw err | 92 | if (err) throw err |
91 | expect(test).to.equal(true) | 93 | expect(test).to.equal(true) |
92 | 94 | ||
@@ -97,8 +99,7 @@ describe('Test a single pod', function () { | |||
97 | expect(torrent.files.length).to.equal(1) | 99 | expect(torrent.files.length).to.equal(1) |
98 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | 100 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') |
99 | 101 | ||
100 | // We remove it because we'll add it again | 102 | done() |
101 | webtorrent.remove(video.magnetUri, done) | ||
102 | }) | 103 | }) |
103 | }) | 104 | }) |
104 | }) | 105 | }) |
@@ -108,7 +109,7 @@ describe('Test a single pod', function () { | |||
108 | // Yes, this could be long | 109 | // Yes, this could be long |
109 | this.timeout(60000) | 110 | this.timeout(60000) |
110 | 111 | ||
111 | utils.getVideo(server.url, videoId, function (err, res) { | 112 | videosUtils.getVideo(server.url, videoId, function (err, res) { |
112 | if (err) throw err | 113 | if (err) throw err |
113 | 114 | ||
114 | const video = res.body | 115 | const video = res.body |
@@ -119,25 +120,19 @@ describe('Test a single pod', function () { | |||
119 | expect(video.author).to.equal('root') | 120 | expect(video.author).to.equal('root') |
120 | expect(video.isLocal).to.be.true | 121 | expect(video.isLocal).to.be.true |
121 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 122 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
122 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 123 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
123 | 124 | ||
124 | utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 125 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
125 | if (err) throw err | 126 | if (err) throw err |
126 | expect(test).to.equal(true) | 127 | expect(test).to.equal(true) |
127 | 128 | ||
128 | webtorrent.add(video.magnetUri, function (torrent) { | 129 | done() |
129 | expect(torrent.files).to.exist | ||
130 | expect(torrent.files.length).to.equal(1) | ||
131 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | ||
132 | |||
133 | done() | ||
134 | }) | ||
135 | }) | 130 | }) |
136 | }) | 131 | }) |
137 | }) | 132 | }) |
138 | 133 | ||
139 | it('Should search the video by name by default', function (done) { | 134 | it('Should search the video by name by default', function (done) { |
140 | utils.searchVideo(server.url, 'my', function (err, res) { | 135 | videosUtils.searchVideo(server.url, 'my', function (err, res) { |
141 | if (err) throw err | 136 | if (err) throw err |
142 | 137 | ||
143 | expect(res.body.total).to.equal(1) | 138 | expect(res.body.total).to.equal(1) |
@@ -151,9 +146,9 @@ describe('Test a single pod', function () { | |||
151 | expect(video.author).to.equal('root') | 146 | expect(video.author).to.equal('root') |
152 | expect(video.isLocal).to.be.true | 147 | expect(video.isLocal).to.be.true |
153 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 148 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
154 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 149 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
155 | 150 | ||
156 | utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 151 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
157 | if (err) throw err | 152 | if (err) throw err |
158 | expect(test).to.equal(true) | 153 | expect(test).to.equal(true) |
159 | 154 | ||
@@ -163,7 +158,7 @@ describe('Test a single pod', function () { | |||
163 | }) | 158 | }) |
164 | 159 | ||
165 | it('Should search the video by podUrl', function (done) { | 160 | it('Should search the video by podUrl', function (done) { |
166 | utils.searchVideo(server.url, '9001', 'podUrl', function (err, res) { | 161 | videosUtils.searchVideo(server.url, '9001', 'podUrl', function (err, res) { |
167 | if (err) throw err | 162 | if (err) throw err |
168 | 163 | ||
169 | expect(res.body.total).to.equal(1) | 164 | expect(res.body.total).to.equal(1) |
@@ -177,9 +172,9 @@ describe('Test a single pod', function () { | |||
177 | expect(video.author).to.equal('root') | 172 | expect(video.author).to.equal('root') |
178 | expect(video.isLocal).to.be.true | 173 | expect(video.isLocal).to.be.true |
179 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 174 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
180 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 175 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
181 | 176 | ||
182 | utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 177 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
183 | if (err) throw err | 178 | if (err) throw err |
184 | expect(test).to.equal(true) | 179 | expect(test).to.equal(true) |
185 | 180 | ||
@@ -189,7 +184,7 @@ describe('Test a single pod', function () { | |||
189 | }) | 184 | }) |
190 | 185 | ||
191 | it('Should search the video by tag', function (done) { | 186 | it('Should search the video by tag', function (done) { |
192 | utils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { | 187 | videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { |
193 | if (err) throw err | 188 | if (err) throw err |
194 | 189 | ||
195 | expect(res.body.total).to.equal(1) | 190 | expect(res.body.total).to.equal(1) |
@@ -203,9 +198,9 @@ describe('Test a single pod', function () { | |||
203 | expect(video.author).to.equal('root') | 198 | expect(video.author).to.equal('root') |
204 | expect(video.isLocal).to.be.true | 199 | expect(video.isLocal).to.be.true |
205 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 200 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
206 | expect(utils.dateIsValid(video.createdDate)).to.be.true | 201 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true |
207 | 202 | ||
208 | utils.testImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 203 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
209 | if (err) throw err | 204 | if (err) throw err |
210 | expect(test).to.equal(true) | 205 | expect(test).to.equal(true) |
211 | 206 | ||
@@ -215,7 +210,7 @@ describe('Test a single pod', function () { | |||
215 | }) | 210 | }) |
216 | 211 | ||
217 | it('Should not find a search by name by default', function (done) { | 212 | it('Should not find a search by name by default', function (done) { |
218 | utils.searchVideo(server.url, 'hello', function (err, res) { | 213 | videosUtils.searchVideo(server.url, 'hello', function (err, res) { |
219 | if (err) throw err | 214 | if (err) throw err |
220 | 215 | ||
221 | expect(res.body.total).to.equal(0) | 216 | expect(res.body.total).to.equal(0) |
@@ -227,7 +222,7 @@ describe('Test a single pod', function () { | |||
227 | }) | 222 | }) |
228 | 223 | ||
229 | it('Should not find a search by author', function (done) { | 224 | it('Should not find a search by author', function (done) { |
230 | utils.searchVideo(server.url, 'hello', 'author', function (err, res) { | 225 | videosUtils.searchVideo(server.url, 'hello', 'author', function (err, res) { |
231 | if (err) throw err | 226 | if (err) throw err |
232 | 227 | ||
233 | expect(res.body.total).to.equal(0) | 228 | expect(res.body.total).to.equal(0) |
@@ -239,7 +234,7 @@ describe('Test a single pod', function () { | |||
239 | }) | 234 | }) |
240 | 235 | ||
241 | it('Should not find a search by tag', function (done) { | 236 | it('Should not find a search by tag', function (done) { |
242 | utils.searchVideo(server.url, 'tag', 'tags', function (err, res) { | 237 | videosUtils.searchVideo(server.url, 'tag', 'tags', function (err, res) { |
243 | if (err) throw err | 238 | if (err) throw err |
244 | 239 | ||
245 | expect(res.body.total).to.equal(0) | 240 | expect(res.body.total).to.equal(0) |
@@ -251,7 +246,7 @@ describe('Test a single pod', function () { | |||
251 | }) | 246 | }) |
252 | 247 | ||
253 | it('Should remove the video', function (done) { | 248 | it('Should remove the video', function (done) { |
254 | utils.removeVideo(server.url, server.accessToken, videoId, function (err) { | 249 | videosUtils.removeVideo(server.url, server.accessToken, videoId, function (err) { |
255 | if (err) throw err | 250 | if (err) throw err |
256 | 251 | ||
257 | fs.readdir(pathUtils.join(__dirname, '../../../test1/uploads/'), function (err, files) { | 252 | fs.readdir(pathUtils.join(__dirname, '../../../test1/uploads/'), function (err, files) { |
@@ -264,7 +259,7 @@ describe('Test a single pod', function () { | |||
264 | }) | 259 | }) |
265 | 260 | ||
266 | it('Should not have videos', function (done) { | 261 | it('Should not have videos', function (done) { |
267 | utils.getVideosList(server.url, function (err, res) { | 262 | videosUtils.getVideosList(server.url, function (err, res) { |
268 | if (err) throw err | 263 | if (err) throw err |
269 | 264 | ||
270 | expect(res.body.total).to.equal(0) | 265 | expect(res.body.total).to.equal(0) |
@@ -286,12 +281,12 @@ describe('Test a single pod', function () { | |||
286 | const description = video + ' description' | 281 | const description = video + ' description' |
287 | const tags = [ 'tag1', 'tag2', 'tag3' ] | 282 | const tags = [ 'tag1', 'tag2', 'tag3' ] |
288 | 283 | ||
289 | utils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach) | 284 | videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, video, callbackEach) |
290 | }, done) | 285 | }, done) |
291 | }) | 286 | }) |
292 | 287 | ||
293 | it('Should have the correct durations', function (done) { | 288 | it('Should have the correct durations', function (done) { |
294 | utils.getVideosList(server.url, function (err, res) { | 289 | videosUtils.getVideosList(server.url, function (err, res) { |
295 | if (err) throw err | 290 | if (err) throw err |
296 | 291 | ||
297 | expect(res.body.total).to.equal(6) | 292 | expect(res.body.total).to.equal(6) |
@@ -312,7 +307,7 @@ describe('Test a single pod', function () { | |||
312 | }) | 307 | }) |
313 | 308 | ||
314 | it('Should have the correct thumbnails', function (done) { | 309 | it('Should have the correct thumbnails', function (done) { |
315 | utils.getVideosList(server.url, function (err, res) { | 310 | videosUtils.getVideosList(server.url, function (err, res) { |
316 | if (err) throw err | 311 | if (err) throw err |
317 | 312 | ||
318 | const videos = res.body.data | 313 | const videos = res.body.data |
@@ -323,7 +318,7 @@ describe('Test a single pod', function () { | |||
323 | if (err) throw err | 318 | if (err) throw err |
324 | const videoName = video.name.replace(' name', '') | 319 | const videoName = video.name.replace(' name', '') |
325 | 320 | ||
326 | utils.testImage(server.url, videoName, video.thumbnailPath, function (err, test) { | 321 | videosUtils.testVideoImage(server.url, videoName, video.thumbnailPath, function (err, test) { |
327 | if (err) throw err | 322 | if (err) throw err |
328 | 323 | ||
329 | expect(test).to.equal(true) | 324 | expect(test).to.equal(true) |
@@ -334,7 +329,7 @@ describe('Test a single pod', function () { | |||
334 | }) | 329 | }) |
335 | 330 | ||
336 | it('Should list only the two first videos', function (done) { | 331 | it('Should list only the two first videos', function (done) { |
337 | utils.getVideosListPagination(server.url, 0, 2, function (err, res) { | 332 | videosUtils.getVideosListPagination(server.url, 0, 2, function (err, res) { |
338 | if (err) throw err | 333 | if (err) throw err |
339 | 334 | ||
340 | const videos = res.body.data | 335 | const videos = res.body.data |
@@ -348,7 +343,7 @@ describe('Test a single pod', function () { | |||
348 | }) | 343 | }) |
349 | 344 | ||
350 | it('Should list only the next three videos', function (done) { | 345 | it('Should list only the next three videos', function (done) { |
351 | utils.getVideosListPagination(server.url, 2, 3, function (err, res) { | 346 | videosUtils.getVideosListPagination(server.url, 2, 3, function (err, res) { |
352 | if (err) throw err | 347 | if (err) throw err |
353 | 348 | ||
354 | const videos = res.body.data | 349 | const videos = res.body.data |
@@ -363,7 +358,7 @@ describe('Test a single pod', function () { | |||
363 | }) | 358 | }) |
364 | 359 | ||
365 | it('Should list the last video', function (done) { | 360 | it('Should list the last video', function (done) { |
366 | utils.getVideosListPagination(server.url, 5, 6, function (err, res) { | 361 | videosUtils.getVideosListPagination(server.url, 5, 6, function (err, res) { |
367 | if (err) throw err | 362 | if (err) throw err |
368 | 363 | ||
369 | const videos = res.body.data | 364 | const videos = res.body.data |
@@ -376,7 +371,7 @@ describe('Test a single pod', function () { | |||
376 | }) | 371 | }) |
377 | 372 | ||
378 | it('Should search the first video', function (done) { | 373 | it('Should search the first video', function (done) { |
379 | utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { | 374 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { |
380 | if (err) throw err | 375 | if (err) throw err |
381 | 376 | ||
382 | const videos = res.body.data | 377 | const videos = res.body.data |
@@ -389,7 +384,7 @@ describe('Test a single pod', function () { | |||
389 | }) | 384 | }) |
390 | 385 | ||
391 | it('Should search the last two videos', function (done) { | 386 | it('Should search the last two videos', function (done) { |
392 | utils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { | 387 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { |
393 | if (err) throw err | 388 | if (err) throw err |
394 | 389 | ||
395 | const videos = res.body.data | 390 | const videos = res.body.data |
@@ -403,7 +398,7 @@ describe('Test a single pod', function () { | |||
403 | }) | 398 | }) |
404 | 399 | ||
405 | it('Should search all the webm videos', function (done) { | 400 | it('Should search all the webm videos', function (done) { |
406 | utils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) { | 401 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 15, function (err, res) { |
407 | if (err) throw err | 402 | if (err) throw err |
408 | 403 | ||
409 | const videos = res.body.data | 404 | const videos = res.body.data |
@@ -415,7 +410,7 @@ describe('Test a single pod', function () { | |||
415 | }) | 410 | }) |
416 | 411 | ||
417 | it('Should search all the root author videos', function (done) { | 412 | it('Should search all the root author videos', function (done) { |
418 | utils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) { | 413 | videosUtils.searchVideoWithPagination(server.url, 'root', 'author', 0, 15, function (err, res) { |
419 | if (err) throw err | 414 | if (err) throw err |
420 | 415 | ||
421 | const videos = res.body.data | 416 | const videos = res.body.data |
@@ -427,7 +422,7 @@ describe('Test a single pod', function () { | |||
427 | }) | 422 | }) |
428 | 423 | ||
429 | it('Should search all the 9001 port videos', function (done) { | 424 | it('Should search all the 9001 port videos', function (done) { |
430 | utils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) { | 425 | videosUtils.searchVideoWithPagination(server.url, '9001', 'podUrl', 0, 15, function (err, res) { |
431 | if (err) throw err | 426 | if (err) throw err |
432 | 427 | ||
433 | const videos = res.body.data | 428 | const videos = res.body.data |
@@ -439,7 +434,7 @@ describe('Test a single pod', function () { | |||
439 | }) | 434 | }) |
440 | 435 | ||
441 | it('Should search all the localhost videos', function (done) { | 436 | it('Should search all the localhost videos', function (done) { |
442 | utils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) { | 437 | videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podUrl', 0, 15, function (err, res) { |
443 | if (err) throw err | 438 | if (err) throw err |
444 | 439 | ||
445 | const videos = res.body.data | 440 | const videos = res.body.data |
@@ -452,7 +447,7 @@ describe('Test a single pod', function () { | |||
452 | 447 | ||
453 | it('Should search the good magnetUri video', function (done) { | 448 | it('Should search the good magnetUri video', function (done) { |
454 | const video = videosListBase[0] | 449 | const video = videosListBase[0] |
455 | utils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) { | 450 | videosUtils.searchVideoWithPagination(server.url, encodeURIComponent(video.magnetUri), 'magnetUri', 0, 15, function (err, res) { |
456 | if (err) throw err | 451 | if (err) throw err |
457 | 452 | ||
458 | const videos = res.body.data | 453 | const videos = res.body.data |
@@ -465,7 +460,7 @@ describe('Test a single pod', function () { | |||
465 | }) | 460 | }) |
466 | 461 | ||
467 | it('Should list and sort by name in descending order', function (done) { | 462 | it('Should list and sort by name in descending order', function (done) { |
468 | utils.getVideosListSort(server.url, '-name', function (err, res) { | 463 | videosUtils.getVideosListSort(server.url, '-name', function (err, res) { |
469 | if (err) throw err | 464 | if (err) throw err |
470 | 465 | ||
471 | const videos = res.body.data | 466 | const videos = res.body.data |
@@ -483,7 +478,7 @@ describe('Test a single pod', function () { | |||
483 | }) | 478 | }) |
484 | 479 | ||
485 | it('Should search and sort by name in ascending order', function (done) { | 480 | it('Should search and sort by name in ascending order', function (done) { |
486 | utils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) { | 481 | videosUtils.searchVideoWithSort(server.url, 'webm', 'name', function (err, res) { |
487 | if (err) throw err | 482 | if (err) throw err |
488 | 483 | ||
489 | const videos = res.body.data | 484 | const videos = res.body.data |
@@ -505,7 +500,7 @@ describe('Test a single pod', function () { | |||
505 | 500 | ||
506 | // Keep the logs if the test failed | 501 | // Keep the logs if the test failed |
507 | if (this.ok) { | 502 | if (this.ok) { |
508 | utils.flushTests(done) | 503 | serversUtils.flushTests(done) |
509 | } else { | 504 | } else { |
510 | done() | 505 | done() |
511 | } | 506 | } |
diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 68ba9de33..c6c892bf2 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js | |||
@@ -5,25 +5,30 @@ const expect = chai.expect | |||
5 | const pathUtils = require('path') | 5 | const pathUtils = require('path') |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
7 | 7 | ||
8 | const loginUtils = require('../utils/login') | ||
9 | const podsUtils = require('../utils/pods') | ||
10 | const serversUtils = require('../utils/servers') | ||
11 | const usersUtils = require('../utils/users') | ||
12 | const videosUtils = require('../utils/videos') | ||
8 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) | 13 | const webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent')) |
9 | webtorrent.silent = true | 14 | webtorrent.silent = true |
10 | 15 | ||
11 | const utils = require('./utils') | ||
12 | |||
13 | describe('Test users', function () { | 16 | describe('Test users', function () { |
14 | let server = null | 17 | let server = null |
15 | let accessToken = null | 18 | let accessToken = null |
16 | let videoId | 19 | let accessTokenUser = null |
20 | let videoId = null | ||
21 | let userId = null | ||
17 | 22 | ||
18 | before(function (done) { | 23 | before(function (done) { |
19 | this.timeout(20000) | 24 | this.timeout(20000) |
20 | 25 | ||
21 | series([ | 26 | series([ |
22 | function (next) { | 27 | function (next) { |
23 | utils.flushTests(next) | 28 | serversUtils.flushTests(next) |
24 | }, | 29 | }, |
25 | function (next) { | 30 | function (next) { |
26 | utils.runServer(1, function (server1) { | 31 | serversUtils.runServer(1, function (server1) { |
27 | server = server1 | 32 | server = server1 |
28 | next() | 33 | next() |
29 | }) | 34 | }) |
@@ -39,7 +44,7 @@ describe('Test users', function () { | |||
39 | 44 | ||
40 | it('Should not login with an invalid client id', function (done) { | 45 | it('Should not login with an invalid client id', function (done) { |
41 | const client = { id: 'client', password: server.client.secret } | 46 | const client = { id: 'client', password: server.client.secret } |
42 | utils.login(server.url, client, server.user, 400, function (err, res) { | 47 | loginUtils.login(server.url, client, server.user, 400, function (err, res) { |
43 | if (err) throw err | 48 | if (err) throw err |
44 | 49 | ||
45 | expect(res.body.error).to.equal('invalid_client') | 50 | expect(res.body.error).to.equal('invalid_client') |
@@ -49,7 +54,7 @@ describe('Test users', function () { | |||
49 | 54 | ||
50 | it('Should not login with an invalid client password', function (done) { | 55 | it('Should not login with an invalid client password', function (done) { |
51 | const client = { id: server.client.id, password: 'coucou' } | 56 | const client = { id: server.client.id, password: 'coucou' } |
52 | utils.login(server.url, client, server.user, 400, function (err, res) { | 57 | loginUtils.login(server.url, client, server.user, 400, function (err, res) { |
53 | if (err) throw err | 58 | if (err) throw err |
54 | 59 | ||
55 | expect(res.body.error).to.equal('invalid_client') | 60 | expect(res.body.error).to.equal('invalid_client') |
@@ -59,7 +64,7 @@ describe('Test users', function () { | |||
59 | 64 | ||
60 | it('Should not login with an invalid username', function (done) { | 65 | it('Should not login with an invalid username', function (done) { |
61 | const user = { username: 'captain crochet', password: server.user.password } | 66 | const user = { username: 'captain crochet', password: server.user.password } |
62 | utils.login(server.url, server.client, user, 400, function (err, res) { | 67 | loginUtils.login(server.url, server.client, user, 400, function (err, res) { |
63 | if (err) throw err | 68 | if (err) throw err |
64 | 69 | ||
65 | expect(res.body.error).to.equal('invalid_grant') | 70 | expect(res.body.error).to.equal('invalid_grant') |
@@ -69,7 +74,7 @@ describe('Test users', function () { | |||
69 | 74 | ||
70 | it('Should not login with an invalid password', function (done) { | 75 | it('Should not login with an invalid password', function (done) { |
71 | const user = { username: server.user.username, password: 'mewthree' } | 76 | const user = { username: server.user.username, password: 'mewthree' } |
72 | utils.login(server.url, server.client, user, 400, function (err, res) { | 77 | loginUtils.login(server.url, server.client, user, 400, function (err, res) { |
73 | if (err) throw err | 78 | if (err) throw err |
74 | 79 | ||
75 | expect(res.body.error).to.equal('invalid_grant') | 80 | expect(res.body.error).to.equal('invalid_grant') |
@@ -84,21 +89,21 @@ describe('Test users', function () { | |||
84 | const description = 'my super description' | 89 | const description = 'my super description' |
85 | const tags = [ 'tag1', 'tag2' ] | 90 | const tags = [ 'tag1', 'tag2' ] |
86 | const video = 'video_short.webm' | 91 | const video = 'video_short.webm' |
87 | utils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done) | 92 | videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 401, done) |
88 | }) | 93 | }) |
89 | 94 | ||
90 | it('Should not be able to make friends', function (done) { | 95 | it('Should not be able to make friends', function (done) { |
91 | accessToken = 'mysupertoken' | 96 | accessToken = 'mysupertoken' |
92 | utils.makeFriends(server.url, accessToken, 401, done) | 97 | podsUtils.makeFriends(server.url, accessToken, 401, done) |
93 | }) | 98 | }) |
94 | 99 | ||
95 | it('Should not be able to quit friends', function (done) { | 100 | it('Should not be able to quit friends', function (done) { |
96 | accessToken = 'mysupertoken' | 101 | accessToken = 'mysupertoken' |
97 | utils.quitFriends(server.url, accessToken, 401, done) | 102 | podsUtils.quitFriends(server.url, accessToken, 401, done) |
98 | }) | 103 | }) |
99 | 104 | ||
100 | it('Should be able to login', function (done) { | 105 | it('Should be able to login', function (done) { |
101 | utils.login(server.url, server.client, server.user, 200, function (err, res) { | 106 | loginUtils.login(server.url, server.client, server.user, 200, function (err, res) { |
102 | if (err) throw err | 107 | if (err) throw err |
103 | 108 | ||
104 | accessToken = res.body.access_token | 109 | accessToken = res.body.access_token |
@@ -111,10 +116,10 @@ describe('Test users', function () { | |||
111 | const description = 'my super description' | 116 | const description = 'my super description' |
112 | const tags = [ 'tag1', 'tag2' ] | 117 | const tags = [ 'tag1', 'tag2' ] |
113 | const video = 'video_short.webm' | 118 | const video = 'video_short.webm' |
114 | utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) { | 119 | videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, function (err, res) { |
115 | if (err) throw err | 120 | if (err) throw err |
116 | 121 | ||
117 | utils.getVideosList(server.url, function (err, res) { | 122 | videosUtils.getVideosList(server.url, function (err, res) { |
118 | if (err) throw err | 123 | if (err) throw err |
119 | 124 | ||
120 | const video = res.body.data[0] | 125 | const video = res.body.data[0] |
@@ -131,17 +136,17 @@ describe('Test users', function () { | |||
131 | const description = 'my super description 2' | 136 | const description = 'my super description 2' |
132 | const tags = [ 'tag1' ] | 137 | const tags = [ 'tag1' ] |
133 | const video = 'video_short.webm' | 138 | const video = 'video_short.webm' |
134 | utils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done) | 139 | videosUtils.uploadVideo(server.url, accessToken, name, description, tags, video, 204, done) |
135 | }) | 140 | }) |
136 | 141 | ||
137 | it('Should not be able to remove the video with an incorrect token', function (done) { | 142 | it('Should not be able to remove the video with an incorrect token', function (done) { |
138 | utils.removeVideo(server.url, 'bad_token', videoId, 401, done) | 143 | videosUtils.removeVideo(server.url, 'bad_token', videoId, 401, done) |
139 | }) | 144 | }) |
140 | 145 | ||
141 | it('Should not be able to remove the video with the token of another account') | 146 | it('Should not be able to remove the video with the token of another account') |
142 | 147 | ||
143 | it('Should be able to remove the video with the correct token', function (done) { | 148 | it('Should be able to remove the video with the correct token', function (done) { |
144 | utils.removeVideo(server.url, accessToken, videoId, done) | 149 | videosUtils.removeVideo(server.url, accessToken, videoId, done) |
145 | }) | 150 | }) |
146 | 151 | ||
147 | it('Should logout (revoke token)') | 152 | it('Should logout (revoke token)') |
@@ -158,12 +163,179 @@ describe('Test users', function () { | |||
158 | 163 | ||
159 | it('Should be able to upload a video again') | 164 | it('Should be able to upload a video again') |
160 | 165 | ||
166 | it('Should be able to create a new user', function (done) { | ||
167 | usersUtils.createUser(server.url, accessToken, 'user_1', 'super password', done) | ||
168 | }) | ||
169 | |||
170 | it('Should be able to login with this user', function (done) { | ||
171 | server.user = { | ||
172 | username: 'user_1', | ||
173 | password: 'super password' | ||
174 | } | ||
175 | |||
176 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
177 | if (err) throw err | ||
178 | |||
179 | accessTokenUser = token | ||
180 | |||
181 | done() | ||
182 | }) | ||
183 | }) | ||
184 | |||
185 | it('Should be able to get the user informations', function (done) { | ||
186 | usersUtils.getUserInformation(server.url, accessTokenUser, function (err, res) { | ||
187 | if (err) throw err | ||
188 | |||
189 | const user = res.body | ||
190 | |||
191 | expect(user.username).to.equal('user_1') | ||
192 | expect(user.id).to.exist | ||
193 | |||
194 | done() | ||
195 | }) | ||
196 | }) | ||
197 | |||
198 | it('Should be able to upload a video with this user', function (done) { | ||
199 | this.timeout(5000) | ||
200 | |||
201 | const name = 'my super name' | ||
202 | const description = 'my super description' | ||
203 | const tags = [ 'tag1', 'tag2', 'tag3' ] | ||
204 | const file = 'video_short.webm' | ||
205 | videosUtils.uploadVideo(server.url, accessTokenUser, name, description, tags, file, done) | ||
206 | }) | ||
207 | |||
208 | it('Should list all the users', function (done) { | ||
209 | usersUtils.getUsersList(server.url, function (err, res) { | ||
210 | if (err) throw err | ||
211 | |||
212 | const result = res.body | ||
213 | const total = result.total | ||
214 | const users = result.data | ||
215 | |||
216 | expect(total).to.equal(2) | ||
217 | expect(users).to.be.an('array') | ||
218 | expect(users.length).to.equal(2) | ||
219 | |||
220 | const user = users[0] | ||
221 | expect(user.username).to.equal('user_1') | ||
222 | |||
223 | const rootUser = users[1] | ||
224 | expect(rootUser.username).to.equal('root') | ||
225 | userId = user.id | ||
226 | |||
227 | done() | ||
228 | }) | ||
229 | }) | ||
230 | |||
231 | it('Should list only the first user by username asc', function (done) { | ||
232 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, 'username', function (err, res) { | ||
233 | if (err) throw err | ||
234 | |||
235 | const result = res.body | ||
236 | const total = result.total | ||
237 | const users = result.data | ||
238 | |||
239 | expect(total).to.equal(2) | ||
240 | expect(users.length).to.equal(1) | ||
241 | |||
242 | const user = users[0] | ||
243 | expect(user.username).to.equal('root') | ||
244 | |||
245 | done() | ||
246 | }) | ||
247 | }) | ||
248 | |||
249 | it('Should list only the first user by username desc', function (done) { | ||
250 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-username', function (err, res) { | ||
251 | if (err) throw err | ||
252 | |||
253 | const result = res.body | ||
254 | const total = result.total | ||
255 | const users = result.data | ||
256 | |||
257 | expect(total).to.equal(2) | ||
258 | expect(users.length).to.equal(1) | ||
259 | |||
260 | const user = users[0] | ||
261 | expect(user.username).to.equal('user_1') | ||
262 | |||
263 | done() | ||
264 | }) | ||
265 | }) | ||
266 | |||
267 | it('Should list only the second user by createdDate desc', function (done) { | ||
268 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) { | ||
269 | if (err) throw err | ||
270 | |||
271 | const result = res.body | ||
272 | const total = result.total | ||
273 | const users = result.data | ||
274 | |||
275 | expect(total).to.equal(2) | ||
276 | expect(users.length).to.equal(1) | ||
277 | |||
278 | const user = users[0] | ||
279 | expect(user.username).to.equal('user_1') | ||
280 | |||
281 | done() | ||
282 | }) | ||
283 | }) | ||
284 | |||
285 | it('Should list all the users by createdDate asc', function (done) { | ||
286 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) { | ||
287 | if (err) throw err | ||
288 | |||
289 | const result = res.body | ||
290 | const total = result.total | ||
291 | const users = result.data | ||
292 | |||
293 | expect(total).to.equal(2) | ||
294 | expect(users.length).to.equal(2) | ||
295 | |||
296 | expect(users[0].username).to.equal('root') | ||
297 | expect(users[1].username).to.equal('user_1') | ||
298 | |||
299 | done() | ||
300 | }) | ||
301 | }) | ||
302 | |||
303 | it('Should update the user password', function (done) { | ||
304 | usersUtils.updateUser(server.url, userId, accessTokenUser, 'new password', function (err, res) { | ||
305 | if (err) throw err | ||
306 | |||
307 | server.user.password = 'new password' | ||
308 | loginUtils.login(server.url, server.client, server.user, 200, done) | ||
309 | }) | ||
310 | }) | ||
311 | |||
312 | it('Should be able to remove this user', function (done) { | ||
313 | usersUtils.removeUser(server.url, userId, accessToken, done) | ||
314 | }) | ||
315 | |||
316 | it('Should not be able to login with this user', function (done) { | ||
317 | // server.user is already set to user 1 | ||
318 | loginUtils.login(server.url, server.client, server.user, 400, done) | ||
319 | }) | ||
320 | |||
321 | it('Should not have videos of this user', function (done) { | ||
322 | videosUtils.getVideosList(server.url, function (err, res) { | ||
323 | if (err) throw err | ||
324 | |||
325 | expect(res.body.total).to.equal(1) | ||
326 | const video = res.body.data[0] | ||
327 | expect(video.author).to.equal('root') | ||
328 | |||
329 | done() | ||
330 | }) | ||
331 | }) | ||
332 | |||
161 | after(function (done) { | 333 | after(function (done) { |
162 | process.kill(-server.app.pid) | 334 | process.kill(-server.app.pid) |
163 | 335 | ||
164 | // Keep the logs if the test failed | 336 | // Keep the logs if the test failed |
165 | if (this.ok) { | 337 | if (this.ok) { |
166 | utils.flushTests(done) | 338 | serversUtils.flushTests(done) |
167 | } else { | 339 | } else { |
168 | done() | 340 | done() |
169 | } | 341 | } |
diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js deleted file mode 100644 index 3cc769f26..000000000 --- a/server/tests/api/utils.js +++ /dev/null | |||
@@ -1,419 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const childProcess = require('child_process') | ||
4 | const exec = childProcess.exec | ||
5 | const fork = childProcess.fork | ||
6 | const fs = require('fs') | ||
7 | const pathUtils = require('path') | ||
8 | const request = require('supertest') | ||
9 | |||
10 | const testUtils = { | ||
11 | dateIsValid: dateIsValid, | ||
12 | flushTests: flushTests, | ||
13 | getAllVideosListBy: getAllVideosListBy, | ||
14 | getClient: getClient, | ||
15 | getFriendsList: getFriendsList, | ||
16 | getVideo: getVideo, | ||
17 | getVideosList: getVideosList, | ||
18 | getVideosListPagination: getVideosListPagination, | ||
19 | getVideosListSort: getVideosListSort, | ||
20 | login: login, | ||
21 | loginAndGetAccessToken: loginAndGetAccessToken, | ||
22 | makeFriends: makeFriends, | ||
23 | quitFriends: quitFriends, | ||
24 | removeVideo: removeVideo, | ||
25 | flushAndRunMultipleServers: flushAndRunMultipleServers, | ||
26 | runServer: runServer, | ||
27 | searchVideo: searchVideo, | ||
28 | searchVideoWithPagination: searchVideoWithPagination, | ||
29 | searchVideoWithSort: searchVideoWithSort, | ||
30 | testImage: testImage, | ||
31 | uploadVideo: uploadVideo | ||
32 | } | ||
33 | |||
34 | // ---------------------- Export functions -------------------- | ||
35 | |||
36 | function dateIsValid (dateString) { | ||
37 | const dateToCheck = new Date(dateString) | ||
38 | const now = new Date() | ||
39 | |||
40 | // Check if the interval is more than 2 minutes | ||
41 | if (now - dateToCheck > 120000) return false | ||
42 | |||
43 | return true | ||
44 | } | ||
45 | |||
46 | function flushTests (callback) { | ||
47 | exec('npm run clean:server:test', callback) | ||
48 | } | ||
49 | |||
50 | function getAllVideosListBy (url, end) { | ||
51 | const path = '/api/v1/videos' | ||
52 | |||
53 | request(url) | ||
54 | .get(path) | ||
55 | .query({ sort: 'createdDate' }) | ||
56 | .query({ start: 0 }) | ||
57 | .query({ count: 10000 }) | ||
58 | .set('Accept', 'application/json') | ||
59 | .expect(200) | ||
60 | .expect('Content-Type', /json/) | ||
61 | .end(end) | ||
62 | } | ||
63 | |||
64 | function getClient (url, end) { | ||
65 | const path = '/api/v1/users/client' | ||
66 | |||
67 | request(url) | ||
68 | .get(path) | ||
69 | .set('Accept', 'application/json') | ||
70 | .expect(200) | ||
71 | .expect('Content-Type', /json/) | ||
72 | .end(end) | ||
73 | } | ||
74 | |||
75 | function getFriendsList (url, end) { | ||
76 | const path = '/api/v1/pods/' | ||
77 | |||
78 | request(url) | ||
79 | .get(path) | ||
80 | .set('Accept', 'application/json') | ||
81 | .expect(200) | ||
82 | .expect('Content-Type', /json/) | ||
83 | .end(end) | ||
84 | } | ||
85 | |||
86 | function getVideo (url, id, end) { | ||
87 | const path = '/api/v1/videos/' + id | ||
88 | |||
89 | request(url) | ||
90 | .get(path) | ||
91 | .set('Accept', 'application/json') | ||
92 | .expect(200) | ||
93 | .expect('Content-Type', /json/) | ||
94 | .end(end) | ||
95 | } | ||
96 | |||
97 | function getVideosList (url, end) { | ||
98 | const path = '/api/v1/videos' | ||
99 | |||
100 | request(url) | ||
101 | .get(path) | ||
102 | .query({ sort: 'name' }) | ||
103 | .set('Accept', 'application/json') | ||
104 | .expect(200) | ||
105 | .expect('Content-Type', /json/) | ||
106 | .end(end) | ||
107 | } | ||
108 | |||
109 | function getVideosListPagination (url, start, count, end) { | ||
110 | const path = '/api/v1/videos' | ||
111 | |||
112 | request(url) | ||
113 | .get(path) | ||
114 | .query({ start: start }) | ||
115 | .query({ count: count }) | ||
116 | .set('Accept', 'application/json') | ||
117 | .expect(200) | ||
118 | .expect('Content-Type', /json/) | ||
119 | .end(end) | ||
120 | } | ||
121 | |||
122 | function getVideosListSort (url, sort, end) { | ||
123 | const path = '/api/v1/videos' | ||
124 | |||
125 | request(url) | ||
126 | .get(path) | ||
127 | .query({ sort: sort }) | ||
128 | .set('Accept', 'application/json') | ||
129 | .expect(200) | ||
130 | .expect('Content-Type', /json/) | ||
131 | .end(end) | ||
132 | } | ||
133 | |||
134 | function login (url, client, user, expectedStatus, end) { | ||
135 | if (!end) { | ||
136 | end = expectedStatus | ||
137 | expectedStatus = 200 | ||
138 | } | ||
139 | |||
140 | const path = '/api/v1/users/token' | ||
141 | |||
142 | const body = { | ||
143 | client_id: client.id, | ||
144 | client_secret: client.secret, | ||
145 | username: user.username, | ||
146 | password: user.password, | ||
147 | response_type: 'code', | ||
148 | grant_type: 'password', | ||
149 | scope: 'upload' | ||
150 | } | ||
151 | |||
152 | request(url) | ||
153 | .post(path) | ||
154 | .type('form') | ||
155 | .send(body) | ||
156 | .expect(expectedStatus) | ||
157 | .end(end) | ||
158 | } | ||
159 | |||
160 | function loginAndGetAccessToken (server, callback) { | ||
161 | login(server.url, server.client, server.user, 200, function (err, res) { | ||
162 | if (err) return callback(err) | ||
163 | |||
164 | return callback(null, res.body.access_token) | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | function makeFriends (url, accessToken, expectedStatus, callback) { | ||
169 | if (!callback) { | ||
170 | callback = expectedStatus | ||
171 | expectedStatus = 204 | ||
172 | } | ||
173 | |||
174 | const path = '/api/v1/pods/makefriends' | ||
175 | |||
176 | // The first pod make friend with the third | ||
177 | request(url) | ||
178 | .get(path) | ||
179 | .set('Accept', 'application/json') | ||
180 | .set('Authorization', 'Bearer ' + accessToken) | ||
181 | .expect(expectedStatus) | ||
182 | .end(function (err, res) { | ||
183 | if (err) throw err | ||
184 | |||
185 | // Wait for the request between pods | ||
186 | setTimeout(callback, 1000) | ||
187 | }) | ||
188 | } | ||
189 | |||
190 | function quitFriends (url, accessToken, expectedStatus, callback) { | ||
191 | if (!callback) { | ||
192 | callback = expectedStatus | ||
193 | expectedStatus = 204 | ||
194 | } | ||
195 | |||
196 | const path = '/api/v1/pods/quitfriends' | ||
197 | |||
198 | // The first pod make friend with the third | ||
199 | request(url) | ||
200 | .get(path) | ||
201 | .set('Accept', 'application/json') | ||
202 | .set('Authorization', 'Bearer ' + accessToken) | ||
203 | .expect(expectedStatus) | ||
204 | .end(function (err, res) { | ||
205 | if (err) throw err | ||
206 | |||
207 | // Wait for the request between pods | ||
208 | setTimeout(callback, 1000) | ||
209 | }) | ||
210 | } | ||
211 | |||
212 | function removeVideo (url, token, id, expectedStatus, end) { | ||
213 | if (!end) { | ||
214 | end = expectedStatus | ||
215 | expectedStatus = 204 | ||
216 | } | ||
217 | |||
218 | const path = '/api/v1/videos' | ||
219 | |||
220 | request(url) | ||
221 | .delete(path + '/' + id) | ||
222 | .set('Accept', 'application/json') | ||
223 | .set('Authorization', 'Bearer ' + token) | ||
224 | .expect(expectedStatus) | ||
225 | .end(end) | ||
226 | } | ||
227 | |||
228 | function flushAndRunMultipleServers (totalServers, serversRun) { | ||
229 | let apps = [] | ||
230 | let urls = [] | ||
231 | let i = 0 | ||
232 | |||
233 | function anotherServerDone (number, app, url) { | ||
234 | apps[number - 1] = app | ||
235 | urls[number - 1] = url | ||
236 | i++ | ||
237 | if (i === totalServers) { | ||
238 | serversRun(apps, urls) | ||
239 | } | ||
240 | } | ||
241 | |||
242 | flushTests(function () { | ||
243 | for (let j = 1; j <= totalServers; j++) { | ||
244 | // For the virtual buffer | ||
245 | setTimeout(function () { | ||
246 | runServer(j, function (app, url) { | ||
247 | anotherServerDone(j, app, url) | ||
248 | }) | ||
249 | }, 1000 * j) | ||
250 | } | ||
251 | }) | ||
252 | } | ||
253 | |||
254 | function runServer (number, callback) { | ||
255 | const server = { | ||
256 | app: null, | ||
257 | url: `http://localhost:${9000 + number}`, | ||
258 | client: { | ||
259 | id: null, | ||
260 | secret: null | ||
261 | }, | ||
262 | user: { | ||
263 | username: null, | ||
264 | password: null | ||
265 | } | ||
266 | } | ||
267 | |||
268 | // These actions are async so we need to be sure that they have both been done | ||
269 | const serverRunString = { | ||
270 | 'Connected to mongodb': false, | ||
271 | 'Server listening on port': false | ||
272 | } | ||
273 | |||
274 | const regexps = { | ||
275 | client_id: 'Client id: ([a-f0-9]+)', | ||
276 | client_secret: 'Client secret: (.+)', | ||
277 | user_username: 'Username: (.+)', | ||
278 | user_password: 'User password: (.+)' | ||
279 | } | ||
280 | |||
281 | // Share the environment | ||
282 | const env = Object.create(process.env) | ||
283 | env.NODE_ENV = 'test' | ||
284 | env.NODE_APP_INSTANCE = number | ||
285 | const options = { | ||
286 | silent: true, | ||
287 | env: env, | ||
288 | detached: true | ||
289 | } | ||
290 | |||
291 | server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options) | ||
292 | server.app.stdout.on('data', function onStdout (data) { | ||
293 | let dontContinue = false | ||
294 | |||
295 | // Capture things if we want to | ||
296 | for (const key of Object.keys(regexps)) { | ||
297 | const regexp = regexps[key] | ||
298 | const matches = data.toString().match(regexp) | ||
299 | if (matches !== null) { | ||
300 | if (key === 'client_id') server.client.id = matches[1] | ||
301 | else if (key === 'client_secret') server.client.secret = matches[1] | ||
302 | else if (key === 'user_username') server.user.username = matches[1] | ||
303 | else if (key === 'user_password') server.user.password = matches[1] | ||
304 | } | ||
305 | } | ||
306 | |||
307 | // Check if all required sentences are here | ||
308 | for (const key of Object.keys(serverRunString)) { | ||
309 | if (data.toString().indexOf(key) !== -1) serverRunString[key] = true | ||
310 | if (serverRunString[key] === false) dontContinue = true | ||
311 | } | ||
312 | |||
313 | // If no, there is maybe one thing not already initialized (mongodb...) | ||
314 | if (dontContinue === true) return | ||
315 | |||
316 | server.app.stdout.removeListener('data', onStdout) | ||
317 | callback(server) | ||
318 | }) | ||
319 | } | ||
320 | |||
321 | function searchVideo (url, search, field, end) { | ||
322 | if (!end) { | ||
323 | end = field | ||
324 | field = null | ||
325 | } | ||
326 | |||
327 | const path = '/api/v1/videos' | ||
328 | const req = request(url) | ||
329 | .get(path + '/search/' + search) | ||
330 | .set('Accept', 'application/json') | ||
331 | |||
332 | if (field) req.query({ field: field }) | ||
333 | req.expect(200) | ||
334 | .expect('Content-Type', /json/) | ||
335 | .end(end) | ||
336 | } | ||
337 | |||
338 | function searchVideoWithPagination (url, search, field, start, count, end) { | ||
339 | const path = '/api/v1/videos' | ||
340 | |||
341 | request(url) | ||
342 | .get(path + '/search/' + search) | ||
343 | .query({ start: start }) | ||
344 | .query({ count: count }) | ||
345 | .query({ field: field }) | ||
346 | .set('Accept', 'application/json') | ||
347 | .expect(200) | ||
348 | .expect('Content-Type', /json/) | ||
349 | .end(end) | ||
350 | } | ||
351 | |||
352 | function searchVideoWithSort (url, search, sort, end) { | ||
353 | const path = '/api/v1/videos' | ||
354 | |||
355 | request(url) | ||
356 | .get(path + '/search/' + search) | ||
357 | .query({ sort: sort }) | ||
358 | .set('Accept', 'application/json') | ||
359 | .expect(200) | ||
360 | .expect('Content-Type', /json/) | ||
361 | .end(end) | ||
362 | } | ||
363 | |||
364 | function testImage (url, videoName, imagePath, callback) { | ||
365 | // Don't test images if the node env is not set | ||
366 | // Because we need a special ffmpeg version for this test | ||
367 | if (process.env.NODE_TEST_IMAGE) { | ||
368 | request(url) | ||
369 | .get(imagePath) | ||
370 | .expect(200) | ||
371 | .end(function (err, res) { | ||
372 | if (err) return callback(err) | ||
373 | |||
374 | fs.readFile(pathUtils.join(__dirname, 'fixtures', videoName + '.jpg'), function (err, data) { | ||
375 | if (err) return callback(err) | ||
376 | |||
377 | callback(null, data.equals(res.body)) | ||
378 | }) | ||
379 | }) | ||
380 | } else { | ||
381 | console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.') | ||
382 | callback(null, true) | ||
383 | } | ||
384 | } | ||
385 | |||
386 | function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) { | ||
387 | if (!end) { | ||
388 | end = specialStatus | ||
389 | specialStatus = 204 | ||
390 | } | ||
391 | |||
392 | const path = '/api/v1/videos' | ||
393 | |||
394 | const req = request(url) | ||
395 | .post(path) | ||
396 | .set('Accept', 'application/json') | ||
397 | .set('Authorization', 'Bearer ' + accessToken) | ||
398 | .field('name', name) | ||
399 | .field('description', description) | ||
400 | |||
401 | for (let i = 0; i < tags.length; i++) { | ||
402 | req.field('tags[' + i + ']', tags[i]) | ||
403 | } | ||
404 | |||
405 | let filepath = '' | ||
406 | if (pathUtils.isAbsolute(fixture)) { | ||
407 | filepath = fixture | ||
408 | } else { | ||
409 | filepath = pathUtils.join(__dirname, 'fixtures', fixture) | ||
410 | } | ||
411 | |||
412 | req.attach('videofile', filepath) | ||
413 | .expect(specialStatus) | ||
414 | .end(end) | ||
415 | } | ||
416 | |||
417 | // --------------------------------------------------------------------------- | ||
418 | |||
419 | module.exports = testUtils | ||
diff --git a/server/tests/real-world/real-world.js b/server/tests/real-world/real-world.js index b28796852..dba1970c5 100644 --- a/server/tests/real-world/real-world.js +++ b/server/tests/real-world/real-world.js | |||
@@ -1,6 +1,6 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const each = require('each') | 3 | const each = require('async/each') |
4 | const isEqual = require('lodash/isEqual') | 4 | const isEqual = require('lodash/isEqual') |
5 | const program = require('commander') | 5 | const program = require('commander') |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
@@ -8,7 +8,10 @@ const series = require('async/series') | |||
8 | process.env.NODE_ENV = 'test' | 8 | process.env.NODE_ENV = 'test' |
9 | const constants = require('../../initializers/constants') | 9 | const constants = require('../../initializers/constants') |
10 | 10 | ||
11 | const utils = require('../api/utils') | 11 | const loginUtils = require('../utils/login') |
12 | const podsUtils = require('../utils/pods') | ||
13 | const serversUtils = require('../utils/servers') | ||
14 | const videosUtils = require('../utils/videos') | ||
12 | 15 | ||
13 | program | 16 | program |
14 | .option('-c, --create [weight]', 'Weight for creating videos') | 17 | .option('-c, --create [weight]', 'Weight for creating videos') |
@@ -97,7 +100,7 @@ function runServers (numberOfPods, callback) { | |||
97 | series([ | 100 | series([ |
98 | // Run servers | 101 | // Run servers |
99 | function (next) { | 102 | function (next) { |
100 | utils.flushAndRunMultipleServers(numberOfPods, function (serversRun) { | 103 | serversUtils.flushAndRunMultipleServers(numberOfPods, function (serversRun) { |
101 | servers = serversRun | 104 | servers = serversRun |
102 | next() | 105 | next() |
103 | }) | 106 | }) |
@@ -105,7 +108,7 @@ function runServers (numberOfPods, callback) { | |||
105 | // Get the access tokens | 108 | // Get the access tokens |
106 | function (next) { | 109 | function (next) { |
107 | each(servers, function (server, callbackEach) { | 110 | each(servers, function (server, callbackEach) { |
108 | utils.loginAndGetAccessToken(server, function (err, accessToken) { | 111 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { |
109 | if (err) return callbackEach(err) | 112 | if (err) return callbackEach(err) |
110 | 113 | ||
111 | server.accessToken = accessToken | 114 | server.accessToken = accessToken |
@@ -115,26 +118,26 @@ function runServers (numberOfPods, callback) { | |||
115 | }, | 118 | }, |
116 | function (next) { | 119 | function (next) { |
117 | const server = servers[1] | 120 | const server = servers[1] |
118 | utils.makeFriends(server.url, server.accessToken, next) | 121 | podsUtils.makeFriends(server.url, server.accessToken, next) |
119 | }, | 122 | }, |
120 | function (next) { | 123 | function (next) { |
121 | const server = servers[0] | 124 | const server = servers[0] |
122 | utils.makeFriends(server.url, server.accessToken, next) | 125 | podsUtils.makeFriends(server.url, server.accessToken, next) |
123 | }, | 126 | }, |
124 | function (next) { | 127 | function (next) { |
125 | setTimeout(next, 1000) | 128 | setTimeout(next, 1000) |
126 | }, | 129 | }, |
127 | function (next) { | 130 | function (next) { |
128 | const server = servers[3] | 131 | const server = servers[3] |
129 | utils.makeFriends(server.url, server.accessToken, next) | 132 | podsUtils.makeFriends(server.url, server.accessToken, next) |
130 | }, | 133 | }, |
131 | function (next) { | 134 | function (next) { |
132 | const server = servers[5] | 135 | const server = servers[5] |
133 | utils.makeFriends(server.url, server.accessToken, next) | 136 | podsUtils.makeFriends(server.url, server.accessToken, next) |
134 | }, | 137 | }, |
135 | function (next) { | 138 | function (next) { |
136 | const server = servers[4] | 139 | const server = servers[4] |
137 | utils.makeFriends(server.url, server.accessToken, next) | 140 | podsUtils.makeFriends(server.url, server.accessToken, next) |
138 | }, | 141 | }, |
139 | function (next) { | 142 | function (next) { |
140 | setTimeout(next, 1000) | 143 | setTimeout(next, 1000) |
@@ -151,7 +154,7 @@ function exitServers (servers, callback) { | |||
151 | if (server.app) process.kill(-server.app.pid) | 154 | if (server.app) process.kill(-server.app.pid) |
152 | }) | 155 | }) |
153 | 156 | ||
154 | if (flushAtExit) utils.flushTests(callback) | 157 | if (flushAtExit) serversUtils.flushTests(callback) |
155 | } | 158 | } |
156 | 159 | ||
157 | function upload (servers, numServer, callback) { | 160 | function upload (servers, numServer, callback) { |
@@ -164,13 +167,13 @@ function upload (servers, numServer, callback) { | |||
164 | 167 | ||
165 | console.log('Upload video to server ' + numServer) | 168 | console.log('Upload video to server ' + numServer) |
166 | 169 | ||
167 | utils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) | 170 | videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) |
168 | } | 171 | } |
169 | 172 | ||
170 | function remove (servers, numServer, callback) { | 173 | function remove (servers, numServer, callback) { |
171 | if (!callback) callback = function () {} | 174 | if (!callback) callback = function () {} |
172 | 175 | ||
173 | utils.getVideosList(servers[numServer].url, function (err, res) { | 176 | videosUtils.getVideosList(servers[numServer].url, function (err, res) { |
174 | if (err) throw err | 177 | if (err) throw err |
175 | 178 | ||
176 | const videos = res.body.data | 179 | const videos = res.body.data |
@@ -179,14 +182,14 @@ function remove (servers, numServer, callback) { | |||
179 | const toRemove = videos[getRandomInt(0, videos.length)].id | 182 | const toRemove = videos[getRandomInt(0, videos.length)].id |
180 | 183 | ||
181 | console.log('Removing video from server ' + numServer) | 184 | console.log('Removing video from server ' + numServer) |
182 | utils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback) | 185 | videosUtils.removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove, callback) |
183 | }) | 186 | }) |
184 | } | 187 | } |
185 | 188 | ||
186 | function checkIntegrity (servers, callback) { | 189 | function checkIntegrity (servers, callback) { |
187 | const videos = [] | 190 | const videos = [] |
188 | each(servers, function (server, callback) { | 191 | each(servers, function (server, callback) { |
189 | utils.getAllVideosListBy(server.url, function (err, res) { | 192 | videosUtils.getAllVideosListBy(server.url, function (err, res) { |
190 | if (err) throw err | 193 | if (err) throw err |
191 | const serverVideos = res.body.data | 194 | const serverVideos = res.body.data |
192 | for (const serverVideo of serverVideos) { | 195 | for (const serverVideo of serverVideos) { |
diff --git a/server/tests/utils/clients.js b/server/tests/utils/clients.js new file mode 100644 index 000000000..e3ded493e --- /dev/null +++ b/server/tests/utils/clients.js | |||
@@ -0,0 +1,24 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const clientsUtils = { | ||
6 | getClient: getClient | ||
7 | } | ||
8 | |||
9 | // ---------------------- Export functions -------------------- | ||
10 | |||
11 | function getClient (url, end) { | ||
12 | const path = '/api/v1/users/client' | ||
13 | |||
14 | request(url) | ||
15 | .get(path) | ||
16 | .set('Accept', 'application/json') | ||
17 | .expect(200) | ||
18 | .expect('Content-Type', /json/) | ||
19 | .end(end) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | module.exports = clientsUtils | ||
diff --git a/server/tests/utils/login.js b/server/tests/utils/login.js new file mode 100644 index 000000000..465564e14 --- /dev/null +++ b/server/tests/utils/login.js | |||
@@ -0,0 +1,48 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const loginUtils = { | ||
6 | login, | ||
7 | loginAndGetAccessToken | ||
8 | } | ||
9 | |||
10 | // ---------------------- Export functions -------------------- | ||
11 | |||
12 | function login (url, client, user, expectedStatus, end) { | ||
13 | if (!end) { | ||
14 | end = expectedStatus | ||
15 | expectedStatus = 200 | ||
16 | } | ||
17 | |||
18 | const path = '/api/v1/users/token' | ||
19 | |||
20 | const body = { | ||
21 | client_id: client.id, | ||
22 | client_secret: client.secret, | ||
23 | username: user.username, | ||
24 | password: user.password, | ||
25 | response_type: 'code', | ||
26 | grant_type: 'password', | ||
27 | scope: 'upload' | ||
28 | } | ||
29 | |||
30 | request(url) | ||
31 | .post(path) | ||
32 | .type('form') | ||
33 | .send(body) | ||
34 | .expect(expectedStatus) | ||
35 | .end(end) | ||
36 | } | ||
37 | |||
38 | function loginAndGetAccessToken (server, callback) { | ||
39 | login(server.url, server.client, server.user, 200, function (err, res) { | ||
40 | if (err) return callback(err) | ||
41 | |||
42 | return callback(null, res.body.access_token) | ||
43 | }) | ||
44 | } | ||
45 | |||
46 | // --------------------------------------------------------------------------- | ||
47 | |||
48 | module.exports = loginUtils | ||
diff --git a/server/tests/utils/miscs.js b/server/tests/utils/miscs.js new file mode 100644 index 000000000..4ceff65df --- /dev/null +++ b/server/tests/utils/miscs.js | |||
@@ -0,0 +1,21 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const miscsUtils = { | ||
4 | dateIsValid | ||
5 | } | ||
6 | |||
7 | // ---------------------- Export functions -------------------- | ||
8 | |||
9 | function dateIsValid (dateString) { | ||
10 | const dateToCheck = new Date(dateString) | ||
11 | const now = new Date() | ||
12 | |||
13 | // Check if the interval is more than 2 minutes | ||
14 | if (now - dateToCheck > 120000) return false | ||
15 | |||
16 | return true | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | module.exports = miscsUtils | ||
diff --git a/server/tests/utils/pods.js b/server/tests/utils/pods.js new file mode 100644 index 000000000..a8551a49d --- /dev/null +++ b/server/tests/utils/pods.js | |||
@@ -0,0 +1,95 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const podsUtils = { | ||
6 | getFriendsList, | ||
7 | makeFriends, | ||
8 | quitFriends | ||
9 | } | ||
10 | |||
11 | // ---------------------- Export functions -------------------- | ||
12 | |||
13 | function getFriendsList (url, end) { | ||
14 | const path = '/api/v1/pods/' | ||
15 | |||
16 | request(url) | ||
17 | .get(path) | ||
18 | .set('Accept', 'application/json') | ||
19 | .expect(200) | ||
20 | .expect('Content-Type', /json/) | ||
21 | .end(end) | ||
22 | } | ||
23 | |||
24 | function makeFriends (url, accessToken, expectedStatus, end) { | ||
25 | if (!end) { | ||
26 | end = expectedStatus | ||
27 | expectedStatus = 204 | ||
28 | } | ||
29 | |||
30 | // Which pod makes friends with which pod | ||
31 | const friendsMatrix = { | ||
32 | 'http://localhost:9001': [ | ||
33 | 'http://localhost:9002' | ||
34 | ], | ||
35 | 'http://localhost:9002': [ | ||
36 | 'http://localhost:9003' | ||
37 | ], | ||
38 | 'http://localhost:9003': [ | ||
39 | 'http://localhost:9001' | ||
40 | ], | ||
41 | 'http://localhost:9004': [ | ||
42 | 'http://localhost:9002' | ||
43 | ], | ||
44 | 'http://localhost:9005': [ | ||
45 | 'http://localhost:9001', | ||
46 | 'http://localhost:9004' | ||
47 | ], | ||
48 | 'http://localhost:9006': [ | ||
49 | 'http://localhost:9001', | ||
50 | 'http://localhost:9002', | ||
51 | 'http://localhost:9003' | ||
52 | ] | ||
53 | } | ||
54 | const path = '/api/v1/pods/makefriends' | ||
55 | |||
56 | // The first pod make friend with the third | ||
57 | request(url) | ||
58 | .post(path) | ||
59 | .set('Accept', 'application/json') | ||
60 | .set('Authorization', 'Bearer ' + accessToken) | ||
61 | .send({ 'urls': friendsMatrix[url] }) | ||
62 | .expect(expectedStatus) | ||
63 | .end(function (err, res) { | ||
64 | if (err) throw err | ||
65 | |||
66 | // Wait for the request between pods | ||
67 | setTimeout(end, 1000) | ||
68 | }) | ||
69 | } | ||
70 | |||
71 | function quitFriends (url, accessToken, expectedStatus, end) { | ||
72 | if (!end) { | ||
73 | end = expectedStatus | ||
74 | expectedStatus = 204 | ||
75 | } | ||
76 | |||
77 | const path = '/api/v1/pods/quitfriends' | ||
78 | |||
79 | // The first pod make friend with the third | ||
80 | request(url) | ||
81 | .get(path) | ||
82 | .set('Accept', 'application/json') | ||
83 | .set('Authorization', 'Bearer ' + accessToken) | ||
84 | .expect(expectedStatus) | ||
85 | .end(function (err, res) { | ||
86 | if (err) throw err | ||
87 | |||
88 | // Wait for the request between pods | ||
89 | setTimeout(end, 1000) | ||
90 | }) | ||
91 | } | ||
92 | |||
93 | // --------------------------------------------------------------------------- | ||
94 | |||
95 | module.exports = podsUtils | ||
diff --git a/server/tests/utils/requests.js b/server/tests/utils/requests.js new file mode 100644 index 000000000..b1470814d --- /dev/null +++ b/server/tests/utils/requests.js | |||
@@ -0,0 +1,68 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const requestsUtils = { | ||
6 | makePostUploadRequest, | ||
7 | makePostBodyRequest, | ||
8 | makePutBodyRequest | ||
9 | } | ||
10 | |||
11 | // ---------------------- Export functions -------------------- | ||
12 | |||
13 | function makePostUploadRequest (url, path, token, fields, attaches, done, statusCodeExpected) { | ||
14 | if (!statusCodeExpected) statusCodeExpected = 400 | ||
15 | |||
16 | const req = request(url) | ||
17 | .post(path) | ||
18 | .set('Accept', 'application/json') | ||
19 | |||
20 | if (token) req.set('Authorization', 'Bearer ' + token) | ||
21 | |||
22 | Object.keys(fields).forEach(function (field) { | ||
23 | const value = fields[field] | ||
24 | |||
25 | if (Array.isArray(value)) { | ||
26 | for (let i = 0; i < value.length; i++) { | ||
27 | req.field(field + '[' + i + ']', value[i]) | ||
28 | } | ||
29 | } else { | ||
30 | req.field(field, value) | ||
31 | } | ||
32 | }) | ||
33 | |||
34 | Object.keys(attaches).forEach(function (attach) { | ||
35 | const value = attaches[attach] | ||
36 | req.attach(attach, value) | ||
37 | }) | ||
38 | |||
39 | req.expect(statusCodeExpected, done) | ||
40 | } | ||
41 | |||
42 | function makePostBodyRequest (url, path, token, fields, done, statusCodeExpected) { | ||
43 | if (!statusCodeExpected) statusCodeExpected = 400 | ||
44 | |||
45 | const req = request(url) | ||
46 | .post(path) | ||
47 | .set('Accept', 'application/json') | ||
48 | |||
49 | if (token) req.set('Authorization', 'Bearer ' + token) | ||
50 | |||
51 | req.send(fields).expect(statusCodeExpected, done) | ||
52 | } | ||
53 | |||
54 | function makePutBodyRequest (url, path, token, fields, done, statusCodeExpected) { | ||
55 | if (!statusCodeExpected) statusCodeExpected = 400 | ||
56 | |||
57 | const req = request(url) | ||
58 | .put(path) | ||
59 | .set('Accept', 'application/json') | ||
60 | |||
61 | if (token) req.set('Authorization', 'Bearer ' + token) | ||
62 | |||
63 | req.send(fields).expect(statusCodeExpected, done) | ||
64 | } | ||
65 | |||
66 | // --------------------------------------------------------------------------- | ||
67 | |||
68 | module.exports = requestsUtils | ||
diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js new file mode 100644 index 000000000..d62838bc7 --- /dev/null +++ b/server/tests/utils/servers.js | |||
@@ -0,0 +1,115 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const childProcess = require('child_process') | ||
4 | const exec = childProcess.exec | ||
5 | const fork = childProcess.fork | ||
6 | const pathUtils = require('path') | ||
7 | |||
8 | const serversUtils = { | ||
9 | flushAndRunMultipleServers, | ||
10 | flushTests, | ||
11 | runServer | ||
12 | } | ||
13 | |||
14 | // ---------------------- Export functions -------------------- | ||
15 | |||
16 | function flushAndRunMultipleServers (totalServers, serversRun) { | ||
17 | let apps = [] | ||
18 | let urls = [] | ||
19 | let i = 0 | ||
20 | |||
21 | function anotherServerDone (number, app, url) { | ||
22 | apps[number - 1] = app | ||
23 | urls[number - 1] = url | ||
24 | i++ | ||
25 | if (i === totalServers) { | ||
26 | serversRun(apps, urls) | ||
27 | } | ||
28 | } | ||
29 | |||
30 | flushTests(function () { | ||
31 | for (let j = 1; j <= totalServers; j++) { | ||
32 | // For the virtual buffer | ||
33 | setTimeout(function () { | ||
34 | runServer(j, function (app, url) { | ||
35 | anotherServerDone(j, app, url) | ||
36 | }) | ||
37 | }, 1000 * j) | ||
38 | } | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | function flushTests (callback) { | ||
43 | exec('npm run clean:server:test', callback) | ||
44 | } | ||
45 | |||
46 | function runServer (number, callback) { | ||
47 | const server = { | ||
48 | app: null, | ||
49 | url: `http://localhost:${9000 + number}`, | ||
50 | client: { | ||
51 | id: null, | ||
52 | secret: null | ||
53 | }, | ||
54 | user: { | ||
55 | username: null, | ||
56 | password: null | ||
57 | } | ||
58 | } | ||
59 | |||
60 | // These actions are async so we need to be sure that they have both been done | ||
61 | const serverRunString = { | ||
62 | 'Connected to mongodb': false, | ||
63 | 'Server listening on port': false | ||
64 | } | ||
65 | |||
66 | const regexps = { | ||
67 | client_id: 'Client id: ([a-f0-9]+)', | ||
68 | client_secret: 'Client secret: (.+)', | ||
69 | user_username: 'Username: (.+)', | ||
70 | user_password: 'User password: (.+)' | ||
71 | } | ||
72 | |||
73 | // Share the environment | ||
74 | const env = Object.create(process.env) | ||
75 | env.NODE_ENV = 'test' | ||
76 | env.NODE_APP_INSTANCE = number | ||
77 | const options = { | ||
78 | silent: true, | ||
79 | env: env, | ||
80 | detached: true | ||
81 | } | ||
82 | |||
83 | server.app = fork(pathUtils.join(__dirname, '../../../server.js'), [], options) | ||
84 | server.app.stdout.on('data', function onStdout (data) { | ||
85 | let dontContinue = false | ||
86 | |||
87 | // Capture things if we want to | ||
88 | for (const key of Object.keys(regexps)) { | ||
89 | const regexp = regexps[key] | ||
90 | const matches = data.toString().match(regexp) | ||
91 | if (matches !== null) { | ||
92 | if (key === 'client_id') server.client.id = matches[1] | ||
93 | else if (key === 'client_secret') server.client.secret = matches[1] | ||
94 | else if (key === 'user_username') server.user.username = matches[1] | ||
95 | else if (key === 'user_password') server.user.password = matches[1] | ||
96 | } | ||
97 | } | ||
98 | |||
99 | // Check if all required sentences are here | ||
100 | for (const key of Object.keys(serverRunString)) { | ||
101 | if (data.toString().indexOf(key) !== -1) serverRunString[key] = true | ||
102 | if (serverRunString[key] === false) dontContinue = true | ||
103 | } | ||
104 | |||
105 | // If no, there is maybe one thing not already initialized (mongodb...) | ||
106 | if (dontContinue === true) return | ||
107 | |||
108 | server.app.stdout.removeListener('data', onStdout) | ||
109 | callback(server) | ||
110 | }) | ||
111 | } | ||
112 | |||
113 | // --------------------------------------------------------------------------- | ||
114 | |||
115 | module.exports = serversUtils | ||
diff --git a/server/tests/utils/users.js b/server/tests/utils/users.js new file mode 100644 index 000000000..2bf9c6e3e --- /dev/null +++ b/server/tests/utils/users.js | |||
@@ -0,0 +1,100 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const usersUtils = { | ||
6 | createUser, | ||
7 | getUserInformation, | ||
8 | getUsersList, | ||
9 | getUsersListPaginationAndSort, | ||
10 | removeUser, | ||
11 | updateUser | ||
12 | } | ||
13 | |||
14 | // ---------------------- Export functions -------------------- | ||
15 | |||
16 | function createUser (url, accessToken, username, password, specialStatus, end) { | ||
17 | if (!end) { | ||
18 | end = specialStatus | ||
19 | specialStatus = 204 | ||
20 | } | ||
21 | |||
22 | const path = '/api/v1/users' | ||
23 | |||
24 | request(url) | ||
25 | .post(path) | ||
26 | .set('Accept', 'application/json') | ||
27 | .set('Authorization', 'Bearer ' + accessToken) | ||
28 | .send({ username: username, password: password }) | ||
29 | .expect(specialStatus) | ||
30 | .end(end) | ||
31 | } | ||
32 | |||
33 | function getUserInformation (url, accessToken, end) { | ||
34 | const path = '/api/v1/users/me' | ||
35 | |||
36 | request(url) | ||
37 | .get(path) | ||
38 | .set('Accept', 'application/json') | ||
39 | .set('Authorization', 'Bearer ' + accessToken) | ||
40 | .expect(200) | ||
41 | .expect('Content-Type', /json/) | ||
42 | .end(end) | ||
43 | } | ||
44 | |||
45 | function getUsersList (url, end) { | ||
46 | const path = '/api/v1/users' | ||
47 | |||
48 | request(url) | ||
49 | .get(path) | ||
50 | .set('Accept', 'application/json') | ||
51 | .expect(200) | ||
52 | .expect('Content-Type', /json/) | ||
53 | .end(end) | ||
54 | } | ||
55 | |||
56 | function getUsersListPaginationAndSort (url, start, count, sort, end) { | ||
57 | const path = '/api/v1/users' | ||
58 | |||
59 | request(url) | ||
60 | .get(path) | ||
61 | .query({ start: start }) | ||
62 | .query({ count: count }) | ||
63 | .query({ sort: sort }) | ||
64 | .set('Accept', 'application/json') | ||
65 | .expect(200) | ||
66 | .expect('Content-Type', /json/) | ||
67 | .end(end) | ||
68 | } | ||
69 | |||
70 | function removeUser (url, userId, accessToken, expectedStatus, end) { | ||
71 | if (!end) { | ||
72 | end = expectedStatus | ||
73 | expectedStatus = 204 | ||
74 | } | ||
75 | |||
76 | const path = '/api/v1/users' | ||
77 | |||
78 | request(url) | ||
79 | .delete(path + '/' + userId) | ||
80 | .set('Accept', 'application/json') | ||
81 | .set('Authorization', 'Bearer ' + accessToken) | ||
82 | .expect(expectedStatus) | ||
83 | .end(end) | ||
84 | } | ||
85 | |||
86 | function updateUser (url, userId, accessToken, newPassword, end) { | ||
87 | const path = '/api/v1/users/' + userId | ||
88 | |||
89 | request(url) | ||
90 | .put(path) | ||
91 | .set('Accept', 'application/json') | ||
92 | .set('Authorization', 'Bearer ' + accessToken) | ||
93 | .send({ password: newPassword }) | ||
94 | .expect(204) | ||
95 | .end(end) | ||
96 | } | ||
97 | |||
98 | // --------------------------------------------------------------------------- | ||
99 | |||
100 | module.exports = usersUtils | ||
diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js new file mode 100644 index 000000000..536093db1 --- /dev/null +++ b/server/tests/utils/videos.js | |||
@@ -0,0 +1,199 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const fs = require('fs') | ||
4 | const pathUtils = require('path') | ||
5 | const request = require('supertest') | ||
6 | |||
7 | const videosUtils = { | ||
8 | getAllVideosListBy, | ||
9 | getVideo, | ||
10 | getVideosList, | ||
11 | getVideosListPagination, | ||
12 | getVideosListSort, | ||
13 | removeVideo, | ||
14 | searchVideo, | ||
15 | searchVideoWithPagination, | ||
16 | searchVideoWithSort, | ||
17 | testVideoImage, | ||
18 | uploadVideo | ||
19 | } | ||
20 | |||
21 | // ---------------------- Export functions -------------------- | ||
22 | |||
23 | function getAllVideosListBy (url, end) { | ||
24 | const path = '/api/v1/videos' | ||
25 | |||
26 | request(url) | ||
27 | .get(path) | ||
28 | .query({ sort: 'createdDate' }) | ||
29 | .query({ start: 0 }) | ||
30 | .query({ count: 10000 }) | ||
31 | .set('Accept', 'application/json') | ||
32 | .expect(200) | ||
33 | .expect('Content-Type', /json/) | ||
34 | .end(end) | ||
35 | } | ||
36 | |||
37 | function getVideo (url, id, end) { | ||
38 | const path = '/api/v1/videos/' + id | ||
39 | |||
40 | request(url) | ||
41 | .get(path) | ||
42 | .set('Accept', 'application/json') | ||
43 | .expect(200) | ||
44 | .expect('Content-Type', /json/) | ||
45 | .end(end) | ||
46 | } | ||
47 | |||
48 | function getVideosList (url, end) { | ||
49 | const path = '/api/v1/videos' | ||
50 | |||
51 | request(url) | ||
52 | .get(path) | ||
53 | .query({ sort: 'name' }) | ||
54 | .set('Accept', 'application/json') | ||
55 | .expect(200) | ||
56 | .expect('Content-Type', /json/) | ||
57 | .end(end) | ||
58 | } | ||
59 | |||
60 | function getVideosListPagination (url, start, count, end) { | ||
61 | const path = '/api/v1/videos' | ||
62 | |||
63 | request(url) | ||
64 | .get(path) | ||
65 | .query({ start: start }) | ||
66 | .query({ count: count }) | ||
67 | .set('Accept', 'application/json') | ||
68 | .expect(200) | ||
69 | .expect('Content-Type', /json/) | ||
70 | .end(end) | ||
71 | } | ||
72 | |||
73 | function getVideosListSort (url, sort, end) { | ||
74 | const path = '/api/v1/videos' | ||
75 | |||
76 | request(url) | ||
77 | .get(path) | ||
78 | .query({ sort: sort }) | ||
79 | .set('Accept', 'application/json') | ||
80 | .expect(200) | ||
81 | .expect('Content-Type', /json/) | ||
82 | .end(end) | ||
83 | } | ||
84 | |||
85 | function removeVideo (url, token, id, expectedStatus, end) { | ||
86 | if (!end) { | ||
87 | end = expectedStatus | ||
88 | expectedStatus = 204 | ||
89 | } | ||
90 | |||
91 | const path = '/api/v1/videos' | ||
92 | |||
93 | request(url) | ||
94 | .delete(path + '/' + id) | ||
95 | .set('Accept', 'application/json') | ||
96 | .set('Authorization', 'Bearer ' + token) | ||
97 | .expect(expectedStatus) | ||
98 | .end(end) | ||
99 | } | ||
100 | |||
101 | function searchVideo (url, search, field, end) { | ||
102 | if (!end) { | ||
103 | end = field | ||
104 | field = null | ||
105 | } | ||
106 | |||
107 | const path = '/api/v1/videos' | ||
108 | const req = request(url) | ||
109 | .get(path + '/search/' + search) | ||
110 | .set('Accept', 'application/json') | ||
111 | |||
112 | if (field) req.query({ field: field }) | ||
113 | req.expect(200) | ||
114 | .expect('Content-Type', /json/) | ||
115 | .end(end) | ||
116 | } | ||
117 | |||
118 | function searchVideoWithPagination (url, search, field, start, count, end) { | ||
119 | const path = '/api/v1/videos' | ||
120 | |||
121 | request(url) | ||
122 | .get(path + '/search/' + search) | ||
123 | .query({ start: start }) | ||
124 | .query({ count: count }) | ||
125 | .query({ field: field }) | ||
126 | .set('Accept', 'application/json') | ||
127 | .expect(200) | ||
128 | .expect('Content-Type', /json/) | ||
129 | .end(end) | ||
130 | } | ||
131 | |||
132 | function searchVideoWithSort (url, search, sort, end) { | ||
133 | const path = '/api/v1/videos' | ||
134 | |||
135 | request(url) | ||
136 | .get(path + '/search/' + search) | ||
137 | .query({ sort: sort }) | ||
138 | .set('Accept', 'application/json') | ||
139 | .expect(200) | ||
140 | .expect('Content-Type', /json/) | ||
141 | .end(end) | ||
142 | } | ||
143 | |||
144 | function testVideoImage (url, videoName, imagePath, callback) { | ||
145 | // Don't test images if the node env is not set | ||
146 | // Because we need a special ffmpeg version for this test | ||
147 | if (process.env.NODE_TEST_IMAGE) { | ||
148 | request(url) | ||
149 | .get(imagePath) | ||
150 | .expect(200) | ||
151 | .end(function (err, res) { | ||
152 | if (err) return callback(err) | ||
153 | |||
154 | fs.readFile(pathUtils.join(__dirname, '..', 'api', 'fixtures', videoName + '.jpg'), function (err, data) { | ||
155 | if (err) return callback(err) | ||
156 | |||
157 | callback(null, data.equals(res.body)) | ||
158 | }) | ||
159 | }) | ||
160 | } else { | ||
161 | console.log('Do not test images. Enable it by setting NODE_TEST_IMAGE env variable.') | ||
162 | callback(null, true) | ||
163 | } | ||
164 | } | ||
165 | |||
166 | function uploadVideo (url, accessToken, name, description, tags, fixture, specialStatus, end) { | ||
167 | if (!end) { | ||
168 | end = specialStatus | ||
169 | specialStatus = 204 | ||
170 | } | ||
171 | |||
172 | const path = '/api/v1/videos' | ||
173 | |||
174 | const req = request(url) | ||
175 | .post(path) | ||
176 | .set('Accept', 'application/json') | ||
177 | .set('Authorization', 'Bearer ' + accessToken) | ||
178 | .field('name', name) | ||
179 | .field('description', description) | ||
180 | |||
181 | for (let i = 0; i < tags.length; i++) { | ||
182 | req.field('tags[' + i + ']', tags[i]) | ||
183 | } | ||
184 | |||
185 | let filepath = '' | ||
186 | if (pathUtils.isAbsolute(fixture)) { | ||
187 | filepath = fixture | ||
188 | } else { | ||
189 | filepath = pathUtils.join(__dirname, '..', 'api', 'fixtures', fixture) | ||
190 | } | ||
191 | |||
192 | req.attach('videofile', filepath) | ||
193 | .expect(specialStatus) | ||
194 | .end(end) | ||
195 | } | ||
196 | |||
197 | // --------------------------------------------------------------------------- | ||
198 | |||
199 | module.exports = videosUtils | ||