aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml14
-rw-r--r--ARCHITECTURE.md11
-rw-r--r--README.md6
-rw-r--r--client/package.json18
-rw-r--r--client/src/app/admin/friends/friend-list/friend-list.component.html2
-rw-r--r--client/src/app/admin/friends/friend-list/friend-list.component.ts2
-rw-r--r--client/src/app/admin/friends/shared/friend.model.ts2
-rw-r--r--client/src/app/admin/friends/shared/friend.service.ts16
-rw-r--r--client/src/app/admin/requests/request-stats/request-stats.component.html2
-rw-r--r--client/src/app/admin/requests/request-stats/request-stats.component.ts2
-rw-r--r--client/src/app/admin/requests/shared/request-stats.model.ts6
-rw-r--r--client/src/app/admin/users/user-list/user-list.component.html2
-rw-r--r--client/src/app/shared/auth/auth-user.model.ts9
-rw-r--r--client/src/app/shared/search/search-field.type.ts2
-rw-r--r--client/src/app/shared/search/search.component.ts4
-rw-r--r--client/src/app/shared/shared.module.ts19
-rw-r--r--client/src/app/shared/users/user.model.ts10
-rw-r--r--client/src/app/videos/shared/sort-field.type.ts2
-rw-r--r--client/src/app/videos/shared/video.model.ts6
-rw-r--r--client/src/app/videos/video-list/video-list.component.ts2
-rw-r--r--client/src/app/videos/video-list/video-miniature.component.html2
-rw-r--r--client/src/app/videos/video-list/video-miniature.component.scss2
-rw-r--r--client/src/app/videos/video-list/video-sort.component.ts4
-rw-r--r--client/src/app/videos/video-watch/video-magnet.component.ts2
-rw-r--r--client/src/app/videos/video-watch/video-share.component.ts2
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.html2
-rw-r--r--client/src/vendor.ts8
-rw-r--r--config/default.yaml6
-rw-r--r--config/production.yaml.example18
-rw-r--r--config/test-1.yaml2
-rw-r--r--config/test-2.yaml2
-rw-r--r--config/test-3.yaml2
-rw-r--r--config/test-4.yaml2
-rw-r--r--config/test-5.yaml2
-rw-r--r--config/test-6.yaml2
-rw-r--r--config/test.yaml2
-rw-r--r--package.json6
-rwxr-xr-xscripts/clean/server/test.sh3
-rw-r--r--scripts/danger/clean/cleaner.js31
-rwxr-xr-xscripts/danger/clean/dev.sh3
-rwxr-xr-xscripts/danger/clean/prod.sh3
-rwxr-xr-xscripts/mongo-to-postgre.js244
-rwxr-xr-xscripts/play.sh4
-rwxr-xr-xscripts/update-host.js25
-rw-r--r--server.js13
-rw-r--r--server/controllers/api/clients.js8
-rw-r--r--server/controllers/api/index.js8
-rw-r--r--server/controllers/api/pods.js34
-rw-r--r--server/controllers/api/remote.js86
-rw-r--r--server/controllers/api/remote/index.js16
-rw-r--r--server/controllers/api/remote/videos.js328
-rw-r--r--server/controllers/api/requests.js10
-rw-r--r--server/controllers/api/users.js72
-rw-r--r--server/controllers/api/videos.js382
-rw-r--r--server/controllers/client.js11
-rw-r--r--server/helpers/custom-validators/index.js2
-rw-r--r--server/helpers/custom-validators/pods.js9
-rw-r--r--server/helpers/custom-validators/remote/index.js11
-rw-r--r--server/helpers/custom-validators/remote/videos.js73
-rw-r--r--server/helpers/custom-validators/videos.js71
-rw-r--r--server/helpers/logger.js6
-rw-r--r--server/helpers/peertube-crypto.js65
-rw-r--r--server/helpers/requests.js46
-rw-r--r--server/helpers/utils.js35
-rw-r--r--server/initializers/checker.js12
-rw-r--r--server/initializers/constants.js83
-rw-r--r--server/initializers/database.js90
-rw-r--r--server/initializers/installer.js36
-rw-r--r--server/initializers/migrations/0005-create-application.js17
-rw-r--r--server/initializers/migrations/0005-example.js14
-rw-r--r--server/initializers/migrations/0010-users-password.js22
-rw-r--r--server/initializers/migrations/0015-admin-role.js16
-rw-r--r--server/initializers/migrations/0020-requests-endpoint.js15
-rw-r--r--server/initializers/migrations/0025-video-filenames.js57
-rw-r--r--server/initializers/migrations/0030-video-magnet.js32
-rw-r--r--server/initializers/migrations/0035-url-to-host.js30
-rw-r--r--server/initializers/migrations/0040-video-remote-id.js59
-rw-r--r--server/initializers/migrator.js92
-rw-r--r--server/lib/friends.js145
-rw-r--r--server/lib/oauth-model.js32
-rw-r--r--server/middlewares/pods.js1
-rw-r--r--server/middlewares/secure.js19
-rw-r--r--server/middlewares/sort.js11
-rw-r--r--server/middlewares/validators/remote.js30
-rw-r--r--server/middlewares/validators/remote/index.js13
-rw-r--r--server/middlewares/validators/remote/signature.js21
-rw-r--r--server/middlewares/validators/remote/videos.js20
-rw-r--r--server/middlewares/validators/sort.js23
-rw-r--r--server/middlewares/validators/users.js13
-rw-r--r--server/middlewares/validators/videos.js87
-rw-r--r--server/models/application.js61
-rw-r--r--server/models/author.js85
-rw-r--r--server/models/oauth-client.js75
-rw-r--r--server/models/oauth-token.js134
-rw-r--r--server/models/pod.js200
-rw-r--r--server/models/pods.js119
-rw-r--r--server/models/request-to-pod.js42
-rw-r--r--server/models/request.js275
-rw-r--r--server/models/tag.js76
-rw-r--r--server/models/user.js158
-rw-r--r--server/models/utils.js31
-rw-r--r--server/models/video-abuse.js113
-rw-r--r--server/models/video-tag.js18
-rw-r--r--server/models/video.js532
-rw-r--r--server/tests/api/check-params.js764
-rw-r--r--server/tests/api/check-params/index.js9
-rw-r--r--server/tests/api/check-params/pods.js204
-rw-r--r--server/tests/api/check-params/remotes.js64
-rw-r--r--server/tests/api/check-params/requests.js87
-rw-r--r--server/tests/api/check-params/users.js284
-rw-r--r--server/tests/api/check-params/video-abuses.js180
-rw-r--r--server/tests/api/check-params/videos.js460
-rw-r--r--server/tests/api/friends-advanced.js10
-rw-r--r--server/tests/api/friends-basic.js16
-rw-r--r--server/tests/api/multiple-pods.js87
-rw-r--r--server/tests/api/requests.js42
-rw-r--r--server/tests/api/single-pod.js243
-rw-r--r--server/tests/api/users.js8
-rw-r--r--server/tests/api/video-abuse.js191
-rw-r--r--server/tests/real-world/real-world.js68
-rw-r--r--server/tests/utils/login.js11
-rw-r--r--server/tests/utils/miscs.js6
-rw-r--r--server/tests/utils/servers.js8
-rw-r--r--server/tests/utils/video-abuses.js73
-rw-r--r--server/tests/utils/videos.js84
-rw-r--r--support/doc/client/code.md2
-rw-r--r--support/doc/server/code.md10
127 files changed, 5195 insertions, 2427 deletions
diff --git a/.travis.yml b/.travis.yml
index 9fcd50dee..76efedd50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
1language: node_js 1language: node_js
2 2
3node_js: 3node_js:
4 - "4.6" 4 - "4"
5 - "6.9" 5 - "6"
6 6
7env: 7env:
8 - CXX=g++-4.8 8 - CXX=g++-4.8
@@ -13,11 +13,12 @@ addons:
13 - ubuntu-toolchain-r-test 13 - ubuntu-toolchain-r-test
14 packages: 14 packages:
15 - g++-4.8 15 - g++-4.8
16 postgresql: "9.4"
16 17
17sudo: false 18sudo: false
18 19
19services: 20services:
20 - mongodb 21 - postgresql
21 22
22before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi 23before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi
23 24
@@ -29,6 +30,13 @@ before_script:
29 - cp ffmpeg-*-64bit-static/{ffmpeg,ffprobe,ffserver} $HOME/bin 30 - cp ffmpeg-*-64bit-static/{ffmpeg,ffprobe,ffserver} $HOME/bin
30 - export PATH=$HOME/bin:$PATH 31 - export PATH=$HOME/bin:$PATH
31 - export NODE_TEST_IMAGE=true 32 - export NODE_TEST_IMAGE=true
33 - psql -c 'create database peertube_test1;' -U postgres
34 - psql -c 'create database peertube_test2;' -U postgres
35 - psql -c 'create database peertube_test3;' -U postgres
36 - psql -c 'create database peertube_test4;' -U postgres
37 - psql -c 'create database peertube_test5;' -U postgres
38 - psql -c 'create database peertube_test6;' -U postgres
39 - psql -c "create user peertube with password 'peertube';" -U postgres
32 40
33after_failure: 41after_failure:
34 - cat test1/logs/all-logs.log 42 - cat test1/logs/all-logs.log
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index cc6864fac..ebcffd6cb 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -54,7 +54,16 @@
54 * A pod is a websocket tracker which is responsible for all the video uploaded in it 54 * A pod is a websocket tracker which is responsible for all the video uploaded in it
55 * A pod has an administrator that can add/remove users, make friends and quit friends 55 * A pod has an administrator that can add/remove users, make friends and quit friends
56 * A pod has different user accounts that can upload videos 56 * A pod has different user accounts that can upload videos
57 * All pods have an index of all videos of the network (name, origin pod url, small description, uploader username, magnet Uri, thumbnail name, created date and the thumbnail file). For example, a test with 1000000 videos with alphanum characters and the following lengths: name = 50, author = 50, url = 25, description = 250, magnerUri = 200, thumbnail name = 50 has a mongodb size of ~ 4GB. To this, we add 1 000 000 thumbnails of 5-15 KB so 15GB maximum 57 * All pods have an index of all videos of the network (name, origin pod url, small description, uploader username, magnet Uri, thumbnail name, created date and the thumbnail file). For example, a test with 1000000 videos (3 tags each) with alphanum characters and the following lengths: name = 50, author = 50, podHost = 25, description = 250, videoExtension = 4, remoteId = 50, infoHash = 50 and tag = 10 has a PostgreSQL size of ~ 2GB with all the useful indexes. To this, we add 1 000 000 thumbnails of 5-15 KB so 15GB maximum
58
59 table_name | row_estimate | index | toast | table
60 pod | 983416 | 140 MB | 83 MB | 57 MB
61 author | 1e+06 | 229 MB | 140 MB | 89 MB
62 tag | 2.96758e+06 | 309 MB | 182 MB | 127 MB
63 video | 1e+06 | 723 MB | 263 MB | 460 MB
64 video_tag | 3e+06 | 316 MB | 212 MB | 104 MB
65
66
58 * After having uploaded a video, the server seeds it (WebSeed protocol), adds the meta data in its database and makes a secure request to all of its friends 67 * After having uploaded a video, the server seeds it (WebSeed protocol), adds the meta data in its database and makes a secure request to all of its friends
59 * If a user wants to watch a video, he asks its pod the magnetUri and the frontend adds the torrent (with WebTorrent), creates the HTML5 video tag and streams the file into it 68 * If a user wants to watch a video, he asks its pod the magnetUri and the frontend adds the torrent (with WebTorrent), creates the HTML5 video tag and streams the file into it
60 * A user watching a video seeds it too (BitTorrent) so another user who is watching the same video can get the data from the origin server and the user 1 (etc) 69 * A user watching a video seeds it too (BitTorrent) so another user who is watching the same video can get the data from the origin server and the user 1 (etc)
diff --git a/README.md b/README.md
index 1a9b03201..f9de61c72 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t
121 * **NodeJS >= 4.x** 121 * **NodeJS >= 4.x**
122 * **npm >= 3.x** 122 * **npm >= 3.x**
123 * OpenSSL (cli) 123 * OpenSSL (cli)
124 * MongoDB 124 * PostgreSQL
125 * ffmpeg 125 * ffmpeg
126 126
127#### Debian 127#### Debian
@@ -131,7 +131,7 @@ Thanks to [WebTorrent](https://github.com/feross/webtorrent), we can make P2P (t
131 * Run: 131 * Run:
132 132
133 # apt-get update 133 # apt-get update
134 # apt-get install ffmpeg mongodb openssl 134 # apt-get install ffmpeg postgresql-9.4 openssl
135 # npm install -g npm@3 135 # npm install -g npm@3
136 136
137#### Other distribution... (PR welcome) 137#### Other distribution... (PR welcome)
@@ -238,7 +238,7 @@ Here are some simple schemes:
238 238
239<img src="https://lutim.cpy.re/MyeS4q1g" alt="Join a network" /> 239<img src="https://lutim.cpy.re/MyeS4q1g" alt="Join a network" />
240 240
241<img src="https://lutim.cpy.re/PqpTTzdP" alt="Many networks" 241<img src="https://lutim.cpy.re/PqpTTzdP" alt="Many networks" />
242 242
243</p> 243</p>
244 244
diff --git a/client/package.json b/client/package.json
index c31f04f7b..e9f41959d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -18,14 +18,14 @@
18 }, 18 },
19 "license": "GPLv3", 19 "license": "GPLv3",
20 "dependencies": { 20 "dependencies": {
21 "@angular/common": "~2.3.0", 21 "@angular/common": "~2.4.1",
22 "@angular/compiler": "~2.3.0", 22 "@angular/compiler": "~2.4.1",
23 "@angular/core": "~2.3.0", 23 "@angular/core": "~2.4.1",
24 "@angular/forms": "~2.3.0", 24 "@angular/forms": "~2.4.1",
25 "@angular/http": "~2.3.0", 25 "@angular/http": "~2.4.1",
26 "@angular/platform-browser": "~2.3.0", 26 "@angular/platform-browser": "~2.4.1",
27 "@angular/platform-browser-dynamic": "~2.3.0", 27 "@angular/platform-browser-dynamic": "~2.4.1",
28 "@angular/router": "~3.3.0", 28 "@angular/router": "~3.4.1",
29 "@angularclass/hmr": "^1.2.0", 29 "@angularclass/hmr": "^1.2.0",
30 "@angularclass/hmr-loader": "^3.0.2", 30 "@angularclass/hmr-loader": "^3.0.2",
31 "@types/core-js": "^0.9.28", 31 "@types/core-js": "^0.9.28",
@@ -51,7 +51,7 @@
51 "ie-shim": "^0.1.0", 51 "ie-shim": "^0.1.0",
52 "intl": "^1.2.4", 52 "intl": "^1.2.4",
53 "json-loader": "^0.5.4", 53 "json-loader": "^0.5.4",
54 "ng2-bootstrap": "1.1.16", 54 "ng2-bootstrap": "1.1.16-10",
55 "ng2-file-upload": "^1.1.0", 55 "ng2-file-upload": "^1.1.0",
56 "ng2-meta": "^2.0.0", 56 "ng2-meta": "^2.0.0",
57 "node-sass": "^3.10.0", 57 "node-sass": "^3.10.0",
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
index 4236fc5f6..06258f8c8 100644
--- a/client/src/app/admin/friends/friend-list/friend-list.component.html
+++ b/client/src/app/admin/friends/friend-list/friend-list.component.html
@@ -15,7 +15,7 @@
15 <td>{{ friend.id }}</td> 15 <td>{{ friend.id }}</td>
16 <td>{{ friend.host }}</td> 16 <td>{{ friend.host }}</td>
17 <td>{{ friend.score }}</td> 17 <td>{{ friend.score }}</td>
18 <td>{{ friend.createdDate | date: 'medium' }}</td> 18 <td>{{ friend.createdAt | date: 'medium' }}</td>
19 </tr> 19 </tr>
20 </tbody> 20 </tbody>
21</table> 21</table>
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
index 88c4800ee..bec10162c 100644
--- a/client/src/app/admin/friends/friend-list/friend-list.component.ts
+++ b/client/src/app/admin/friends/friend-list/friend-list.component.ts
@@ -30,7 +30,7 @@ export class FriendListComponent implements OnInit {
30 30
31 private getFriends() { 31 private getFriends() {
32 this.friendService.getFriends().subscribe( 32 this.friendService.getFriends().subscribe(
33 friends => this.friends = friends, 33 res => this.friends = res.friends,
34 34
35 err => alert(err.text) 35 err => alert(err.text)
36 ); 36 );
diff --git a/client/src/app/admin/friends/shared/friend.model.ts b/client/src/app/admin/friends/shared/friend.model.ts
index 3c23feebc..462cc82ed 100644
--- a/client/src/app/admin/friends/shared/friend.model.ts
+++ b/client/src/app/admin/friends/shared/friend.model.ts
@@ -2,5 +2,5 @@ export interface Friend {
2 id: string; 2 id: string;
3 host: string; 3 host: string;
4 score: number; 4 score: number;
5 createdDate: Date; 5 createdAt: Date;
6} 6}
diff --git a/client/src/app/admin/friends/shared/friend.service.ts b/client/src/app/admin/friends/shared/friend.service.ts
index 8a1ba6b02..85ac04ba0 100644
--- a/client/src/app/admin/friends/shared/friend.service.ts
+++ b/client/src/app/admin/friends/shared/friend.service.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
2import { Observable } from 'rxjs/Observable'; 2import { Observable } from 'rxjs/Observable';
3 3
4import { Friend } from './friend.model'; 4import { Friend } from './friend.model';
5import { AuthHttp, RestExtractor } from '../../../shared'; 5import { AuthHttp, RestExtractor, ResultList } from '../../../shared';
6 6
7@Injectable() 7@Injectable()
8export class FriendService { 8export class FriendService {
@@ -13,11 +13,10 @@ export class FriendService {
13 private restExtractor: RestExtractor 13 private restExtractor: RestExtractor
14 ) {} 14 ) {}
15 15
16 getFriends(): Observable<Friend[]> { 16 getFriends() {
17 return this.authHttp.get(FriendService.BASE_FRIEND_URL) 17 return this.authHttp.get(FriendService.BASE_FRIEND_URL)
18 // Not implemented as a data list by the server yet 18 .map(this.restExtractor.extractDataList)
19 // .map(this.restExtractor.extractDataList) 19 .map(this.extractFriends)
20 .map((res) => res.json())
21 .catch((res) => this.restExtractor.handleError(res)); 20 .catch((res) => this.restExtractor.handleError(res));
22 } 21 }
23 22
@@ -36,4 +35,11 @@ export class FriendService {
36 .map(res => res.status) 35 .map(res => res.status)
37 .catch((res) => this.restExtractor.handleError(res)); 36 .catch((res) => this.restExtractor.handleError(res));
38 } 37 }
38
39 private extractFriends(result: ResultList) {
40 const friends: Friend[] = result.data;
41 const totalFriends = result.total;
42
43 return { friends, totalFriends };
44 }
39} 45}
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
index b5ac59a9a..6698eac48 100644
--- a/client/src/app/admin/requests/request-stats/request-stats.component.html
+++ b/client/src/app/admin/requests/request-stats/request-stats.component.html
@@ -18,6 +18,6 @@
18 18
19 <div> 19 <div>
20 <span class="label-description">Remaining requests:</span> 20 <span class="label-description">Remaining requests:</span>
21 {{ stats.requests.length }} 21 {{ stats.totalRequests }}
22 </div> 22 </div>
23</div> 23</div>
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
index d20b12199..9e2af219c 100644
--- a/client/src/app/admin/requests/request-stats/request-stats.component.ts
+++ b/client/src/app/admin/requests/request-stats/request-stats.component.ts
@@ -19,7 +19,7 @@ export class RequestStatsComponent implements OnInit, OnDestroy {
19 } 19 }
20 20
21 ngOnDestroy() { 21 ngOnDestroy() {
22 if (this.stats.secondsInterval !== null) { 22 if (this.stats !== null && this.stats.secondsInterval !== null) {
23 clearInterval(this.interval); 23 clearInterval(this.interval);
24 } 24 }
25 } 25 }
diff --git a/client/src/app/admin/requests/shared/request-stats.model.ts b/client/src/app/admin/requests/shared/request-stats.model.ts
index 766e80836..49ecbc79e 100644
--- a/client/src/app/admin/requests/shared/request-stats.model.ts
+++ b/client/src/app/admin/requests/shared/request-stats.model.ts
@@ -7,18 +7,18 @@ export class RequestStats {
7 maxRequestsInParallel: number; 7 maxRequestsInParallel: number;
8 milliSecondsInterval: number; 8 milliSecondsInterval: number;
9 remainingMilliSeconds: number; 9 remainingMilliSeconds: number;
10 requests: Request[]; 10 totalRequests: number;
11 11
12 constructor(hash: { 12 constructor(hash: {
13 maxRequestsInParallel: number, 13 maxRequestsInParallel: number,
14 milliSecondsInterval: number, 14 milliSecondsInterval: number,
15 remainingMilliSeconds: number, 15 remainingMilliSeconds: number,
16 requests: Request[]; 16 totalRequests: number;
17 }) { 17 }) {
18 this.maxRequestsInParallel = hash.maxRequestsInParallel; 18 this.maxRequestsInParallel = hash.maxRequestsInParallel;
19 this.milliSecondsInterval = hash.milliSecondsInterval; 19 this.milliSecondsInterval = hash.milliSecondsInterval;
20 this.remainingMilliSeconds = hash.remainingMilliSeconds; 20 this.remainingMilliSeconds = hash.remainingMilliSeconds;
21 this.requests = hash.requests; 21 this.totalRequests = hash.totalRequests;
22 } 22 }
23 23
24 get remainingSeconds() { 24 get remainingSeconds() {
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
index 328b1be77..36193d119 100644
--- a/client/src/app/admin/users/user-list/user-list.component.html
+++ b/client/src/app/admin/users/user-list/user-list.component.html
@@ -14,7 +14,7 @@
14 <tr *ngFor="let user of users"> 14 <tr *ngFor="let user of users">
15 <td>{{ user.id }}</td> 15 <td>{{ user.id }}</td>
16 <td>{{ user.username }}</td> 16 <td>{{ user.username }}</td>
17 <td>{{ user.createdDate | date: 'medium' }}</td> 17 <td>{{ user.createdAt | date: 'medium' }}</td>
18 <td class="text-right"> 18 <td class="text-right">
19 <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span> 19 <span class="glyphicon glyphicon-remove" *ngIf="!user.isAdmin()" (click)="removeUser(user)"></span>
20 </td> 20 </td>
diff --git a/client/src/app/shared/auth/auth-user.model.ts b/client/src/app/shared/auth/auth-user.model.ts
index bdd5ea5a9..f560351f4 100644
--- a/client/src/app/shared/auth/auth-user.model.ts
+++ b/client/src/app/shared/auth/auth-user.model.ts
@@ -7,9 +7,6 @@ export class AuthUser extends User {
7 USERNAME: 'username' 7 USERNAME: 'username'
8 }; 8 };
9 9
10 id: string;
11 role: string;
12 username: string;
13 tokens: Tokens; 10 tokens: Tokens;
14 11
15 static load() { 12 static load() {
@@ -17,7 +14,7 @@ export class AuthUser extends User {
17 if (usernameLocalStorage) { 14 if (usernameLocalStorage) {
18 return new AuthUser( 15 return new AuthUser(
19 { 16 {
20 id: localStorage.getItem(this.KEYS.ID), 17 id: parseInt(localStorage.getItem(this.KEYS.ID)),
21 username: localStorage.getItem(this.KEYS.USERNAME), 18 username: localStorage.getItem(this.KEYS.USERNAME),
22 role: localStorage.getItem(this.KEYS.ROLE) 19 role: localStorage.getItem(this.KEYS.ROLE)
23 }, 20 },
@@ -35,7 +32,7 @@ export class AuthUser extends User {
35 Tokens.flush(); 32 Tokens.flush();
36 } 33 }
37 34
38 constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) { 35 constructor(userHash: { id: number, username: string, role: string }, hashTokens: any) {
39 super(userHash); 36 super(userHash);
40 this.tokens = new Tokens(hashTokens); 37 this.tokens = new Tokens(hashTokens);
41 } 38 }
@@ -58,7 +55,7 @@ export class AuthUser extends User {
58 } 55 }
59 56
60 save() { 57 save() {
61 localStorage.setItem(AuthUser.KEYS.ID, this.id); 58 localStorage.setItem(AuthUser.KEYS.ID, this.id.toString());
62 localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); 59 localStorage.setItem(AuthUser.KEYS.USERNAME, this.username);
63 localStorage.setItem(AuthUser.KEYS.ROLE, this.role); 60 localStorage.setItem(AuthUser.KEYS.ROLE, this.role);
64 this.tokens.save(); 61 this.tokens.save();
diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts
index 5228ee68a..6be584ed1 100644
--- a/client/src/app/shared/search/search-field.type.ts
+++ b/client/src/app/shared/search/search-field.type.ts
@@ -1 +1 @@
export type SearchField = "name" | "author" | "podUrl" | "magnetUri" | "tags"; export type SearchField = "name" | "author" | "host" | "magnetUri" | "tags";
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts
index b6237469b..9f7e156ec 100644
--- a/client/src/app/shared/search/search.component.ts
+++ b/client/src/app/shared/search/search.component.ts
@@ -14,8 +14,8 @@ export class SearchComponent implements OnInit {
14 fieldChoices = { 14 fieldChoices = {
15 name: 'Name', 15 name: 'Name',
16 author: 'Author', 16 author: 'Author',
17 podUrl: 'Pod Url', 17 host: 'Pod Host',
18 magnetUri: 'Magnet Uri', 18 magnetUri: 'Magnet URI',
19 tags: 'Tags' 19 tags: 'Tags'
20 }; 20 };
21 searchCriterias: Search = { 21 searchCriterias: Search = {
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 141922322..748c5d520 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -5,10 +5,10 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
5import { RouterModule } from '@angular/router'; 5import { RouterModule } from '@angular/router';
6 6
7import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; 7import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
8import { DropdownModule } from 'ng2-bootstrap/components/dropdown'; 8import { DropdownModule } from 'ng2-bootstrap/dropdown';
9import { ProgressbarModule } from 'ng2-bootstrap/components/progressbar'; 9import { ProgressbarModule } from 'ng2-bootstrap/progressbar';
10import { PaginationModule } from 'ng2-bootstrap/components/pagination'; 10import { PaginationModule } from 'ng2-bootstrap/pagination';
11import { ModalModule } from 'ng2-bootstrap/components/modal'; 11import { ModalModule } from 'ng2-bootstrap/modal';
12import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; 12import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload';
13 13
14import { AUTH_HTTP_PROVIDERS } from './auth'; 14import { AUTH_HTTP_PROVIDERS } from './auth';
@@ -23,11 +23,12 @@ import { SearchComponent, SearchService } from './search';
23 HttpModule, 23 HttpModule,
24 RouterModule, 24 RouterModule,
25 25
26 DropdownModule, 26 DropdownModule.forRoot(),
27 FileUploadModule, 27 ModalModule.forRoot(),
28 ModalModule, 28 PaginationModule.forRoot(),
29 PaginationModule, 29 ProgressbarModule.forRoot(),
30 ProgressbarModule 30
31 FileUploadModule
31 ], 32 ],
32 33
33 declarations: [ 34 declarations: [
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 726495d11..52d89e004 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,16 +1,16 @@
1export class User { 1export class User {
2 id: string; 2 id: number;
3 username: string; 3 username: string;
4 role: string; 4 role: string;
5 createdDate: Date; 5 createdAt: Date;
6 6
7 constructor(hash: { id: string, username: string, role: string, createdDate?: Date }) { 7 constructor(hash: { id: number, username: string, role: string, createdAt?: Date }) {
8 this.id = hash.id; 8 this.id = hash.id;
9 this.username = hash.username; 9 this.username = hash.username;
10 this.role = hash.role; 10 this.role = hash.role;
11 11
12 if (hash.createdDate) { 12 if (hash.createdAt) {
13 this.createdDate = hash.createdDate; 13 this.createdAt = hash.createdAt;
14 } 14 }
15 } 15 }
16 16
diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/videos/shared/sort-field.type.ts
index 6e8cc7936..74908e344 100644
--- a/client/src/app/videos/shared/sort-field.type.ts
+++ b/client/src/app/videos/shared/sort-field.type.ts
@@ -1,3 +1,3 @@
1export type SortField = "name" | "-name" 1export type SortField = "name" | "-name"
2 | "duration" | "-duration" 2 | "duration" | "-duration"
3 | "createdDate" | "-createdDate"; 3 | "createdAt" | "-createdAt";
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
index b51a0e9de..fae001d78 100644
--- a/client/src/app/videos/shared/video.model.ts
+++ b/client/src/app/videos/shared/video.model.ts
@@ -1,7 +1,7 @@
1export class Video { 1export class Video {
2 author: string; 2 author: string;
3 by: string; 3 by: string;
4 createdDate: Date; 4 createdAt: Date;
5 description: string; 5 description: string;
6 duration: string; 6 duration: string;
7 id: string; 7 id: string;
@@ -27,7 +27,7 @@ export class Video {
27 27
28 constructor(hash: { 28 constructor(hash: {
29 author: string, 29 author: string,
30 createdDate: string, 30 createdAt: string,
31 description: string, 31 description: string,
32 duration: number; 32 duration: number;
33 id: string, 33 id: string,
@@ -39,7 +39,7 @@ export class Video {
39 thumbnailPath: string 39 thumbnailPath: string
40 }) { 40 }) {
41 this.author = hash.author; 41 this.author = hash.author;
42 this.createdDate = new Date(hash.createdDate); 42 this.createdAt = new Date(hash.createdAt);
43 this.description = hash.description; 43 this.description = hash.description;
44 this.duration = Video.createDurationString(hash.duration); 44 this.duration = Video.createDurationString(hash.duration);
45 this.id = hash.id; 45 this.id = hash.id;
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 a8b92480b..6c42ba5be 100644
--- a/client/src/app/videos/video-list/video-list.component.ts
+++ b/client/src/app/videos/video-list/video-list.component.ts
@@ -145,7 +145,7 @@ export class VideoListComponent implements OnInit, OnDestroy {
145 }; 145 };
146 } 146 }
147 147
148 this.sort = <SortField>routeParams['sort'] || '-createdDate'; 148 this.sort = <SortField>routeParams['sort'] || '-createdAt';
149 149
150 if (routeParams['page'] !== undefined) { 150 if (routeParams['page'] !== undefined) {
151 this.pagination.currentPage = parseInt(routeParams['page']); 151 this.pagination.currentPage = parseInt(routeParams['page']);
diff --git a/client/src/app/videos/video-list/video-miniature.component.html b/client/src/app/videos/video-list/video-miniature.component.html
index 16513902b..f2f4a53a9 100644
--- a/client/src/app/videos/video-list/video-miniature.component.html
+++ b/client/src/app/videos/video-list/video-miniature.component.html
@@ -23,6 +23,6 @@
23 </span> 23 </span>
24 24
25 <a [routerLink]="['/videos/list', { field: 'author', search: video.author, sort: currentSort }]" class="video-miniature-author">{{ video.by }}</a> 25 <a [routerLink]="['/videos/list', { field: 'author', search: video.author, sort: currentSort }]" class="video-miniature-author">{{ video.by }}</a>
26 <span class="video-miniature-created-date">{{ video.createdDate | date:'short' }}</span> 26 <span class="video-miniature-created-at">{{ video.createdAt | date:'short' }}</span>
27 </div> 27 </div>
28</div> 28</div>
diff --git a/client/src/app/videos/video-list/video-miniature.component.scss b/client/src/app/videos/video-list/video-miniature.component.scss
index 6b3fa3bf0..d70b1b50d 100644
--- a/client/src/app/videos/video-list/video-miniature.component.scss
+++ b/client/src/app/videos/video-list/video-miniature.component.scss
@@ -79,7 +79,7 @@
79 } 79 }
80 } 80 }
81 81
82 .video-miniature-author, .video-miniature-created-date { 82 .video-miniature-author, .video-miniature-created-at {
83 display: block; 83 display: block;
84 margin-left: 1px; 84 margin-left: 1px;
85 font-size: 12px; 85 font-size: 12px;
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 ca94b07c2..53951deb4 100644
--- a/client/src/app/videos/video-list/video-sort.component.ts
+++ b/client/src/app/videos/video-list/video-sort.component.ts
@@ -17,8 +17,8 @@ export class VideoSortComponent {
17 '-name': 'Name - Desc', 17 '-name': 'Name - Desc',
18 'duration': 'Duration - Asc', 18 'duration': 'Duration - Asc',
19 '-duration': 'Duration - Desc', 19 '-duration': 'Duration - Desc',
20 'createdDate': 'Created Date - Asc', 20 'createdAt': 'Created Date - Asc',
21 '-createdDate': 'Created Date - Desc' 21 '-createdAt': 'Created Date - Desc'
22 }; 22 };
23 23
24 get choiceKeys() { 24 get choiceKeys() {
diff --git a/client/src/app/videos/video-watch/video-magnet.component.ts b/client/src/app/videos/video-watch/video-magnet.component.ts
index 2894e7df6..8bee848a4 100644
--- a/client/src/app/videos/video-watch/video-magnet.component.ts
+++ b/client/src/app/videos/video-watch/video-magnet.component.ts
@@ -1,6 +1,6 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core';
2 2
3import { ModalDirective } from 'ng2-bootstrap/components/modal'; 3import { ModalDirective } from 'ng2-bootstrap/modal';
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared';
6 6
diff --git a/client/src/app/videos/video-watch/video-share.component.ts b/client/src/app/videos/video-watch/video-share.component.ts
index 8e6de1294..0b85052cd 100644
--- a/client/src/app/videos/video-watch/video-share.component.ts
+++ b/client/src/app/videos/video-watch/video-share.component.ts
@@ -1,6 +1,6 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core';
2 2
3import { ModalDirective } from 'ng2-bootstrap/components/modal'; 3import { ModalDirective } from 'ng2-bootstrap/modal';
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared';
6 6
diff --git a/client/src/app/videos/video-watch/video-watch.component.html b/client/src/app/videos/video-watch/video-watch.component.html
index 0f0fa68cc..a726ef3ff 100644
--- a/client/src/app/videos/video-watch/video-watch.component.html
+++ b/client/src/app/videos/video-watch/video-watch.component.html
@@ -47,7 +47,7 @@
47 {{ video.by }} 47 {{ video.by }}
48 </a> 48 </a>
49 </span> 49 </span>
50 <span id="video-date">on {{ video.createdDate | date:'short' }}</span> 50 <span id="video-date">on {{ video.createdAt | date:'short' }}</span>
51 </div> 51 </div>
52 </div> 52 </div>
53 </div> 53 </div>
diff --git a/client/src/vendor.ts b/client/src/vendor.ts
index 760fc8257..436c58f48 100644
--- a/client/src/vendor.ts
+++ b/client/src/vendor.ts
@@ -29,7 +29,7 @@ import 'angular-pipes/src/math/bytes.pipe';
29import 'ng2-file-upload'; 29import 'ng2-file-upload';
30import 'video.js'; 30import 'video.js';
31import 'ng2-meta'; 31import 'ng2-meta';
32import 'ng2-bootstrap/components/pagination'; 32import 'ng2-bootstrap/pagination';
33import 'ng2-bootstrap/components/dropdown'; 33import 'ng2-bootstrap/dropdown';
34import 'ng2-bootstrap/components/progressbar'; 34import 'ng2-bootstrap/progressbar';
35import 'ng2-bootstrap/components/modal'; 35import 'ng2-bootstrap/modal';
diff --git a/config/default.yaml b/config/default.yaml
index 90f4b9466..0939ae4ba 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -8,8 +8,10 @@ webserver:
8 8
9database: 9database:
10 hostname: 'localhost' 10 hostname: 'localhost'
11 port: 27017 11 port: 5432
12 suffix: '-dev' 12 suffix: '_dev'
13 username: 'peertube'
14 password: 'peertube'
13 15
14# From the project root directory 16# From the project root directory
15storage: 17storage:
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 056ace434..005444e73 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -1,3 +1,6 @@
1listen:
2 port: 9000
3
1# Correspond to your reverse proxy "listen" configuration 4# Correspond to your reverse proxy "listen" configuration
2webserver: 5webserver:
3 https: false 6 https: false
@@ -5,4 +8,17 @@ webserver:
5 port: 80 8 port: 80
6 9
7database: 10database:
8 suffix: '-prod' 11 hostname: 'localhost'
12 port: 5432
13 suffix: '_prod'
14 username: peertube
15 password: peertube
16
17# From the project root directory
18storage:
19 certs: 'certs/'
20 videos: 'videos/'
21 logs: 'logs/'
22 previews: 'previews/'
23 thumbnails: 'thumbnails/'
24 torrents: 'torrents/'
diff --git a/config/test-1.yaml b/config/test-1.yaml
index 6dcc7f294..b2a0a5422 100644
--- a/config/test-1.yaml
+++ b/config/test-1.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9001 6 port: 9001
7 7
8database: 8database:
9 suffix: '-test1' 9 suffix: '_test1'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test-2.yaml b/config/test-2.yaml
index 209525963..7285f3394 100644
--- a/config/test-2.yaml
+++ b/config/test-2.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9002 6 port: 9002
7 7
8database: 8database:
9 suffix: '-test2' 9 suffix: '_test2'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test-3.yaml b/config/test-3.yaml
index 15719d107..138a2cd53 100644
--- a/config/test-3.yaml
+++ b/config/test-3.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9003 6 port: 9003
7 7
8database: 8database:
9 suffix: '-test3' 9 suffix: '_test3'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test-4.yaml b/config/test-4.yaml
index e6f34d013..7425f4af7 100644
--- a/config/test-4.yaml
+++ b/config/test-4.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9004 6 port: 9004
7 7
8database: 8database:
9 suffix: '-test4' 9 suffix: '_test4'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test-5.yaml b/config/test-5.yaml
index fdeec76d4..1bf0de658 100644
--- a/config/test-5.yaml
+++ b/config/test-5.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9005 6 port: 9005
7 7
8database: 8database:
9 suffix: '-test5' 9 suffix: '_test5'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test-6.yaml b/config/test-6.yaml
index 0c9630c89..7303a08fc 100644
--- a/config/test-6.yaml
+++ b/config/test-6.yaml
@@ -6,7 +6,7 @@ webserver:
6 port: 9006 6 port: 9006
7 7
8database: 8database:
9 suffix: '-test6' 9 suffix: '_test6'
10 10
11# From the project root directory 11# From the project root directory
12storage: 12storage:
diff --git a/config/test.yaml b/config/test.yaml
index 06705a987..493a23076 100644
--- a/config/test.yaml
+++ b/config/test.yaml
@@ -6,4 +6,4 @@ webserver:
6 6
7database: 7database:
8 hostname: 'localhost' 8 hostname: 'localhost'
9 port: 27017 9 port: 5432
diff --git a/package.json b/package.json
index 300af4867..554ad16df 100644
--- a/package.json
+++ b/package.json
@@ -56,17 +56,19 @@
56 "lodash": "^4.11.1", 56 "lodash": "^4.11.1",
57 "magnet-uri": "^5.1.4", 57 "magnet-uri": "^5.1.4",
58 "mkdirp": "^0.5.1", 58 "mkdirp": "^0.5.1",
59 "mongoose": "^4.0.5",
60 "morgan": "^1.5.3", 59 "morgan": "^1.5.3",
61 "multer": "^1.1.0", 60 "multer": "^1.1.0",
62 "openssl-wrapper": "^0.3.4", 61 "openssl-wrapper": "^0.3.4",
63 "parse-torrent": "^5.8.0", 62 "parse-torrent": "^5.8.0",
64 "password-generator": "^2.0.2", 63 "password-generator": "^2.0.2",
64 "pg": "^6.1.0",
65 "pg-hstore": "^2.3.2",
65 "request": "^2.57.0", 66 "request": "^2.57.0",
66 "request-replay": "^1.0.2", 67 "request-replay": "^1.0.2",
67 "rimraf": "^2.5.4", 68 "rimraf": "^2.5.4",
69 "safe-buffer": "^5.0.1",
68 "scripty": "^1.5.0", 70 "scripty": "^1.5.0",
69 "ursa": "^0.9.1", 71 "sequelize": "^3.27.0",
70 "winston": "^2.1.1", 72 "winston": "^2.1.1",
71 "ws": "^1.1.1" 73 "ws": "^1.1.1"
72 }, 74 },
diff --git a/scripts/clean/server/test.sh b/scripts/clean/server/test.sh
index 927671dd4..35d3ad50f 100755
--- a/scripts/clean/server/test.sh
+++ b/scripts/clean/server/test.sh
@@ -1,6 +1,7 @@
1#!/usr/bin/env sh 1#!/usr/bin/env sh
2 2
3for i in $(seq 1 6); do 3for i in $(seq 1 6); do
4 printf "use peertube-test%s;\ndb.dropDatabase();" "$i" | mongo 4 dropdb "peertube_test$i"
5 rm -rf "./test$i" 5 rm -rf "./test$i"
6 createdb "peertube_test$i"
6done 7done
diff --git a/scripts/danger/clean/cleaner.js b/scripts/danger/clean/cleaner.js
index 10e91e700..1a1e3dee7 100644
--- a/scripts/danger/clean/cleaner.js
+++ b/scripts/danger/clean/cleaner.js
@@ -1,24 +1,25 @@
1const eachSeries = require('async/eachSeries')
1const rimraf = require('rimraf') 2const rimraf = require('rimraf')
2const mongoose = require('mongoose')
3mongoose.Promise = global.Promise
4 3
5const constants = require('../../../server/initializers/constants') 4const constants = require('../../../server/initializers/constants')
5const db = require('../../../server/initializers/database')
6 6
7const mongodbUrl = 'mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME 7db.init(true, function () {
8mongoose.connect(mongodbUrl, function () { 8 db.sequelize.drop().asCallback(function (err) {
9 console.info('Deleting MongoDB %s database.', constants.CONFIG.DATABASE.DBNAME) 9 if (err) throw err
10 mongoose.connection.dropDatabase(function () {
11 mongoose.connection.close()
12 })
13})
14 10
15const STORAGE = constants.CONFIG.STORAGE 11 console.info('Tables of %s deleted.', db.sequelize.config.database)
16Object.keys(STORAGE).forEach(function (storage) {
17 const storageDir = STORAGE[storage]
18 12
19 rimraf(storageDir, function (err) { 13 const STORAGE = constants.CONFIG.STORAGE
20 if (err) throw err 14 eachSeries(Object.keys(STORAGE), function (storage, callbackEach) {
15 const storageDir = STORAGE[storage]
21 16
22 console.info('Deleting %s.', storageDir) 17 rimraf(storageDir, function (err) {
18 console.info('%s deleted.', storageDir)
19 return callbackEach(err)
20 })
21 }, function () {
22 process.exit(0)
23 })
23 }) 24 })
24}) 25})
diff --git a/scripts/danger/clean/dev.sh b/scripts/danger/clean/dev.sh
index 5f09565cf..f4a5d1e8a 100755
--- a/scripts/danger/clean/dev.sh
+++ b/scripts/danger/clean/dev.sh
@@ -1,6 +1,7 @@
1#!/bin/bash 1#!/bin/bash
2 2
3read -p "This will remove all directories and Mongo database. Are you sure? " -n 1 -r 3read -p "This will remove all directories and SQL tables. Are you sure? (y/*) " -n 1 -r
4echo
4 5
5if [[ "$REPLY" =~ ^[Yy]$ ]]; then 6if [[ "$REPLY" =~ ^[Yy]$ ]]; then
6 NODE_ENV=test node "./scripts/danger/clean/cleaner" 7 NODE_ENV=test node "./scripts/danger/clean/cleaner"
diff --git a/scripts/danger/clean/prod.sh b/scripts/danger/clean/prod.sh
index e6c92671d..7ee13ba83 100755
--- a/scripts/danger/clean/prod.sh
+++ b/scripts/danger/clean/prod.sh
@@ -1,6 +1,7 @@
1#!/bin/bash 1#!/bin/bash
2 2
3read -p "This will remove all directories and Mongo database. Are you sure? " -n 1 -r 3read -p "This will remove all directories and SQL tables. Are you sure? (y/*) " -n 1 -r
4echo
4 5
5if [[ "$REPLY" =~ ^[Yy]$ ]]; then 6if [[ "$REPLY" =~ ^[Yy]$ ]]; then
6 NODE_ENV=production node "./scripts/danger/clean/cleaner" 7 NODE_ENV=production node "./scripts/danger/clean/cleaner"
diff --git a/scripts/mongo-to-postgre.js b/scripts/mongo-to-postgre.js
new file mode 100755
index 000000000..4a581b46a
--- /dev/null
+++ b/scripts/mongo-to-postgre.js
@@ -0,0 +1,244 @@
1#!/usr/bin/env node
2
3'use strict'
4
5// TODO: document this script
6
7const program = require('commander')
8const eachSeries = require('async/eachSeries')
9const series = require('async/series')
10const waterfall = require('async/waterfall')
11const fs = require('fs')
12const path = require('path')
13const MongoClient = require('mongodb').MongoClient
14
15const constants = require('../server/initializers/constants')
16
17program
18 .option('-mh, --mongo-host [host]', 'MongoDB host', 'localhost')
19 .option('-mp, --mongo-port [weight]', 'MongoDB port', '27017')
20 .option('-md, --mongo-database [dbname]', 'MongoDB database')
21 .parse(process.argv)
22
23if (!program.mongoDatabase) {
24 console.error('The mongodb database is mandatory.')
25 process.exit(-1)
26}
27
28const mongoUrl = 'mongodb://' + program.mongoHost + ':' + program.mongoPort + '/' + program.mongoDatabase
29const dbSequelize = require('../server/initializers/database')
30
31console.log('Connecting to ' + mongoUrl)
32MongoClient.connect(mongoUrl, function (err, dbMongo) {
33 if (err) throw err
34
35 console.log('Connected to ' + mongoUrl)
36
37 const videoMongo = dbMongo.collection('videos')
38 const userMongo = dbMongo.collection('users')
39 const podMongo = dbMongo.collection('pods')
40
41 podMongo.count(function (err, podsLength) {
42 if (err) throw err
43
44 if (podsLength > 0) {
45 console.error('You need to quit friends first.')
46 process.exit(-1)
47 }
48
49 console.log('Connecting to ' + dbSequelize.sequelize.config.database)
50 dbSequelize.init(true, function (err) {
51 if (err) throw err
52
53 console.log('Connected to SQL database %s.', dbSequelize.sequelize.config.database)
54
55 series([
56 function (next) {
57 dbSequelize.sequelize.sync({ force: true }).asCallback(next)
58 },
59
60 function (next) {
61 migrateVideos(videoMongo, dbSequelize, next)
62 },
63
64 function (next) {
65 migrateUsers(userMongo, dbSequelize, next)
66 }
67 ], function (err) {
68 if (err) console.error(err)
69
70 process.exit(0)
71 })
72 })
73 })
74})
75
76// ---------------------------------------------------------------------------
77
78function migrateUsers (userMongo, dbSequelize, callback) {
79 userMongo.find().toArray(function (err, mongoUsers) {
80 if (err) return callback(err)
81
82 eachSeries(mongoUsers, function (mongoUser, callbackEach) {
83 console.log('Migrating user %s', mongoUser.username)
84
85 const userData = {
86 username: mongoUser.username,
87 password: mongoUser.password,
88 role: mongoUser.role
89 }
90 const options = {
91 hooks: false
92 }
93
94 dbSequelize.User.create(userData, options).asCallback(callbackEach)
95 }, callback)
96 })
97}
98
99function migrateVideos (videoMongo, dbSequelize, finalCallback) {
100 videoMongo.find().toArray(function (err, mongoVideos) {
101 if (err) return finalCallback(err)
102
103 eachSeries(mongoVideos, function (mongoVideo, callbackEach) {
104 console.log('Migrating video %s.', mongoVideo.name)
105
106 waterfall([
107
108 function startTransaction (callback) {
109 dbSequelize.sequelize.transaction().asCallback(function (err, t) {
110 return callback(err, t)
111 })
112 },
113
114 function findOrCreatePod (t, callback) {
115 if (mongoVideo.remoteId === null) return callback(null, t, null)
116
117 const query = {
118 where: {
119 host: mongoVideo.podHost
120 },
121 defaults: {
122 host: mongoVideo.podHost
123 },
124 transaction: t
125 }
126
127 dbSequelize.Pod.findOrCreate(query).asCallback(function (err, result) {
128 // [ instance, wasCreated ]
129 return callback(err, t, result[0])
130 })
131 },
132
133 function findOrCreateAuthor (t, pod, callback) {
134 const podId = pod ? pod.id : null
135 const username = mongoVideo.author
136
137 const query = {
138 where: {
139 podId,
140 name: username
141 },
142 defaults: {
143 podId,
144 name: username
145 },
146 transaction: t
147 }
148
149 dbSequelize.Author.findOrCreate(query).asCallback(function (err, result) {
150 // [ instance, wasCreated ]
151 return callback(err, t, result[0])
152 })
153 },
154
155 function findOrCreateTags (t, author, callback) {
156 const tags = mongoVideo.tags
157 const tagInstances = []
158
159 eachSeries(tags, function (tag, callbackEach) {
160 const query = {
161 where: {
162 name: tag
163 },
164 defaults: {
165 name: tag
166 },
167 transaction: t
168 }
169
170 dbSequelize.Tag.findOrCreate(query).asCallback(function (err, res) {
171 if (err) return callbackEach(err)
172
173 // res = [ tag, isCreated ]
174 const tag = res[0]
175 tagInstances.push(tag)
176 return callbackEach()
177 })
178 }, function (err) {
179 return callback(err, t, author, tagInstances)
180 })
181 },
182
183 function createVideoObject (t, author, tagInstances, callback) {
184 const videoData = {
185 name: mongoVideo.name,
186 remoteId: mongoVideo.remoteId,
187 extname: mongoVideo.extname,
188 infoHash: mongoVideo.magnet.infoHash,
189 description: mongoVideo.description,
190 authorId: author.id,
191 duration: mongoVideo.duration,
192 createdAt: mongoVideo.createdDate
193 }
194
195 const video = dbSequelize.Video.build(videoData)
196
197 return callback(null, t, tagInstances, video)
198 },
199
200 function moveVideoFile (t, tagInstances, video, callback) {
201 const basePath = constants.CONFIG.STORAGE.VIDEOS_DIR
202 const src = path.join(basePath, mongoVideo._id.toString()) + video.extname
203 const dst = path.join(basePath, video.id) + video.extname
204 fs.rename(src, dst, function (err) {
205 if (err) return callback(err)
206
207 return callback(null, t, tagInstances, video)
208 })
209 },
210
211 function insertVideoIntoDB (t, tagInstances, video, callback) {
212 const options = {
213 transaction: t
214 }
215
216 video.save(options).asCallback(function (err, videoCreated) {
217 return callback(err, t, tagInstances, videoCreated)
218 })
219 },
220
221 function associateTagsToVideo (t, tagInstances, video, callback) {
222 const options = { transaction: t }
223
224 video.setTags(tagInstances, options).asCallback(function (err) {
225 return callback(err, t)
226 })
227 }
228
229 ], function (err, t) {
230 if (err) {
231 // Abort transaction?
232 if (t) t.rollback()
233
234 return callbackEach(err)
235 }
236
237 // Commit transaction
238 t.commit()
239
240 return callbackEach()
241 })
242 }, finalCallback)
243 })
244}
diff --git a/scripts/play.sh b/scripts/play.sh
index 33dc1a545..ab812025c 100755
--- a/scripts/play.sh
+++ b/scripts/play.sh
@@ -5,7 +5,9 @@ if [ ! -f server.js ]; then
5 exit -1 5 exit -1
6fi 6fi
7 7
8for i in 1 2 3; do 8max=${1:-3}
9
10for i in $(seq 1 $max); do
9 NODE_ENV=test NODE_APP_INSTANCE=$i node server.js & 11 NODE_ENV=test NODE_APP_INSTANCE=$i node server.js &
10 sleep 1 12 sleep 1
11done 13done
diff --git a/scripts/update-host.js b/scripts/update-host.js
index 8a17f2402..91051e68b 100755
--- a/scripts/update-host.js
+++ b/scripts/update-host.js
@@ -5,31 +5,24 @@
5// TODO: document this script 5// TODO: document this script
6 6
7const fs = require('fs') 7const fs = require('fs')
8const mongoose = require('mongoose')
9const parseTorrent = require('parse-torrent') 8const parseTorrent = require('parse-torrent')
10 9
11const constants = require('../server/initializers/constants') 10const constants = require('../server/initializers/constants')
12const database = require('../server/initializers/database') 11const db = require('../server/initializers/database')
13
14database.connect()
15 12
16const friends = require('../server/lib/friends') 13const friends = require('../server/lib/friends')
17const Video = mongoose.model('Video')
18
19friends.hasFriends(function (err, hasFriends) {
20 if (err) throw err
21 14
22 if (hasFriends === true) { 15db.init(true, function () {
23 console.log('Cannot update host because you have friends!') 16 friends.hasFriends(function (err, hasFriends) {
24 process.exit(-1)
25 }
26
27 console.log('Updating videos host in database.')
28 Video.update({ }, { podHost: constants.CONFIG.WEBSERVER.HOST }, { multi: true }, function (err) {
29 if (err) throw err 17 if (err) throw err
30 18
19 if (hasFriends === true) {
20 console.log('Cannot update host because you have friends!')
21 process.exit(-1)
22 }
23
31 console.log('Updating torrent files.') 24 console.log('Updating torrent files.')
32 Video.find().lean().exec(function (err, videos) { 25 db.Video.list(function (err, videos) {
33 if (err) throw err 26 if (err) throw err
34 27
35 videos.forEach(function (video) { 28 videos.forEach(function (video) {
diff --git a/server.js b/server.js
index 6eb022000..7503072af 100644
--- a/server.js
+++ b/server.js
@@ -17,10 +17,10 @@ const app = express()
17 17
18// ----------- Database ----------- 18// ----------- Database -----------
19const constants = require('./server/initializers/constants') 19const constants = require('./server/initializers/constants')
20const database = require('./server/initializers/database')
21const logger = require('./server/helpers/logger') 20const logger = require('./server/helpers/logger')
22 21// Initialize database and models
23database.connect() 22const db = require('./server/initializers/database')
23db.init()
24 24
25// ----------- Checker ----------- 25// ----------- Checker -----------
26const checker = require('./server/initializers/checker') 26const checker = require('./server/initializers/checker')
@@ -39,9 +39,7 @@ if (errorMessage !== null) {
39const customValidators = require('./server/helpers/custom-validators') 39const customValidators = require('./server/helpers/custom-validators')
40const installer = require('./server/initializers/installer') 40const installer = require('./server/initializers/installer')
41const migrator = require('./server/initializers/migrator') 41const migrator = require('./server/initializers/migrator')
42const mongoose = require('mongoose')
43const routes = require('./server/controllers') 42const routes = require('./server/controllers')
44const Request = mongoose.model('Request')
45 43
46// ----------- Command line ----------- 44// ----------- Command line -----------
47 45
@@ -59,7 +57,8 @@ app.use(expressValidator({
59 customValidators.misc, 57 customValidators.misc,
60 customValidators.pods, 58 customValidators.pods,
61 customValidators.users, 59 customValidators.users,
62 customValidators.videos 60 customValidators.videos,
61 customValidators.remote.videos
63 ) 62 )
64})) 63}))
65 64
@@ -130,7 +129,7 @@ installer.installApplication(function (err) {
130 // ----------- Make the server listening ----------- 129 // ----------- Make the server listening -----------
131 server.listen(port, function () { 130 server.listen(port, function () {
132 // Activate the pool requests 131 // Activate the pool requests
133 Request.activate() 132 db.Request.activate()
134 133
135 logger.info('Server listening on port %d', port) 134 logger.info('Server listening on port %d', port)
136 logger.info('Webserver: %s', constants.CONFIG.WEBSERVER.URL) 135 logger.info('Webserver: %s', constants.CONFIG.WEBSERVER.URL)
diff --git a/server/controllers/api/clients.js b/server/controllers/api/clients.js
index 7755f6c2b..cf83cb835 100644
--- a/server/controllers/api/clients.js
+++ b/server/controllers/api/clients.js
@@ -1,13 +1,11 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5 4
6const constants = require('../../initializers/constants') 5const constants = require('../../initializers/constants')
6const db = require('../../initializers/database')
7const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
8 8
9const Client = mongoose.model('OAuthClient')
10
11const router = express.Router() 9const router = express.Router()
12 10
13router.get('/local', getLocalClient) 11router.get('/local', getLocalClient)
@@ -27,12 +25,12 @@ function getLocalClient (req, res, next) {
27 return res.type('json').status(403).end() 25 return res.type('json').status(403).end()
28 } 26 }
29 27
30 Client.loadFirstClient(function (err, client) { 28 db.OAuthClient.loadFirstClient(function (err, client) {
31 if (err) return next(err) 29 if (err) return next(err)
32 if (!client) return next(new Error('No client available.')) 30 if (!client) return next(new Error('No client available.'))
33 31
34 res.json({ 32 res.json({
35 client_id: client._id, 33 client_id: client.clientId,
36 client_secret: client.clientSecret 34 client_secret: client.clientSecret
37 }) 35 })
38 }) 36 })
diff --git a/server/controllers/api/index.js b/server/controllers/api/index.js
index 4cb65ed55..f13ff922c 100644
--- a/server/controllers/api/index.js
+++ b/server/controllers/api/index.js
@@ -2,6 +2,8 @@
2 2
3const express = require('express') 3const express = require('express')
4 4
5const utils = require('../../helpers/utils')
6
5const router = express.Router() 7const router = express.Router()
6 8
7const clientsController = require('./clients') 9const clientsController = require('./clients')
@@ -18,7 +20,7 @@ router.use('/requests', requestsController)
18router.use('/users', usersController) 20router.use('/users', usersController)
19router.use('/videos', videosController) 21router.use('/videos', videosController)
20router.use('/ping', pong) 22router.use('/ping', pong)
21router.use('/*', badRequest) 23router.use('/*', utils.badRequest)
22 24
23// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
24 26
@@ -29,7 +31,3 @@ module.exports = router
29function pong (req, res, next) { 31function pong (req, res, next) {
30 return res.send('pong').status(200).end() 32 return res.send('pong').status(200).end()
31} 33}
32
33function badRequest (req, res, next) {
34 res.type('json').status(400).end()
35}
diff --git a/server/controllers/api/pods.js b/server/controllers/api/pods.js
index 7857fcee0..38702face 100644
--- a/server/controllers/api/pods.js
+++ b/server/controllers/api/pods.js
@@ -1,10 +1,11 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5const waterfall = require('async/waterfall') 4const waterfall = require('async/waterfall')
6 5
6const db = require('../../initializers/database')
7const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
8const utils = require('../../helpers/utils')
8const friends = require('../../lib/friends') 9const friends = require('../../lib/friends')
9const middlewares = require('../../middlewares') 10const middlewares = require('../../middlewares')
10const admin = middlewares.admin 11const admin = middlewares.admin
@@ -15,7 +16,6 @@ const validators = middlewares.validators.pods
15const signatureValidator = middlewares.validators.remote.signature 16const signatureValidator = middlewares.validators.remote.signature
16 17
17const router = express.Router() 18const router = express.Router()
18const Pod = mongoose.model('Pod')
19 19
20router.get('/', listPods) 20router.get('/', listPods)
21router.post('/', 21router.post('/',
@@ -37,7 +37,7 @@ router.get('/quitfriends',
37) 37)
38// Post because this is a secured request 38// Post because this is a secured request
39router.post('/remove', 39router.post('/remove',
40 signatureValidator, 40 signatureValidator.signature,
41 checkSignature, 41 checkSignature,
42 removePods 42 removePods
43) 43)
@@ -53,15 +53,15 @@ function addPods (req, res, next) {
53 53
54 waterfall([ 54 waterfall([
55 function addPod (callback) { 55 function addPod (callback) {
56 const pod = new Pod(informations) 56 const pod = db.Pod.build(informations)
57 pod.save(function (err, podCreated) { 57 pod.save().asCallback(function (err, podCreated) {
58 // Be sure about the number of parameters for the callback 58 // Be sure about the number of parameters for the callback
59 return callback(err, podCreated) 59 return callback(err, podCreated)
60 }) 60 })
61 }, 61 },
62 62
63 function sendMyVideos (podCreated, callback) { 63 function sendMyVideos (podCreated, callback) {
64 friends.sendOwnedVideosToPod(podCreated._id) 64 friends.sendOwnedVideosToPod(podCreated.id)
65 65
66 callback(null) 66 callback(null)
67 }, 67 },
@@ -84,10 +84,10 @@ function addPods (req, res, next) {
84} 84}
85 85
86function listPods (req, res, next) { 86function listPods (req, res, next) {
87 Pod.list(function (err, podsList) { 87 db.Pod.list(function (err, podsList) {
88 if (err) return next(err) 88 if (err) return next(err)
89 89
90 res.json(getFormatedPods(podsList)) 90 res.json(utils.getFormatedObjects(podsList, podsList.length))
91 }) 91 })
92} 92}
93 93
@@ -111,11 +111,11 @@ function removePods (req, res, next) {
111 111
112 waterfall([ 112 waterfall([
113 function loadPod (callback) { 113 function loadPod (callback) {
114 Pod.loadByHost(host, callback) 114 db.Pod.loadByHost(host, callback)
115 }, 115 },
116 116
117 function removePod (pod, callback) { 117 function deletePod (pod, callback) {
118 pod.remove(callback) 118 pod.destroy().asCallback(callback)
119 } 119 }
120 ], function (err) { 120 ], function (err) {
121 if (err) return next(err) 121 if (err) return next(err)
@@ -131,15 +131,3 @@ function quitFriends (req, res, next) {
131 res.type('json').status(204).end() 131 res.type('json').status(204).end()
132 }) 132 })
133} 133}
134
135// ---------------------------------------------------------------------------
136
137function getFormatedPods (pods) {
138 const formatedPods = []
139
140 pods.forEach(function (pod) {
141 formatedPods.push(pod.toFormatedJSON())
142 })
143
144 return formatedPods
145}
diff --git a/server/controllers/api/remote.js b/server/controllers/api/remote.js
deleted file mode 100644
index f1046c534..000000000
--- a/server/controllers/api/remote.js
+++ /dev/null
@@ -1,86 +0,0 @@
1'use strict'
2
3const each = require('async/each')
4const eachSeries = require('async/eachSeries')
5const express = require('express')
6const mongoose = require('mongoose')
7
8const middlewares = require('../../middlewares')
9const secureMiddleware = middlewares.secure
10const validators = middlewares.validators.remote
11const logger = require('../../helpers/logger')
12
13const router = express.Router()
14const Video = mongoose.model('Video')
15
16router.post('/videos',
17 validators.signature,
18 secureMiddleware.checkSignature,
19 validators.remoteVideos,
20 remoteVideos
21)
22
23// ---------------------------------------------------------------------------
24
25module.exports = router
26
27// ---------------------------------------------------------------------------
28
29function remoteVideos (req, res, next) {
30 const requests = req.body.data
31 const fromHost = req.body.signature.host
32
33 // We need to process in the same order to keep consistency
34 // TODO: optimization
35 eachSeries(requests, function (request, callbackEach) {
36 const videoData = request.data
37
38 if (request.type === 'add') {
39 addRemoteVideo(videoData, fromHost, callbackEach)
40 } else if (request.type === 'remove') {
41 removeRemoteVideo(videoData, fromHost, callbackEach)
42 } else {
43 logger.error('Unkown remote request type %s.', request.type)
44 }
45 }, function (err) {
46 if (err) logger.error('Error managing remote videos.', { error: err })
47 })
48
49 // We don't need to keep the other pod waiting
50 return res.type('json').status(204).end()
51}
52
53function addRemoteVideo (videoToCreateData, fromHost, callback) {
54 logger.debug('Adding remote video "%s".', videoToCreateData.name)
55
56 const video = new Video(videoToCreateData)
57 video.podHost = fromHost
58 Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) {
59 if (err) {
60 logger.error('Cannot generate thumbnail from base 64 data.', { error: err })
61 return callback(err)
62 }
63
64 video.save(callback)
65 })
66}
67
68function removeRemoteVideo (videoToRemoveData, fromHost, callback) {
69 // We need the list because we have to remove some other stuffs (thumbnail etc)
70 Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) {
71 if (err) {
72 logger.error('Cannot list videos from host and magnets.', { error: err })
73 return callback(err)
74 }
75
76 if (videosList.length === 0) {
77 logger.error('No remote video was found for this pod.', { magnetUri: videoToRemoveData.magnetUri, podHost: fromHost })
78 }
79
80 each(videosList, function (video, callbackEach) {
81 logger.debug('Removing remote video %s.', video.magnetUri)
82
83 video.remove(callbackEach)
84 }, callback)
85 })
86}
diff --git a/server/controllers/api/remote/index.js b/server/controllers/api/remote/index.js
new file mode 100644
index 000000000..2947632d5
--- /dev/null
+++ b/server/controllers/api/remote/index.js
@@ -0,0 +1,16 @@
1'use strict'
2
3const express = require('express')
4
5const utils = require('../../../helpers/utils')
6
7const router = express.Router()
8
9const videosRemoteController = require('./videos')
10
11router.use('/videos', videosRemoteController)
12router.use('/*', utils.badRequest)
13
14// ---------------------------------------------------------------------------
15
16module.exports = router
diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js
new file mode 100644
index 000000000..c45a86dbb
--- /dev/null
+++ b/server/controllers/api/remote/videos.js
@@ -0,0 +1,328 @@
1'use strict'
2
3const eachSeries = require('async/eachSeries')
4const express = require('express')
5const waterfall = require('async/waterfall')
6
7const db = require('../../../initializers/database')
8const middlewares = require('../../../middlewares')
9const secureMiddleware = middlewares.secure
10const videosValidators = middlewares.validators.remote.videos
11const signatureValidators = middlewares.validators.remote.signature
12const logger = require('../../../helpers/logger')
13const utils = require('../../../helpers/utils')
14
15const router = express.Router()
16
17router.post('/',
18 signatureValidators.signature,
19 secureMiddleware.checkSignature,
20 videosValidators.remoteVideos,
21 remoteVideos
22)
23
24// ---------------------------------------------------------------------------
25
26module.exports = router
27
28// ---------------------------------------------------------------------------
29
30function remoteVideos (req, res, next) {
31 const requests = req.body.data
32 const fromPod = res.locals.secure.pod
33
34 // We need to process in the same order to keep consistency
35 // TODO: optimization
36 eachSeries(requests, function (request, callbackEach) {
37 const data = request.data
38
39 switch (request.type) {
40 case 'add':
41 addRemoteVideoRetryWrapper(data, fromPod, callbackEach)
42 break
43
44 case 'update':
45 updateRemoteVideoRetryWrapper(data, fromPod, callbackEach)
46 break
47
48 case 'remove':
49 removeRemoteVideo(data, fromPod, callbackEach)
50 break
51
52 case 'report-abuse':
53 reportAbuseRemoteVideo(data, fromPod, callbackEach)
54 break
55
56 default:
57 logger.error('Unkown remote request type %s.', request.type)
58 }
59 }, function (err) {
60 if (err) logger.error('Error managing remote videos.', { error: err })
61 })
62
63 // We don't need to keep the other pod waiting
64 return res.type('json').status(204).end()
65}
66
67// Handle retries on fail
68function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
69 utils.transactionRetryer(
70 function (callback) {
71 return addRemoteVideo(videoToCreateData, fromPod, callback)
72 },
73 function (err) {
74 if (err) {
75 logger.error('Cannot insert the remote video with many retries.', { error: err })
76 }
77
78 // Do not return the error, continue the process
79 return finalCallback(null)
80 }
81 )
82}
83
84function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
85 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
86
87 waterfall([
88
89 function startTransaction (callback) {
90 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
91 return callback(err, t)
92 })
93 },
94
95 function findOrCreateAuthor (t, callback) {
96 const name = videoToCreateData.author
97 const podId = fromPod.id
98 // This author is from another pod so we do not associate a user
99 const userId = null
100
101 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
102 return callback(err, t, authorInstance)
103 })
104 },
105
106 function findOrCreateTags (t, author, callback) {
107 const tags = videoToCreateData.tags
108
109 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
110 return callback(err, t, author, tagInstances)
111 })
112 },
113
114 function createVideoObject (t, author, tagInstances, callback) {
115 const videoData = {
116 name: videoToCreateData.name,
117 remoteId: videoToCreateData.remoteId,
118 extname: videoToCreateData.extname,
119 infoHash: videoToCreateData.infoHash,
120 description: videoToCreateData.description,
121 authorId: author.id,
122 duration: videoToCreateData.duration,
123 createdAt: videoToCreateData.createdAt,
124 // FIXME: updatedAt does not seems to be considered by Sequelize
125 updatedAt: videoToCreateData.updatedAt
126 }
127
128 const video = db.Video.build(videoData)
129
130 return callback(null, t, tagInstances, video)
131 },
132
133 function generateThumbnail (t, tagInstances, video, callback) {
134 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
135 if (err) {
136 logger.error('Cannot generate thumbnail from data.', { error: err })
137 return callback(err)
138 }
139
140 return callback(err, t, tagInstances, video)
141 })
142 },
143
144 function insertVideoIntoDB (t, tagInstances, video, callback) {
145 const options = {
146 transaction: t
147 }
148
149 video.save(options).asCallback(function (err, videoCreated) {
150 return callback(err, t, tagInstances, videoCreated)
151 })
152 },
153
154 function associateTagsToVideo (t, tagInstances, video, callback) {
155 const options = { transaction: t }
156
157 video.setTags(tagInstances, options).asCallback(function (err) {
158 return callback(err, t)
159 })
160 }
161
162 ], function (err, t) {
163 if (err) {
164 // This is just a debug because we will retry the insert
165 logger.debug('Cannot insert the remote video.', { error: err })
166
167 // Abort transaction?
168 if (t) t.rollback()
169
170 return finalCallback(err)
171 }
172
173 // Commit transaction
174 t.commit().asCallback(function (err) {
175 if (err) return finalCallback(err)
176
177 logger.info('Remote video %s inserted.', videoToCreateData.name)
178 return finalCallback(null)
179 })
180 })
181}
182
183// Handle retries on fail
184function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
185 utils.transactionRetryer(
186 function (callback) {
187 return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback)
188 },
189 function (err) {
190 if (err) {
191 logger.error('Cannot update the remote video with many retries.', { error: err })
192 }
193
194 // Do not return the error, continue the process
195 return finalCallback(null)
196 }
197 )
198}
199
200function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
201 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
202
203 waterfall([
204
205 function startTransaction (callback) {
206 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
207 return callback(err, t)
208 })
209 },
210
211 function findVideo (t, callback) {
212 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
213 return callback(err, t, videoInstance)
214 })
215 },
216
217 function findOrCreateTags (t, videoInstance, callback) {
218 const tags = videoAttributesToUpdate.tags
219
220 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
221 return callback(err, t, videoInstance, tagInstances)
222 })
223 },
224
225 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
226 const options = { transaction: t }
227
228 videoInstance.set('name', videoAttributesToUpdate.name)
229 videoInstance.set('description', videoAttributesToUpdate.description)
230 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
231 videoInstance.set('duration', videoAttributesToUpdate.duration)
232 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
233 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
234 videoInstance.set('extname', videoAttributesToUpdate.extname)
235
236 videoInstance.save(options).asCallback(function (err) {
237 return callback(err, t, videoInstance, tagInstances)
238 })
239 },
240
241 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
242 const options = { transaction: t }
243
244 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
245 return callback(err, t)
246 })
247 }
248
249 ], function (err, t) {
250 if (err) {
251 // This is just a debug because we will retry the insert
252 logger.debug('Cannot update the remote video.', { error: err })
253
254 // Abort transaction?
255 if (t) t.rollback()
256
257 return finalCallback(err)
258 }
259
260 // Commit transaction
261 t.commit().asCallback(function (err) {
262 if (err) return finalCallback(err)
263
264 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
265 return finalCallback(null)
266 })
267 })
268}
269
270function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
271 // We need the instance because we have to remove some other stuffs (thumbnail etc)
272 fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
273 // Do not return the error, continue the process
274 if (err) return callback(null)
275
276 logger.debug('Removing remote video %s.', video.remoteId)
277 video.destroy().asCallback(function (err) {
278 // Do not return the error, continue the process
279 if (err) {
280 logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
281 }
282
283 return callback(null)
284 })
285 })
286}
287
288function reportAbuseRemoteVideo (reportData, fromPod, callback) {
289 db.Video.load(reportData.videoRemoteId, function (err, video) {
290 if (err || !video) {
291 if (!err) err = new Error('video not found')
292
293 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
294 // Do not return the error, continue the process
295 return callback(null)
296 }
297
298 logger.debug('Reporting remote abuse for video %s.', video.id)
299
300 const videoAbuseData = {
301 reporterUsername: reportData.reporterUsername,
302 reason: reportData.reportReason,
303 reporterPodId: fromPod.id,
304 videoId: video.id
305 }
306
307 db.VideoAbuse.create(videoAbuseData).asCallback(function (err) {
308 if (err) {
309 logger.error('Cannot create remote abuse video.', { error: err })
310 }
311
312 return callback(null)
313 })
314 })
315}
316
317function fetchVideo (podHost, remoteId, callback) {
318 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
319 if (err || !video) {
320 if (!err) err = new Error('video not found')
321
322 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
323 return callback(err)
324 }
325
326 return callback(null, video)
327 })
328}
diff --git a/server/controllers/api/requests.js b/server/controllers/api/requests.js
index 52aad6997..1f9193fc8 100644
--- a/server/controllers/api/requests.js
+++ b/server/controllers/api/requests.js
@@ -1,15 +1,13 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5 4
6const constants = require('../../initializers/constants') 5const constants = require('../../initializers/constants')
6const db = require('../../initializers/database')
7const middlewares = require('../../middlewares') 7const middlewares = require('../../middlewares')
8const admin = middlewares.admin 8const admin = middlewares.admin
9const oAuth = middlewares.oauth 9const oAuth = middlewares.oauth
10 10
11const Request = mongoose.model('Request')
12
13const router = express.Router() 11const router = express.Router()
14 12
15router.get('/stats', 13router.get('/stats',
@@ -25,13 +23,13 @@ module.exports = router
25// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
26 24
27function getStatsRequests (req, res, next) { 25function getStatsRequests (req, res, next) {
28 Request.list(function (err, requests) { 26 db.Request.countTotalRequests(function (err, totalRequests) {
29 if (err) return next(err) 27 if (err) return next(err)
30 28
31 return res.json({ 29 return res.json({
32 requests: requests, 30 totalRequests: totalRequests,
33 maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, 31 maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL,
34 remainingMilliSeconds: Request.remainingMilliSeconds(), 32 remainingMilliSeconds: db.Request.remainingMilliSeconds(),
35 milliSecondsInterval: constants.REQUESTS_INTERVAL 33 milliSecondsInterval: constants.REQUESTS_INTERVAL
36 }) 34 })
37 }) 35 })
diff --git a/server/controllers/api/users.js b/server/controllers/api/users.js
index b4d687312..6cd0e84f7 100644
--- a/server/controllers/api/users.js
+++ b/server/controllers/api/users.js
@@ -1,13 +1,12 @@
1'use strict' 1'use strict'
2 2
3const each = require('async/each')
4const express = require('express') 3const express = require('express')
5const mongoose = require('mongoose')
6const waterfall = require('async/waterfall') 4const waterfall = require('async/waterfall')
7 5
8const constants = require('../../initializers/constants') 6const constants = require('../../initializers/constants')
9const friends = require('../../lib/friends') 7const db = require('../../initializers/database')
10const logger = require('../../helpers/logger') 8const logger = require('../../helpers/logger')
9const utils = require('../../helpers/utils')
11const middlewares = require('../../middlewares') 10const middlewares = require('../../middlewares')
12const admin = middlewares.admin 11const admin = middlewares.admin
13const oAuth = middlewares.oauth 12const oAuth = middlewares.oauth
@@ -17,9 +16,6 @@ const validatorsPagination = middlewares.validators.pagination
17const validatorsSort = middlewares.validators.sort 16const validatorsSort = middlewares.validators.sort
18const validatorsUsers = middlewares.validators.users 17const validatorsUsers = middlewares.validators.users
19 18
20const User = mongoose.model('User')
21const Video = mongoose.model('Video')
22
23const router = express.Router() 19const router = express.Router()
24 20
25router.get('/me', oAuth.authenticate, getUserInformation) 21router.get('/me', oAuth.authenticate, getUserInformation)
@@ -62,13 +58,13 @@ module.exports = router
62// --------------------------------------------------------------------------- 58// ---------------------------------------------------------------------------
63 59
64function createUser (req, res, next) { 60function createUser (req, res, next) {
65 const user = new User({ 61 const user = db.User.build({
66 username: req.body.username, 62 username: req.body.username,
67 password: req.body.password, 63 password: req.body.password,
68 role: constants.USER_ROLES.USER 64 role: constants.USER_ROLES.USER
69 }) 65 })
70 66
71 user.save(function (err, createdUser) { 67 user.save().asCallback(function (err, createdUser) {
72 if (err) return next(err) 68 if (err) return next(err)
73 69
74 return res.type('json').status(204).end() 70 return res.type('json').status(204).end()
@@ -76,7 +72,7 @@ function createUser (req, res, next) {
76} 72}
77 73
78function getUserInformation (req, res, next) { 74function getUserInformation (req, res, next) {
79 User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 75 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
80 if (err) return next(err) 76 if (err) return next(err)
81 77
82 return res.json(user.toFormatedJSON()) 78 return res.json(user.toFormatedJSON())
@@ -84,48 +80,21 @@ function getUserInformation (req, res, next) {
84} 80}
85 81
86function listUsers (req, res, next) { 82function listUsers (req, res, next) {
87 User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { 83 db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
88 if (err) return next(err) 84 if (err) return next(err)
89 85
90 res.json(getFormatedUsers(usersList, usersTotal)) 86 res.json(utils.getFormatedObjects(usersList, usersTotal))
91 }) 87 })
92} 88}
93 89
94function removeUser (req, res, next) { 90function removeUser (req, res, next) {
95 waterfall([ 91 waterfall([
96 function getUser (callback) { 92 function loadUser (callback) {
97 User.loadById(req.params.id, callback) 93 db.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 }, 94 },
126 95
127 function removeUserFromDB (user, callback) { 96 function deleteUser (user, callback) {
128 user.remove(callback) 97 user.destroy().asCallback(callback)
129 } 98 }
130 ], function andFinally (err) { 99 ], function andFinally (err) {
131 if (err) { 100 if (err) {
@@ -138,11 +107,11 @@ function removeUser (req, res, next) {
138} 107}
139 108
140function updateUser (req, res, next) { 109function updateUser (req, res, next) {
141 User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 110 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
142 if (err) return next(err) 111 if (err) return next(err)
143 112
144 user.password = req.body.password 113 user.password = req.body.password
145 user.save(function (err) { 114 user.save().asCallback(function (err) {
146 if (err) return next(err) 115 if (err) return next(err)
147 116
148 return res.sendStatus(204) 117 return res.sendStatus(204)
@@ -153,18 +122,3 @@ function updateUser (req, res, next) {
153function success (req, res, next) { 122function success (req, res, next) {
154 res.end() 123 res.end()
155} 124}
156
157// ---------------------------------------------------------------------------
158
159function 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/videos.js b/server/controllers/api/videos.js
index daf452573..2c4af520e 100644
--- a/server/controllers/api/videos.js
+++ b/server/controllers/api/videos.js
@@ -2,15 +2,16 @@
2 2
3const express = require('express') 3const express = require('express')
4const fs = require('fs') 4const fs = require('fs')
5const mongoose = require('mongoose')
6const multer = require('multer') 5const multer = require('multer')
7const path = require('path') 6const path = require('path')
8const waterfall = require('async/waterfall') 7const waterfall = require('async/waterfall')
9 8
10const constants = require('../../initializers/constants') 9const constants = require('../../initializers/constants')
10const db = require('../../initializers/database')
11const logger = require('../../helpers/logger') 11const logger = require('../../helpers/logger')
12const friends = require('../../lib/friends') 12const friends = require('../../lib/friends')
13const middlewares = require('../../middlewares') 13const middlewares = require('../../middlewares')
14const admin = middlewares.admin
14const oAuth = middlewares.oauth 15const oAuth = middlewares.oauth
15const pagination = middlewares.pagination 16const pagination = middlewares.pagination
16const validators = middlewares.validators 17const validators = middlewares.validators
@@ -22,7 +23,6 @@ const sort = middlewares.sort
22const utils = require('../../helpers/utils') 23const utils = require('../../helpers/utils')
23 24
24const router = express.Router() 25const router = express.Router()
25const Video = mongoose.model('Video')
26 26
27// multer configuration 27// multer configuration
28const storage = multer.diskStorage({ 28const storage = multer.diskStorage({
@@ -44,6 +44,21 @@ const storage = multer.diskStorage({
44 44
45const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) 45const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }])
46 46
47router.get('/abuse',
48 oAuth.authenticate,
49 admin.ensureIsAdmin,
50 validatorsPagination.pagination,
51 validatorsSort.videoAbusesSort,
52 sort.setVideoAbusesSort,
53 pagination.setPagination,
54 listVideoAbuses
55)
56router.post('/:id/abuse',
57 oAuth.authenticate,
58 validatorsVideos.videoAbuseReport,
59 reportVideoAbuseRetryWrapper
60)
61
47router.get('/', 62router.get('/',
48 validatorsPagination.pagination, 63 validatorsPagination.pagination,
49 validatorsSort.videosSort, 64 validatorsSort.videosSort,
@@ -51,11 +66,17 @@ router.get('/',
51 pagination.setPagination, 66 pagination.setPagination,
52 listVideos 67 listVideos
53) 68)
69router.put('/:id',
70 oAuth.authenticate,
71 reqFiles,
72 validatorsVideos.videosUpdate,
73 updateVideoRetryWrapper
74)
54router.post('/', 75router.post('/',
55 oAuth.authenticate, 76 oAuth.authenticate,
56 reqFiles, 77 reqFiles,
57 validatorsVideos.videosAdd, 78 validatorsVideos.videosAdd,
58 addVideo 79 addVideoRetryWrapper
59) 80)
60router.get('/:id', 81router.get('/:id',
61 validatorsVideos.videosGet, 82 validatorsVideos.videosGet,
@@ -82,117 +103,264 @@ module.exports = router
82 103
83// --------------------------------------------------------------------------- 104// ---------------------------------------------------------------------------
84 105
85function addVideo (req, res, next) { 106// Wrapper to video add that retry the function if there is a database error
86 const videoFile = req.files.videofile[0] 107// We need this because we run the transaction in SERIALIZABLE isolation that can fail
108function addVideoRetryWrapper (req, res, next) {
109 utils.transactionRetryer(
110 function (callback) {
111 return addVideo(req, res, req.files.videofile[0], callback)
112 },
113 function (err) {
114 if (err) {
115 logger.error('Cannot insert the video with many retries.', { error: err })
116 return next(err)
117 }
118
119 // TODO : include Location of the new video -> 201
120 return res.type('json').status(204).end()
121 }
122 )
123}
124
125function addVideo (req, res, videoFile, callback) {
87 const videoInfos = req.body 126 const videoInfos = req.body
88 127
89 waterfall([ 128 waterfall([
90 function createVideoObject (callback) {
91 const id = mongoose.Types.ObjectId()
92 129
130 function startTransaction (callbackWaterfall) {
131 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
132 return callbackWaterfall(err, t)
133 })
134 },
135
136 function findOrCreateAuthor (t, callbackWaterfall) {
137 const user = res.locals.oauth.token.User
138
139 const name = user.username
140 // null because it is OUR pod
141 const podId = null
142 const userId = user.id
143
144 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
145 return callbackWaterfall(err, t, authorInstance)
146 })
147 },
148
149 function findOrCreateTags (t, author, callbackWaterfall) {
150 const tags = videoInfos.tags
151
152 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
153 return callbackWaterfall(err, t, author, tagInstances)
154 })
155 },
156
157 function createVideoObject (t, author, tagInstances, callbackWaterfall) {
93 const videoData = { 158 const videoData = {
94 _id: id,
95 name: videoInfos.name, 159 name: videoInfos.name,
96 remoteId: null, 160 remoteId: null,
97 extname: path.extname(videoFile.filename), 161 extname: path.extname(videoFile.filename),
98 description: videoInfos.description, 162 description: videoInfos.description,
99 author: res.locals.oauth.token.user.username,
100 duration: videoFile.duration, 163 duration: videoFile.duration,
101 tags: videoInfos.tags 164 authorId: author.id
102 } 165 }
103 166
104 const video = new Video(videoData) 167 const video = db.Video.build(videoData)
105 168
106 return callback(null, video) 169 return callbackWaterfall(null, t, author, tagInstances, video)
107 }, 170 },
108 171
109 // Set the videoname the same as the MongoDB id 172 // Set the videoname the same as the id
110 function renameVideoFile (video, callback) { 173 function renameVideoFile (t, author, tagInstances, video, callbackWaterfall) {
111 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR 174 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
112 const source = path.join(videoDir, videoFile.filename) 175 const source = path.join(videoDir, videoFile.filename)
113 const destination = path.join(videoDir, video.getVideoFilename()) 176 const destination = path.join(videoDir, video.getVideoFilename())
114 177
115 fs.rename(source, destination, function (err) { 178 fs.rename(source, destination, function (err) {
116 return callback(err, video) 179 if (err) return callbackWaterfall(err)
180
181 // This is important in case if there is another attempt
182 videoFile.filename = video.getVideoFilename()
183 return callbackWaterfall(null, t, author, tagInstances, video)
184 })
185 },
186
187 function insertVideoIntoDB (t, author, tagInstances, video, callbackWaterfall) {
188 const options = { transaction: t }
189
190 // Add tags association
191 video.save(options).asCallback(function (err, videoCreated) {
192 if (err) return callbackWaterfall(err)
193
194 // Do not forget to add Author informations to the created video
195 videoCreated.Author = author
196
197 return callbackWaterfall(err, t, tagInstances, videoCreated)
117 }) 198 })
118 }, 199 },
119 200
120 function insertIntoDB (video, callback) { 201 function associateTagsToVideo (t, tagInstances, video, callbackWaterfall) {
121 video.save(function (err, video) { 202 const options = { transaction: t }
122 // Assert there are only one argument sent to the next function (video) 203
123 return callback(err, video) 204 video.setTags(tagInstances, options).asCallback(function (err) {
205 video.Tags = tagInstances
206
207 return callbackWaterfall(err, t, video)
124 }) 208 })
125 }, 209 },
126 210
127 function sendToFriends (video, callback) { 211 function sendToFriends (t, video, callbackWaterfall) {
128 video.toRemoteJSON(function (err, remoteVideo) { 212 video.toAddRemoteJSON(function (err, remoteVideo) {
129 if (err) return callback(err) 213 if (err) return callbackWaterfall(err)
130 214
131 // Now we'll add the video's meta data to our friends 215 // Now we'll add the video's meta data to our friends
132 friends.addVideoToFriends(remoteVideo) 216 friends.addVideoToFriends(remoteVideo, t, function (err) {
133 217 return callbackWaterfall(err, t)
134 return callback(null) 218 })
135 }) 219 })
136 } 220 }
137 221
138 ], function andFinally (err) { 222 ], function andFinally (err, t) {
139 if (err) { 223 if (err) {
140 logger.error('Cannot insert the video.') 224 // This is just a debug because we will retry the insert
141 return next(err) 225 logger.debug('Cannot insert the video.', { error: err })
226
227 // Abort transaction?
228 if (t) t.rollback()
229
230 return callback(err)
142 } 231 }
143 232
144 // TODO : include Location of the new video -> 201 233 // Commit transaction
145 return res.type('json').status(204).end() 234 t.commit().asCallback(function (err) {
235 if (err) return callback(err)
236
237 logger.info('Video with name %s created.', videoInfos.name)
238 return callback(null)
239 })
146 }) 240 })
147} 241}
148 242
149function getVideo (req, res, next) { 243function updateVideoRetryWrapper (req, res, next) {
150 Video.load(req.params.id, function (err, video) { 244 utils.transactionRetryer(
151 if (err) return next(err) 245 function (callback) {
246 return updateVideo(req, res, callback)
247 },
248 function (err) {
249 if (err) {
250 logger.error('Cannot update the video with many retries.', { error: err })
251 return next(err)
252 }
152 253
153 if (!video) { 254 // TODO : include Location of the new video -> 201
154 return res.type('json').status(204).end() 255 return res.type('json').status(204).end()
155 } 256 }
156 257 )
157 res.json(video.toFormatedJSON())
158 })
159} 258}
160 259
161function listVideos (req, res, next) { 260function updateVideo (req, res, finalCallback) {
162 Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { 261 const videoInstance = res.locals.video
163 if (err) return next(err) 262 const videoFieldsSave = videoInstance.toJSON()
263 const videoInfosToUpdate = req.body
164 264
165 res.json(getFormatedVideos(videosList, videosTotal)) 265 waterfall([
166 })
167}
168 266
169function removeVideo (req, res, next) { 267 function startTransaction (callback) {
170 const videoId = req.params.id 268 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
269 return callback(err, t)
270 })
271 },
171 272
172 waterfall([ 273 function findOrCreateTags (t, callback) {
173 function getVideo (callback) { 274 if (videoInfosToUpdate.tags) {
174 Video.load(videoId, callback) 275 db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) {
276 return callback(err, t, tagInstances)
277 })
278 } else {
279 return callback(null, t, null)
280 }
175 }, 281 },
176 282
177 function removeFromDB (video, callback) { 283 function updateVideoIntoDB (t, tagInstances, callback) {
178 video.remove(function (err) { 284 const options = {
179 if (err) return callback(err) 285 transaction: t
286 }
287
288 if (videoInfosToUpdate.name) videoInstance.set('name', videoInfosToUpdate.name)
289 if (videoInfosToUpdate.description) videoInstance.set('description', videoInfosToUpdate.description)
180 290
181 return callback(null, video) 291 videoInstance.save(options).asCallback(function (err) {
292 return callback(err, t, tagInstances)
182 }) 293 })
183 }, 294 },
184 295
185 function sendInformationToFriends (video, callback) { 296 function associateTagsToVideo (t, tagInstances, callback) {
186 const params = { 297 if (tagInstances) {
187 name: video.name, 298 const options = { transaction: t }
188 remoteId: video._id 299
300 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
301 videoInstance.Tags = tagInstances
302
303 return callback(err, t)
304 })
305 } else {
306 return callback(null, t)
189 } 307 }
308 },
190 309
191 friends.removeVideoToFriends(params) 310 function sendToFriends (t, callback) {
311 const json = videoInstance.toUpdateRemoteJSON()
192 312
193 return callback(null) 313 // Now we'll update the video's meta data to our friends
314 friends.updateVideoToFriends(json, t, function (err) {
315 return callback(err, t)
316 })
317 }
318
319 ], function andFinally (err, t) {
320 if (err) {
321 logger.debug('Cannot update the video.', { error: err })
322
323 // Abort transaction?
324 if (t) t.rollback()
325
326 // Force fields we want to update
327 // If the transaction is retried, sequelize will think the object has not changed
328 // So it will skip the SQL request, even if the last one was ROLLBACKed!
329 Object.keys(videoFieldsSave).forEach(function (key) {
330 const value = videoFieldsSave[key]
331 videoInstance.set(key, value)
332 })
333
334 return finalCallback(err)
194 } 335 }
195 ], function andFinally (err) { 336
337 // Commit transaction
338 t.commit().asCallback(function (err) {
339 if (err) return finalCallback(err)
340
341 logger.info('Video with name %s updated.', videoInfosToUpdate.name)
342 return finalCallback(null)
343 })
344 })
345}
346
347function getVideo (req, res, next) {
348 const videoInstance = res.locals.video
349 res.json(videoInstance.toFormatedJSON())
350}
351
352function listVideos (req, res, next) {
353 db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
354 if (err) return next(err)
355
356 res.json(utils.getFormatedObjects(videosList, videosTotal))
357 })
358}
359
360function removeVideo (req, res, next) {
361 const videoInstance = res.locals.video
362
363 videoInstance.destroy().asCallback(function (err) {
196 if (err) { 364 if (err) {
197 logger.error('Errors when removed the video.', { error: err }) 365 logger.error('Errors when removed the video.', { error: err })
198 return next(err) 366 return next(err)
@@ -203,25 +371,97 @@ function removeVideo (req, res, next) {
203} 371}
204 372
205function searchVideos (req, res, next) { 373function searchVideos (req, res, next) {
206 Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, 374 db.Video.searchAndPopulateAuthorAndPodAndTags(
207 function (err, videosList, videosTotal) { 375 req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
376 function (err, videosList, videosTotal) {
377 if (err) return next(err)
378
379 res.json(utils.getFormatedObjects(videosList, videosTotal))
380 }
381 )
382}
383
384function listVideoAbuses (req, res, next) {
385 db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) {
208 if (err) return next(err) 386 if (err) return next(err)
209 387
210 res.json(getFormatedVideos(videosList, videosTotal)) 388 res.json(utils.getFormatedObjects(abusesList, abusesTotal))
211 }) 389 })
212} 390}
213 391
214// --------------------------------------------------------------------------- 392function reportVideoAbuseRetryWrapper (req, res, next) {
393 utils.transactionRetryer(
394 function (callback) {
395 return reportVideoAbuse(req, res, callback)
396 },
397 function (err) {
398 if (err) {
399 logger.error('Cannot report abuse to the video with many retries.', { error: err })
400 return next(err)
401 }
215 402
216function getFormatedVideos (videos, videosTotal) { 403 return res.type('json').status(204).end()
217 const formatedVideos = [] 404 }
405 )
406}
218 407
219 videos.forEach(function (video) { 408function reportVideoAbuse (req, res, finalCallback) {
220 formatedVideos.push(video.toFormatedJSON()) 409 const videoInstance = res.locals.video
221 }) 410 const reporterUsername = res.locals.oauth.token.User.username
222 411
223 return { 412 const abuse = {
224 total: videosTotal, 413 reporterUsername,
225 data: formatedVideos 414 reason: req.body.reason,
415 videoId: videoInstance.id,
416 reporterPodId: null // This is our pod that reported this abuse
226 } 417 }
418
419 waterfall([
420
421 function startTransaction (callback) {
422 db.sequelize.transaction().asCallback(function (err, t) {
423 return callback(err, t)
424 })
425 },
426
427 function createAbuse (t, callback) {
428 db.VideoAbuse.create(abuse).asCallback(function (err, abuse) {
429 return callback(err, t, abuse)
430 })
431 },
432
433 function sendToFriendsIfNeeded (t, abuse, callback) {
434 // We send the information to the destination pod
435 if (videoInstance.isOwned() === false) {
436 const reportData = {
437 reporterUsername,
438 reportReason: abuse.reason,
439 videoRemoteId: videoInstance.remoteId
440 }
441
442 friends.reportAbuseVideoToFriend(reportData, videoInstance)
443 }
444
445 return callback(null, t)
446 }
447
448 ], function andFinally (err, t) {
449 if (err) {
450 logger.debug('Cannot update the video.', { error: err })
451
452 // Abort transaction?
453 if (t) t.rollback()
454
455 return finalCallback(err)
456 }
457
458 // Commit transaction
459 t.commit().asCallback(function (err) {
460 if (err) return finalCallback(err)
461
462 logger.info('Abuse report for video %s created.', videoInstance.name)
463 return finalCallback(null)
464 })
465 })
227} 466}
467
diff --git a/server/controllers/client.js b/server/controllers/client.js
index 572db6133..8c242af07 100644
--- a/server/controllers/client.js
+++ b/server/controllers/client.js
@@ -3,13 +3,12 @@
3const parallel = require('async/parallel') 3const parallel = require('async/parallel')
4const express = require('express') 4const express = require('express')
5const fs = require('fs') 5const fs = require('fs')
6const mongoose = require('mongoose')
7const path = require('path') 6const path = require('path')
8const validator = require('express-validator').validator 7const validator = require('express-validator').validator
9 8
10const constants = require('../initializers/constants') 9const constants = require('../initializers/constants')
10const db = require('../initializers/database')
11 11
12const Video = mongoose.model('Video')
13const router = express.Router() 12const router = express.Router()
14 13
15const opengraphComment = '<!-- opengraph tags -->' 14const opengraphComment = '<!-- opengraph tags -->'
@@ -45,14 +44,14 @@ function addOpenGraphTags (htmlStringPage, video) {
45 if (video.isOwned()) { 44 if (video.isOwned()) {
46 basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL 45 basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL
47 } else { 46 } else {
48 basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.podHost 47 basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
49 } 48 }
50 49
51 // We fetch the remote preview (bigger than the thumbnail) 50 // We fetch the remote preview (bigger than the thumbnail)
52 // This should not overhead the remote server since social websites put in a cache the OpenGraph tags 51 // This should not overhead the remote server since social websites put in a cache the OpenGraph tags
53 // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) 52 // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example)
54 const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() 53 const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName()
55 const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video._id 54 const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id
56 55
57 const metaTags = { 56 const metaTags = {
58 'og:type': 'video', 57 'og:type': 'video',
@@ -86,7 +85,7 @@ function generateWatchHtmlPage (req, res, next) {
86 const videoId = req.params.id 85 const videoId = req.params.id
87 86
88 // Let Angular application handle errors 87 // Let Angular application handle errors
89 if (!validator.isMongoId(videoId)) return res.sendFile(indexPath) 88 if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
90 89
91 parallel({ 90 parallel({
92 file: function (callback) { 91 file: function (callback) {
@@ -94,7 +93,7 @@ function generateWatchHtmlPage (req, res, next) {
94 }, 93 },
95 94
96 video: function (callback) { 95 video: function (callback) {
97 Video.load(videoId, callback) 96 db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback)
98 } 97 }
99 }, function (err, results) { 98 }, function (err, results) {
100 if (err) return next(err) 99 if (err) return next(err)
diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js
index 96b5b20b9..9383e0304 100644
--- a/server/helpers/custom-validators/index.js
+++ b/server/helpers/custom-validators/index.js
@@ -2,12 +2,14 @@
2 2
3const miscValidators = require('./misc') 3const miscValidators = require('./misc')
4const podsValidators = require('./pods') 4const podsValidators = require('./pods')
5const remoteValidators = require('./remote')
5const usersValidators = require('./users') 6const usersValidators = require('./users')
6const videosValidators = require('./videos') 7const videosValidators = require('./videos')
7 8
8const validators = { 9const validators = {
9 misc: miscValidators, 10 misc: miscValidators,
10 pods: podsValidators, 11 pods: podsValidators,
12 remote: remoteValidators,
11 users: usersValidators, 13 users: usersValidators,
12 videos: videosValidators 14 videos: videosValidators
13} 15}
diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js
index 0154a2424..8bb3733ff 100644
--- a/server/helpers/custom-validators/pods.js
+++ b/server/helpers/custom-validators/pods.js
@@ -5,14 +5,19 @@ const validator = require('express-validator').validator
5const miscValidators = require('./misc') 5const miscValidators = require('./misc')
6 6
7const podsValidators = { 7const podsValidators = {
8 isEachUniqueHostValid 8 isEachUniqueHostValid,
9 isHostValid
10}
11
12function isHostValid (host) {
13 return validator.isURL(host) && host.split('://').length === 1
9} 14}
10 15
11function isEachUniqueHostValid (hosts) { 16function isEachUniqueHostValid (hosts) {
12 return miscValidators.isArray(hosts) && 17 return miscValidators.isArray(hosts) &&
13 hosts.length !== 0 && 18 hosts.length !== 0 &&
14 hosts.every(function (host) { 19 hosts.every(function (host) {
15 return validator.isURL(host) && host.split('://').length === 1 && hosts.indexOf(host) === hosts.lastIndexOf(host) 20 return isHostValid(host) && hosts.indexOf(host) === hosts.lastIndexOf(host)
16 }) 21 })
17} 22}
18 23
diff --git a/server/helpers/custom-validators/remote/index.js b/server/helpers/custom-validators/remote/index.js
new file mode 100644
index 000000000..1939a95f4
--- /dev/null
+++ b/server/helpers/custom-validators/remote/index.js
@@ -0,0 +1,11 @@
1'use strict'
2
3const remoteVideosValidators = require('./videos')
4
5const validators = {
6 videos: remoteVideosValidators
7}
8
9// ---------------------------------------------------------------------------
10
11module.exports = validators
diff --git a/server/helpers/custom-validators/remote/videos.js b/server/helpers/custom-validators/remote/videos.js
new file mode 100644
index 000000000..7c27b9dbb
--- /dev/null
+++ b/server/helpers/custom-validators/remote/videos.js
@@ -0,0 +1,73 @@
1'use strict'
2
3const videosValidators = require('../videos')
4const miscValidators = require('../misc')
5
6const remoteVideosValidators = {
7 isEachRemoteRequestVideosValid
8}
9
10function isEachRemoteRequestVideosValid (requests) {
11 return miscValidators.isArray(requests) &&
12 requests.every(function (request) {
13 const video = request.data
14 return (
15 isRequestTypeAddValid(request.type) &&
16 videosValidators.isVideoAuthorValid(video.author) &&
17 videosValidators.isVideoDateValid(video.createdAt) &&
18 videosValidators.isVideoDateValid(video.updatedAt) &&
19 videosValidators.isVideoDescriptionValid(video.description) &&
20 videosValidators.isVideoDurationValid(video.duration) &&
21 videosValidators.isVideoInfoHashValid(video.infoHash) &&
22 videosValidators.isVideoNameValid(video.name) &&
23 videosValidators.isVideoTagsValid(video.tags) &&
24 videosValidators.isVideoThumbnailDataValid(video.thumbnailData) &&
25 videosValidators.isVideoRemoteIdValid(video.remoteId) &&
26 videosValidators.isVideoExtnameValid(video.extname)
27 ) ||
28 (
29 isRequestTypeUpdateValid(request.type) &&
30 videosValidators.isVideoDateValid(video.createdAt) &&
31 videosValidators.isVideoDateValid(video.updatedAt) &&
32 videosValidators.isVideoDescriptionValid(video.description) &&
33 videosValidators.isVideoDurationValid(video.duration) &&
34 videosValidators.isVideoInfoHashValid(video.infoHash) &&
35 videosValidators.isVideoNameValid(video.name) &&
36 videosValidators.isVideoTagsValid(video.tags) &&
37 videosValidators.isVideoRemoteIdValid(video.remoteId) &&
38 videosValidators.isVideoExtnameValid(video.extname)
39 ) ||
40 (
41 isRequestTypeRemoveValid(request.type) &&
42 videosValidators.isVideoRemoteIdValid(video.remoteId)
43 ) ||
44 (
45 isRequestTypeReportAbuseValid(request.type) &&
46 videosValidators.isVideoRemoteIdValid(request.data.videoRemoteId) &&
47 videosValidators.isVideoAbuseReasonValid(request.data.reportReason) &&
48 videosValidators.isVideoAbuseReporterUsernameValid(request.data.reporterUsername)
49 )
50 })
51}
52
53// ---------------------------------------------------------------------------
54
55module.exports = remoteVideosValidators
56
57// ---------------------------------------------------------------------------
58
59function isRequestTypeAddValid (value) {
60 return value === 'add'
61}
62
63function isRequestTypeUpdateValid (value) {
64 return value === 'update'
65}
66
67function isRequestTypeRemoveValid (value) {
68 return value === 'remove'
69}
70
71function isRequestTypeReportAbuseValid (value) {
72 return value === 'report-abuse'
73}
diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js
index 1a7753265..7f727854d 100644
--- a/server/helpers/custom-validators/videos.js
+++ b/server/helpers/custom-validators/videos.js
@@ -6,43 +6,22 @@ const constants = require('../../initializers/constants')
6const usersValidators = require('./users') 6const usersValidators = require('./users')
7const miscValidators = require('./misc') 7const miscValidators = require('./misc')
8const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS 8const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS
9const VIDEO_ABUSES_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEO_ABUSES
9 10
10const videosValidators = { 11const videosValidators = {
11 isEachRemoteVideosValid,
12 isVideoAuthorValid, 12 isVideoAuthorValid,
13 isVideoDateValid, 13 isVideoDateValid,
14 isVideoDescriptionValid, 14 isVideoDescriptionValid,
15 isVideoDurationValid, 15 isVideoDurationValid,
16 isVideoMagnetValid, 16 isVideoInfoHashValid,
17 isVideoNameValid, 17 isVideoNameValid,
18 isVideoPodHostValid,
19 isVideoTagsValid, 18 isVideoTagsValid,
20 isVideoThumbnailValid, 19 isVideoThumbnailValid,
21 isVideoThumbnail64Valid 20 isVideoThumbnailDataValid,
22} 21 isVideoExtnameValid,
23 22 isVideoRemoteIdValid,
24function isEachRemoteVideosValid (requests) { 23 isVideoAbuseReasonValid,
25 return miscValidators.isArray(requests) && 24 isVideoAbuseReporterUsernameValid
26 requests.every(function (request) {
27 const video = request.data
28 return (
29 isRequestTypeAddValid(request.type) &&
30 isVideoAuthorValid(video.author) &&
31 isVideoDateValid(video.createdDate) &&
32 isVideoDescriptionValid(video.description) &&
33 isVideoDurationValid(video.duration) &&
34 isVideoMagnetValid(video.magnet) &&
35 isVideoNameValid(video.name) &&
36 isVideoTagsValid(video.tags) &&
37 isVideoThumbnail64Valid(video.thumbnailBase64) &&
38 isVideoRemoteIdValid(video.remoteId)
39 ) ||
40 (
41 isRequestTypeRemoveValid(request.type) &&
42 isVideoNameValid(video.name) &&
43 isVideoRemoteIdValid(video.remoteId)
44 )
45 })
46} 25}
47 26
48function isVideoAuthorValid (value) { 27function isVideoAuthorValid (value) {
@@ -61,17 +40,16 @@ function isVideoDurationValid (value) {
61 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) 40 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
62} 41}
63 42
64function isVideoMagnetValid (value) { 43function isVideoExtnameValid (value) {
65 return validator.isLength(value.infoHash, VIDEOS_CONSTRAINTS_FIELDS.MAGNET.INFO_HASH) 44 return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
66} 45}
67 46
68function isVideoNameValid (value) { 47function isVideoInfoHashValid (value) {
69 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) 48 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
70} 49}
71 50
72function isVideoPodHostValid (value) { 51function isVideoNameValid (value) {
73 // TODO: set options (TLD...) 52 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME)
74 return validator.isURL(value)
75} 53}
76 54
77function isVideoTagsValid (tags) { 55function isVideoTagsValid (tags) {
@@ -87,25 +65,22 @@ function isVideoThumbnailValid (value) {
87 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL) 65 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL)
88} 66}
89 67
90function isVideoThumbnail64Valid (value) { 68function isVideoThumbnailDataValid (value) {
91 return validator.isBase64(value) && 69 return validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA)
92 validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64)
93} 70}
94 71
95function isVideoRemoteIdValid (value) { 72function isVideoRemoteIdValid (value) {
96 return validator.isMongoId(value) 73 return validator.isUUID(value, 4)
97} 74}
98 75
99// --------------------------------------------------------------------------- 76function isVideoAbuseReasonValid (value) {
77 return validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
78}
100 79
101module.exports = videosValidators 80function isVideoAbuseReporterUsernameValid (value) {
81 return usersValidators.isUserUsernameValid(value)
82}
102 83
103// --------------------------------------------------------------------------- 84// ---------------------------------------------------------------------------
104 85
105function isRequestTypeAddValid (value) { 86module.exports = videosValidators
106 return value === 'add'
107}
108
109function isRequestTypeRemoveValid (value) {
110 return value === 'remove'
111}
diff --git a/server/helpers/logger.js b/server/helpers/logger.js
index fcc1789fd..281acedb8 100644
--- a/server/helpers/logger.js
+++ b/server/helpers/logger.js
@@ -22,7 +22,8 @@ const logger = new winston.Logger({
22 json: true, 22 json: true,
23 maxsize: 5242880, 23 maxsize: 5242880,
24 maxFiles: 5, 24 maxFiles: 5,
25 colorize: false 25 colorize: false,
26 prettyPrint: true
26 }), 27 }),
27 new winston.transports.Console({ 28 new winston.transports.Console({
28 level: 'debug', 29 level: 'debug',
@@ -30,7 +31,8 @@ const logger = new winston.Logger({
30 handleExceptions: true, 31 handleExceptions: true,
31 humanReadableUnhandledException: true, 32 humanReadableUnhandledException: true,
32 json: false, 33 json: false,
33 colorize: true 34 colorize: true,
35 prettyPrint: true
34 }) 36 })
35 ], 37 ],
36 exitOnError: true 38 exitOnError: true
diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js
index 2e07df00e..0f1e02ad6 100644
--- a/server/helpers/peertube-crypto.js
+++ b/server/helpers/peertube-crypto.js
@@ -1,16 +1,13 @@
1'use strict' 1'use strict'
2 2
3const bcrypt = require('bcrypt')
4const crypto = require('crypto') 3const crypto = require('crypto')
4const bcrypt = require('bcrypt')
5const fs = require('fs') 5const fs = require('fs')
6const openssl = require('openssl-wrapper') 6const openssl = require('openssl-wrapper')
7const ursa = require('ursa')
8 7
9const constants = require('../initializers/constants') 8const constants = require('../initializers/constants')
10const logger = require('./logger') 9const logger = require('./logger')
11 10
12const algorithm = 'aes-256-ctr'
13
14const peertubeCrypto = { 11const peertubeCrypto = {
15 checkSignature, 12 checkSignature,
16 comparePassword, 13 comparePassword,
@@ -19,12 +16,51 @@ const peertubeCrypto = {
19 sign 16 sign
20} 17}
21 18
22function checkSignature (publicKey, rawData, hexSignature) { 19function checkSignature (publicKey, data, hexSignature) {
23 const crt = ursa.createPublicKey(publicKey) 20 const verify = crypto.createVerify(constants.SIGNATURE_ALGORITHM)
24 const isValid = crt.hashAndVerify('sha256', new Buffer(rawData).toString('hex'), hexSignature, 'hex') 21
22 let dataString
23 if (typeof data === 'string') {
24 dataString = data
25 } else {
26 try {
27 dataString = JSON.stringify(data)
28 } catch (err) {
29 logger.error('Cannot check signature.', { error: err })
30 return false
31 }
32 }
33
34 verify.update(dataString, 'utf8')
35
36 const isValid = verify.verify(publicKey, hexSignature, constants.SIGNATURE_ENCODING)
25 return isValid 37 return isValid
26} 38}
27 39
40function sign (data) {
41 const sign = crypto.createSign(constants.SIGNATURE_ALGORITHM)
42
43 let dataString
44 if (typeof data === 'string') {
45 dataString = data
46 } else {
47 try {
48 dataString = JSON.stringify(data)
49 } catch (err) {
50 logger.error('Cannot sign data.', { error: err })
51 return ''
52 }
53 }
54
55 sign.update(dataString, 'utf8')
56
57 // TODO: make async
58 const myKey = fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem')
59 const signature = sign.sign(myKey, constants.SIGNATURE_ENCODING)
60
61 return signature
62}
63
28function comparePassword (plainPassword, hashPassword, callback) { 64function comparePassword (plainPassword, hashPassword, callback) {
29 bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { 65 bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) {
30 if (err) return callback(err) 66 if (err) return callback(err)
@@ -55,13 +91,6 @@ function cryptPassword (password, callback) {
55 }) 91 })
56} 92}
57 93
58function sign (data) {
59 const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem'))
60 const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
61
62 return signature
63}
64
65// --------------------------------------------------------------------------- 94// ---------------------------------------------------------------------------
66 95
67module.exports = peertubeCrypto 96module.exports = peertubeCrypto
@@ -113,11 +142,3 @@ function createCerts (callback) {
113 }) 142 })
114 }) 143 })
115} 144}
116
117function generatePassword (callback) {
118 crypto.randomBytes(32, function (err, buf) {
119 if (err) return callback(err)
120
121 callback(null, buf.toString('utf8'))
122 })
123}
diff --git a/server/helpers/requests.js b/server/helpers/requests.js
index b0cda09fe..095b95e1c 100644
--- a/server/helpers/requests.js
+++ b/server/helpers/requests.js
@@ -28,31 +28,37 @@ function makeSecureRequest (params, callback) {
28 url: constants.REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path 28 url: constants.REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path
29 } 29 }
30 30
31 // Add data with POST requst ? 31 if (params.method !== 'POST') {
32 if (params.method === 'POST') { 32 return callback(new Error('Cannot make a secure request with a non POST method.'))
33 requestParams.json = {} 33 }
34 34
35 // Add signature if it is specified in the params 35 requestParams.json = {}
36 if (params.sign === true) {
37 const host = constants.CONFIG.WEBSERVER.HOST
38
39 requestParams.json.signature = {
40 host,
41 signature: peertubeCrypto.sign(host)
42 }
43 }
44 36
45 // If there are data informations 37 // Add signature if it is specified in the params
38 if (params.sign === true) {
39 const host = constants.CONFIG.WEBSERVER.HOST
40
41 let dataToSign
46 if (params.data) { 42 if (params.data) {
47 requestParams.json.data = params.data 43 dataToSign = dataToSign = params.data
48 request.post(requestParams, callback)
49 } else { 44 } else {
50 // No data 45 // We do not have data to sign so we just take our host
51 request.post(requestParams, callback) 46 // It is not ideal but the connection should be in HTTPS
47 dataToSign = host
52 } 48 }
53 } else { 49
54 request.get(requestParams, callback) 50 requestParams.json.signature = {
51 host, // Which host we pretend to be
52 signature: peertubeCrypto.sign(dataToSign)
53 }
54 }
55
56 // If there are data informations
57 if (params.data) {
58 requestParams.json.data = params.data
55 } 59 }
60
61 request.post(requestParams, callback)
56} 62}
57 63
58// --------------------------------------------------------------------------- 64// ---------------------------------------------------------------------------
diff --git a/server/helpers/utils.js b/server/helpers/utils.js
index 9f27671b6..a902850cd 100644
--- a/server/helpers/utils.js
+++ b/server/helpers/utils.js
@@ -1,13 +1,21 @@
1'use strict' 1'use strict'
2 2
3const crypto = require('crypto') 3const crypto = require('crypto')
4const retry = require('async/retry')
4 5
5const logger = require('./logger') 6const logger = require('./logger')
6 7
7const utils = { 8const utils = {
9 badRequest,
8 cleanForExit, 10 cleanForExit,
9 generateRandomString, 11 generateRandomString,
10 isTestInstance 12 isTestInstance,
13 getFormatedObjects,
14 transactionRetryer
15}
16
17function badRequest (req, res, next) {
18 res.type('json').status(400).end()
11} 19}
12 20
13function generateRandomString (size, callback) { 21function generateRandomString (size, callback) {
@@ -27,6 +35,31 @@ function isTestInstance () {
27 return (process.env.NODE_ENV === 'test') 35 return (process.env.NODE_ENV === 'test')
28} 36}
29 37
38function getFormatedObjects (objects, objectsTotal) {
39 const formatedObjects = []
40
41 objects.forEach(function (object) {
42 formatedObjects.push(object.toFormatedJSON())
43 })
44
45 return {
46 total: objectsTotal,
47 data: formatedObjects
48 }
49}
50
51function transactionRetryer (func, callback) {
52 retry({
53 times: 5,
54
55 errorFilter: function (err) {
56 const willRetry = (err.name === 'SequelizeDatabaseError')
57 logger.debug('Maybe retrying the transaction function.', { willRetry })
58 return willRetry
59 }
60 }, func, callback)
61}
62
30// --------------------------------------------------------------------------- 63// ---------------------------------------------------------------------------
31 64
32module.exports = utils 65module.exports = utils
diff --git a/server/initializers/checker.js b/server/initializers/checker.js
index aea013fa9..6471bb4f1 100644
--- a/server/initializers/checker.js
+++ b/server/initializers/checker.js
@@ -1,10 +1,8 @@
1'use strict' 1'use strict'
2 2
3const config = require('config') 3const config = require('config')
4const mongoose = require('mongoose')
5 4
6const Client = mongoose.model('OAuthClient') 5const db = require('./database')
7const User = mongoose.model('User')
8 6
9const checker = { 7const checker = {
10 checkConfig, 8 checkConfig,
@@ -29,7 +27,7 @@ function checkConfig () {
29function checkMissedConfig () { 27function checkMissedConfig () {
30 const required = [ 'listen.port', 28 const required = [ 'listen.port',
31 'webserver.https', 'webserver.hostname', 'webserver.port', 29 'webserver.https', 'webserver.hostname', 'webserver.port',
32 'database.hostname', 'database.port', 'database.suffix', 30 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password',
33 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews' 31 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews'
34 ] 32 ]
35 const miss = [] 33 const miss = []
@@ -44,15 +42,15 @@ function checkMissedConfig () {
44} 42}
45 43
46function clientsExist (callback) { 44function clientsExist (callback) {
47 Client.list(function (err, clients) { 45 db.OAuthClient.countTotal(function (err, totalClients) {
48 if (err) return callback(err) 46 if (err) return callback(err)
49 47
50 return callback(null, clients.length !== 0) 48 return callback(null, totalClients !== 0)
51 }) 49 })
52} 50}
53 51
54function usersExist (callback) { 52function usersExist (callback) {
55 User.countTotal(function (err, totalUsers) { 53 db.User.countTotal(function (err, totalUsers) {
56 if (err) return callback(err) 54 if (err) return callback(err)
57 55
58 return callback(null, totalUsers !== 0) 56 return callback(null, totalUsers !== 0)
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index 3ddf87454..97e3c5296 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -1,7 +1,6 @@
1'use strict' 1'use strict'
2 2
3const config = require('config') 3const config = require('config')
4const maxBy = require('lodash/maxBy')
5const path = require('path') 4const path = require('path')
6 5
7// --------------------------------------------------------------------------- 6// ---------------------------------------------------------------------------
@@ -14,13 +13,14 @@ const PAGINATION_COUNT_DEFAULT = 15
14 13
15// Sortable columns per schema 14// Sortable columns per schema
16const SEARCHABLE_COLUMNS = { 15const SEARCHABLE_COLUMNS = {
17 VIDEOS: [ 'name', 'magnetUri', 'podHost', 'author', 'tags' ] 16 VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ]
18} 17}
19 18
20// Sortable columns per schema 19// Sortable columns per schema
21const SORTABLE_COLUMNS = { 20const SORTABLE_COLUMNS = {
22 USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], 21 USERS: [ 'username', '-username', 'createdAt', '-createdAt' ],
23 VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] 22 VIDEO_ABUSES: [ 'createdAt', '-createdAt' ],
23 VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ]
24} 24}
25 25
26const OAUTH_LIFETIME = { 26const OAUTH_LIFETIME = {
@@ -37,7 +37,9 @@ const CONFIG = {
37 DATABASE: { 37 DATABASE: {
38 DBNAME: 'peertube' + config.get('database.suffix'), 38 DBNAME: 'peertube' + config.get('database.suffix'),
39 HOSTNAME: config.get('database.hostname'), 39 HOSTNAME: config.get('database.hostname'),
40 PORT: config.get('database.port') 40 PORT: config.get('database.port'),
41 USERNAME: config.get('database.username'),
42 PASSWORD: config.get('database.password')
41 }, 43 },
42 STORAGE: { 44 STORAGE: {
43 CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), 45 CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')),
@@ -64,17 +66,19 @@ const CONSTRAINTS_FIELDS = {
64 USERNAME: { min: 3, max: 20 }, // Length 66 USERNAME: { min: 3, max: 20 }, // Length
65 PASSWORD: { min: 6, max: 255 } // Length 67 PASSWORD: { min: 6, max: 255 } // Length
66 }, 68 },
69 VIDEO_ABUSES: {
70 REASON: { min: 2, max: 300 } // Length
71 },
67 VIDEOS: { 72 VIDEOS: {
68 NAME: { min: 3, max: 50 }, // Length 73 NAME: { min: 3, max: 50 }, // Length
69 DESCRIPTION: { min: 3, max: 250 }, // Length 74 DESCRIPTION: { min: 3, max: 250 }, // Length
70 MAGNET: { 75 EXTNAME: [ '.mp4', '.ogv', '.webm' ],
71 INFO_HASH: { min: 10, max: 50 } // Length 76 INFO_HASH: { min: 40, max: 40 }, // Length, infohash is 20 bytes length but we represent it in hexa so 20 * 2
72 },
73 DURATION: { min: 1, max: 7200 }, // Number 77 DURATION: { min: 1, max: 7200 }, // Number
74 TAGS: { min: 1, max: 3 }, // Number of total tags 78 TAGS: { min: 1, max: 3 }, // Number of total tags
75 TAG: { min: 2, max: 10 }, // Length 79 TAG: { min: 2, max: 10 }, // Length
76 THUMBNAIL: { min: 2, max: 30 }, 80 THUMBNAIL: { min: 2, max: 30 },
77 THUMBNAIL64: { min: 0, max: 20000 } // Bytes 81 THUMBNAIL_DATA: { min: 0, max: 20000 } // Bytes
78 } 82 }
79} 83}
80 84
@@ -88,41 +92,7 @@ const FRIEND_SCORE = {
88 92
89// --------------------------------------------------------------------------- 93// ---------------------------------------------------------------------------
90 94
91const MONGO_MIGRATION_SCRIPTS = [ 95const LAST_MIGRATION_VERSION = 0
92 {
93 script: '0005-create-application',
94 version: 5
95 },
96 {
97 script: '0010-users-password',
98 version: 10
99 },
100 {
101 script: '0015-admin-role',
102 version: 15
103 },
104 {
105 script: '0020-requests-endpoint',
106 version: 20
107 },
108 {
109 script: '0025-video-filenames',
110 version: 25
111 },
112 {
113 script: '0030-video-magnet',
114 version: 30
115 },
116 {
117 script: '0035-url-to-host',
118 version: 35
119 },
120 {
121 script: '0040-video-remote-id',
122 version: 40
123 }
124]
125const LAST_MONGO_SCHEMA_VERSION = (maxBy(MONGO_MIGRATION_SCRIPTS, 'version'))['version']
126 96
127// --------------------------------------------------------------------------- 97// ---------------------------------------------------------------------------
128 98
@@ -138,8 +108,10 @@ let REQUESTS_INTERVAL = 600000
138// Number of requests in parallel we can make 108// Number of requests in parallel we can make
139const REQUESTS_IN_PARALLEL = 10 109const REQUESTS_IN_PARALLEL = 10
140 110
141// How many requests we put in request 111// To how many pods we send requests
142const REQUESTS_LIMIT = 10 112const REQUESTS_LIMIT_PODS = 10
113// How many requests we send to a pod per interval
114const REQUESTS_LIMIT_PER_POD = 5
143 115
144// Number of requests to retry for replay requests module 116// Number of requests to retry for replay requests module
145const RETRY_REQUESTS = 5 117const RETRY_REQUESTS = 5
@@ -148,16 +120,21 @@ const REQUEST_ENDPOINTS = {
148 VIDEOS: 'videos' 120 VIDEOS: 'videos'
149} 121}
150 122
151// ---------------------------------------------------------------------------
152
153const REMOTE_SCHEME = { 123const REMOTE_SCHEME = {
154 HTTP: 'https', 124 HTTP: 'https',
155 WS: 'wss' 125 WS: 'wss'
156} 126}
157 127
128// ---------------------------------------------------------------------------
129
130const SIGNATURE_ALGORITHM = 'RSA-SHA256'
131const SIGNATURE_ENCODING = 'hex'
132
158// Password encryption 133// Password encryption
159const BCRYPT_SALT_SIZE = 10 134const BCRYPT_SALT_SIZE = 10
160 135
136// ---------------------------------------------------------------------------
137
161// Express static paths (router) 138// Express static paths (router)
162const STATIC_PATHS = { 139const STATIC_PATHS = {
163 PREVIEWS: '/static/previews/', 140 PREVIEWS: '/static/previews/',
@@ -173,6 +150,8 @@ let STATIC_MAX_AGE = '30d'
173const THUMBNAILS_SIZE = '200x110' 150const THUMBNAILS_SIZE = '200x110'
174const PREVIEWS_SIZE = '640x480' 151const PREVIEWS_SIZE = '640x480'
175 152
153// ---------------------------------------------------------------------------
154
176const USER_ROLES = { 155const USER_ROLES = {
177 ADMIN: 'admin', 156 ADMIN: 'admin',
178 USER: 'user' 157 USER: 'user'
@@ -198,8 +177,7 @@ module.exports = {
198 CONFIG, 177 CONFIG,
199 CONSTRAINTS_FIELDS, 178 CONSTRAINTS_FIELDS,
200 FRIEND_SCORE, 179 FRIEND_SCORE,
201 LAST_MONGO_SCHEMA_VERSION, 180 LAST_MIGRATION_VERSION,
202 MONGO_MIGRATION_SCRIPTS,
203 OAUTH_LIFETIME, 181 OAUTH_LIFETIME,
204 PAGINATION_COUNT_DEFAULT, 182 PAGINATION_COUNT_DEFAULT,
205 PODS_SCORE, 183 PODS_SCORE,
@@ -208,9 +186,12 @@ module.exports = {
208 REQUEST_ENDPOINTS, 186 REQUEST_ENDPOINTS,
209 REQUESTS_IN_PARALLEL, 187 REQUESTS_IN_PARALLEL,
210 REQUESTS_INTERVAL, 188 REQUESTS_INTERVAL,
211 REQUESTS_LIMIT, 189 REQUESTS_LIMIT_PODS,
190 REQUESTS_LIMIT_PER_POD,
212 RETRY_REQUESTS, 191 RETRY_REQUESTS,
213 SEARCHABLE_COLUMNS, 192 SEARCHABLE_COLUMNS,
193 SIGNATURE_ALGORITHM,
194 SIGNATURE_ENCODING,
214 SORTABLE_COLUMNS, 195 SORTABLE_COLUMNS,
215 STATIC_MAX_AGE, 196 STATIC_MAX_AGE,
216 STATIC_PATHS, 197 STATIC_PATHS,
diff --git a/server/initializers/database.js b/server/initializers/database.js
index 0564e4e77..f8f68adeb 100644
--- a/server/initializers/database.js
+++ b/server/initializers/database.js
@@ -1,37 +1,77 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose') 3const fs = require('fs')
4const path = require('path')
5const Sequelize = require('sequelize')
4 6
5const constants = require('../initializers/constants') 7const constants = require('../initializers/constants')
6const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
9const utils = require('../helpers/utils')
7 10
8// Bootstrap models 11const database = {}
9require('../models/application')
10require('../models/oauth-token')
11require('../models/user')
12require('../models/oauth-client')
13require('../models/video')
14// Request model needs Video model
15require('../models/pods')
16// Request model needs Pod model
17require('../models/request')
18
19const database = {
20 connect: connect
21}
22 12
23function connect () { 13const dbname = constants.CONFIG.DATABASE.DBNAME
24 mongoose.Promise = global.Promise 14const username = constants.CONFIG.DATABASE.USERNAME
25 mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME) 15const password = constants.CONFIG.DATABASE.PASSWORD
26 mongoose.connection.on('error', function () {
27 throw new Error('Mongodb connection error.')
28 })
29 16
30 mongoose.connection.on('open', function () { 17const sequelize = new Sequelize(dbname, username, password, {
31 logger.info('Connected to mongodb.') 18 dialect: 'postgres',
32 }) 19 host: constants.CONFIG.DATABASE.HOSTNAME,
33} 20 port: constants.CONFIG.DATABASE.PORT,
21 benchmark: utils.isTestInstance(),
22
23 logging: function (message, benchmark) {
24 let newMessage = message
25 if (benchmark !== undefined) {
26 newMessage += ' | ' + benchmark + 'ms'
27 }
28
29 logger.debug(newMessage)
30 }
31})
32
33database.sequelize = sequelize
34database.Sequelize = Sequelize
35database.init = init
34 36
35// --------------------------------------------------------------------------- 37// ---------------------------------------------------------------------------
36 38
37module.exports = database 39module.exports = database
40
41// ---------------------------------------------------------------------------
42
43function init (silent, callback) {
44 if (!callback) {
45 callback = silent
46 silent = false
47 }
48
49 if (!callback) callback = function () {}
50
51 const modelDirectory = path.join(__dirname, '..', 'models')
52 fs.readdir(modelDirectory, function (err, files) {
53 if (err) throw err
54
55 files.filter(function (file) {
56 // For all models but not utils.js
57 if (file === 'utils.js') return false
58
59 return true
60 })
61 .forEach(function (file) {
62 const model = sequelize.import(path.join(modelDirectory, file))
63
64 database[model.name] = model
65 })
66
67 Object.keys(database).forEach(function (modelName) {
68 if ('associate' in database[modelName]) {
69 database[modelName].associate(database)
70 }
71 })
72
73 if (!silent) logger.info('Database is ready.')
74
75 return callback(null)
76 })
77}
diff --git a/server/initializers/installer.js b/server/initializers/installer.js
index 1df300ba8..fb63b81ac 100644
--- a/server/initializers/installer.js
+++ b/server/initializers/installer.js
@@ -3,26 +3,27 @@
3const config = require('config') 3const config = require('config')
4const each = require('async/each') 4const each = require('async/each')
5const mkdirp = require('mkdirp') 5const mkdirp = require('mkdirp')
6const mongoose = require('mongoose')
7const passwordGenerator = require('password-generator') 6const passwordGenerator = require('password-generator')
8const path = require('path') 7const path = require('path')
9const series = require('async/series') 8const series = require('async/series')
10 9
11const checker = require('./checker') 10const checker = require('./checker')
12const constants = require('./constants') 11const constants = require('./constants')
12const db = require('./database')
13const logger = require('../helpers/logger') 13const logger = require('../helpers/logger')
14const peertubeCrypto = require('../helpers/peertube-crypto') 14const peertubeCrypto = require('../helpers/peertube-crypto')
15 15
16const Application = mongoose.model('Application')
17const Client = mongoose.model('OAuthClient')
18const User = mongoose.model('User')
19
20const installer = { 16const installer = {
21 installApplication 17 installApplication
22} 18}
23 19
24function installApplication (callback) { 20function installApplication (callback) {
25 series([ 21 series([
22 function createDatabase (callbackAsync) {
23 db.sequelize.sync().asCallback(callbackAsync)
24 // db.sequelize.sync({ force: true }).asCallback(callbackAsync)
25 },
26
26 function createDirectories (callbackAsync) { 27 function createDirectories (callbackAsync) {
27 createDirectoriesIfNotExist(callbackAsync) 28 createDirectoriesIfNotExist(callbackAsync)
28 }, 29 },
@@ -65,16 +66,18 @@ function createOAuthClientIfNotExist (callback) {
65 66
66 logger.info('Creating a default OAuth Client.') 67 logger.info('Creating a default OAuth Client.')
67 68
68 const secret = passwordGenerator(32, false) 69 const id = passwordGenerator(32, false, /[a-z0-9]/)
69 const client = new Client({ 70 const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/)
71 const client = db.OAuthClient.build({
72 clientId: id,
70 clientSecret: secret, 73 clientSecret: secret,
71 grants: [ 'password', 'refresh_token' ] 74 grants: [ 'password', 'refresh_token' ]
72 }) 75 })
73 76
74 client.save(function (err, createdClient) { 77 client.save().asCallback(function (err, createdClient) {
75 if (err) return callback(err) 78 if (err) return callback(err)
76 79
77 logger.info('Client id: ' + createdClient._id) 80 logger.info('Client id: ' + createdClient.clientId)
78 logger.info('Client secret: ' + createdClient.clientSecret) 81 logger.info('Client secret: ' + createdClient.clientSecret)
79 82
80 return callback(null) 83 return callback(null)
@@ -93,6 +96,7 @@ function createOAuthAdminIfNotExist (callback) {
93 96
94 const username = 'root' 97 const username = 'root'
95 const role = constants.USER_ROLES.ADMIN 98 const role = constants.USER_ROLES.ADMIN
99 const createOptions = {}
96 let password = '' 100 let password = ''
97 101
98 // Do not generate a random password for tests 102 // Do not generate a random password for tests
@@ -102,25 +106,27 @@ function createOAuthAdminIfNotExist (callback) {
102 if (process.env.NODE_APP_INSTANCE) { 106 if (process.env.NODE_APP_INSTANCE) {
103 password += process.env.NODE_APP_INSTANCE 107 password += process.env.NODE_APP_INSTANCE
104 } 108 }
109
110 // Our password is weak so do not validate it
111 createOptions.validate = false
105 } else { 112 } else {
106 password = passwordGenerator(8, true) 113 password = passwordGenerator(8, true)
107 } 114 }
108 115
109 const user = new User({ 116 const userData = {
110 username, 117 username,
111 password, 118 password,
112 role 119 role
113 }) 120 }
114 121
115 user.save(function (err, createdUser) { 122 db.User.create(userData, createOptions).asCallback(function (err, createdUser) {
116 if (err) return callback(err) 123 if (err) return callback(err)
117 124
118 logger.info('Username: ' + username) 125 logger.info('Username: ' + username)
119 logger.info('User password: ' + password) 126 logger.info('User password: ' + password)
120 127
121 logger.info('Creating Application collection.') 128 logger.info('Creating Application table.')
122 const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION }) 129 db.Application.create({ migrationVersion: constants.LAST_MIGRATION_VERSION }).asCallback(callback)
123 application.save(callback)
124 }) 130 })
125 }) 131 })
126} 132}
diff --git a/server/initializers/migrations/0005-create-application.js b/server/initializers/migrations/0005-create-application.js
deleted file mode 100644
index e99dec019..000000000
--- a/server/initializers/migrations/0005-create-application.js
+++ /dev/null
@@ -1,17 +0,0 @@
1/*
2 Create the application collection in MongoDB.
3 Used to store the actual MongoDB scheme version.
4*/
5
6const mongoose = require('mongoose')
7
8const Application = mongoose.model('Application')
9
10exports.up = function (callback) {
11 const application = new Application()
12 application.save(callback)
13}
14
15exports.down = function (callback) {
16 throw new Error('Not implemented.')
17}
diff --git a/server/initializers/migrations/0005-example.js b/server/initializers/migrations/0005-example.js
new file mode 100644
index 000000000..cedc42919
--- /dev/null
+++ b/server/initializers/migrations/0005-example.js
@@ -0,0 +1,14 @@
1// /*
2// This is just an example.
3// */
4
5// const db = require('../database')
6
7// // options contains the transaction
8// exports.up = function (options, callback) {
9// db.Application.create({ migrationVersion: 42 }, { transaction: options.transaction }).asCallback(callback)
10// }
11
12// exports.down = function (options, callback) {
13// throw new Error('Not implemented.')
14// }
diff --git a/server/initializers/migrations/0010-users-password.js b/server/initializers/migrations/0010-users-password.js
deleted file mode 100644
index a0616a269..000000000
--- a/server/initializers/migrations/0010-users-password.js
+++ /dev/null
@@ -1,22 +0,0 @@
1/*
2 Convert plain user password to encrypted user password.
3*/
4
5const eachSeries = require('async/eachSeries')
6const mongoose = require('mongoose')
7
8const User = mongoose.model('User')
9
10exports.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
20exports.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
deleted file mode 100644
index af06dca9e..000000000
--- a/server/initializers/migrations/0015-admin-role.js
+++ /dev/null
@@ -1,16 +0,0 @@
1/*
2 Set the admin role to the root user.
3*/
4
5const constants = require('../constants')
6const mongoose = require('mongoose')
7
8const User = mongoose.model('User')
9
10exports.up = function (callback) {
11 User.update({ username: 'root' }, { role: constants.USER_ROLES.ADMIN }, callback)
12}
13
14exports.down = function (callback) {
15 throw new Error('Not implemented.')
16}
diff --git a/server/initializers/migrations/0020-requests-endpoint.js b/server/initializers/migrations/0020-requests-endpoint.js
deleted file mode 100644
index 55feec571..000000000
--- a/server/initializers/migrations/0020-requests-endpoint.js
+++ /dev/null
@@ -1,15 +0,0 @@
1/*
2 Set the endpoint videos for requests.
3*/
4
5const mongoose = require('mongoose')
6
7const Request = mongoose.model('Request')
8
9exports.up = function (callback) {
10 Request.update({ }, { endpoint: 'videos' }, callback)
11}
12
13exports.down = function (callback) {
14 throw new Error('Not implemented.')
15}
diff --git a/server/initializers/migrations/0025-video-filenames.js b/server/initializers/migrations/0025-video-filenames.js
deleted file mode 100644
index df21494d7..000000000
--- a/server/initializers/migrations/0025-video-filenames.js
+++ /dev/null
@@ -1,57 +0,0 @@
1/*
2 Rename thumbnails and video filenames to _id.extension
3*/
4
5const each = require('async/each')
6const fs = require('fs')
7const path = require('path')
8const mongoose = require('mongoose')
9
10const constants = require('../constants')
11const logger = require('../../helpers/logger')
12
13const Video = mongoose.model('Video')
14
15exports.up = function (callback) {
16 // Use of lean because the new Video scheme does not have filename field
17 Video.find({ filename: { $ne: null } }).lean().exec(function (err, videos) {
18 if (err) throw err
19
20 each(videos, function (video, callbackEach) {
21 const torrentName = video.filename + '.torrent'
22 const thumbnailName = video.thumbnail
23 const thumbnailExtension = path.extname(thumbnailName)
24 const videoName = video.filename
25 const videoExtension = path.extname(videoName)
26
27 const newTorrentName = video._id + '.torrent'
28 const newThumbnailName = video._id + thumbnailExtension
29 const newVideoName = video._id + videoExtension
30
31 const torrentsDir = constants.CONFIG.STORAGE.TORRENTS_DIR
32 const thumbnailsDir = constants.CONFIG.STORAGE.THUMBNAILS_DIR
33 const videosDir = constants.CONFIG.STORAGE.VIDEOS_DIR
34
35 logger.info('Renaming %s to %s.', torrentsDir + torrentName, torrentsDir + newTorrentName)
36 fs.renameSync(torrentsDir + torrentName, torrentsDir + newTorrentName)
37
38 logger.info('Renaming %s to %s.', thumbnailsDir + thumbnailName, thumbnailsDir + newThumbnailName)
39 fs.renameSync(thumbnailsDir + thumbnailName, thumbnailsDir + newThumbnailName)
40
41 logger.info('Renaming %s to %s.', videosDir + videoName, videosDir + newVideoName)
42 fs.renameSync(videosDir + videoName, videosDir + newVideoName)
43
44 Video.load(video._id, function (err, videoObj) {
45 if (err) return callbackEach(err)
46
47 videoObj.extname = videoExtension
48 videoObj.remoteId = null
49 videoObj.save(callbackEach)
50 })
51 }, callback)
52 })
53}
54
55exports.down = function (callback) {
56 throw new Error('Not implemented.')
57}
diff --git a/server/initializers/migrations/0030-video-magnet.js b/server/initializers/migrations/0030-video-magnet.js
deleted file mode 100644
index b9119d61c..000000000
--- a/server/initializers/migrations/0030-video-magnet.js
+++ /dev/null
@@ -1,32 +0,0 @@
1/*
2 Change video magnet structures
3*/
4
5const each = require('async/each')
6const magnet = require('magnet-uri')
7const mongoose = require('mongoose')
8
9const Video = mongoose.model('Video')
10
11exports.up = function (callback) {
12 // Use of lean because the new Video scheme does not have magnetUri field
13 Video.find({ }).lean().exec(function (err, videos) {
14 if (err) throw err
15
16 each(videos, function (video, callbackEach) {
17 const parsed = magnet.decode(video.magnetUri)
18 const infoHash = parsed.infoHash
19
20 Video.load(video._id, function (err, videoObj) {
21 if (err) return callbackEach(err)
22
23 videoObj.magnet.infoHash = infoHash
24 videoObj.save(callbackEach)
25 })
26 }, callback)
27 })
28}
29
30exports.down = function (callback) {
31 throw new Error('Not implemented.')
32}
diff --git a/server/initializers/migrations/0035-url-to-host.js b/server/initializers/migrations/0035-url-to-host.js
deleted file mode 100644
index 6243304d5..000000000
--- a/server/initializers/migrations/0035-url-to-host.js
+++ /dev/null
@@ -1,30 +0,0 @@
1/*
2 Change video magnet structures
3*/
4
5const each = require('async/each')
6const mongoose = require('mongoose')
7
8const Video = mongoose.model('Video')
9
10exports.up = function (callback) {
11 // Use of lean because the new Video scheme does not have podUrl field
12 Video.find({ }).lean().exec(function (err, videos) {
13 if (err) throw err
14
15 each(videos, function (video, callbackEach) {
16 Video.load(video._id, function (err, videoObj) {
17 if (err) return callbackEach(err)
18
19 const host = video.podUrl.split('://')[1]
20
21 videoObj.podHost = host
22 videoObj.save(callbackEach)
23 })
24 }, callback)
25 })
26}
27
28exports.down = function (callback) {
29 throw new Error('Not implemented.')
30}
diff --git a/server/initializers/migrations/0040-video-remote-id.js b/server/initializers/migrations/0040-video-remote-id.js
deleted file mode 100644
index 46a14a689..000000000
--- a/server/initializers/migrations/0040-video-remote-id.js
+++ /dev/null
@@ -1,59 +0,0 @@
1/*
2 Use remote id as identifier
3*/
4
5const map = require('lodash/map')
6const mongoose = require('mongoose')
7const readline = require('readline')
8
9const rl = readline.createInterface({
10 input: process.stdin,
11 output: process.stdout
12})
13
14const logger = require('../../helpers/logger')
15const friends = require('../../lib/friends')
16
17const Pod = mongoose.model('Pod')
18const Video = mongoose.model('Video')
19
20exports.up = function (callback) {
21 Pod.find({}).lean().exec(function (err, pods) {
22 if (err) return callback(err)
23
24 // We need to quit friends first
25 if (pods.length === 0) {
26 return setVideosRemoteId(callback)
27 }
28
29 const timeout = setTimeout(function () {
30 throw new Error('You need to enter a value!')
31 }, 10000)
32
33 rl.question('I am sorry but I need to quit friends for upgrading. Do you want to continue? (yes/*)', function (answer) {
34 if (answer !== 'yes') throw new Error('I cannot continue.')
35
36 clearTimeout(timeout)
37 rl.close()
38
39 const urls = map(pods, 'url')
40 logger.info('Saying goodbye to: ' + urls.join(', '))
41
42 setVideosRemoteId(function () {
43 friends.quitFriends(callback)
44 })
45 })
46 })
47}
48
49exports.down = function (callback) {
50 throw new Error('Not implemented.')
51}
52
53function setVideosRemoteId (callback) {
54 Video.update({ filename: { $ne: null } }, { remoteId: null }, function (err) {
55 if (err) throw err
56
57 Video.update({ filename: null }, { remoteId: mongoose.Types.ObjectId() }, callback)
58 })
59}
diff --git a/server/initializers/migrator.js b/server/initializers/migrator.js
index 6b31d994f..e5288b615 100644
--- a/server/initializers/migrator.js
+++ b/server/initializers/migrator.js
@@ -1,48 +1,36 @@
1'use strict' 1'use strict'
2 2
3const eachSeries = require('async/eachSeries') 3const eachSeries = require('async/eachSeries')
4const mongoose = require('mongoose') 4const fs = require('fs')
5const path = require('path') 5const path = require('path')
6 6
7const constants = require('./constants') 7const constants = require('./constants')
8const db = require('./database')
8const logger = require('../helpers/logger') 9const logger = require('../helpers/logger')
9 10
10const Application = mongoose.model('Application')
11
12const migrator = { 11const migrator = {
13 migrate: migrate 12 migrate: migrate
14} 13}
15 14
16function migrate (callback) { 15function migrate (callback) {
17 Application.loadMongoSchemaVersion(function (err, actualVersion) { 16 db.Application.loadMigrationVersion(function (err, actualVersion) {
18 if (err) return callback(err) 17 if (err) return callback(err)
19 18
20 // If there are a new mongo schemas 19 // If there are a new migration scripts
21 if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) { 20 if (actualVersion < constants.LAST_MIGRATION_VERSION) {
22 logger.info('Begin migrations.') 21 logger.info('Begin migrations.')
23 22
24 eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) { 23 getMigrationScripts(function (err, migrationScripts) {
25 const versionScript = entity.version 24 if (err) return callback(err)
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 25
34 const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) 26 eachSeries(migrationScripts, function (entity, callbackEach) {
35 migrationScript.up(function (err) { 27 executeMigration(actualVersion, entity, callbackEach)
36 if (err) return callbackEach(err) 28 }, function (err) {
29 if (err) return callback(err)
37 30
38 // Update the new mongo version schema 31 logger.info('Migrations finished. New migration version schema: %s', constants.LAST_MIGRATION_VERSION)
39 Application.updateMongoSchemaVersion(versionScript, callbackEach) 32 return callback(null)
40 }) 33 })
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 }) 34 })
47 } else { 35 } else {
48 return callback(null) 36 return callback(null)
@@ -54,3 +42,57 @@ function migrate (callback) {
54 42
55module.exports = migrator 43module.exports = migrator
56 44
45// ---------------------------------------------------------------------------
46
47function getMigrationScripts (callback) {
48 fs.readdir(path.join(__dirname, 'migrations'), function (err, files) {
49 if (err) return callback(err)
50
51 const filesToMigrate = []
52
53 files.forEach(function (file) {
54 // Filename is something like 'version-blabla.js'
55 const version = file.split('-')[0]
56 filesToMigrate.push({
57 version,
58 script: file
59 })
60 })
61
62 return callback(err, filesToMigrate)
63 })
64}
65
66function executeMigration (actualVersion, entity, callback) {
67 const versionScript = entity.version
68
69 // Do not execute old migration scripts
70 if (versionScript <= actualVersion) return callback(null)
71
72 // Load the migration module and run it
73 const migrationScriptName = entity.script
74 logger.info('Executing %s migration script.', migrationScriptName)
75
76 const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName))
77
78 db.sequelize.transaction().asCallback(function (err, t) {
79 if (err) return callback(err)
80
81 migrationScript.up({ transaction: t }, function (err) {
82 if (err) {
83 t.rollback()
84 return callback(err)
85 }
86
87 // Update the new migration version
88 db.Application.updateMigrationVersion(versionScript, t, function (err) {
89 if (err) {
90 t.rollback()
91 return callback(err)
92 }
93
94 t.commit().asCallback(callback)
95 })
96 })
97 })
98}
diff --git a/server/lib/friends.js b/server/lib/friends.js
index eaea040ca..f0575ff2f 100644
--- a/server/lib/friends.js
+++ b/server/lib/friends.js
@@ -4,20 +4,18 @@ const each = require('async/each')
4const eachLimit = require('async/eachLimit') 4const eachLimit = require('async/eachLimit')
5const eachSeries = require('async/eachSeries') 5const eachSeries = require('async/eachSeries')
6const fs = require('fs') 6const fs = require('fs')
7const mongoose = require('mongoose')
8const request = require('request') 7const request = require('request')
9const waterfall = require('async/waterfall') 8const waterfall = require('async/waterfall')
10 9
11const constants = require('../initializers/constants') 10const constants = require('../initializers/constants')
11const db = require('../initializers/database')
12const logger = require('../helpers/logger') 12const logger = require('../helpers/logger')
13const requests = require('../helpers/requests') 13const requests = require('../helpers/requests')
14 14
15const Pod = mongoose.model('Pod')
16const Request = mongoose.model('Request')
17const Video = mongoose.model('Video')
18
19const friends = { 15const friends = {
20 addVideoToFriends, 16 addVideoToFriends,
17 updateVideoToFriends,
18 reportAbuseVideoToFriend,
21 hasFriends, 19 hasFriends,
22 getMyCertificate, 20 getMyCertificate,
23 makeFriends, 21 makeFriends,
@@ -26,12 +24,47 @@ const friends = {
26 sendOwnedVideosToPod 24 sendOwnedVideosToPod
27} 25}
28 26
29function addVideoToFriends (video) { 27function addVideoToFriends (videoData, transaction, callback) {
30 createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, video) 28 const options = {
29 type: 'add',
30 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
31 data: videoData,
32 transaction
33 }
34 createRequest(options, callback)
35}
36
37function updateVideoToFriends (videoData, transaction, callback) {
38 const options = {
39 type: 'update',
40 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
41 data: videoData,
42 transaction
43 }
44 createRequest(options, callback)
45}
46
47function removeVideoToFriends (videoParams) {
48 const options = {
49 type: 'remove',
50 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
51 data: videoParams
52 }
53 createRequest(options)
54}
55
56function reportAbuseVideoToFriend (reportData, video) {
57 const options = {
58 type: 'report-abuse',
59 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
60 data: reportData,
61 toIds: [ video.Author.podId ]
62 }
63 createRequest(options)
31} 64}
32 65
33function hasFriends (callback) { 66function hasFriends (callback) {
34 Pod.countAll(function (err, count) { 67 db.Pod.countAll(function (err, count) {
35 if (err) return callback(err) 68 if (err) return callback(err)
36 69
37 const hasFriends = (count !== 0) 70 const hasFriends = (count !== 0)
@@ -69,13 +102,15 @@ function makeFriends (hosts, callback) {
69 102
70function quitFriends (callback) { 103function quitFriends (callback) {
71 // Stop pool requests 104 // Stop pool requests
72 Request.deactivate() 105 db.Request.deactivate()
73 // Flush pool requests
74 Request.flush()
75 106
76 waterfall([ 107 waterfall([
108 function flushRequests (callbackAsync) {
109 db.Request.flush(callbackAsync)
110 },
111
77 function getPodsList (callbackAsync) { 112 function getPodsList (callbackAsync) {
78 return Pod.list(callbackAsync) 113 return db.Pod.list(callbackAsync)
79 }, 114 },
80 115
81 function announceIQuitMyFriends (pods, callbackAsync) { 116 function announceIQuitMyFriends (pods, callbackAsync) {
@@ -103,12 +138,12 @@ function quitFriends (callback) {
103 138
104 function removePodsFromDB (pods, callbackAsync) { 139 function removePodsFromDB (pods, callbackAsync) {
105 each(pods, function (pod, callbackEach) { 140 each(pods, function (pod, callbackEach) {
106 pod.remove(callbackEach) 141 pod.destroy().asCallback(callbackEach)
107 }, callbackAsync) 142 }, callbackAsync)
108 } 143 }
109 ], function (err) { 144 ], function (err) {
110 // Don't forget to re activate the scheduler, even if there was an error 145 // Don't forget to re activate the scheduler, even if there was an error
111 Request.activate() 146 db.Request.activate()
112 147
113 if (err) return callback(err) 148 if (err) return callback(err)
114 149
@@ -117,26 +152,28 @@ function quitFriends (callback) {
117 }) 152 })
118} 153}
119 154
120function removeVideoToFriends (videoParams) {
121 createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams)
122}
123
124function sendOwnedVideosToPod (podId) { 155function sendOwnedVideosToPod (podId) {
125 Video.listOwned(function (err, videosList) { 156 db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) {
126 if (err) { 157 if (err) {
127 logger.error('Cannot get the list of videos we own.') 158 logger.error('Cannot get the list of videos we own.')
128 return 159 return
129 } 160 }
130 161
131 videosList.forEach(function (video) { 162 videosList.forEach(function (video) {
132 video.toRemoteJSON(function (err, remoteVideo) { 163 video.toAddRemoteJSON(function (err, remoteVideo) {
133 if (err) { 164 if (err) {
134 logger.error('Cannot convert video to remote.', { error: err }) 165 logger.error('Cannot convert video to remote.', { error: err })
135 // Don't break the process 166 // Don't break the process
136 return 167 return
137 } 168 }
138 169
139 createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, remoteVideo, [ podId ]) 170 const options = {
171 type: 'add',
172 endpoint: constants.REQUEST_ENDPOINTS.VIDEOS,
173 data: remoteVideo,
174 toIds: [ podId ]
175 }
176 createRequest(options)
140 }) 177 })
141 }) 178 })
142 }) 179 })
@@ -149,10 +186,10 @@ module.exports = friends
149// --------------------------------------------------------------------------- 186// ---------------------------------------------------------------------------
150 187
151function computeForeignPodsList (host, podsScore, callback) { 188function computeForeignPodsList (host, podsScore, callback) {
152 getForeignPodsList(host, function (err, foreignPodsList) { 189 getForeignPodsList(host, function (err, res) {
153 if (err) return callback(err) 190 if (err) return callback(err)
154 191
155 if (!foreignPodsList) foreignPodsList = [] 192 const foreignPodsList = res.data
156 193
157 // Let's give 1 point to the pod we ask the friends list 194 // Let's give 1 point to the pod we ask the friends list
158 foreignPodsList.push({ host }) 195 foreignPodsList.push({ host })
@@ -200,9 +237,9 @@ function getForeignPodsList (host, callback) {
200 237
201function makeRequestsToWinningPods (cert, podsList, callback) { 238function makeRequestsToWinningPods (cert, podsList, callback) {
202 // Stop pool requests 239 // Stop pool requests
203 Request.deactivate() 240 db.Request.deactivate()
204 // Flush pool requests 241 // Flush pool requests
205 Request.forceSend() 242 db.Request.forceSend()
206 243
207 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { 244 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
208 const params = { 245 const params = {
@@ -222,15 +259,15 @@ function makeRequestsToWinningPods (cert, podsList, callback) {
222 } 259 }
223 260
224 if (res.statusCode === 200) { 261 if (res.statusCode === 200) {
225 const podObj = new Pod({ host: pod.host, publicKey: body.cert }) 262 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert })
226 podObj.save(function (err, podCreated) { 263 podObj.save().asCallback(function (err, podCreated) {
227 if (err) { 264 if (err) {
228 logger.error('Cannot add friend %s pod.', pod.host, { error: err }) 265 logger.error('Cannot add friend %s pod.', pod.host, { error: err })
229 return callbackEach() 266 return callbackEach()
230 } 267 }
231 268
232 // Add our videos to the request scheduler 269 // Add our videos to the request scheduler
233 sendOwnedVideosToPod(podCreated._id) 270 sendOwnedVideosToPod(podCreated.id)
234 271
235 return callbackEach() 272 return callbackEach()
236 }) 273 })
@@ -242,28 +279,64 @@ function makeRequestsToWinningPods (cert, podsList, callback) {
242 }, function endRequests () { 279 }, function endRequests () {
243 // Final callback, we've ended all the requests 280 // Final callback, we've ended all the requests
244 // Now we made new friends, we can re activate the pool of requests 281 // Now we made new friends, we can re activate the pool of requests
245 Request.activate() 282 db.Request.activate()
246 283
247 logger.debug('makeRequestsToWinningPods finished.') 284 logger.debug('makeRequestsToWinningPods finished.')
248 return callback() 285 return callback()
249 }) 286 })
250} 287}
251 288
252function createRequest (type, endpoint, data, to) { 289// Wrapper that populate "toIds" argument with all our friends if it is not specified
253 const req = new Request({ 290// { type, endpoint, data, toIds, transaction }
291function createRequest (options, callback) {
292 if (!callback) callback = function () {}
293 if (options.toIds) return _createRequest(options, callback)
294
295 // If the "toIds" pods is not specified, we send the request to all our friends
296 db.Pod.listAllIds(options.transaction, function (err, podIds) {
297 if (err) {
298 logger.error('Cannot get pod ids', { error: err })
299 return
300 }
301
302 const newOptions = Object.assign(options, { toIds: podIds })
303 return _createRequest(newOptions, callback)
304 })
305}
306
307// { type, endpoint, data, toIds, transaction }
308function _createRequest (options, callback) {
309 const type = options.type
310 const endpoint = options.endpoint
311 const data = options.data
312 const toIds = options.toIds
313 const transaction = options.transaction
314
315 const pods = []
316
317 // If there are no destination pods abort
318 if (toIds.length === 0) return callback(null)
319
320 toIds.forEach(function (toPod) {
321 pods.push(db.Pod.build({ id: toPod }))
322 })
323
324 const createQuery = {
254 endpoint, 325 endpoint,
255 request: { 326 request: {
256 type: type, 327 type: type,
257 data: data 328 data: data
258 } 329 }
259 }) 330 }
260 331
261 if (to) { 332 const dbRequestOptions = {
262 req.to = to 333 transaction
263 } 334 }
264 335
265 req.save(function (err) { 336 return db.Request.create(createQuery, dbRequestOptions).asCallback(function (err, request) {
266 if (err) logger.error('Cannot save the request.', { error: err }) 337 if (err) return callback(err)
338
339 return request.setPods(pods, dbRequestOptions).asCallback(callback)
267 }) 340 })
268} 341}
269 342
diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js
index d011c4b72..1c12f1b14 100644
--- a/server/lib/oauth-model.js
+++ b/server/lib/oauth-model.js
@@ -1,11 +1,6 @@
1const mongoose = require('mongoose') 1const db = require('../initializers/database')
2
3const logger = require('../helpers/logger') 2const logger = require('../helpers/logger')
4 3
5const OAuthClient = mongoose.model('OAuthClient')
6const OAuthToken = mongoose.model('OAuthToken')
7const User = mongoose.model('User')
8
9// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications 4// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
10const OAuthModel = { 5const OAuthModel = {
11 getAccessToken, 6 getAccessToken,
@@ -21,27 +16,25 @@ const OAuthModel = {
21function getAccessToken (bearerToken) { 16function getAccessToken (bearerToken) {
22 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') 17 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
23 18
24 return OAuthToken.getByTokenAndPopulateUser(bearerToken) 19 return db.OAuthToken.getByTokenAndPopulateUser(bearerToken)
25} 20}
26 21
27function getClient (clientId, clientSecret) { 22function getClient (clientId, clientSecret) {
28 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') 23 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').')
29 24
30 // TODO req validator 25 return db.OAuthClient.getByIdAndSecret(clientId, clientSecret)
31 const mongoId = new mongoose.mongo.ObjectID(clientId)
32 return OAuthClient.getByIdAndSecret(mongoId, clientSecret)
33} 26}
34 27
35function getRefreshToken (refreshToken) { 28function getRefreshToken (refreshToken) {
36 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') 29 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').')
37 30
38 return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) 31 return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken)
39} 32}
40 33
41function getUser (username, password) { 34function getUser (username, password) {
42 logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') 35 logger.debug('Getting User (username: ' + username + ', password: ' + password + ').')
43 36
44 return User.getByUsername(username).then(function (user) { 37 return db.User.getByUsername(username).then(function (user) {
45 if (!user) return null 38 if (!user) return null
46 39
47 // We need to return a promise 40 // We need to return a promise
@@ -60,8 +53,8 @@ function getUser (username, password) {
60} 53}
61 54
62function revokeToken (token) { 55function revokeToken (token) {
63 return OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) { 56 return db.OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) {
64 if (tokenDB) tokenDB.remove() 57 if (tokenDB) tokenDB.destroy()
65 58
66 /* 59 /*
67 * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js 60 * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js
@@ -80,18 +73,19 @@ function revokeToken (token) {
80function saveToken (token, client, user) { 73function saveToken (token, client, user) {
81 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') 74 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
82 75
83 const tokenObj = new OAuthToken({ 76 const tokenToCreate = {
84 accessToken: token.accessToken, 77 accessToken: token.accessToken,
85 accessTokenExpiresAt: token.accessTokenExpiresAt, 78 accessTokenExpiresAt: token.accessTokenExpiresAt,
86 client: client.id,
87 refreshToken: token.refreshToken, 79 refreshToken: token.refreshToken,
88 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 80 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
89 user: user.id 81 oAuthClientId: client.id,
90 }) 82 userId: user.id
83 }
91 84
92 return tokenObj.save().then(function (tokenCreated) { 85 return db.OAuthToken.create(tokenToCreate).then(function (tokenCreated) {
93 tokenCreated.client = client 86 tokenCreated.client = client
94 tokenCreated.user = user 87 tokenCreated.user = user
88
95 return tokenCreated 89 return tokenCreated
96 }).catch(function (err) { 90 }).catch(function (err) {
97 throw err 91 throw err
diff --git a/server/middlewares/pods.js b/server/middlewares/pods.js
index 487ea1259..e38fb341d 100644
--- a/server/middlewares/pods.js
+++ b/server/middlewares/pods.js
@@ -44,7 +44,6 @@ module.exports = podsMiddleware
44function getHostWithPort (host) { 44function getHostWithPort (host) {
45 const splitted = host.split(':') 45 const splitted = host.split(':')
46 46
47 console.log(splitted)
48 // The port was not specified 47 // The port was not specified
49 if (splitted.length === 1) { 48 if (splitted.length === 1) {
50 if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443' 49 if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443'
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js
index ee836beed..b6e6d818b 100644
--- a/server/middlewares/secure.js
+++ b/server/middlewares/secure.js
@@ -1,18 +1,16 @@
1'use strict' 1'use strict'
2 2
3const db = require('../initializers/database')
3const logger = require('../helpers/logger') 4const logger = require('../helpers/logger')
4const mongoose = require('mongoose')
5const peertubeCrypto = require('../helpers/peertube-crypto') 5const peertubeCrypto = require('../helpers/peertube-crypto')
6 6
7const Pod = mongoose.model('Pod')
8
9const secureMiddleware = { 7const secureMiddleware = {
10 checkSignature 8 checkSignature
11} 9}
12 10
13function checkSignature (req, res, next) { 11function checkSignature (req, res, next) {
14 const host = req.body.signature.host 12 const host = req.body.signature.host
15 Pod.loadByHost(host, function (err, pod) { 13 db.Pod.loadByHost(host, function (err, pod) {
16 if (err) { 14 if (err) {
17 logger.error('Cannot get signed host in body.', { error: err }) 15 logger.error('Cannot get signed host in body.', { error: err })
18 return res.sendStatus(500) 16 return res.sendStatus(500)
@@ -25,9 +23,20 @@ function checkSignature (req, res, next) {
25 23
26 logger.debug('Checking signature from %s.', host) 24 logger.debug('Checking signature from %s.', host)
27 25
28 const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, host, req.body.signature.signature) 26 let signatureShouldBe
27 if (req.body.data) {
28 signatureShouldBe = req.body.data
29 } else {
30 signatureShouldBe = host
31 }
32
33 const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature)
29 34
30 if (signatureOk === true) { 35 if (signatureOk === true) {
36 res.locals.secure = {
37 pod
38 }
39
31 return next() 40 return next()
32 } 41 }
33 42
diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js
index f0b7274eb..39e167265 100644
--- a/server/middlewares/sort.js
+++ b/server/middlewares/sort.js
@@ -2,17 +2,24 @@
2 2
3const sortMiddleware = { 3const sortMiddleware = {
4 setUsersSort, 4 setUsersSort,
5 setVideoAbusesSort,
5 setVideosSort 6 setVideosSort
6} 7}
7 8
8function setUsersSort (req, res, next) { 9function setUsersSort (req, res, next) {
9 if (!req.query.sort) req.query.sort = '-createdDate' 10 if (!req.query.sort) req.query.sort = '-createdAt'
11
12 return next()
13}
14
15function setVideoAbusesSort (req, res, next) {
16 if (!req.query.sort) req.query.sort = '-createdAt'
10 17
11 return next() 18 return next()
12} 19}
13 20
14function setVideosSort (req, res, next) { 21function setVideosSort (req, res, next) {
15 if (!req.query.sort) req.query.sort = '-createdDate' 22 if (!req.query.sort) req.query.sort = '-createdAt'
16 23
17 return next() 24 return next()
18} 25}
diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js
deleted file mode 100644
index 858d193cc..000000000
--- a/server/middlewares/validators/remote.js
+++ /dev/null
@@ -1,30 +0,0 @@
1'use strict'
2
3const checkErrors = require('./utils').checkErrors
4const logger = require('../../helpers/logger')
5
6const validatorsRemote = {
7 remoteVideos,
8 signature
9}
10
11function remoteVideos (req, res, next) {
12 req.checkBody('data').isEachRemoteVideosValid()
13
14 logger.debug('Checking remoteVideos parameters', { parameters: req.body })
15
16 checkErrors(req, res, next)
17}
18
19function signature (req, res, next) {
20 req.checkBody('signature.host', 'Should have a signature host').isURL()
21 req.checkBody('signature.signature', 'Should have a signature').notEmpty()
22
23 logger.debug('Checking signature parameters', { parameters: { signatureHost: req.body.signature.host } })
24
25 checkErrors(req, res, next)
26}
27
28// ---------------------------------------------------------------------------
29
30module.exports = validatorsRemote
diff --git a/server/middlewares/validators/remote/index.js b/server/middlewares/validators/remote/index.js
new file mode 100644
index 000000000..022a2fe50
--- /dev/null
+++ b/server/middlewares/validators/remote/index.js
@@ -0,0 +1,13 @@
1'use strict'
2
3const remoteSignatureValidators = require('./signature')
4const remoteVideosValidators = require('./videos')
5
6const validators = {
7 signature: remoteSignatureValidators,
8 videos: remoteVideosValidators
9}
10
11// ---------------------------------------------------------------------------
12
13module.exports = validators
diff --git a/server/middlewares/validators/remote/signature.js b/server/middlewares/validators/remote/signature.js
new file mode 100644
index 000000000..002232c05
--- /dev/null
+++ b/server/middlewares/validators/remote/signature.js
@@ -0,0 +1,21 @@
1'use strict'
2
3const checkErrors = require('../utils').checkErrors
4const logger = require('../../../helpers/logger')
5
6const validatorsRemoteSignature = {
7 signature
8}
9
10function signature (req, res, next) {
11 req.checkBody('signature.host', 'Should have a signature host').isURL()
12 req.checkBody('signature.signature', 'Should have a signature').notEmpty()
13
14 logger.debug('Checking signature parameters', { parameters: { signature: req.body.signature } })
15
16 checkErrors(req, res, next)
17}
18
19// ---------------------------------------------------------------------------
20
21module.exports = validatorsRemoteSignature
diff --git a/server/middlewares/validators/remote/videos.js b/server/middlewares/validators/remote/videos.js
new file mode 100644
index 000000000..cf9925b6c
--- /dev/null
+++ b/server/middlewares/validators/remote/videos.js
@@ -0,0 +1,20 @@
1'use strict'
2
3const checkErrors = require('../utils').checkErrors
4const logger = require('../../../helpers/logger')
5
6const validatorsRemoteVideos = {
7 remoteVideos
8}
9
10function remoteVideos (req, res, next) {
11 req.checkBody('data').isEachRemoteRequestVideosValid()
12
13 logger.debug('Checking remoteVideos parameters', { parameters: req.body })
14
15 checkErrors(req, res, next)
16}
17
18// ---------------------------------------------------------------------------
19
20module.exports = validatorsRemoteVideos
diff --git a/server/middlewares/validators/sort.js b/server/middlewares/validators/sort.js
index 431d3fffd..b7eec0316 100644
--- a/server/middlewares/validators/sort.js
+++ b/server/middlewares/validators/sort.js
@@ -6,29 +6,38 @@ const logger = require('../../helpers/logger')
6 6
7const validatorsSort = { 7const validatorsSort = {
8 usersSort, 8 usersSort,
9 videoAbusesSort,
9 videosSort 10 videosSort
10} 11}
11 12
12function usersSort (req, res, next) { 13function usersSort (req, res, next) {
13 const sortableColumns = constants.SORTABLE_COLUMNS.USERS 14 const sortableColumns = constants.SORTABLE_COLUMNS.USERS
14 15
15 req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) 16 checkSort(req, res, next, sortableColumns)
17}
16 18
17 logger.debug('Checking sort parameters', { parameters: req.query }) 19function videoAbusesSort (req, res, next) {
20 const sortableColumns = constants.SORTABLE_COLUMNS.VIDEO_ABUSES
18 21
19 checkErrors(req, res, next) 22 checkSort(req, res, next, sortableColumns)
20} 23}
21 24
22function videosSort (req, res, next) { 25function videosSort (req, res, next) {
23 const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS 26 const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS
24 27
28 checkSort(req, res, next, sortableColumns)
29}
30
31// ---------------------------------------------------------------------------
32
33module.exports = validatorsSort
34
35// ---------------------------------------------------------------------------
36
37function checkSort (req, res, next, sortableColumns) {
25 req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) 38 req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns)
26 39
27 logger.debug('Checking sort parameters', { parameters: req.query }) 40 logger.debug('Checking sort parameters', { parameters: req.query })
28 41
29 checkErrors(req, res, next) 42 checkErrors(req, res, next)
30} 43}
31
32// ---------------------------------------------------------------------------
33
34module.exports = validatorsSort
diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js
index 02e4f34cb..0629550bc 100644
--- a/server/middlewares/validators/users.js
+++ b/server/middlewares/validators/users.js
@@ -1,12 +1,9 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose')
4
5const checkErrors = require('./utils').checkErrors 3const checkErrors = require('./utils').checkErrors
4const db = require('../../initializers/database')
6const logger = require('../../helpers/logger') 5const logger = require('../../helpers/logger')
7 6
8const User = mongoose.model('User')
9
10const validatorsUsers = { 7const validatorsUsers = {
11 usersAdd, 8 usersAdd,
12 usersRemove, 9 usersRemove,
@@ -20,7 +17,7 @@ function usersAdd (req, res, next) {
20 logger.debug('Checking usersAdd parameters', { parameters: req.body }) 17 logger.debug('Checking usersAdd parameters', { parameters: req.body })
21 18
22 checkErrors(req, res, function () { 19 checkErrors(req, res, function () {
23 User.loadByUsername(req.body.username, function (err, user) { 20 db.User.loadByUsername(req.body.username, function (err, user) {
24 if (err) { 21 if (err) {
25 logger.error('Error in usersAdd request validator.', { error: err }) 22 logger.error('Error in usersAdd request validator.', { error: err })
26 return res.sendStatus(500) 23 return res.sendStatus(500)
@@ -34,12 +31,12 @@ function usersAdd (req, res, next) {
34} 31}
35 32
36function usersRemove (req, res, next) { 33function usersRemove (req, res, next) {
37 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 34 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
38 35
39 logger.debug('Checking usersRemove parameters', { parameters: req.params }) 36 logger.debug('Checking usersRemove parameters', { parameters: req.params })
40 37
41 checkErrors(req, res, function () { 38 checkErrors(req, res, function () {
42 User.loadById(req.params.id, function (err, user) { 39 db.User.loadById(req.params.id, function (err, user) {
43 if (err) { 40 if (err) {
44 logger.error('Error in usersRemove request validator.', { error: err }) 41 logger.error('Error in usersRemove request validator.', { error: err })
45 return res.sendStatus(500) 42 return res.sendStatus(500)
@@ -55,7 +52,7 @@ function usersRemove (req, res, next) {
55} 52}
56 53
57function usersUpdate (req, res, next) { 54function usersUpdate (req, res, next) {
58 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 55 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
59 // Add old password verification 56 // Add old password verification
60 req.checkBody('password', 'Should have a valid password').isUserPasswordValid() 57 req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
61 58
diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js
index 76e943e77..4fe6dcd8b 100644
--- a/server/middlewares/validators/videos.js
+++ b/server/middlewares/validators/videos.js
@@ -1,19 +1,19 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose')
4
5const checkErrors = require('./utils').checkErrors 3const checkErrors = require('./utils').checkErrors
6const constants = require('../../initializers/constants') 4const constants = require('../../initializers/constants')
7const customVideosValidators = require('../../helpers/custom-validators').videos 5const customVideosValidators = require('../../helpers/custom-validators').videos
6const db = require('../../initializers/database')
8const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
9 8
10const Video = mongoose.model('Video')
11
12const validatorsVideos = { 9const validatorsVideos = {
13 videosAdd, 10 videosAdd,
11 videosUpdate,
14 videosGet, 12 videosGet,
15 videosRemove, 13 videosRemove,
16 videosSearch 14 videosSearch,
15
16 videoAbuseReport
17} 17}
18 18
19function videosAdd (req, res, next) { 19function videosAdd (req, res, next) {
@@ -29,7 +29,7 @@ function videosAdd (req, res, next) {
29 checkErrors(req, res, function () { 29 checkErrors(req, res, function () {
30 const videoFile = req.files.videofile[0] 30 const videoFile = req.files.videofile[0]
31 31
32 Video.getDurationFromFile(videoFile.path, function (err, duration) { 32 db.Video.getDurationFromFile(videoFile.path, function (err, duration) {
33 if (err) { 33 if (err) {
34 return res.status(400).send('Cannot retrieve metadata of the file.') 34 return res.status(400).send('Cannot retrieve metadata of the file.')
35 } 35 }
@@ -44,40 +44,56 @@ function videosAdd (req, res, next) {
44 }) 44 })
45} 45}
46 46
47function videosGet (req, res, next) { 47function videosUpdate (req, res, next) {
48 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 48 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
49 req.checkBody('name', 'Should have a valid name').optional().isVideoNameValid()
50 req.checkBody('description', 'Should have a valid description').optional().isVideoDescriptionValid()
51 req.checkBody('tags', 'Should have correct tags').optional().isVideoTagsValid()
49 52
50 logger.debug('Checking videosGet parameters', { parameters: req.params }) 53 logger.debug('Checking videosUpdate parameters', { parameters: req.body })
51 54
52 checkErrors(req, res, function () { 55 checkErrors(req, res, function () {
53 Video.load(req.params.id, function (err, video) { 56 checkVideoExists(req.params.id, res, function () {
54 if (err) { 57 // We need to make additional checks
55 logger.error('Error in videosGet request validator.', { error: err }) 58 if (res.locals.video.isOwned() === false) {
56 return res.sendStatus(500) 59 return res.status(403).send('Cannot update video of another pod')
57 } 60 }
58 61
59 if (!video) return res.status(404).send('Video not found') 62 if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
63 return res.status(403).send('Cannot update video of another user')
64 }
60 65
61 next() 66 next()
62 }) 67 })
63 }) 68 })
64} 69}
65 70
71function videosGet (req, res, next) {
72 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
73
74 logger.debug('Checking videosGet parameters', { parameters: req.params })
75
76 checkErrors(req, res, function () {
77 checkVideoExists(req.params.id, res, next)
78 })
79}
80
66function videosRemove (req, res, next) { 81function videosRemove (req, res, next) {
67 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 82 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
68 83
69 logger.debug('Checking videosRemove parameters', { parameters: req.params }) 84 logger.debug('Checking videosRemove parameters', { parameters: req.params })
70 85
71 checkErrors(req, res, function () { 86 checkErrors(req, res, function () {
72 Video.load(req.params.id, function (err, video) { 87 checkVideoExists(req.params.id, res, function () {
73 if (err) { 88 // We need to make additional checks
74 logger.error('Error in videosRemove request validator.', { error: err }) 89
75 return res.sendStatus(500) 90 if (res.locals.video.isOwned() === false) {
91 return res.status(403).send('Cannot remove video of another pod')
76 } 92 }
77 93
78 if (!video) return res.status(404).send('Video not found') 94 if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) {
79 else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') 95 return res.status(403).send('Cannot remove video of another user')
80 else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user') 96 }
81 97
82 next() 98 next()
83 }) 99 })
@@ -94,6 +110,33 @@ function videosSearch (req, res, next) {
94 checkErrors(req, res, next) 110 checkErrors(req, res, next)
95} 111}
96 112
113function videoAbuseReport (req, res, next) {
114 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
115 req.checkBody('reason', 'Should have a valid reason').isVideoAbuseReasonValid()
116
117 logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
118
119 checkErrors(req, res, function () {
120 checkVideoExists(req.params.id, res, next)
121 })
122}
123
97// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
98 125
99module.exports = validatorsVideos 126module.exports = validatorsVideos
127
128// ---------------------------------------------------------------------------
129
130function checkVideoExists (id, res, callback) {
131 db.Video.loadAndPopulateAuthorAndPodAndTags(id, function (err, video) {
132 if (err) {
133 logger.error('Error in video request validator.', { error: err })
134 return res.sendStatus(500)
135 }
136
137 if (!video) return res.status(404).send('Video not found')
138
139 res.locals.video = video
140 callback()
141 })
142}
diff --git a/server/models/application.js b/server/models/application.js
index 452ac4283..46dcfde33 100644
--- a/server/models/application.js
+++ b/server/models/application.js
@@ -1,31 +1,52 @@
1const mongoose = require('mongoose') 1'use strict'
2
3module.exports = function (sequelize, DataTypes) {
4 const Application = sequelize.define('Application',
5 {
6 migrationVersion: {
7 type: DataTypes.INTEGER,
8 defaultValue: 0,
9 allowNull: false,
10 validate: {
11 isInt: true
12 }
13 }
14 },
15 {
16 classMethods: {
17 loadMigrationVersion,
18 updateMigrationVersion
19 }
20 }
21 )
22
23 return Application
24}
2 25
3// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
4 27
5const ApplicationSchema = mongoose.Schema({ 28function loadMigrationVersion (callback) {
6 mongoSchemaVersion: { 29 const query = {
7 type: Number, 30 attributes: [ 'migrationVersion' ]
8 default: 0
9 } 31 }
10})
11
12ApplicationSchema.statics = {
13 loadMongoSchemaVersion,
14 updateMongoSchemaVersion
15}
16
17mongoose.model('Application', ApplicationSchema)
18
19// ---------------------------------------------------------------------------
20 32
21function loadMongoSchemaVersion (callback) { 33 return this.findOne(query).asCallback(function (err, data) {
22 return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) { 34 const version = data ? data.migrationVersion : 0
23 const version = data ? data.mongoSchemaVersion : 0
24 35
25 return callback(err, version) 36 return callback(err, version)
26 }) 37 })
27} 38}
28 39
29function updateMongoSchemaVersion (newVersion, callback) { 40function updateMigrationVersion (newVersion, transaction, callback) {
30 return this.update({}, { mongoSchemaVersion: newVersion }, callback) 41 const options = {
42 where: {}
43 }
44
45 if (!callback) {
46 transaction = callback
47 } else {
48 options.transaction = transaction
49 }
50
51 return this.update({ migrationVersion: newVersion }, options).asCallback(callback)
31} 52}
diff --git a/server/models/author.js b/server/models/author.js
new file mode 100644
index 000000000..7d15fb6ec
--- /dev/null
+++ b/server/models/author.js
@@ -0,0 +1,85 @@
1'use strict'
2
3const customUsersValidators = require('../helpers/custom-validators').users
4
5module.exports = function (sequelize, DataTypes) {
6 const Author = sequelize.define('Author',
7 {
8 name: {
9 type: DataTypes.STRING,
10 allowNull: false,
11 validate: {
12 usernameValid: function (value) {
13 const res = customUsersValidators.isUserUsernameValid(value)
14 if (res === false) throw new Error('Username is not valid.')
15 }
16 }
17 }
18 },
19 {
20 indexes: [
21 {
22 fields: [ 'name' ]
23 },
24 {
25 fields: [ 'podId' ]
26 },
27 {
28 fields: [ 'userId' ]
29 }
30 ],
31 classMethods: {
32 associate,
33
34 findOrCreateAuthor
35 }
36 }
37 )
38
39 return Author
40}
41
42// ---------------------------------------------------------------------------
43
44function associate (models) {
45 this.belongsTo(models.Pod, {
46 foreignKey: {
47 name: 'podId',
48 allowNull: true
49 },
50 onDelete: 'cascade'
51 })
52
53 this.belongsTo(models.User, {
54 foreignKey: {
55 name: 'userId',
56 allowNull: true
57 },
58 onDelete: 'cascade'
59 })
60}
61
62function findOrCreateAuthor (name, podId, userId, transaction, callback) {
63 if (!callback) {
64 callback = transaction
65 transaction = null
66 }
67
68 const author = {
69 name,
70 podId,
71 userId
72 }
73
74 const query = {
75 where: author,
76 defaults: author
77 }
78
79 if (transaction) query.transaction = transaction
80
81 this.findOrCreate(query).asCallback(function (err, result) {
82 // [ instance, wasCreated ]
83 return callback(err, result[0])
84 })
85}
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js
index a1aefa985..021a34007 100644
--- a/server/models/oauth-client.js
+++ b/server/models/oauth-client.js
@@ -1,33 +1,62 @@
1const mongoose = require('mongoose') 1'use strict'
2 2
3// --------------------------------------------------------------------------- 3module.exports = function (sequelize, DataTypes) {
4 4 const OAuthClient = sequelize.define('OAuthClient',
5const OAuthClientSchema = mongoose.Schema({ 5 {
6 clientSecret: String, 6 clientId: {
7 grants: Array, 7 type: DataTypes.STRING,
8 redirectUris: Array 8 allowNull: false
9}) 9 },
10 10 clientSecret: {
11OAuthClientSchema.path('clientSecret').required(true) 11 type: DataTypes.STRING,
12 12 allowNull: false
13OAuthClientSchema.statics = { 13 },
14 getByIdAndSecret, 14 grants: {
15 list, 15 type: DataTypes.ARRAY(DataTypes.STRING)
16 loadFirstClient 16 },
17 redirectUris: {
18 type: DataTypes.ARRAY(DataTypes.STRING)
19 }
20 },
21 {
22 indexes: [
23 {
24 fields: [ 'clientId' ],
25 unique: true
26 },
27 {
28 fields: [ 'clientId', 'clientSecret' ],
29 unique: true
30 }
31 ],
32 classMethods: {
33 countTotal,
34 getByIdAndSecret,
35 loadFirstClient
36 }
37 }
38 )
39
40 return OAuthClient
17} 41}
18 42
19mongoose.model('OAuthClient', OAuthClientSchema)
20
21// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
22 44
23function list (callback) { 45function countTotal (callback) {
24 return this.find(callback) 46 return this.count().asCallback(callback)
25} 47}
26 48
27function loadFirstClient (callback) { 49function loadFirstClient (callback) {
28 return this.findOne({}, callback) 50 return this.findOne().asCallback(callback)
29} 51}
30 52
31function getByIdAndSecret (id, clientSecret) { 53function getByIdAndSecret (clientId, clientSecret) {
32 return this.findOne({ _id: id, clientSecret: clientSecret }).exec() 54 const query = {
55 where: {
56 clientId: clientId,
57 clientSecret: clientSecret
58 }
59 }
60
61 return this.findOne(query)
33} 62}
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js
index aff73bfb1..68e7c9ff7 100644
--- a/server/models/oauth-token.js
+++ b/server/models/oauth-token.js
@@ -1,42 +1,96 @@
1const mongoose = require('mongoose') 1'use strict'
2 2
3const logger = require('../helpers/logger') 3const logger = require('../helpers/logger')
4 4
5// --------------------------------------------------------------------------- 5// ---------------------------------------------------------------------------
6 6
7const OAuthTokenSchema = mongoose.Schema({ 7module.exports = function (sequelize, DataTypes) {
8 accessToken: String, 8 const OAuthToken = sequelize.define('OAuthToken',
9 accessTokenExpiresAt: Date, 9 {
10 client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, 10 accessToken: {
11 refreshToken: String, 11 type: DataTypes.STRING,
12 refreshTokenExpiresAt: Date, 12 allowNull: false
13 user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } 13 },
14}) 14 accessTokenExpiresAt: {
15 15 type: DataTypes.DATE,
16OAuthTokenSchema.path('accessToken').required(true) 16 allowNull: false
17OAuthTokenSchema.path('client').required(true) 17 },
18OAuthTokenSchema.path('user').required(true) 18 refreshToken: {
19 19 type: DataTypes.STRING,
20OAuthTokenSchema.statics = { 20 allowNull: false
21 getByRefreshTokenAndPopulateClient, 21 },
22 getByTokenAndPopulateUser, 22 refreshTokenExpiresAt: {
23 getByRefreshTokenAndPopulateUser, 23 type: DataTypes.DATE,
24 removeByUserId 24 allowNull: false
25} 25 }
26 },
27 {
28 indexes: [
29 {
30 fields: [ 'refreshToken' ],
31 unique: true
32 },
33 {
34 fields: [ 'accessToken' ],
35 unique: true
36 },
37 {
38 fields: [ 'userId' ]
39 },
40 {
41 fields: [ 'oAuthClientId' ]
42 }
43 ],
44 classMethods: {
45 associate,
46
47 getByRefreshTokenAndPopulateClient,
48 getByTokenAndPopulateUser,
49 getByRefreshTokenAndPopulateUser,
50 removeByUserId
51 }
52 }
53 )
26 54
27mongoose.model('OAuthToken', OAuthTokenSchema) 55 return OAuthToken
56}
28 57
29// --------------------------------------------------------------------------- 58// ---------------------------------------------------------------------------
30 59
60function associate (models) {
61 this.belongsTo(models.User, {
62 foreignKey: {
63 name: 'userId',
64 allowNull: false
65 },
66 onDelete: 'cascade'
67 })
68
69 this.belongsTo(models.OAuthClient, {
70 foreignKey: {
71 name: 'oAuthClientId',
72 allowNull: false
73 },
74 onDelete: 'cascade'
75 })
76}
77
31function getByRefreshTokenAndPopulateClient (refreshToken) { 78function getByRefreshTokenAndPopulateClient (refreshToken) {
32 return this.findOne({ refreshToken: refreshToken }).populate('client').exec().then(function (token) { 79 const query = {
80 where: {
81 refreshToken: refreshToken
82 },
83 include: [ this.associations.OAuthClient ]
84 }
85
86 return this.findOne(query).then(function (token) {
33 if (!token) return token 87 if (!token) return token
34 88
35 const tokenInfos = { 89 const tokenInfos = {
36 refreshToken: token.refreshToken, 90 refreshToken: token.refreshToken,
37 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 91 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
38 client: { 92 client: {
39 id: token.client._id.toString() 93 id: token.client.id
40 }, 94 },
41 user: { 95 user: {
42 id: token.user 96 id: token.user
@@ -50,13 +104,41 @@ function getByRefreshTokenAndPopulateClient (refreshToken) {
50} 104}
51 105
52function getByTokenAndPopulateUser (bearerToken) { 106function getByTokenAndPopulateUser (bearerToken) {
53 return this.findOne({ accessToken: bearerToken }).populate('user').exec() 107 const query = {
108 where: {
109 accessToken: bearerToken
110 },
111 include: [ this.sequelize.models.User ]
112 }
113
114 return this.findOne(query).then(function (token) {
115 if (token) token.user = token.User
116
117 return token
118 })
54} 119}
55 120
56function getByRefreshTokenAndPopulateUser (refreshToken) { 121function getByRefreshTokenAndPopulateUser (refreshToken) {
57 return this.findOne({ refreshToken: refreshToken }).populate('user').exec() 122 const query = {
123 where: {
124 refreshToken: refreshToken
125 },
126 include: [ this.sequelize.models.User ]
127 }
128
129 return this.findOne(query).then(function (token) {
130 token.user = token.User
131
132 return token
133 })
58} 134}
59 135
60function removeByUserId (userId, callback) { 136function removeByUserId (userId, callback) {
61 return this.remove({ user: userId }, callback) 137 const query = {
138 where: {
139 userId: userId
140 }
141 }
142
143 return this.destroy(query).asCallback(callback)
62} 144}
diff --git a/server/models/pod.js b/server/models/pod.js
new file mode 100644
index 000000000..b3c6db8e8
--- /dev/null
+++ b/server/models/pod.js
@@ -0,0 +1,200 @@
1'use strict'
2
3const map = require('lodash/map')
4
5const constants = require('../initializers/constants')
6const customPodsValidators = require('../helpers/custom-validators').pods
7
8// ---------------------------------------------------------------------------
9
10module.exports = function (sequelize, DataTypes) {
11 const Pod = sequelize.define('Pod',
12 {
13 host: {
14 type: DataTypes.STRING,
15 allowNull: false,
16 validate: {
17 isHost: function (value) {
18 const res = customPodsValidators.isHostValid(value)
19 if (res === false) throw new Error('Host not valid.')
20 }
21 }
22 },
23 publicKey: {
24 type: DataTypes.STRING(5000),
25 allowNull: false
26 },
27 score: {
28 type: DataTypes.INTEGER,
29 defaultValue: constants.FRIEND_SCORE.BASE,
30 allowNull: false,
31 validate: {
32 isInt: true,
33 max: constants.FRIEND_SCORE.MAX
34 }
35 }
36 },
37 {
38 indexes: [
39 {
40 fields: [ 'host' ]
41 },
42 {
43 fields: [ 'score' ]
44 }
45 ],
46 classMethods: {
47 associate,
48
49 countAll,
50 incrementScores,
51 list,
52 listAllIds,
53 listRandomPodIdsWithRequest,
54 listBadPods,
55 load,
56 loadByHost,
57 removeAll
58 },
59 instanceMethods: {
60 toFormatedJSON
61 }
62 }
63 )
64
65 return Pod
66}
67
68// ------------------------------ METHODS ------------------------------
69
70function toFormatedJSON () {
71 const json = {
72 id: this.id,
73 host: this.host,
74 score: this.score,
75 createdAt: this.createdAt
76 }
77
78 return json
79}
80
81// ------------------------------ Statics ------------------------------
82
83function associate (models) {
84 this.belongsToMany(models.Request, {
85 foreignKey: 'podId',
86 through: models.RequestToPod,
87 onDelete: 'cascade'
88 })
89}
90
91function countAll (callback) {
92 return this.count().asCallback(callback)
93}
94
95function incrementScores (ids, value, callback) {
96 if (!callback) callback = function () {}
97
98 const update = {
99 score: this.sequelize.literal('score +' + value)
100 }
101
102 const options = {
103 where: {
104 id: {
105 $in: ids
106 }
107 },
108 // In this case score is a literal and not an integer so we do not validate it
109 validate: false
110 }
111
112 return this.update(update, options).asCallback(callback)
113}
114
115function list (callback) {
116 return this.findAll().asCallback(callback)
117}
118
119function listAllIds (transaction, callback) {
120 if (!callback) {
121 callback = transaction
122 transaction = null
123 }
124
125 const query = {
126 attributes: [ 'id' ]
127 }
128
129 if (transaction) query.transaction = transaction
130
131 return this.findAll(query).asCallback(function (err, pods) {
132 if (err) return callback(err)
133
134 return callback(null, map(pods, 'id'))
135 })
136}
137
138function listRandomPodIdsWithRequest (limit, callback) {
139 const self = this
140
141 self.count().asCallback(function (err, count) {
142 if (err) return callback(err)
143
144 // Optimization...
145 if (count === 0) return callback(null, [])
146
147 let start = Math.floor(Math.random() * count) - limit
148 if (start < 0) start = 0
149
150 const query = {
151 attributes: [ 'id' ],
152 order: [
153 [ 'id', 'ASC' ]
154 ],
155 offset: start,
156 limit: limit,
157 where: {
158 id: {
159 $in: [
160 this.sequelize.literal('SELECT "podId" FROM "RequestToPods"')
161 ]
162 }
163 }
164 }
165
166 return this.findAll(query).asCallback(function (err, pods) {
167 if (err) return callback(err)
168
169 return callback(null, map(pods, 'id'))
170 })
171 })
172}
173
174function listBadPods (callback) {
175 const query = {
176 where: {
177 score: { $lte: 0 }
178 }
179 }
180
181 return this.findAll(query).asCallback(callback)
182}
183
184function load (id, callback) {
185 return this.findById(id).asCallback(callback)
186}
187
188function loadByHost (host, callback) {
189 const query = {
190 where: {
191 host: host
192 }
193 }
194
195 return this.findOne(query).asCallback(callback)
196}
197
198function removeAll (callback) {
199 return this.destroy().asCallback(callback)
200}
diff --git a/server/models/pods.js b/server/models/pods.js
deleted file mode 100644
index 49c73472a..000000000
--- a/server/models/pods.js
+++ /dev/null
@@ -1,119 +0,0 @@
1'use strict'
2
3const each = require('async/each')
4const mongoose = require('mongoose')
5const map = require('lodash/map')
6const validator = require('express-validator').validator
7
8const constants = require('../initializers/constants')
9
10const Video = mongoose.model('Video')
11
12// ---------------------------------------------------------------------------
13
14const PodSchema = mongoose.Schema({
15 host: String,
16 publicKey: String,
17 score: { type: Number, max: constants.FRIEND_SCORE.MAX },
18 createdDate: {
19 type: Date,
20 default: Date.now
21 }
22})
23
24PodSchema.path('host').validate(validator.isURL)
25PodSchema.path('publicKey').required(true)
26PodSchema.path('score').validate(function (value) { return !isNaN(value) })
27
28PodSchema.methods = {
29 toFormatedJSON
30}
31
32PodSchema.statics = {
33 countAll,
34 incrementScores,
35 list,
36 listAllIds,
37 listBadPods,
38 load,
39 loadByHost,
40 removeAll
41}
42
43PodSchema.pre('save', function (next) {
44 const self = this
45
46 Pod.loadByHost(this.host, function (err, pod) {
47 if (err) return next(err)
48
49 if (pod) return next(new Error('Pod already exists.'))
50
51 self.score = constants.FRIEND_SCORE.BASE
52 return next()
53 })
54})
55
56PodSchema.pre('remove', function (next) {
57 // Remove the videos owned by this pod too
58 Video.listByHost(this.host, function (err, videos) {
59 if (err) return next(err)
60
61 each(videos, function (video, callbackEach) {
62 video.remove(callbackEach)
63 }, next)
64 })
65})
66
67const Pod = mongoose.model('Pod', PodSchema)
68
69// ------------------------------ METHODS ------------------------------
70
71function toFormatedJSON () {
72 const json = {
73 id: this._id,
74 host: this.host,
75 score: this.score,
76 createdDate: this.createdDate
77 }
78
79 return json
80}
81
82// ------------------------------ Statics ------------------------------
83
84function countAll (callback) {
85 return this.count(callback)
86}
87
88function incrementScores (ids, value, callback) {
89 if (!callback) callback = function () {}
90 return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
91}
92
93function list (callback) {
94 return this.find(callback)
95}
96
97function listAllIds (callback) {
98 return this.find({}, { _id: 1 }, function (err, pods) {
99 if (err) return callback(err)
100
101 return callback(null, map(pods, '_id'))
102 })
103}
104
105function listBadPods (callback) {
106 return this.find({ score: 0 }, callback)
107}
108
109function load (id, callback) {
110 return this.findById(id, callback)
111}
112
113function loadByHost (host, callback) {
114 return this.findOne({ host }, callback)
115}
116
117function removeAll (callback) {
118 return this.remove({}, callback)
119}
diff --git a/server/models/request-to-pod.js b/server/models/request-to-pod.js
new file mode 100644
index 000000000..f42a53458
--- /dev/null
+++ b/server/models/request-to-pod.js
@@ -0,0 +1,42 @@
1'use strict'
2
3// ---------------------------------------------------------------------------
4
5module.exports = function (sequelize, DataTypes) {
6 const RequestToPod = sequelize.define('RequestToPod', {}, {
7 indexes: [
8 {
9 fields: [ 'requestId' ]
10 },
11 {
12 fields: [ 'podId' ]
13 },
14 {
15 fields: [ 'requestId', 'podId' ],
16 unique: true
17 }
18 ],
19 classMethods: {
20 removePodOf
21 }
22 })
23
24 return RequestToPod
25}
26
27// ---------------------------------------------------------------------------
28
29function removePodOf (requestsIds, podId, callback) {
30 if (!callback) callback = function () {}
31
32 const query = {
33 where: {
34 requestId: {
35 $in: requestsIds
36 },
37 podId: podId
38 }
39 }
40
41 this.destroy(query).asCallback(callback)
42}
diff --git a/server/models/request.js b/server/models/request.js
index c2cfe83ce..cd52ea767 100644
--- a/server/models/request.js
+++ b/server/models/request.js
@@ -2,66 +2,60 @@
2 2
3const each = require('async/each') 3const each = require('async/each')
4const eachLimit = require('async/eachLimit') 4const eachLimit = require('async/eachLimit')
5const values = require('lodash/values')
6const mongoose = require('mongoose')
7const waterfall = require('async/waterfall') 5const waterfall = require('async/waterfall')
6const values = require('lodash/values')
8 7
9const constants = require('../initializers/constants') 8const constants = require('../initializers/constants')
10const logger = require('../helpers/logger') 9const logger = require('../helpers/logger')
11const requests = require('../helpers/requests') 10const requests = require('../helpers/requests')
12 11
13const Pod = mongoose.model('Pod')
14
15let timer = null 12let timer = null
16let lastRequestTimestamp = 0 13let lastRequestTimestamp = 0
17 14
18// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
19 16
20const RequestSchema = mongoose.Schema({ 17module.exports = function (sequelize, DataTypes) {
21 request: mongoose.Schema.Types.Mixed, 18 const Request = sequelize.define('Request',
22 endpoint: { 19 {
23 type: String, 20 request: {
24 enum: [ values(constants.REQUEST_ENDPOINTS) ] 21 type: DataTypes.JSON,
25 }, 22 allowNull: false
26 to: [ 23 },
24 endpoint: {
25 type: DataTypes.ENUM(values(constants.REQUEST_ENDPOINTS)),
26 allowNull: false
27 }
28 },
27 { 29 {
28 type: mongoose.Schema.Types.ObjectId, 30 classMethods: {
29 ref: 'Pod' 31 associate,
32
33 activate,
34 countTotalRequests,
35 deactivate,
36 flush,
37 forceSend,
38 remainingMilliSeconds
39 }
30 } 40 }
31 ] 41 )
32})
33
34RequestSchema.statics = {
35 activate,
36 deactivate,
37 flush,
38 forceSend,
39 list,
40 remainingMilliSeconds
41}
42
43RequestSchema.pre('save', function (next) {
44 const self = this
45
46 if (self.to.length === 0) {
47 Pod.listAllIds(function (err, podIds) {
48 if (err) return next(err)
49 42
50 // No friends 43 return Request
51 if (podIds.length === 0) return 44}
52
53 self.to = podIds
54 return next()
55 })
56 } else {
57 return next()
58 }
59})
60
61mongoose.model('Request', RequestSchema)
62 45
63// ------------------------------ STATICS ------------------------------ 46// ------------------------------ STATICS ------------------------------
64 47
48function associate (models) {
49 this.belongsToMany(models.Pod, {
50 foreignKey: {
51 name: 'requestId',
52 allowNull: false
53 },
54 through: models.RequestToPod,
55 onDelete: 'CASCADE'
56 })
57}
58
65function activate () { 59function activate () {
66 logger.info('Requests scheduler activated.') 60 logger.info('Requests scheduler activated.')
67 lastRequestTimestamp = Date.now() 61 lastRequestTimestamp = Date.now()
@@ -73,15 +67,25 @@ function activate () {
73 }, constants.REQUESTS_INTERVAL) 67 }, constants.REQUESTS_INTERVAL)
74} 68}
75 69
70function countTotalRequests (callback) {
71 const query = {
72 include: [ this.sequelize.models.Pod ]
73 }
74
75 return this.count(query).asCallback(callback)
76}
77
76function deactivate () { 78function deactivate () {
77 logger.info('Requests scheduler deactivated.') 79 logger.info('Requests scheduler deactivated.')
78 clearInterval(timer) 80 clearInterval(timer)
79 timer = null 81 timer = null
80} 82}
81 83
82function flush () { 84function flush (callback) {
83 removeAll.call(this, function (err) { 85 removeAll.call(this, function (err) {
84 if (err) logger.error('Cannot flush the requests.', { error: err }) 86 if (err) logger.error('Cannot flush the requests.', { error: err })
87
88 return callback(err)
85 }) 89 })
86} 90}
87 91
@@ -90,10 +94,6 @@ function forceSend () {
90 makeRequests.call(this) 94 makeRequests.call(this)
91} 95}
92 96
93function list (callback) {
94 this.find({ }, callback)
95}
96
97function remainingMilliSeconds () { 97function remainingMilliSeconds () {
98 if (timer === null) return -1 98 if (timer === null) return -1
99 99
@@ -122,7 +122,7 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) {
122 'Error sending secure request to %s pod.', 122 'Error sending secure request to %s pod.',
123 toPod.host, 123 toPod.host,
124 { 124 {
125 error: err || new Error('Status code not 20x : ' + res.statusCode) 125 error: err ? err.message : 'Status code not 20x : ' + res.statusCode
126 } 126 }
127 ) 127 )
128 128
@@ -136,10 +136,11 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) {
136// Make all the requests of the scheduler 136// Make all the requests of the scheduler
137function makeRequests () { 137function makeRequests () {
138 const self = this 138 const self = this
139 const RequestToPod = this.sequelize.models.RequestToPod
139 140
140 // We limit the size of the requests (REQUESTS_LIMIT) 141 // We limit the size of the requests
141 // We don't want to stuck with the same failing requests so we get a random list 142 // We don't want to stuck with the same failing requests so we get a random list
142 listWithLimitAndRandom.call(self, constants.REQUESTS_LIMIT, function (err, requests) { 143 listWithLimitAndRandom.call(self, constants.REQUESTS_LIMIT_PODS, constants.REQUESTS_LIMIT_PER_POD, function (err, requests) {
143 if (err) { 144 if (err) {
144 logger.error('Cannot get the list of requests.', { err: err }) 145 logger.error('Cannot get the list of requests.', { err: err })
145 return // Abort 146 return // Abort
@@ -151,78 +152,77 @@ function makeRequests () {
151 return 152 return
152 } 153 }
153 154
154 logger.info('Making requests to friends.')
155
156 // We want to group requests by destinations pod and endpoint 155 // We want to group requests by destinations pod and endpoint
157 const requestsToMakeGrouped = {} 156 const requestsToMakeGrouped = {}
157 Object.keys(requests).forEach(function (toPodId) {
158 requests[toPodId].forEach(function (data) {
159 const request = data.request
160 const pod = data.pod
161 const hashKey = toPodId + request.endpoint
158 162
159 requests.forEach(function (poolRequest) {
160 poolRequest.to.forEach(function (toPodId) {
161 const hashKey = toPodId + poolRequest.endpoint
162 if (!requestsToMakeGrouped[hashKey]) { 163 if (!requestsToMakeGrouped[hashKey]) {
163 requestsToMakeGrouped[hashKey] = { 164 requestsToMakeGrouped[hashKey] = {
164 toPodId, 165 toPod: pod,
165 endpoint: poolRequest.endpoint, 166 endpoint: request.endpoint,
166 ids: [], // pool request ids, to delete them from the DB in the future 167 ids: [], // request ids, to delete them from the DB in the future
167 datas: [] // requests data, 168 datas: [] // requests data,
168 } 169 }
169 } 170 }
170 171
171 requestsToMakeGrouped[hashKey].ids.push(poolRequest._id) 172 requestsToMakeGrouped[hashKey].ids.push(request.id)
172 requestsToMakeGrouped[hashKey].datas.push(poolRequest.request) 173 requestsToMakeGrouped[hashKey].datas.push(request.request)
173 }) 174 })
174 }) 175 })
175 176
177 logger.info('Making requests to friends.')
178
176 const goodPods = [] 179 const goodPods = []
177 const badPods = [] 180 const badPods = []
178 181
179 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) { 182 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) {
180 const requestToMake = requestsToMakeGrouped[hashKey] 183 const requestToMake = requestsToMakeGrouped[hashKey]
184 const toPod = requestToMake.toPod
181 185
182 // FIXME: mongodb request inside a loop :/ 186 // Maybe the pod is not our friend anymore so simply remove it
183 Pod.load(requestToMake.toPodId, function (err, toPod) { 187 if (!toPod) {
184 if (err) { 188 const requestIdsToDelete = requestToMake.ids
185 logger.error('Error finding pod by id.', { err: err })
186 return callbackEach()
187 }
188
189 // Maybe the pod is not our friend anymore so simply remove it
190 if (!toPod) {
191 const requestIdsToDelete = requestToMake.ids
192 189
193 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId) 190 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPod.id)
194 removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId) 191 RequestToPod.removePodOf.call(self, requestIdsToDelete, requestToMake.toPod.id)
195 return callbackEach() 192 return callbackEach()
196 } 193 }
197 194
198 makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, function (success) { 195 makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, function (success) {
199 if (success === true) { 196 if (success === true) {
200 logger.debug('Removing requests for %s pod.', requestToMake.toPodId, { requestsIds: requestToMake.ids }) 197 logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids })
201 198
202 goodPods.push(requestToMake.toPodId) 199 goodPods.push(requestToMake.toPod.id)
203 200
204 // Remove the pod id of these request ids 201 // Remove the pod id of these request ids
205 removePodOf.call(self, requestToMake.ids, requestToMake.toPodId, callbackEach) 202 RequestToPod.removePodOf(requestToMake.ids, requestToMake.toPod.id, callbackEach)
206 } else { 203 } else {
207 badPods.push(requestToMake.toPodId) 204 badPods.push(requestToMake.toPod.id)
208 callbackEach() 205 callbackEach()
209 } 206 }
210 })
211 }) 207 })
212 }, function () { 208 }, function () {
213 // All the requests were made, we update the pods score 209 // All the requests were made, we update the pods score
214 updatePodsScore(goodPods, badPods) 210 updatePodsScore.call(self, goodPods, badPods)
215 // Flush requests with no pod 211 // Flush requests with no pod
216 removeWithEmptyTo.call(self) 212 removeWithEmptyTo.call(self, function (err) {
213 if (err) logger.error('Error when removing requests with no pods.', { error: err })
214 })
217 }) 215 })
218 }) 216 })
219} 217}
220 218
221// Remove pods with a score of 0 (too many requests where they were unreachable) 219// Remove pods with a score of 0 (too many requests where they were unreachable)
222function removeBadPods () { 220function removeBadPods () {
221 const self = this
222
223 waterfall([ 223 waterfall([
224 function findBadPods (callback) { 224 function findBadPods (callback) {
225 Pod.listBadPods(function (err, pods) { 225 self.sequelize.models.Pod.listBadPods(function (err, pods) {
226 if (err) { 226 if (err) {
227 logger.error('Cannot find bad pods.', { error: err }) 227 logger.error('Cannot find bad pods.', { error: err })
228 return callback(err) 228 return callback(err)
@@ -233,10 +233,8 @@ function removeBadPods () {
233 }, 233 },
234 234
235 function removeTheseBadPods (pods, callback) { 235 function removeTheseBadPods (pods, callback) {
236 if (pods.length === 0) return callback(null, 0)
237
238 each(pods, function (pod, callbackEach) { 236 each(pods, function (pod, callbackEach) {
239 pod.remove(callbackEach) 237 pod.destroy().asCallback(callbackEach)
240 }, function (err) { 238 }, function (err) {
241 return callback(err, pods.length) 239 return callback(err, pods.length)
242 }) 240 })
@@ -253,43 +251,98 @@ function removeBadPods () {
253} 251}
254 252
255function updatePodsScore (goodPods, badPods) { 253function updatePodsScore (goodPods, badPods) {
254 const self = this
255 const Pod = this.sequelize.models.Pod
256
256 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) 257 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
257 258
258 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { 259 if (goodPods.length !== 0) {
259 if (err) logger.error('Cannot increment scores of good pods.') 260 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
260 }) 261 if (err) logger.error('Cannot increment scores of good pods.', { error: err })
262 })
263 }
261 264
262 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { 265 if (badPods.length !== 0) {
263 if (err) logger.error('Cannot decrement scores of bad pods.') 266 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
264 removeBadPods() 267 if (err) logger.error('Cannot decrement scores of bad pods.', { error: err })
265 }) 268 removeBadPods.call(self)
269 })
270 }
266} 271}
267 272
268function listWithLimitAndRandom (limit, callback) { 273function listWithLimitAndRandom (limitPods, limitRequestsPerPod, callback) {
269 const self = this 274 const self = this
275 const Pod = this.sequelize.models.Pod
270 276
271 self.count(function (err, count) { 277 Pod.listRandomPodIdsWithRequest(limitPods, function (err, podIds) {
272 if (err) return callback(err) 278 if (err) return callback(err)
273 279
274 let start = Math.floor(Math.random() * count) - limit 280 // We don't have friends that have requests
275 if (start < 0) start = 0 281 if (podIds.length === 0) return callback(null, [])
282
283 // The the first x requests of these pods
284 // It is very important to sort by id ASC to keep the requests order!
285 const query = {
286 order: [
287 [ 'id', 'ASC' ]
288 ],
289 include: [
290 {
291 model: self.sequelize.models.Pod,
292 where: {
293 id: {
294 $in: podIds
295 }
296 }
297 }
298 ]
299 }
300
301 self.findAll(query).asCallback(function (err, requests) {
302 if (err) return callback(err)
276 303
277 self.find().sort({ _id: 1 }).skip(start).limit(limit).exec(callback) 304 const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod)
305 return callback(err, requestsGrouped)
306 })
278 }) 307 })
279} 308}
280 309
281function removeAll (callback) { 310function groupAndTruncateRequests (requests, limitRequestsPerPod) {
282 this.remove({ }, callback) 311 const requestsGrouped = {}
283}
284 312
285function removePodOf (requestsIds, podId, callback) { 313 requests.forEach(function (request) {
286 if (!callback) callback = function () {} 314 request.Pods.forEach(function (pod) {
315 if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = []
287 316
288 this.update({ _id: { $in: requestsIds } }, { $pull: { to: podId } }, { multi: true }, callback) 317 if (requestsGrouped[pod.id].length < limitRequestsPerPod) {
318 requestsGrouped[pod.id].push({
319 request,
320 pod
321 })
322 }
323 })
324 })
325
326 return requestsGrouped
327}
328
329function removeAll (callback) {
330 // Delete all requests
331 this.truncate({ cascade: true }).asCallback(callback)
289} 332}
290 333
291function removeWithEmptyTo (callback) { 334function removeWithEmptyTo (callback) {
292 if (!callback) callback = function () {} 335 if (!callback) callback = function () {}
293 336
294 this.remove({ to: { $size: 0 } }, callback) 337 const query = {
338 where: {
339 id: {
340 $notIn: [
341 this.sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
342 ]
343 }
344 }
345 }
346
347 this.destroy(query).asCallback(callback)
295} 348}
diff --git a/server/models/tag.js b/server/models/tag.js
new file mode 100644
index 000000000..145e090c1
--- /dev/null
+++ b/server/models/tag.js
@@ -0,0 +1,76 @@
1'use strict'
2
3const each = require('async/each')
4
5// ---------------------------------------------------------------------------
6
7module.exports = function (sequelize, DataTypes) {
8 const Tag = sequelize.define('Tag',
9 {
10 name: {
11 type: DataTypes.STRING,
12 allowNull: false
13 }
14 },
15 {
16 timestamps: false,
17 indexes: [
18 {
19 fields: [ 'name' ],
20 unique: true
21 }
22 ],
23 classMethods: {
24 associate,
25
26 findOrCreateTags
27 }
28 }
29 )
30
31 return Tag
32}
33
34// ---------------------------------------------------------------------------
35
36function associate (models) {
37 this.belongsToMany(models.Video, {
38 foreignKey: 'tagId',
39 through: models.VideoTag,
40 onDelete: 'cascade'
41 })
42}
43
44function findOrCreateTags (tags, transaction, callback) {
45 if (!callback) {
46 callback = transaction
47 transaction = null
48 }
49
50 const self = this
51 const tagInstances = []
52
53 each(tags, function (tag, callbackEach) {
54 const query = {
55 where: {
56 name: tag
57 },
58 defaults: {
59 name: tag
60 }
61 }
62
63 if (transaction) query.transaction = transaction
64
65 self.findOrCreate(query).asCallback(function (err, res) {
66 if (err) return callbackEach(err)
67
68 // res = [ tag, isCreated ]
69 const tag = res[0]
70 tagInstances.push(tag)
71 return callbackEach()
72 })
73 }, function (err) {
74 return callback(err, tagInstances)
75 })
76}
diff --git a/server/models/user.js b/server/models/user.js
index a19de7072..36ed723cc 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -1,60 +1,81 @@
1const mongoose = require('mongoose') 1'use strict'
2
3const values = require('lodash/values')
2 4
3const customUsersValidators = require('../helpers/custom-validators').users
4const modelUtils = require('./utils') 5const modelUtils = require('./utils')
6const constants = require('../initializers/constants')
5const peertubeCrypto = require('../helpers/peertube-crypto') 7const peertubeCrypto = require('../helpers/peertube-crypto')
6 8const customUsersValidators = require('../helpers/custom-validators').users
7const OAuthToken = mongoose.model('OAuthToken')
8 9
9// --------------------------------------------------------------------------- 10// ---------------------------------------------------------------------------
10 11
11const UserSchema = mongoose.Schema({ 12module.exports = function (sequelize, DataTypes) {
12 createdDate: { 13 const User = sequelize.define('User',
13 type: Date, 14 {
14 default: Date.now 15 password: {
15 }, 16 type: DataTypes.STRING,
16 password: String, 17 allowNull: false,
17 username: String, 18 validate: {
18 role: String 19 passwordValid: function (value) {
19}) 20 const res = customUsersValidators.isUserPasswordValid(value)
20 21 if (res === false) throw new Error('Password not valid.')
21UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) 22 }
22UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) 23 }
23UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) 24 },
24 25 username: {
25UserSchema.methods = { 26 type: DataTypes.STRING,
26 isPasswordMatch, 27 allowNull: false,
27 toFormatedJSON 28 validate: {
28} 29 usernameValid: function (value) {
29 30 const res = customUsersValidators.isUserUsernameValid(value)
30UserSchema.statics = { 31 if (res === false) throw new Error('Username not valid.')
31 countTotal, 32 }
32 getByUsername, 33 }
33 list, 34 },
34 listForApi, 35 role: {
35 loadById, 36 type: DataTypes.ENUM(values(constants.USER_ROLES)),
36 loadByUsername 37 allowNull: false
38 }
39 },
40 {
41 indexes: [
42 {
43 fields: [ 'username' ]
44 }
45 ],
46 classMethods: {
47 associate,
48
49 countTotal,
50 getByUsername,
51 list,
52 listForApi,
53 loadById,
54 loadByUsername
55 },
56 instanceMethods: {
57 isPasswordMatch,
58 toFormatedJSON
59 },
60 hooks: {
61 beforeCreate: beforeCreateOrUpdate,
62 beforeUpdate: beforeCreateOrUpdate
63 }
64 }
65 )
66
67 return User
37} 68}
38 69
39UserSchema.pre('save', function (next) { 70function beforeCreateOrUpdate (user, options, next) {
40 const user = this 71 peertubeCrypto.cryptPassword(user.password, function (err, hash) {
41
42 peertubeCrypto.cryptPassword(this.password, function (err, hash) {
43 if (err) return next(err) 72 if (err) return next(err)
44 73
45 user.password = hash 74 user.password = hash
46 75
47 return next() 76 return next()
48 }) 77 })
49}) 78}
50
51UserSchema.pre('remove', function (next) {
52 const user = this
53
54 OAuthToken.removeByUserId(user._id, next)
55})
56
57mongoose.model('User', UserSchema)
58 79
59// ------------------------------ METHODS ------------------------------ 80// ------------------------------ METHODS ------------------------------
60 81
@@ -64,35 +85,68 @@ function isPasswordMatch (password, callback) {
64 85
65function toFormatedJSON () { 86function toFormatedJSON () {
66 return { 87 return {
67 id: this._id, 88 id: this.id,
68 username: this.username, 89 username: this.username,
69 role: this.role, 90 role: this.role,
70 createdDate: this.createdDate 91 createdAt: this.createdAt
71 } 92 }
72} 93}
73// ------------------------------ STATICS ------------------------------ 94// ------------------------------ STATICS ------------------------------
74 95
96function associate (models) {
97 this.hasOne(models.Author, {
98 foreignKey: 'userId',
99 onDelete: 'cascade'
100 })
101
102 this.hasMany(models.OAuthToken, {
103 foreignKey: 'userId',
104 onDelete: 'cascade'
105 })
106}
107
75function countTotal (callback) { 108function countTotal (callback) {
76 return this.count(callback) 109 return this.count().asCallback(callback)
77} 110}
78 111
79function getByUsername (username) { 112function getByUsername (username) {
80 return this.findOne({ username: username }) 113 const query = {
114 where: {
115 username: username
116 }
117 }
118
119 return this.findOne(query)
81} 120}
82 121
83function list (callback) { 122function list (callback) {
84 return this.find(callback) 123 return this.find().asCallback(callback)
85} 124}
86 125
87function listForApi (start, count, sort, callback) { 126function listForApi (start, count, sort, callback) {
88 const query = {} 127 const query = {
89 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 128 offset: start,
129 limit: count,
130 order: [ modelUtils.getSort(sort) ]
131 }
132
133 return this.findAndCountAll(query).asCallback(function (err, result) {
134 if (err) return callback(err)
135
136 return callback(null, result.rows, result.count)
137 })
90} 138}
91 139
92function loadById (id, callback) { 140function loadById (id, callback) {
93 return this.findById(id, callback) 141 return this.findById(id).asCallback(callback)
94} 142}
95 143
96function loadByUsername (username, callback) { 144function loadByUsername (username, callback) {
97 return this.findOne({ username: username }, callback) 145 const query = {
146 where: {
147 username: username
148 }
149 }
150
151 return this.findOne(query).asCallback(callback)
98} 152}
diff --git a/server/models/utils.js b/server/models/utils.js
index e798aabe6..49636b3d8 100644
--- a/server/models/utils.js
+++ b/server/models/utils.js
@@ -1,28 +1,23 @@
1'use strict' 1'use strict'
2 2
3const parallel = require('async/parallel')
4
5const utils = { 3const utils = {
6 listForApiWithCount 4 getSort
7} 5}
8 6
9function listForApiWithCount (query, start, count, sort, callback) { 7// Translate for example "-name" to [ 'name', 'DESC' ]
10 const self = this 8function getSort (value) {
9 let field
10 let direction
11 11
12 parallel([ 12 if (value.substring(0, 1) === '-') {
13 function (asyncCallback) { 13 direction = 'DESC'
14 self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) 14 field = value.substring(1)
15 }, 15 } else {
16 function (asyncCallback) { 16 direction = 'ASC'
17 self.count(query, asyncCallback) 17 field = value
18 } 18 }
19 ], function (err, results) {
20 if (err) return callback(err)
21 19
22 const data = results[0] 20 return [ field, direction ]
23 const total = results[1]
24 return callback(null, data, total)
25 })
26} 21}
27 22
28// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
diff --git a/server/models/video-abuse.js b/server/models/video-abuse.js
new file mode 100644
index 000000000..766a7568d
--- /dev/null
+++ b/server/models/video-abuse.js
@@ -0,0 +1,113 @@
1'use strict'
2
3const constants = require('../initializers/constants')
4const modelUtils = require('./utils')
5const customVideosValidators = require('../helpers/custom-validators').videos
6
7module.exports = function (sequelize, DataTypes) {
8 const VideoAbuse = sequelize.define('VideoAbuse',
9 {
10 reporterUsername: {
11 type: DataTypes.STRING,
12 allowNull: false,
13 validate: {
14 reporterUsernameValid: function (value) {
15 const res = customVideosValidators.isVideoAbuseReporterUsernameValid(value)
16 if (res === false) throw new Error('Video abuse reporter username is not valid.')
17 }
18 }
19 },
20 reason: {
21 type: DataTypes.STRING,
22 allowNull: false,
23 validate: {
24 reasonValid: function (value) {
25 const res = customVideosValidators.isVideoAbuseReasonValid(value)
26 if (res === false) throw new Error('Video abuse reason is not valid.')
27 }
28 }
29 }
30 },
31 {
32 indexes: [
33 {
34 fields: [ 'videoId' ]
35 },
36 {
37 fields: [ 'reporterPodId' ]
38 }
39 ],
40 classMethods: {
41 associate,
42
43 listForApi
44 },
45 instanceMethods: {
46 toFormatedJSON
47 }
48 }
49 )
50
51 return VideoAbuse
52}
53
54// ---------------------------------------------------------------------------
55
56function associate (models) {
57 this.belongsTo(models.Pod, {
58 foreignKey: {
59 name: 'reporterPodId',
60 allowNull: true
61 },
62 onDelete: 'cascade'
63 })
64
65 this.belongsTo(models.Video, {
66 foreignKey: {
67 name: 'videoId',
68 allowNull: false
69 },
70 onDelete: 'cascade'
71 })
72}
73
74function listForApi (start, count, sort, callback) {
75 const query = {
76 offset: start,
77 limit: count,
78 order: [ modelUtils.getSort(sort) ],
79 include: [
80 {
81 model: this.sequelize.models.Pod,
82 required: false
83 }
84 ]
85 }
86
87 return this.findAndCountAll(query).asCallback(function (err, result) {
88 if (err) return callback(err)
89
90 return callback(null, result.rows, result.count)
91 })
92}
93
94function toFormatedJSON () {
95 let reporterPodHost
96
97 if (this.Pod) {
98 reporterPodHost = this.Pod.host
99 } else {
100 // It means it's our video
101 reporterPodHost = constants.CONFIG.WEBSERVER.HOST
102 }
103
104 const json = {
105 id: this.id,
106 reporterPodHost,
107 reason: this.reason,
108 reporterUsername: this.reporterUsername,
109 videoId: this.videoId
110 }
111
112 return json
113}
diff --git a/server/models/video-tag.js b/server/models/video-tag.js
new file mode 100644
index 000000000..cd9277a6e
--- /dev/null
+++ b/server/models/video-tag.js
@@ -0,0 +1,18 @@
1'use strict'
2
3// ---------------------------------------------------------------------------
4
5module.exports = function (sequelize, DataTypes) {
6 const VideoTag = sequelize.define('VideoTag', {}, {
7 indexes: [
8 {
9 fields: [ 'videoId' ]
10 },
11 {
12 fields: [ 'tagId' ]
13 }
14 ]
15 })
16
17 return VideoTag
18}
diff --git a/server/models/video.js b/server/models/video.js
index 330067cdf..17eff6428 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -1,108 +1,160 @@
1'use strict' 1'use strict'
2 2
3const Buffer = require('safe-buffer').Buffer
3const createTorrent = require('create-torrent') 4const createTorrent = require('create-torrent')
4const ffmpeg = require('fluent-ffmpeg') 5const ffmpeg = require('fluent-ffmpeg')
5const fs = require('fs') 6const fs = require('fs')
6const magnetUtil = require('magnet-uri') 7const magnetUtil = require('magnet-uri')
8const map = require('lodash/map')
7const parallel = require('async/parallel') 9const parallel = require('async/parallel')
8const parseTorrent = require('parse-torrent') 10const parseTorrent = require('parse-torrent')
9const pathUtils = require('path') 11const pathUtils = require('path')
10const mongoose = require('mongoose') 12const values = require('lodash/values')
11 13
12const constants = require('../initializers/constants') 14const constants = require('../initializers/constants')
13const customVideosValidators = require('../helpers/custom-validators').videos
14const logger = require('../helpers/logger') 15const logger = require('../helpers/logger')
16const friends = require('../lib/friends')
15const modelUtils = require('./utils') 17const modelUtils = require('./utils')
18const customVideosValidators = require('../helpers/custom-validators').videos
16 19
17// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
18 21
19// TODO: add indexes on searchable columns 22module.exports = function (sequelize, DataTypes) {
20const VideoSchema = mongoose.Schema({ 23 const Video = sequelize.define('Video',
21 name: String, 24 {
22 extname: { 25 id: {
23 type: String, 26 type: DataTypes.UUID,
24 enum: [ '.mp4', '.webm', '.ogv' ] 27 defaultValue: DataTypes.UUIDV4,
25 }, 28 primaryKey: true,
26 remoteId: mongoose.Schema.Types.ObjectId, 29 validate: {
27 description: String, 30 isUUID: 4
28 magnet: { 31 }
29 infoHash: String
30 },
31 podHost: String,
32 author: String,
33 duration: Number,
34 tags: [ String ],
35 createdDate: {
36 type: Date,
37 default: Date.now
38 }
39})
40
41VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
42VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
43VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
44VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
45VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
46VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
47
48VideoSchema.methods = {
49 generateMagnetUri,
50 getVideoFilename,
51 getThumbnailName,
52 getPreviewName,
53 getTorrentName,
54 isOwned,
55 toFormatedJSON,
56 toRemoteJSON
57}
58
59VideoSchema.statics = {
60 generateThumbnailFromBase64,
61 getDurationFromFile,
62 listForApi,
63 listByHostAndRemoteId,
64 listByHost,
65 listOwned,
66 listOwnedByAuthor,
67 listRemotes,
68 load,
69 search
70}
71
72VideoSchema.pre('remove', function (next) {
73 const video = this
74 const tasks = []
75
76 tasks.push(
77 function (callback) {
78 removeThumbnail(video, callback)
79 }
80 )
81
82 if (video.isOwned()) {
83 tasks.push(
84 function (callback) {
85 removeFile(video, callback)
86 }, 32 },
87 function (callback) { 33 name: {
88 removeTorrent(video, callback) 34 type: DataTypes.STRING,
35 allowNull: false,
36 validate: {
37 nameValid: function (value) {
38 const res = customVideosValidators.isVideoNameValid(value)
39 if (res === false) throw new Error('Video name is not valid.')
40 }
41 }
89 }, 42 },
90 function (callback) { 43 extname: {
91 removePreview(video, callback) 44 type: DataTypes.ENUM(values(constants.CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)),
45 allowNull: false
46 },
47 remoteId: {
48 type: DataTypes.UUID,
49 allowNull: true,
50 validate: {
51 isUUID: 4
52 }
53 },
54 description: {
55 type: DataTypes.STRING,
56 allowNull: false,
57 validate: {
58 descriptionValid: function (value) {
59 const res = customVideosValidators.isVideoDescriptionValid(value)
60 if (res === false) throw new Error('Video description is not valid.')
61 }
62 }
63 },
64 infoHash: {
65 type: DataTypes.STRING,
66 allowNull: false,
67 validate: {
68 infoHashValid: function (value) {
69 const res = customVideosValidators.isVideoInfoHashValid(value)
70 if (res === false) throw new Error('Video info hash is not valid.')
71 }
72 }
73 },
74 duration: {
75 type: DataTypes.INTEGER,
76 allowNull: false,
77 validate: {
78 durationValid: function (value) {
79 const res = customVideosValidators.isVideoDurationValid(value)
80 if (res === false) throw new Error('Video duration is not valid.')
81 }
82 }
92 } 83 }
93 ) 84 },
85 {
86 indexes: [
87 {
88 fields: [ 'authorId' ]
89 },
90 {
91 fields: [ 'remoteId' ]
92 },
93 {
94 fields: [ 'name' ]
95 },
96 {
97 fields: [ 'createdAt' ]
98 },
99 {
100 fields: [ 'duration' ]
101 },
102 {
103 fields: [ 'infoHash' ]
104 }
105 ],
106 classMethods: {
107 associate,
108
109 generateThumbnailFromData,
110 getDurationFromFile,
111 list,
112 listForApi,
113 listOwnedAndPopulateAuthorAndTags,
114 listOwnedByAuthor,
115 load,
116 loadByHostAndRemoteId,
117 loadAndPopulateAuthor,
118 loadAndPopulateAuthorAndPodAndTags,
119 searchAndPopulateAuthorAndPodAndTags
120 },
121 instanceMethods: {
122 generateMagnetUri,
123 getVideoFilename,
124 getThumbnailName,
125 getPreviewName,
126 getTorrentName,
127 isOwned,
128 toFormatedJSON,
129 toAddRemoteJSON,
130 toUpdateRemoteJSON
131 },
132 hooks: {
133 beforeValidate,
134 beforeCreate,
135 afterDestroy
136 }
137 }
138 )
139
140 return Video
141}
142
143function beforeValidate (video, options, next) {
144 // Put a fake infoHash if it does not exists yet
145 if (video.isOwned() && !video.infoHash) {
146 // 40 hexa length
147 video.infoHash = '0123456789abcdef0123456789abcdef01234567'
94 } 148 }
95 149
96 parallel(tasks, next) 150 return next(null)
97}) 151}
98 152
99VideoSchema.pre('save', function (next) { 153function beforeCreate (video, options, next) {
100 const video = this
101 const tasks = [] 154 const tasks = []
102 155
103 if (video.isOwned()) { 156 if (video.isOwned()) {
104 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) 157 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
105 this.podHost = constants.CONFIG.WEBSERVER.HOST
106 158
107 tasks.push( 159 tasks.push(
108 // TODO: refractoring 160 // TODO: refractoring
@@ -123,9 +175,8 @@ VideoSchema.pre('save', function (next) {
123 if (err) return callback(err) 175 if (err) return callback(err)
124 176
125 const parsedTorrent = parseTorrent(torrent) 177 const parsedTorrent = parseTorrent(torrent)
126 video.magnet.infoHash = parsedTorrent.infoHash 178 video.set('infoHash', parsedTorrent.infoHash)
127 179 video.validate().asCallback(callback)
128 callback(null)
129 }) 180 })
130 }) 181 })
131 }, 182 },
@@ -141,12 +192,72 @@ VideoSchema.pre('save', function (next) {
141 } 192 }
142 193
143 return next() 194 return next()
144}) 195}
196
197function afterDestroy (video, options, next) {
198 const tasks = []
145 199
146mongoose.model('Video', VideoSchema) 200 tasks.push(
201 function (callback) {
202 removeThumbnail(video, callback)
203 }
204 )
205
206 if (video.isOwned()) {
207 tasks.push(
208 function (callback) {
209 removeFile(video, callback)
210 },
211
212 function (callback) {
213 removeTorrent(video, callback)
214 },
215
216 function (callback) {
217 removePreview(video, callback)
218 },
219
220 function (callback) {
221 const params = {
222 remoteId: video.id
223 }
224
225 friends.removeVideoToFriends(params)
226
227 return callback()
228 }
229 )
230 }
231
232 parallel(tasks, next)
233}
147 234
148// ------------------------------ METHODS ------------------------------ 235// ------------------------------ METHODS ------------------------------
149 236
237function associate (models) {
238 this.belongsTo(models.Author, {
239 foreignKey: {
240 name: 'authorId',
241 allowNull: false
242 },
243 onDelete: 'cascade'
244 })
245
246 this.belongsToMany(models.Tag, {
247 foreignKey: 'videoId',
248 through: models.VideoTag,
249 onDelete: 'cascade'
250 })
251
252 this.hasMany(models.VideoAbuse, {
253 foreignKey: {
254 name: 'videoId',
255 allowNull: false
256 },
257 onDelete: 'cascade'
258 })
259}
260
150function generateMagnetUri () { 261function generateMagnetUri () {
151 let baseUrlHttp, baseUrlWs 262 let baseUrlHttp, baseUrlWs
152 263
@@ -154,8 +265,8 @@ function generateMagnetUri () {
154 baseUrlHttp = constants.CONFIG.WEBSERVER.URL 265 baseUrlHttp = constants.CONFIG.WEBSERVER.URL
155 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT 266 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
156 } else { 267 } else {
157 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.podHost 268 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
158 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.podHost 269 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
159 } 270 }
160 271
161 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName() 272 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
@@ -166,7 +277,7 @@ function generateMagnetUri () {
166 xs, 277 xs,
167 announce, 278 announce,
168 urlList, 279 urlList,
169 infoHash: this.magnet.infoHash, 280 infoHash: this.infoHash,
170 name: this.name 281 name: this.name
171 } 282 }
172 283
@@ -174,20 +285,20 @@ function generateMagnetUri () {
174} 285}
175 286
176function getVideoFilename () { 287function getVideoFilename () {
177 if (this.isOwned()) return this._id + this.extname 288 if (this.isOwned()) return this.id + this.extname
178 289
179 return this.remoteId + this.extname 290 return this.remoteId + this.extname
180} 291}
181 292
182function getThumbnailName () { 293function getThumbnailName () {
183 // We always have a copy of the thumbnail 294 // We always have a copy of the thumbnail
184 return this._id + '.jpg' 295 return this.id + '.jpg'
185} 296}
186 297
187function getPreviewName () { 298function getPreviewName () {
188 const extension = '.jpg' 299 const extension = '.jpg'
189 300
190 if (this.isOwned()) return this._id + extension 301 if (this.isOwned()) return this.id + extension
191 302
192 return this.remoteId + extension 303 return this.remoteId + extension
193} 304}
@@ -195,7 +306,7 @@ function getPreviewName () {
195function getTorrentName () { 306function getTorrentName () {
196 const extension = '.torrent' 307 const extension = '.torrent'
197 308
198 if (this.isOwned()) return this._id + extension 309 if (this.isOwned()) return this.id + extension
199 310
200 return this.remoteId + extension 311 return this.remoteId + extension
201} 312}
@@ -205,27 +316,37 @@ function isOwned () {
205} 316}
206 317
207function toFormatedJSON () { 318function toFormatedJSON () {
319 let podHost
320
321 if (this.Author.Pod) {
322 podHost = this.Author.Pod.host
323 } else {
324 // It means it's our video
325 podHost = constants.CONFIG.WEBSERVER.HOST
326 }
327
208 const json = { 328 const json = {
209 id: this._id, 329 id: this.id,
210 name: this.name, 330 name: this.name,
211 description: this.description, 331 description: this.description,
212 podHost: this.podHost, 332 podHost,
213 isLocal: this.isOwned(), 333 isLocal: this.isOwned(),
214 magnetUri: this.generateMagnetUri(), 334 magnetUri: this.generateMagnetUri(),
215 author: this.author, 335 author: this.Author.name,
216 duration: this.duration, 336 duration: this.duration,
217 tags: this.tags, 337 tags: map(this.Tags, 'name'),
218 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(), 338 thumbnailPath: pathUtils.join(constants.STATIC_PATHS.THUMBNAILS, this.getThumbnailName()),
219 createdDate: this.createdDate 339 createdAt: this.createdAt,
340 updatedAt: this.updatedAt
220 } 341 }
221 342
222 return json 343 return json
223} 344}
224 345
225function toRemoteJSON (callback) { 346function toAddRemoteJSON (callback) {
226 const self = this 347 const self = this
227 348
228 // Convert thumbnail to base64 349 // Get thumbnail data to send to the other pod
229 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) 350 const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
230 fs.readFile(thumbnailPath, function (err, thumbnailData) { 351 fs.readFile(thumbnailPath, function (err, thumbnailData) {
231 if (err) { 352 if (err) {
@@ -236,13 +357,14 @@ function toRemoteJSON (callback) {
236 const remoteVideo = { 357 const remoteVideo = {
237 name: self.name, 358 name: self.name,
238 description: self.description, 359 description: self.description,
239 magnet: self.magnet, 360 infoHash: self.infoHash,
240 remoteId: self._id, 361 remoteId: self.id,
241 author: self.author, 362 author: self.Author.name,
242 duration: self.duration, 363 duration: self.duration,
243 thumbnailBase64: new Buffer(thumbnailData).toString('base64'), 364 thumbnailData: thumbnailData.toString('binary'),
244 tags: self.tags, 365 tags: map(self.Tags, 'name'),
245 createdDate: self.createdDate, 366 createdAt: self.createdAt,
367 updatedAt: self.updatedAt,
246 extname: self.extname 368 extname: self.extname
247 } 369 }
248 370
@@ -250,14 +372,31 @@ function toRemoteJSON (callback) {
250 }) 372 })
251} 373}
252 374
375function toUpdateRemoteJSON (callback) {
376 const json = {
377 name: this.name,
378 description: this.description,
379 infoHash: this.infoHash,
380 remoteId: this.id,
381 author: this.Author.name,
382 duration: this.duration,
383 tags: map(this.Tags, 'name'),
384 createdAt: this.createdAt,
385 updatedAt: this.updatedAt,
386 extname: this.extname
387 }
388
389 return json
390}
391
253// ------------------------------ STATICS ------------------------------ 392// ------------------------------ STATICS ------------------------------
254 393
255function generateThumbnailFromBase64 (video, thumbnailData, callback) { 394function generateThumbnailFromData (video, thumbnailData, callback) {
256 // Creating the thumbnail for a remote video 395 // Creating the thumbnail for a remote video
257 396
258 const thumbnailName = video.getThumbnailName() 397 const thumbnailName = video.getThumbnailName()
259 const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName 398 const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName
260 fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) { 399 fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) {
261 if (err) return callback(err) 400 if (err) return callback(err)
262 401
263 return callback(null, thumbnailName) 402 return callback(null, thumbnailName)
@@ -272,51 +411,186 @@ function getDurationFromFile (videoPath, callback) {
272 }) 411 })
273} 412}
274 413
275function listForApi (start, count, sort, callback) { 414function list (callback) {
276 const query = {} 415 return this.find().asCallback()
277 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback)
278} 416}
279 417
280function listByHostAndRemoteId (fromHost, remoteId, callback) { 418function listForApi (start, count, sort, callback) {
281 this.find({ podHost: fromHost, remoteId: remoteId }, callback) 419 const query = {
420 offset: start,
421 limit: count,
422 distinct: true, // For the count, a video can have many tags
423 order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ],
424 include: [
425 {
426 model: this.sequelize.models.Author,
427 include: [ { model: this.sequelize.models.Pod, required: false } ]
428 },
429
430 this.sequelize.models.Tag
431 ]
432 }
433
434 return this.findAndCountAll(query).asCallback(function (err, result) {
435 if (err) return callback(err)
436
437 return callback(null, result.rows, result.count)
438 })
282} 439}
283 440
284function listByHost (fromHost, callback) { 441function loadByHostAndRemoteId (fromHost, remoteId, callback) {
285 this.find({ podHost: fromHost }, callback) 442 const query = {
443 where: {
444 remoteId: remoteId
445 },
446 include: [
447 {
448 model: this.sequelize.models.Author,
449 include: [
450 {
451 model: this.sequelize.models.Pod,
452 required: true,
453 where: {
454 host: fromHost
455 }
456 }
457 ]
458 }
459 ]
460 }
461
462 return this.findOne(query).asCallback(callback)
286} 463}
287 464
288function listOwned (callback) { 465function listOwnedAndPopulateAuthorAndTags (callback) {
289 // If remoteId is null this is *our* video 466 // If remoteId is null this is *our* video
290 this.find({ remoteId: null }, callback) 467 const query = {
468 where: {
469 remoteId: null
470 },
471 include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
472 }
473
474 return this.findAll(query).asCallback(callback)
291} 475}
292 476
293function listOwnedByAuthor (author, callback) { 477function listOwnedByAuthor (author, callback) {
294 this.find({ remoteId: null, author: author }, callback) 478 const query = {
295} 479 where: {
480 remoteId: null
481 },
482 include: [
483 {
484 model: this.sequelize.models.Author,
485 where: {
486 name: author
487 }
488 }
489 ]
490 }
296 491
297function listRemotes (callback) { 492 return this.findAll(query).asCallback(callback)
298 this.find({ remoteId: { $ne: null } }, callback)
299} 493}
300 494
301function load (id, callback) { 495function load (id, callback) {
302 this.findById(id, callback) 496 return this.findById(id).asCallback(callback)
497}
498
499function loadAndPopulateAuthor (id, callback) {
500 const options = {
501 include: [ this.sequelize.models.Author ]
502 }
503
504 return this.findById(id, options).asCallback(callback)
505}
506
507function loadAndPopulateAuthorAndPodAndTags (id, callback) {
508 const options = {
509 include: [
510 {
511 model: this.sequelize.models.Author,
512 include: [ { model: this.sequelize.models.Pod, required: false } ]
513 },
514 this.sequelize.models.Tag
515 ]
516 }
517
518 return this.findById(id, options).asCallback(callback)
303} 519}
304 520
305function search (value, field, start, count, sort, callback) { 521function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
306 const query = {} 522 const podInclude = {
523 model: this.sequelize.models.Pod,
524 required: false
525 }
526
527 const authorInclude = {
528 model: this.sequelize.models.Author,
529 include: [
530 podInclude
531 ]
532 }
533
534 const tagInclude = {
535 model: this.sequelize.models.Tag
536 }
537
538 const query = {
539 where: {},
540 offset: start,
541 limit: count,
542 distinct: true, // For the count, a video can have many tags
543 order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ]
544 }
545
307 // Make an exact search with the magnet 546 // Make an exact search with the magnet
308 if (field === 'magnetUri') { 547 if (field === 'magnetUri') {
309 const infoHash = magnetUtil.decode(value).infoHash 548 const infoHash = magnetUtil.decode(value).infoHash
310 query.magnet = { 549 query.where.infoHash = infoHash
311 infoHash
312 }
313 } else if (field === 'tags') { 550 } else if (field === 'tags') {
314 query[field] = value 551 const escapedValue = this.sequelize.escape('%' + value + '%')
552 query.where = {
553 id: {
554 $in: this.sequelize.literal(
555 '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
556 )
557 }
558 }
559 } else if (field === 'host') {
560 // FIXME: Include our pod? (not stored in the database)
561 podInclude.where = {
562 host: {
563 $like: '%' + value + '%'
564 }
565 }
566 podInclude.required = true
567 } else if (field === 'author') {
568 authorInclude.where = {
569 name: {
570 $like: '%' + value + '%'
571 }
572 }
573
574 // authorInclude.or = true
315 } else { 575 } else {
316 query[field] = new RegExp(value, 'i') 576 query.where[field] = {
577 $like: '%' + value + '%'
578 }
317 } 579 }
318 580
319 modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 581 query.include = [
582 authorInclude, tagInclude
583 ]
584
585 if (tagInclude.where) {
586 // query.include.push([ this.sequelize.models.Tag ])
587 }
588
589 return this.findAndCountAll(query).asCallback(function (err, result) {
590 if (err) return callback(err)
591
592 return callback(null, result.rows, result.count)
593 })
320} 594}
321 595
322// --------------------------------------------------------------------------- 596// ---------------------------------------------------------------------------
diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js
deleted file mode 100644
index 444c2fc55..000000000
--- a/server/tests/api/check-params.js
+++ /dev/null
@@ -1,764 +0,0 @@
1'use strict'
2
3const chai = require('chai')
4const expect = chai.expect
5const pathUtils = require('path')
6const request = require('supertest')
7const series = require('async/series')
8
9const loginUtils = require('../utils/login')
10const requestsUtils = require('../utils/requests')
11const serversUtils = require('../utils/servers')
12const usersUtils = require('../utils/users')
13
14describe('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 hosts: [ 'localhost:9002' ]
71 }
72
73 it('Should fail without hosts', 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 if hosts is not an array', function (done) {
82 request(server.url)
83 .post(path + '/makefriends')
84 .send({ hosts: '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 hosts', function (done) {
91 request(server.url)
92 .post(path + '/makefriends')
93 .send({ hosts: [ '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 the array is composed with http schemes', function (done) {
100 request(server.url)
101 .post(path + '/makefriends')
102 .send({ hosts: [ 'localhost:9002', 'http://localhost:9003' ] })
103 .set('Authorization', 'Bearer ' + server.accessToken)
104 .set('Accept', 'application/json')
105 .expect(400, done)
106 })
107
108 it('Should fail if hosts are not unique', function (done) {
109 request(server.url)
110 .post(path + '/makefriends')
111 .send({ urls: [ 'localhost:9002', 'localhost:9002' ] })
112 .set('Authorization', 'Bearer ' + server.accessToken)
113 .set('Accept', 'application/json')
114 .expect(400, done)
115 })
116
117 it('Should fail with a invalid token', function (done) {
118 request(server.url)
119 .post(path + '/makefriends')
120 .send(body)
121 .set('Authorization', 'Bearer faketoken')
122 .set('Accept', 'application/json')
123 .expect(401, done)
124 })
125
126 it('Should fail if the user is not an administrator', function (done) {
127 request(server.url)
128 .post(path + '/makefriends')
129 .send(body)
130 .set('Authorization', 'Bearer ' + userAccessToken)
131 .set('Accept', 'application/json')
132 .expect(403, done)
133 })
134 })
135
136 describe('When quitting friends', function () {
137 it('Should fail with a invalid token', function (done) {
138 request(server.url)
139 .get(path + '/quitfriends')
140 .query({ start: 'hello' })
141 .set('Authorization', 'Bearer faketoken')
142 .set('Accept', 'application/json')
143 .expect(401, done)
144 })
145
146 it('Should fail if the user is not an administrator', function (done) {
147 request(server.url)
148 .get(path + '/quitfriends')
149 .query({ start: 'hello' })
150 .set('Authorization', 'Bearer ' + userAccessToken)
151 .set('Accept', 'application/json')
152 .expect(403, done)
153 })
154 })
155 })
156
157 describe('When adding a pod', function () {
158 it('Should fail with nothing', function (done) {
159 const data = {}
160 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
161 })
162
163 it('Should fail without public key', function (done) {
164 const data = {
165 host: 'coucou.com'
166 }
167 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
168 })
169
170 it('Should fail without an host', function (done) {
171 const data = {
172 publicKey: 'mysuperpublickey'
173 }
174 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
175 })
176
177 it('Should fail with an incorrect host', function (done) {
178 const data = {
179 host: 'http://coucou.com',
180 publicKey: 'mysuperpublickey'
181 }
182 requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
183 data.host = 'http://coucou'
184 requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
185 data.host = 'coucou'
186 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
187 })
188 })
189 })
190
191 it('Should succeed with the correct parameters', function (done) {
192 const data = {
193 host: 'coucou.com',
194 publicKey: 'mysuperpublickey'
195 }
196 requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200)
197 })
198 })
199 })
200
201 describe('Of the videos API', function () {
202 const path = '/api/v1/videos/'
203
204 describe('When listing a video', function () {
205 it('Should fail with a bad start pagination', function (done) {
206 request(server.url)
207 .get(path)
208 .query({ start: 'hello' })
209 .set('Accept', 'application/json')
210 .expect(400, done)
211 })
212
213 it('Should fail with a bad count pagination', function (done) {
214 request(server.url)
215 .get(path)
216 .query({ count: 'hello' })
217 .set('Accept', 'application/json')
218 .expect(400, done)
219 })
220
221 it('Should fail with an incorrect sort', function (done) {
222 request(server.url)
223 .get(path)
224 .query({ sort: 'hello' })
225 .set('Accept', 'application/json')
226 .expect(400, done)
227 })
228 })
229
230 describe('When searching a video', function () {
231 it('Should fail with nothing', function (done) {
232 request(server.url)
233 .get(pathUtils.join(path, 'search'))
234 .set('Accept', 'application/json')
235 .expect(400, done)
236 })
237
238 it('Should fail with a bad start pagination', function (done) {
239 request(server.url)
240 .get(pathUtils.join(path, 'search', 'test'))
241 .query({ start: 'hello' })
242 .set('Accept', 'application/json')
243 .expect(400, done)
244 })
245
246 it('Should fail with a bad count pagination', function (done) {
247 request(server.url)
248 .get(pathUtils.join(path, 'search', 'test'))
249 .query({ count: 'hello' })
250 .set('Accept', 'application/json')
251 .expect(400, done)
252 })
253
254 it('Should fail with an incorrect sort', function (done) {
255 request(server.url)
256 .get(pathUtils.join(path, 'search', 'test'))
257 .query({ sort: 'hello' })
258 .set('Accept', 'application/json')
259 .expect(400, done)
260 })
261 })
262
263 describe('When adding a video', function () {
264 it('Should fail with nothing', function (done) {
265 const data = {}
266 const attach = {}
267 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
268 })
269
270 it('Should fail without name', function (done) {
271 const data = {
272 description: 'my super description',
273 tags: [ 'tag1', 'tag2' ]
274 }
275 const attach = {
276 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
277 }
278 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
279 })
280
281 it('Should fail with a long name', function (done) {
282 const data = {
283 name: 'My very very very very very very very very very very very very very very very very long name',
284 description: 'my super description',
285 tags: [ 'tag1', 'tag2' ]
286 }
287 const attach = {
288 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
289 }
290 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
291 })
292
293 it('Should fail without description', function (done) {
294 const data = {
295 name: 'my super name',
296 tags: [ 'tag1', 'tag2' ]
297 }
298 const attach = {
299 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
300 }
301 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
302 })
303
304 it('Should fail with a long description', function (done) {
305 const data = {
306 name: 'my super name',
307 description: 'my super description which is very very very very very very very very very very very very very very' +
308 'very very very very very very very very very very very very very very very very very very very very very' +
309 'very very very very very very very very very very very very very very very long',
310 tags: [ 'tag1', 'tag2' ]
311 }
312 const attach = {
313 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
314 }
315 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
316 })
317
318 it('Should fail without tags', function (done) {
319 const data = {
320 name: 'my super name',
321 description: 'my super description'
322 }
323 const attach = {
324 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
325 }
326 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
327 })
328
329 it('Should fail with too many tags', function (done) {
330 const data = {
331 name: 'my super name',
332 description: 'my super description',
333 tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ]
334 }
335 const attach = {
336 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
337 }
338 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
339 })
340
341 it('Should fail with not enough tags', function (done) {
342 const data = {
343 name: 'my super name',
344 description: 'my super description',
345 tags: [ ]
346 }
347 const attach = {
348 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
349 }
350 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
351 })
352
353 it('Should fail with a tag length too low', function (done) {
354 const data = {
355 name: 'my super name',
356 description: 'my super description',
357 tags: [ 'tag1', 't' ]
358 }
359 const attach = {
360 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
361 }
362 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
363 })
364
365 it('Should fail with a tag length too big', function (done) {
366 const data = {
367 name: 'my super name',
368 description: 'my super description',
369 tags: [ 'mysupertagtoolong', 'tag1' ]
370 }
371 const attach = {
372 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
373 }
374 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
375 })
376
377 it('Should fail with malformed tags', function (done) {
378 const data = {
379 name: 'my super name',
380 description: 'my super description',
381 tags: [ 'my tag' ]
382 }
383 const attach = {
384 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
385 }
386 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
387 })
388
389 it('Should fail without an input file', function (done) {
390 const data = {
391 name: 'my super name',
392 description: 'my super description',
393 tags: [ 'tag1', 'tag2' ]
394 }
395 const attach = {}
396 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
397 })
398
399 it('Should fail without an incorrect input file', function (done) {
400 const data = {
401 name: 'my super name',
402 description: 'my super description',
403 tags: [ 'tag1', 'tag2' ]
404 }
405 const attach = {
406 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm')
407 }
408 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
409 })
410
411 it('Should fail with a too big duration', function (done) {
412 const data = {
413 name: 'my super name',
414 description: 'my super description',
415 tags: [ 'tag1', 'tag2' ]
416 }
417 const attach = {
418 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm')
419 }
420 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
421 })
422
423 it('Should succeed with the correct parameters', function (done) {
424 const data = {
425 name: 'my super name',
426 description: 'my super description',
427 tags: [ 'tag1', 'tag2' ]
428 }
429 const attach = {
430 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
431 }
432 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
433 attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
434 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
435 attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
436 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204)
437 }, false)
438 }, false)
439 })
440 })
441
442 describe('When getting a video', function () {
443 it('Should return the list of the videos with nothing', function (done) {
444 request(server.url)
445 .get(path)
446 .set('Accept', 'application/json')
447 .expect(200)
448 .expect('Content-Type', /json/)
449 .end(function (err, res) {
450 if (err) throw err
451
452 expect(res.body.data).to.be.an('array')
453 expect(res.body.data.length).to.equal(3)
454
455 done()
456 })
457 })
458
459 it('Should fail without a mongodb id', function (done) {
460 request(server.url)
461 .get(path + 'coucou')
462 .set('Accept', 'application/json')
463 .expect(400, done)
464 })
465
466 it('Should return 404 with an incorrect video', function (done) {
467 request(server.url)
468 .get(path + '123456789012345678901234')
469 .set('Accept', 'application/json')
470 .expect(404, done)
471 })
472
473 it('Should succeed with the correct parameters')
474 })
475
476 describe('When removing a video', function () {
477 it('Should have 404 with nothing', function (done) {
478 request(server.url)
479 .delete(path)
480 .set('Authorization', 'Bearer ' + server.accessToken)
481 .expect(400, done)
482 })
483
484 it('Should fail without a mongodb id', function (done) {
485 request(server.url)
486 .delete(path + 'hello')
487 .set('Authorization', 'Bearer ' + server.accessToken)
488 .expect(400, done)
489 })
490
491 it('Should fail with a video which does not exist', function (done) {
492 request(server.url)
493 .delete(path + '123456789012345678901234')
494 .set('Authorization', 'Bearer ' + server.accessToken)
495 .expect(404, done)
496 })
497
498 it('Should fail with a video of another user')
499
500 it('Should fail with a video of another pod')
501
502 it('Should succeed with the correct parameters')
503 })
504 })
505
506 describe('Of the users API', function () {
507 const path = '/api/v1/users/'
508 let userId = null
509 let rootId = null
510
511 describe('When listing users', function () {
512 it('Should fail with a bad start pagination', function (done) {
513 request(server.url)
514 .get(path)
515 .query({ start: 'hello' })
516 .set('Accept', 'application/json')
517 .expect(400, done)
518 })
519
520 it('Should fail with a bad count pagination', function (done) {
521 request(server.url)
522 .get(path)
523 .query({ count: 'hello' })
524 .set('Accept', 'application/json')
525 .expect(400, done)
526 })
527
528 it('Should fail with an incorrect sort', function (done) {
529 request(server.url)
530 .get(path)
531 .query({ sort: 'hello' })
532 .set('Accept', 'application/json')
533 .expect(400, done)
534 })
535 })
536
537 describe('When adding a new user', function () {
538 it('Should fail with a too small username', function (done) {
539 const data = {
540 username: 'ji',
541 password: 'mysuperpassword'
542 }
543
544 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
545 })
546
547 it('Should fail with a too long username', function (done) {
548 const data = {
549 username: 'mysuperusernamewhichisverylong',
550 password: 'mysuperpassword'
551 }
552
553 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
554 })
555
556 it('Should fail with an incorrect username', function (done) {
557 const data = {
558 username: 'my username',
559 password: 'mysuperpassword'
560 }
561
562 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
563 })
564
565 it('Should fail with a too small password', function (done) {
566 const data = {
567 username: 'myusername',
568 password: 'bla'
569 }
570
571 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
572 })
573
574 it('Should fail with a too long password', function (done) {
575 const data = {
576 username: 'myusername',
577 password: 'my super long password which is very very very very very very very very very very very very very very' +
578 'very very very very very very very very very very very very very very very veryv very very very very' +
579 'very very very very very very very very very very very very very very very very very very very very long'
580 }
581
582 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
583 })
584
585 it('Should fail with an non authenticated user', function (done) {
586 const data = {
587 username: 'myusername',
588 password: 'my super password'
589 }
590
591 requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401)
592 })
593
594 it('Should fail if we add a user with the same username', function (done) {
595 const data = {
596 username: 'user1',
597 password: 'my super password'
598 }
599
600 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409)
601 })
602
603 it('Should succeed with the correct params', function (done) {
604 const data = {
605 username: 'user2',
606 password: 'my super password'
607 }
608
609 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204)
610 })
611
612 it('Should fail with a non admin user', function (done) {
613 server.user = {
614 username: 'user1',
615 password: 'password'
616 }
617
618 loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
619 if (err) throw err
620
621 userAccessToken = accessToken
622
623 const data = {
624 username: 'user3',
625 password: 'my super password'
626 }
627
628 requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403)
629 })
630 })
631 })
632
633 describe('When updating a user', function () {
634 before(function (done) {
635 usersUtils.getUsersList(server.url, function (err, res) {
636 if (err) throw err
637
638 userId = res.body.data[1].id
639 rootId = res.body.data[2].id
640 done()
641 })
642 })
643
644 it('Should fail with a too small password', function (done) {
645 const data = {
646 password: 'bla'
647 }
648
649 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
650 })
651
652 it('Should fail with a too long password', function (done) {
653 const data = {
654 password: 'my super long password which is very very very very very very very very very very very very very very' +
655 'very very very very very very very very very very very very very very very veryv very very very very' +
656 'very very very very very very very very very very very very very very very very very very very very long'
657 }
658
659 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
660 })
661
662 it('Should fail with an non authenticated user', function (done) {
663 const data = {
664 password: 'my super password'
665 }
666
667 requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401)
668 })
669
670 it('Should succeed with the correct params', function (done) {
671 const data = {
672 password: 'my super password'
673 }
674
675 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204)
676 })
677 })
678
679 describe('When getting my information', function () {
680 it('Should fail with a non authenticated user', function (done) {
681 request(server.url)
682 .get(path + 'me')
683 .set('Authorization', 'Bearer faketoken')
684 .set('Accept', 'application/json')
685 .expect(401, done)
686 })
687
688 it('Should success with the correct parameters', function (done) {
689 request(server.url)
690 .get(path + 'me')
691 .set('Authorization', 'Bearer ' + userAccessToken)
692 .set('Accept', 'application/json')
693 .expect(200, done)
694 })
695 })
696
697 describe('When removing an user', function () {
698 it('Should fail with an incorrect id', function (done) {
699 request(server.url)
700 .delete(path + 'bla-bla')
701 .set('Authorization', 'Bearer ' + server.accessToken)
702 .expect(400, done)
703 })
704
705 it('Should fail with the root user', function (done) {
706 request(server.url)
707 .delete(path + rootId)
708 .set('Authorization', 'Bearer ' + server.accessToken)
709 .expect(400, done)
710 })
711
712 it('Should return 404 with a non existing id', function (done) {
713 request(server.url)
714 .delete(path + '579f982228c99c221d8092b8')
715 .set('Authorization', 'Bearer ' + server.accessToken)
716 .expect(404, done)
717 })
718 })
719 })
720
721 describe('Of the remote videos API', function () {
722 describe('When making a secure request', function () {
723 it('Should check a secure request')
724 })
725
726 describe('When adding a video', function () {
727 it('Should check when adding a video')
728 })
729
730 describe('When removing a video', function () {
731 it('Should check when removing a video')
732 })
733 })
734
735 describe('Of the requests API', function () {
736 const path = '/api/v1/requests/stats'
737
738 it('Should fail with an non authenticated user', function (done) {
739 request(server.url)
740 .get(path)
741 .set('Accept', 'application/json')
742 .expect(401, done)
743 })
744
745 it('Should fail with a non admin user', function (done) {
746 request(server.url)
747 .get(path)
748 .set('Authorization', 'Bearer ' + userAccessToken)
749 .set('Accept', 'application/json')
750 .expect(403, done)
751 })
752 })
753
754 after(function (done) {
755 process.kill(-server.app.pid)
756
757 // Keep the logs if the test failed
758 if (this.ok) {
759 serversUtils.flushTests(done)
760 } else {
761 done()
762 }
763 })
764})
diff --git a/server/tests/api/check-params/index.js b/server/tests/api/check-params/index.js
new file mode 100644
index 000000000..d0824f08a
--- /dev/null
+++ b/server/tests/api/check-params/index.js
@@ -0,0 +1,9 @@
1'use strict'
2
3// Order of the tests we want to execute
4require('./pods')
5require('./remotes')
6require('./users')
7require('./requests')
8require('./videos')
9require('./video-abuses')
diff --git a/server/tests/api/check-params/pods.js b/server/tests/api/check-params/pods.js
new file mode 100644
index 000000000..2f85af644
--- /dev/null
+++ b/server/tests/api/check-params/pods.js
@@ -0,0 +1,204 @@
1'use strict'
2
3const request = require('supertest')
4const series = require('async/series')
5
6const loginUtils = require('../../utils/login')
7const requestsUtils = require('../../utils/requests')
8const serversUtils = require('../../utils/servers')
9const usersUtils = require('../../utils/users')
10
11describe('Test pods API validators', function () {
12 const path = '/api/v1/pods/'
13 let server = null
14
15 // ---------------------------------------------------------------
16
17 before(function (done) {
18 this.timeout(20000)
19
20 series([
21 function (next) {
22 serversUtils.flushTests(next)
23 },
24 function (next) {
25 serversUtils.runServer(1, function (server1) {
26 server = server1
27
28 next()
29 })
30 },
31 function (next) {
32 loginUtils.loginAndGetAccessToken(server, function (err, token) {
33 if (err) throw err
34 server.accessToken = token
35
36 next()
37 })
38 }
39 ], done)
40 })
41
42 describe('When making friends', function () {
43 let userAccessToken = null
44
45 before(function (done) {
46 usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () {
47 server.user = {
48 username: 'user1',
49 password: 'password'
50 }
51
52 loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
53 if (err) throw err
54
55 userAccessToken = accessToken
56
57 done()
58 })
59 })
60 })
61
62 describe('When making friends', function () {
63 const body = {
64 hosts: [ 'localhost:9002' ]
65 }
66
67 it('Should fail without hosts', function (done) {
68 request(server.url)
69 .post(path + '/makefriends')
70 .set('Authorization', 'Bearer ' + server.accessToken)
71 .set('Accept', 'application/json')
72 .expect(400, done)
73 })
74
75 it('Should fail if hosts is not an array', function (done) {
76 request(server.url)
77 .post(path + '/makefriends')
78 .send({ hosts: 'localhost:9002' })
79 .set('Authorization', 'Bearer ' + server.accessToken)
80 .set('Accept', 'application/json')
81 .expect(400, done)
82 })
83
84 it('Should fail if the array is not composed by hosts', function (done) {
85 request(server.url)
86 .post(path + '/makefriends')
87 .send({ hosts: [ 'localhost:9002', 'localhost:coucou' ] })
88 .set('Authorization', 'Bearer ' + server.accessToken)
89 .set('Accept', 'application/json')
90 .expect(400, done)
91 })
92
93 it('Should fail if the array is composed with http schemes', function (done) {
94 request(server.url)
95 .post(path + '/makefriends')
96 .send({ hosts: [ 'localhost:9002', 'http://localhost:9003' ] })
97 .set('Authorization', 'Bearer ' + server.accessToken)
98 .set('Accept', 'application/json')
99 .expect(400, done)
100 })
101
102 it('Should fail if hosts are not unique', function (done) {
103 request(server.url)
104 .post(path + '/makefriends')
105 .send({ urls: [ 'localhost:9002', 'localhost:9002' ] })
106 .set('Authorization', 'Bearer ' + server.accessToken)
107 .set('Accept', 'application/json')
108 .expect(400, done)
109 })
110
111 it('Should fail with a invalid token', function (done) {
112 request(server.url)
113 .post(path + '/makefriends')
114 .send(body)
115 .set('Authorization', 'Bearer faketoken')
116 .set('Accept', 'application/json')
117 .expect(401, done)
118 })
119
120 it('Should fail if the user is not an administrator', function (done) {
121 request(server.url)
122 .post(path + '/makefriends')
123 .send(body)
124 .set('Authorization', 'Bearer ' + userAccessToken)
125 .set('Accept', 'application/json')
126 .expect(403, done)
127 })
128 })
129
130 describe('When quitting friends', function () {
131 it('Should fail with a invalid token', function (done) {
132 request(server.url)
133 .get(path + '/quitfriends')
134 .query({ start: 'hello' })
135 .set('Authorization', 'Bearer faketoken')
136 .set('Accept', 'application/json')
137 .expect(401, done)
138 })
139
140 it('Should fail if the user is not an administrator', function (done) {
141 request(server.url)
142 .get(path + '/quitfriends')
143 .query({ start: 'hello' })
144 .set('Authorization', 'Bearer ' + userAccessToken)
145 .set('Accept', 'application/json')
146 .expect(403, done)
147 })
148 })
149 })
150
151 describe('When adding a pod', function () {
152 it('Should fail with nothing', function (done) {
153 const data = {}
154 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
155 })
156
157 it('Should fail without public key', function (done) {
158 const data = {
159 host: 'coucou.com'
160 }
161 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
162 })
163
164 it('Should fail without an host', function (done) {
165 const data = {
166 publicKey: 'mysuperpublickey'
167 }
168 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
169 })
170
171 it('Should fail with an incorrect host', function (done) {
172 const data = {
173 host: 'http://coucou.com',
174 publicKey: 'mysuperpublickey'
175 }
176 requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
177 data.host = 'http://coucou'
178 requestsUtils.makePostBodyRequest(server.url, path, null, data, function () {
179 data.host = 'coucou'
180 requestsUtils.makePostBodyRequest(server.url, path, null, data, done)
181 })
182 })
183 })
184
185 it('Should succeed with the correct parameters', function (done) {
186 const data = {
187 host: 'coucou.com',
188 publicKey: 'mysuperpublickey'
189 }
190 requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200)
191 })
192 })
193
194 after(function (done) {
195 process.kill(-server.app.pid)
196
197 // Keep the logs if the test failed
198 if (this.ok) {
199 serversUtils.flushTests(done)
200 } else {
201 done()
202 }
203 })
204})
diff --git a/server/tests/api/check-params/remotes.js b/server/tests/api/check-params/remotes.js
new file mode 100644
index 000000000..c1ab9fb2b
--- /dev/null
+++ b/server/tests/api/check-params/remotes.js
@@ -0,0 +1,64 @@
1'use strict'
2
3const series = require('async/series')
4
5const loginUtils = require('../../utils/login')
6const serversUtils = require('../../utils/servers')
7
8describe('Test remote videos API validators', function () {
9 let server = null
10
11 // ---------------------------------------------------------------
12
13 before(function (done) {
14 this.timeout(20000)
15
16 series([
17 function (next) {
18 serversUtils.flushTests(next)
19 },
20 function (next) {
21 serversUtils.runServer(1, function (server1) {
22 server = server1
23
24 next()
25 })
26 },
27 function (next) {
28 loginUtils.loginAndGetAccessToken(server, function (err, token) {
29 if (err) throw err
30 server.accessToken = token
31
32 next()
33 })
34 }
35 ], done)
36 })
37
38 describe('When making a secure request', function () {
39 it('Should check a secure request')
40 })
41
42 describe('When adding a video', function () {
43 it('Should check when adding a video')
44 })
45
46 describe('When removing a video', function () {
47 it('Should check when removing a video')
48 })
49
50 describe('When reporting abuse on a video', function () {
51 it('Should check when reporting a video abuse')
52 })
53
54 after(function (done) {
55 process.kill(-server.app.pid)
56
57 // Keep the logs if the test failed
58 if (this.ok) {
59 serversUtils.flushTests(done)
60 } else {
61 done()
62 }
63 })
64})
diff --git a/server/tests/api/check-params/requests.js b/server/tests/api/check-params/requests.js
new file mode 100644
index 000000000..08f58db43
--- /dev/null
+++ b/server/tests/api/check-params/requests.js
@@ -0,0 +1,87 @@
1'use strict'
2
3const request = require('supertest')
4const series = require('async/series')
5
6const loginUtils = require('../../utils/login')
7const usersUtils = require('../../utils/users')
8const serversUtils = require('../../utils/servers')
9
10describe('Test requests API validators', function () {
11 const path = '/api/v1/requests/stats'
12 let server = null
13 let userAccessToken = null
14
15 // ---------------------------------------------------------------
16
17 before(function (done) {
18 this.timeout(20000)
19
20 series([
21 function (next) {
22 serversUtils.flushTests(next)
23 },
24 function (next) {
25 serversUtils.runServer(1, function (server1) {
26 server = server1
27
28 next()
29 })
30 },
31 function (next) {
32 loginUtils.loginAndGetAccessToken(server, function (err, token) {
33 if (err) throw err
34 server.accessToken = token
35
36 next()
37 })
38 },
39 function (next) {
40 const username = 'user'
41 const password = 'my super password'
42
43 usersUtils.createUser(server.url, server.accessToken, username, password, next)
44 },
45 function (next) {
46 const user = {
47 username: 'user',
48 password: 'my super password'
49 }
50
51 loginUtils.getUserAccessToken(server, user, function (err, accessToken) {
52 if (err) throw err
53
54 userAccessToken = accessToken
55
56 next()
57 })
58 }
59 ], done)
60 })
61
62 it('Should fail with an non authenticated user', function (done) {
63 request(server.url)
64 .get(path)
65 .set('Accept', 'application/json')
66 .expect(401, done)
67 })
68
69 it('Should fail with a non admin user', function (done) {
70 request(server.url)
71 .get(path)
72 .set('Authorization', 'Bearer ' + userAccessToken)
73 .set('Accept', 'application/json')
74 .expect(403, done)
75 })
76
77 after(function (done) {
78 process.kill(-server.app.pid)
79
80 // Keep the logs if the test failed
81 if (this.ok) {
82 serversUtils.flushTests(done)
83 } else {
84 done()
85 }
86 })
87})
diff --git a/server/tests/api/check-params/users.js b/server/tests/api/check-params/users.js
new file mode 100644
index 000000000..c1fcf34a4
--- /dev/null
+++ b/server/tests/api/check-params/users.js
@@ -0,0 +1,284 @@
1'use strict'
2
3const request = require('supertest')
4const series = require('async/series')
5
6const loginUtils = require('../../utils/login')
7const requestsUtils = require('../../utils/requests')
8const serversUtils = require('../../utils/servers')
9const usersUtils = require('../../utils/users')
10
11describe('Test users API validators', function () {
12 const path = '/api/v1/users/'
13 let userId = null
14 let rootId = null
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 function (next) {
43 const username = 'user1'
44 const password = 'my super password'
45
46 usersUtils.createUser(server.url, server.accessToken, username, password, next)
47 },
48 function (next) {
49 const user = {
50 username: 'user1',
51 password: 'my super password'
52 }
53
54 loginUtils.getUserAccessToken(server, user, function (err, accessToken) {
55 if (err) throw err
56
57 userAccessToken = accessToken
58
59 next()
60 })
61 }
62 ], done)
63 })
64
65 describe('When listing users', function () {
66 it('Should fail with a bad start pagination', function (done) {
67 request(server.url)
68 .get(path)
69 .query({ start: 'hello' })
70 .set('Accept', 'application/json')
71 .expect(400, done)
72 })
73
74 it('Should fail with a bad count pagination', function (done) {
75 request(server.url)
76 .get(path)
77 .query({ count: 'hello' })
78 .set('Accept', 'application/json')
79 .expect(400, done)
80 })
81
82 it('Should fail with an incorrect sort', function (done) {
83 request(server.url)
84 .get(path)
85 .query({ sort: 'hello' })
86 .set('Accept', 'application/json')
87 .expect(400, done)
88 })
89 })
90
91 describe('When adding a new user', function () {
92 it('Should fail with a too small username', function (done) {
93 const data = {
94 username: 'ji',
95 password: 'mysuperpassword'
96 }
97
98 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
99 })
100
101 it('Should fail with a too long username', function (done) {
102 const data = {
103 username: 'mysuperusernamewhichisverylong',
104 password: 'mysuperpassword'
105 }
106
107 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
108 })
109
110 it('Should fail with an incorrect username', function (done) {
111 const data = {
112 username: 'my username',
113 password: 'mysuperpassword'
114 }
115
116 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
117 })
118
119 it('Should fail with a too small password', function (done) {
120 const data = {
121 username: 'myusername',
122 password: 'bla'
123 }
124
125 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
126 })
127
128 it('Should fail with a too long password', function (done) {
129 const data = {
130 username: 'myusername',
131 password: 'my super long password which is very very very very very very very very very very very very very very' +
132 'very very very very very very very very very very very very very very very veryv very very very very' +
133 'very very very very very very very very very very very very very very very very very very very very long'
134 }
135
136 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
137 })
138
139 it('Should fail with an non authenticated user', function (done) {
140 const data = {
141 username: 'myusername',
142 password: 'my super password'
143 }
144
145 requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401)
146 })
147
148 it('Should fail if we add a user with the same username', function (done) {
149 const data = {
150 username: 'user1',
151 password: 'my super password'
152 }
153
154 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409)
155 })
156
157 it('Should succeed with the correct params', function (done) {
158 const data = {
159 username: 'user2',
160 password: 'my super password'
161 }
162
163 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204)
164 })
165
166 it('Should fail with a non admin user', function (done) {
167 server.user = {
168 username: 'user1',
169 password: 'my super password'
170 }
171
172 loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
173 if (err) throw err
174
175 userAccessToken = accessToken
176
177 const data = {
178 username: 'user3',
179 password: 'my super password'
180 }
181
182 requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403)
183 })
184 })
185 })
186
187 describe('When updating a user', function () {
188 before(function (done) {
189 usersUtils.getUsersList(server.url, function (err, res) {
190 if (err) throw err
191
192 userId = res.body.data[1].id
193 rootId = res.body.data[2].id
194 done()
195 })
196 })
197
198 it('Should fail with a too small password', function (done) {
199 const data = {
200 password: 'bla'
201 }
202
203 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
204 })
205
206 it('Should fail with a too long password', function (done) {
207 const data = {
208 password: 'my super long password which is very very very very very very very very very very very very very very' +
209 'very very very very very very very very very very very very very very very veryv very very very very' +
210 'very very very very very very very very very very very very very very very very very very very very long'
211 }
212
213 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done)
214 })
215
216 it('Should fail with an non authenticated user', function (done) {
217 const data = {
218 password: 'my super password'
219 }
220
221 requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401)
222 })
223
224 it('Should succeed with the correct params', function (done) {
225 const data = {
226 password: 'my super password'
227 }
228
229 requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204)
230 })
231 })
232
233 describe('When getting my information', function () {
234 it('Should fail with a non authenticated user', function (done) {
235 request(server.url)
236 .get(path + 'me')
237 .set('Authorization', 'Bearer faketoken')
238 .set('Accept', 'application/json')
239 .expect(401, done)
240 })
241
242 it('Should success with the correct parameters', function (done) {
243 request(server.url)
244 .get(path + 'me')
245 .set('Authorization', 'Bearer ' + userAccessToken)
246 .set('Accept', 'application/json')
247 .expect(200, done)
248 })
249 })
250
251 describe('When removing an user', function () {
252 it('Should fail with an incorrect id', function (done) {
253 request(server.url)
254 .delete(path + 'bla-bla')
255 .set('Authorization', 'Bearer ' + server.accessToken)
256 .expect(400, done)
257 })
258
259 it('Should fail with the root user', function (done) {
260 request(server.url)
261 .delete(path + rootId)
262 .set('Authorization', 'Bearer ' + server.accessToken)
263 .expect(400, done)
264 })
265
266 it('Should return 404 with a non existing id', function (done) {
267 request(server.url)
268 .delete(path + '45')
269 .set('Authorization', 'Bearer ' + server.accessToken)
270 .expect(404, done)
271 })
272 })
273
274 after(function (done) {
275 process.kill(-server.app.pid)
276
277 // Keep the logs if the test failed
278 if (this.ok) {
279 serversUtils.flushTests(done)
280 } else {
281 done()
282 }
283 })
284})
diff --git a/server/tests/api/check-params/video-abuses.js b/server/tests/api/check-params/video-abuses.js
new file mode 100644
index 000000000..8cb4ccdc1
--- /dev/null
+++ b/server/tests/api/check-params/video-abuses.js
@@ -0,0 +1,180 @@
1'use strict'
2
3const request = require('supertest')
4const series = require('async/series')
5
6const loginUtils = require('../../utils/login')
7const requestsUtils = require('../../utils/requests')
8const serversUtils = require('../../utils/servers')
9const usersUtils = require('../../utils/users')
10const videosUtils = require('../../utils/videos')
11
12describe('Test video abuses API validators', function () {
13 let server = null
14 let userAccessToken = null
15
16 // ---------------------------------------------------------------
17
18 before(function (done) {
19 this.timeout(20000)
20
21 series([
22 function (next) {
23 serversUtils.flushTests(next)
24 },
25 function (next) {
26 serversUtils.runServer(1, function (server1) {
27 server = server1
28
29 next()
30 })
31 },
32 function (next) {
33 loginUtils.loginAndGetAccessToken(server, function (err, token) {
34 if (err) throw err
35 server.accessToken = token
36
37 next()
38 })
39 },
40 function (next) {
41 const username = 'user1'
42 const password = 'my super password'
43
44 usersUtils.createUser(server.url, server.accessToken, username, password, next)
45 },
46 function (next) {
47 const user = {
48 username: 'user1',
49 password: 'my super password'
50 }
51
52 loginUtils.getUserAccessToken(server, user, function (err, accessToken) {
53 if (err) throw err
54
55 userAccessToken = accessToken
56
57 next()
58 })
59 },
60 // Upload some videos on each pods
61 function (next) {
62 const name = 'my super name for pod'
63 const description = 'my super description for pod'
64 const tags = [ 'tag' ]
65 const file = 'video_short2.webm'
66 videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, next)
67 },
68 function (next) {
69 videosUtils.getVideosList(server.url, function (err, res) {
70 if (err) throw err
71
72 const videos = res.body.data
73 server.video = videos[0]
74
75 next()
76 })
77 }
78 ], done)
79 })
80
81 describe('When listing video abuses', function () {
82 const path = '/api/v1/videos/abuse'
83
84 it('Should fail with a bad start pagination', function (done) {
85 request(server.url)
86 .get(path)
87 .query({ start: 'hello' })
88 .set('Authorization', 'Bearer ' + server.accessToken)
89 .set('Accept', 'application/json')
90 .expect(400, done)
91 })
92
93 it('Should fail with a bad count pagination', function (done) {
94 request(server.url)
95 .get(path)
96 .query({ count: 'hello' })
97 .set('Accept', 'application/json')
98 .set('Authorization', 'Bearer ' + server.accessToken)
99 .expect(400, done)
100 })
101
102 it('Should fail with an incorrect sort', function (done) {
103 request(server.url)
104 .get(path)
105 .query({ sort: 'hello' })
106 .set('Accept', 'application/json')
107 .set('Authorization', 'Bearer ' + server.accessToken)
108 .expect(400, done)
109 })
110
111 it('Should fail with a non authenticated user', function (done) {
112 request(server.url)
113 .get(path)
114 .query({ sort: 'hello' })
115 .set('Accept', 'application/json')
116 .expect(401, done)
117 })
118
119 it('Should fail with a non admin user', function (done) {
120 request(server.url)
121 .get(path)
122 .query({ sort: 'hello' })
123 .set('Accept', 'application/json')
124 .set('Authorization', 'Bearer ' + userAccessToken)
125 .expect(403, done)
126 })
127 })
128
129 describe('When reporting a video abuse', function () {
130 const basePath = '/api/v1/videos/'
131
132 it('Should fail with nothing', function (done) {
133 const path = basePath + server.video + '/abuse'
134 const data = {}
135 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
136 })
137
138 it('Should fail with a wrong video', function (done) {
139 const wrongPath = '/api/v1/videos/blabla/abuse'
140 const data = {}
141 requestsUtils.makePostBodyRequest(server.url, wrongPath, server.accessToken, data, done)
142 })
143
144 it('Should fail with a non authenticated user', function (done) {
145 const data = {}
146 const path = basePath + server.video + '/abuse'
147 requestsUtils.makePostBodyRequest(server.url, path, 'hello', data, done, 401)
148 })
149
150 it('Should fail with a reason too short', function (done) {
151 const data = {
152 reason: 'h'
153 }
154 const path = basePath + server.video + '/abuse'
155 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
156 })
157
158 it('Should fail with a reason too big', function (done) {
159 const data = {
160 reason: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
161 '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
162 '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' +
163 '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
164 }
165 const path = basePath + server.video + '/abuse'
166 requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done)
167 })
168 })
169
170 after(function (done) {
171 process.kill(-server.app.pid)
172
173 // Keep the logs if the test failed
174 if (this.ok) {
175 serversUtils.flushTests(done)
176 } else {
177 done()
178 }
179 })
180})
diff --git a/server/tests/api/check-params/videos.js b/server/tests/api/check-params/videos.js
new file mode 100644
index 000000000..fac903715
--- /dev/null
+++ b/server/tests/api/check-params/videos.js
@@ -0,0 +1,460 @@
1'use strict'
2
3const chai = require('chai')
4const expect = chai.expect
5const pathUtils = require('path')
6const request = require('supertest')
7const series = require('async/series')
8
9const loginUtils = require('../../utils/login')
10const requestsUtils = require('../../utils/requests')
11const serversUtils = require('../../utils/servers')
12const videosUtils = require('../../utils/videos')
13
14describe('Test videos API validator', function () {
15 const path = '/api/v1/videos/'
16 let server = 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('When listing a video', function () {
46 it('Should fail with a bad start pagination', function (done) {
47 request(server.url)
48 .get(path)
49 .query({ start: 'hello' })
50 .set('Accept', 'application/json')
51 .expect(400, done)
52 })
53
54 it('Should fail with a bad count pagination', function (done) {
55 request(server.url)
56 .get(path)
57 .query({ count: 'hello' })
58 .set('Accept', 'application/json')
59 .expect(400, done)
60 })
61
62 it('Should fail with an incorrect sort', function (done) {
63 request(server.url)
64 .get(path)
65 .query({ sort: 'hello' })
66 .set('Accept', 'application/json')
67 .expect(400, done)
68 })
69 })
70
71 describe('When searching a video', function () {
72 it('Should fail with nothing', function (done) {
73 request(server.url)
74 .get(pathUtils.join(path, 'search'))
75 .set('Accept', 'application/json')
76 .expect(400, done)
77 })
78
79 it('Should fail with a bad start pagination', function (done) {
80 request(server.url)
81 .get(pathUtils.join(path, 'search', 'test'))
82 .query({ start: 'hello' })
83 .set('Accept', 'application/json')
84 .expect(400, done)
85 })
86
87 it('Should fail with a bad count pagination', function (done) {
88 request(server.url)
89 .get(pathUtils.join(path, 'search', 'test'))
90 .query({ count: 'hello' })
91 .set('Accept', 'application/json')
92 .expect(400, done)
93 })
94
95 it('Should fail with an incorrect sort', function (done) {
96 request(server.url)
97 .get(pathUtils.join(path, 'search', 'test'))
98 .query({ sort: 'hello' })
99 .set('Accept', 'application/json')
100 .expect(400, done)
101 })
102 })
103
104 describe('When adding a video', function () {
105 it('Should fail with nothing', function (done) {
106 const data = {}
107 const attach = {}
108 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
109 })
110
111 it('Should fail without name', function (done) {
112 const data = {
113 description: 'my super description',
114 tags: [ 'tag1', 'tag2' ]
115 }
116 const attach = {
117 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
118 }
119 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
120 })
121
122 it('Should fail with a long name', function (done) {
123 const data = {
124 name: 'My very very very very very very very very very very very very very very very very long name',
125 description: 'my super description',
126 tags: [ 'tag1', 'tag2' ]
127 }
128 const attach = {
129 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
130 }
131 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
132 })
133
134 it('Should fail without description', function (done) {
135 const data = {
136 name: 'my super name',
137 tags: [ 'tag1', 'tag2' ]
138 }
139 const attach = {
140 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
141 }
142 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
143 })
144
145 it('Should fail with a long description', function (done) {
146 const data = {
147 name: 'my super name',
148 description: 'my super description which is very very very very very very very very very very very very very very' +
149 'very very very very very very very very very very very very very very very very very very very very very' +
150 'very very very very very very very very very very very very very very very long',
151 tags: [ 'tag1', 'tag2' ]
152 }
153 const attach = {
154 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
155 }
156 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
157 })
158
159 it('Should fail without tags', function (done) {
160 const data = {
161 name: 'my super name',
162 description: 'my super description'
163 }
164 const attach = {
165 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
166 }
167 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
168 })
169
170 it('Should fail with too many tags', function (done) {
171 const data = {
172 name: 'my super name',
173 description: 'my super description',
174 tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ]
175 }
176 const attach = {
177 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
178 }
179 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
180 })
181
182 it('Should fail with not enough tags', function (done) {
183 const data = {
184 name: 'my super name',
185 description: 'my super description',
186 tags: [ ]
187 }
188 const attach = {
189 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
190 }
191 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
192 })
193
194 it('Should fail with a tag length too low', function (done) {
195 const data = {
196 name: 'my super name',
197 description: 'my super description',
198 tags: [ 'tag1', 't' ]
199 }
200 const attach = {
201 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
202 }
203 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
204 })
205
206 it('Should fail with a tag length too big', function (done) {
207 const data = {
208 name: 'my super name',
209 description: 'my super description',
210 tags: [ 'mysupertagtoolong', 'tag1' ]
211 }
212 const attach = {
213 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
214 }
215 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
216 })
217
218 it('Should fail with malformed tags', function (done) {
219 const data = {
220 name: 'my super name',
221 description: 'my super description',
222 tags: [ 'my tag' ]
223 }
224 const attach = {
225 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
226 }
227 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
228 })
229
230 it('Should fail without an input file', function (done) {
231 const data = {
232 name: 'my super name',
233 description: 'my super description',
234 tags: [ 'tag1', 'tag2' ]
235 }
236 const attach = {}
237 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
238 })
239
240 it('Should fail without an incorrect input file', function (done) {
241 const data = {
242 name: 'my super name',
243 description: 'my super description',
244 tags: [ 'tag1', 'tag2' ]
245 }
246 const attach = {
247 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
248 }
249 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
250 })
251
252 it('Should fail with a too big duration', function (done) {
253 const data = {
254 name: 'my super name',
255 description: 'my super description',
256 tags: [ 'tag1', 'tag2' ]
257 }
258 const attach = {
259 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_too_long.webm')
260 }
261 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done)
262 })
263
264 it('Should succeed with the correct parameters', function (done) {
265 const data = {
266 name: 'my super name',
267 description: 'my super description',
268 tags: [ 'tag1', 'tag2' ]
269 }
270 const attach = {
271 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm')
272 }
273 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
274 attach.videofile = pathUtils.join(__dirname, '..', 'fixtures', 'video_short.mp4')
275 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () {
276 attach.videofile = pathUtils.join(__dirname, '..', 'fixtures', 'video_short.ogv')
277 requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204)
278 }, false)
279 }, false)
280 })
281 })
282
283 describe('When updating a video', function () {
284 let videoId
285
286 before(function (done) {
287 videosUtils.getVideosList(server.url, function (err, res) {
288 if (err) throw err
289
290 videoId = res.body.data[0].id
291
292 return done()
293 })
294 })
295
296 it('Should fail with nothing', function (done) {
297 const data = {}
298 requestsUtils.makePutBodyRequest(server.url, path, server.accessToken, data, done)
299 })
300
301 it('Should fail without a valid uuid', function (done) {
302 const data = {
303 description: 'my super description',
304 tags: [ 'tag1', 'tag2' ]
305 }
306 requestsUtils.makePutBodyRequest(server.url, path + 'blabla', server.accessToken, data, done)
307 })
308
309 it('Should fail with an unknown id', function (done) {
310 const data = {
311 description: 'my super description',
312 tags: [ 'tag1', 'tag2' ]
313 }
314 requestsUtils.makePutBodyRequest(server.url, path + '4da6fde3-88f7-4d16-b119-108df5630b06', server.accessToken, data, done, 404)
315 })
316
317 it('Should fail with a long name', function (done) {
318 const data = {
319 name: 'My very very very very very very very very very very very very very very very very long name',
320 description: 'my super description',
321 tags: [ 'tag1', 'tag2' ]
322 }
323 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
324 })
325
326 it('Should fail with a long description', function (done) {
327 const data = {
328 name: 'my super name',
329 description: 'my super description which is very very very very very very very very very very very very very very' +
330 'very very very very very very very very very very very very very very very very very very very very very' +
331 'very very very very very very very very very very very very very very very long',
332 tags: [ 'tag1', 'tag2' ]
333 }
334 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
335 })
336
337 it('Should fail with too many tags', function (done) {
338 const data = {
339 name: 'my super name',
340 description: 'my super description',
341 tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ]
342 }
343 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
344 })
345
346 it('Should fail with not enough tags', function (done) {
347 const data = {
348 name: 'my super name',
349 description: 'my super description',
350 tags: [ ]
351 }
352 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
353 })
354
355 it('Should fail with a tag length too low', function (done) {
356 const data = {
357 name: 'my super name',
358 description: 'my super description',
359 tags: [ 'tag1', 't' ]
360 }
361 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
362 })
363
364 it('Should fail with a tag length too big', function (done) {
365 const data = {
366 name: 'my super name',
367 description: 'my super description',
368 tags: [ 'mysupertagtoolong', 'tag1' ]
369 }
370 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
371 })
372
373 it('Should fail with malformed tags', function (done) {
374 const data = {
375 name: 'my super name',
376 description: 'my super description',
377 tags: [ 'my tag' ]
378 }
379 requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done)
380 })
381
382 it('Should fail with a video of another user')
383
384 it('Should fail with a video of another pod')
385 })
386
387 describe('When getting a video', function () {
388 it('Should return the list of the videos with nothing', function (done) {
389 request(server.url)
390 .get(path)
391 .set('Accept', 'application/json')
392 .expect(200)
393 .expect('Content-Type', /json/)
394 .end(function (err, res) {
395 if (err) throw err
396
397 expect(res.body.data).to.be.an('array')
398 expect(res.body.data.length).to.equal(3)
399
400 done()
401 })
402 })
403
404 it('Should fail without a correct uuid', function (done) {
405 request(server.url)
406 .get(path + 'coucou')
407 .set('Accept', 'application/json')
408 .expect(400, done)
409 })
410
411 it('Should return 404 with an incorrect video', function (done) {
412 request(server.url)
413 .get(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
414 .set('Accept', 'application/json')
415 .expect(404, done)
416 })
417
418 it('Should succeed with the correct parameters')
419 })
420
421 describe('When removing a video', function () {
422 it('Should have 404 with nothing', function (done) {
423 request(server.url)
424 .delete(path)
425 .set('Authorization', 'Bearer ' + server.accessToken)
426 .expect(400, done)
427 })
428
429 it('Should fail without a correct uuid', function (done) {
430 request(server.url)
431 .delete(path + 'hello')
432 .set('Authorization', 'Bearer ' + server.accessToken)
433 .expect(400, done)
434 })
435
436 it('Should fail with a video which does not exist', function (done) {
437 request(server.url)
438 .delete(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
439 .set('Authorization', 'Bearer ' + server.accessToken)
440 .expect(404, done)
441 })
442
443 it('Should fail with a video of another user')
444
445 it('Should fail with a video of another pod')
446
447 it('Should succeed with the correct parameters')
448 })
449
450 after(function (done) {
451 process.kill(-server.app.pid)
452
453 // Keep the logs if the test failed
454 if (this.ok) {
455 serversUtils.flushTests(done)
456 } else {
457 done()
458 }
459 })
460})
diff --git a/server/tests/api/friends-advanced.js b/server/tests/api/friends-advanced.js
index 0a2d58d82..708138bc9 100644
--- a/server/tests/api/friends-advanced.js
+++ b/server/tests/api/friends-advanced.js
@@ -86,7 +86,7 @@ describe('Test advanced friends', function () {
86 getFriendsList(5, function (err, res) { 86 getFriendsList(5, function (err, res) {
87 if (err) throw err 87 if (err) throw err
88 88
89 expect(res.body.length).to.equal(0) 89 expect(res.body.data.length).to.equal(0)
90 90
91 done() 91 done()
92 }) 92 })
@@ -111,7 +111,7 @@ describe('Test advanced friends', function () {
111 getFriendsList(i, function (err, res) { 111 getFriendsList(i, function (err, res) {
112 if (err) throw err 112 if (err) throw err
113 113
114 expect(res.body.length).to.equal(0) 114 expect(res.body.data.length).to.equal(0)
115 115
116 callback() 116 callback()
117 }) 117 })
@@ -140,7 +140,7 @@ describe('Test advanced friends', function () {
140 getFriendsList(i, function (err, res) { 140 getFriendsList(i, function (err, res) {
141 if (err) throw err 141 if (err) throw err
142 142
143 expect(res.body.length).to.equal(3) 143 expect(res.body.data.length).to.equal(3)
144 144
145 callback() 145 callback()
146 }) 146 })
@@ -182,7 +182,7 @@ describe('Test advanced friends', function () {
182 if (err) throw err 182 if (err) throw err
183 183
184 // Pod 4 didn't know pod 1 and 2 removed it 184 // Pod 4 didn't know pod 1 and 2 removed it
185 expect(res.body.length).to.equal(3) 185 expect(res.body.data.length).to.equal(3)
186 next() 186 next()
187 }) 187 })
188 }, 188 },
@@ -200,7 +200,7 @@ describe('Test advanced friends', function () {
200 if (err) throw err 200 if (err) throw err
201 201
202 // Pod 4 should not be our friend 202 // Pod 4 should not be our friend
203 const result = res.body 203 const result = res.body.data
204 expect(result.length).to.equal(3) 204 expect(result.length).to.equal(3)
205 for (const pod of result) { 205 for (const pod of result) {
206 expect(pod.host).not.equal(servers[3].host) 206 expect(pod.host).not.equal(servers[3].host)
diff --git a/server/tests/api/friends-basic.js b/server/tests/api/friends-basic.js
index a871f9838..6f37ff291 100644
--- a/server/tests/api/friends-basic.js
+++ b/server/tests/api/friends-basic.js
@@ -28,7 +28,7 @@ describe('Test basic friends', function () {
28 podsUtils.getFriendsList(serverToTest.url, function (err, res) { 28 podsUtils.getFriendsList(serverToTest.url, function (err, res) {
29 if (err) throw err 29 if (err) throw err
30 30
31 const result = res.body 31 const result = res.body.data
32 expect(result).to.be.an('array') 32 expect(result).to.be.an('array')
33 expect(result.length).to.equal(2) 33 expect(result.length).to.equal(2)
34 34
@@ -65,7 +65,7 @@ describe('Test basic friends', function () {
65 podsUtils.getFriendsList(server.url, function (err, res) { 65 podsUtils.getFriendsList(server.url, function (err, res) {
66 if (err) throw err 66 if (err) throw err
67 67
68 const result = res.body 68 const result = res.body.data
69 expect(result).to.be.an('array') 69 expect(result).to.be.an('array')
70 expect(result.length).to.equal(0) 70 expect(result.length).to.equal(0)
71 callback() 71 callback()
@@ -90,14 +90,14 @@ describe('Test basic friends', function () {
90 podsUtils.getFriendsList(servers[1].url, function (err, res) { 90 podsUtils.getFriendsList(servers[1].url, function (err, res) {
91 if (err) throw err 91 if (err) throw err
92 92
93 const result = res.body 93 const result = res.body.data
94 expect(result).to.be.an('array') 94 expect(result).to.be.an('array')
95 expect(result.length).to.equal(1) 95 expect(result.length).to.equal(1)
96 96
97 const pod = result[0] 97 const pod = result[0]
98 expect(pod.host).to.equal(servers[2].host) 98 expect(pod.host).to.equal(servers[2].host)
99 expect(pod.score).to.equal(20) 99 expect(pod.score).to.equal(20)
100 expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true 100 expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
101 101
102 next() 102 next()
103 }) 103 })
@@ -107,14 +107,14 @@ describe('Test basic friends', function () {
107 podsUtils.getFriendsList(servers[2].url, function (err, res) { 107 podsUtils.getFriendsList(servers[2].url, function (err, res) {
108 if (err) throw err 108 if (err) throw err
109 109
110 const result = res.body 110 const result = res.body.data
111 expect(result).to.be.an('array') 111 expect(result).to.be.an('array')
112 expect(result.length).to.equal(1) 112 expect(result.length).to.equal(1)
113 113
114 const pod = result[0] 114 const pod = result[0]
115 expect(pod.host).to.equal(servers[1].host) 115 expect(pod.host).to.equal(servers[1].host)
116 expect(pod.score).to.equal(20) 116 expect(pod.score).to.equal(20)
117 expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true 117 expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
118 118
119 next() 119 next()
120 }) 120 })
@@ -154,7 +154,7 @@ describe('Test basic friends', function () {
154 podsUtils.getFriendsList(servers[1].url, function (err, res) { 154 podsUtils.getFriendsList(servers[1].url, function (err, res) {
155 if (err) throw err 155 if (err) throw err
156 156
157 const result = res.body 157 const result = res.body.data
158 expect(result).to.be.an('array') 158 expect(result).to.be.an('array')
159 expect(result.length).to.equal(0) 159 expect(result.length).to.equal(0)
160 160
@@ -167,7 +167,7 @@ describe('Test basic friends', function () {
167 podsUtils.getFriendsList(url, function (err, res) { 167 podsUtils.getFriendsList(url, function (err, res) {
168 if (err) throw err 168 if (err) throw err
169 169
170 const result = res.body 170 const result = res.body.data
171 expect(result).to.be.an('array') 171 expect(result).to.be.an('array')
172 expect(result.length).to.equal(1) 172 expect(result.length).to.equal(1)
173 expect(result[0].host).not.to.be.equal(servers[1].host) 173 expect(result[0].host).not.to.be.equal(servers[1].host)
diff --git a/server/tests/api/multiple-pods.js b/server/tests/api/multiple-pods.js
index be278d7c5..df12ba0e9 100644
--- a/server/tests/api/multiple-pods.js
+++ b/server/tests/api/multiple-pods.js
@@ -4,7 +4,8 @@ const chai = require('chai')
4const each = require('async/each') 4const each = require('async/each')
5const expect = chai.expect 5const expect = chai.expect
6const series = require('async/series') 6const series = require('async/series')
7const webtorrent = new (require('webtorrent'))() 7const WebTorrent = require('webtorrent')
8const webtorrent = new WebTorrent()
8 9
9const loginUtils = require('../utils/login') 10const loginUtils = require('../utils/login')
10const miscsUtils = require('../utils/miscs') 11const miscsUtils = require('../utils/miscs')
@@ -104,7 +105,8 @@ describe('Test multiple pods', function () {
104 expect(video.magnetUri).to.exist 105 expect(video.magnetUri).to.exist
105 expect(video.duration).to.equal(10) 106 expect(video.duration).to.equal(10)
106 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) 107 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
107 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 108 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
109 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
108 expect(video.author).to.equal('root') 110 expect(video.author).to.equal('root')
109 111
110 if (server.url !== 'http://localhost:9001') { 112 if (server.url !== 'http://localhost:9001') {
@@ -166,7 +168,8 @@ describe('Test multiple pods', function () {
166 expect(video.magnetUri).to.exist 168 expect(video.magnetUri).to.exist
167 expect(video.duration).to.equal(5) 169 expect(video.duration).to.equal(5)
168 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) 170 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
169 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 171 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
172 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
170 expect(video.author).to.equal('root') 173 expect(video.author).to.equal('root')
171 174
172 if (server.url !== 'http://localhost:9002') { 175 if (server.url !== 'http://localhost:9002') {
@@ -246,7 +249,8 @@ describe('Test multiple pods', function () {
246 expect(video1.duration).to.equal(5) 249 expect(video1.duration).to.equal(5)
247 expect(video1.tags).to.deep.equal([ 'tag1p3' ]) 250 expect(video1.tags).to.deep.equal([ 'tag1p3' ])
248 expect(video1.author).to.equal('root') 251 expect(video1.author).to.equal('root')
249 expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true 252 expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
253 expect(miscsUtils.dateIsValid(video1.updatedAt)).to.be.true
250 254
251 expect(video2.name).to.equal('my super name for pod 3-2') 255 expect(video2.name).to.equal('my super name for pod 3-2')
252 expect(video2.description).to.equal('my super description for pod 3-2') 256 expect(video2.description).to.equal('my super description for pod 3-2')
@@ -255,7 +259,8 @@ describe('Test multiple pods', function () {
255 expect(video2.duration).to.equal(5) 259 expect(video2.duration).to.equal(5)
256 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) 260 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
257 expect(video2.author).to.equal('root') 261 expect(video2.author).to.equal('root')
258 expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true 262 expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
263 expect(miscsUtils.dateIsValid(video2.updatedAt)).to.be.true
259 264
260 if (server.url !== 'http://localhost:9003') { 265 if (server.url !== 'http://localhost:9003') {
261 expect(video1.isLocal).to.be.false 266 expect(video1.isLocal).to.be.false
@@ -299,8 +304,8 @@ describe('Test multiple pods', function () {
299 if (err) throw err 304 if (err) throw err
300 305
301 const video = res.body.data[0] 306 const video = res.body.data[0]
302 toRemove.push(res.body.data[2].id) 307 toRemove.push(res.body.data[2])
303 toRemove.push(res.body.data[3].id) 308 toRemove.push(res.body.data[3])
304 309
305 webtorrent.add(video.magnetUri, function (torrent) { 310 webtorrent.add(video.magnetUri, function (torrent) {
306 expect(torrent.files).to.exist 311 expect(torrent.files).to.exist
@@ -368,16 +373,68 @@ describe('Test multiple pods', function () {
368 }) 373 })
369 }) 374 })
370 }) 375 })
376 })
377
378 describe('Should manipulate these videos', function () {
379 it('Should update the video 3 by asking pod 3', function (done) {
380 this.timeout(15000)
381
382 const name = 'my super video updated'
383 const description = 'my super description updated'
384 const tags = [ 'tagup1', 'tagup2' ]
385
386 videosUtils.updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, name, description, tags, function (err) {
387 if (err) throw err
388
389 setTimeout(done, 11000)
390 })
391 })
392
393 it('Should have the video 3 updated on each pod', function (done) {
394 this.timeout(200000)
395
396 each(servers, function (server, callback) {
397 // Avoid "duplicate torrent" errors
398 const webtorrent = new WebTorrent()
399
400 videosUtils.getVideosList(server.url, function (err, res) {
401 if (err) throw err
402
403 const videos = res.body.data
404 const videoUpdated = videos.find(function (video) {
405 return video.name === 'my super video updated'
406 })
407
408 expect(!!videoUpdated).to.be.true
409 expect(videoUpdated.description).to.equal('my super description updated')
410 expect(videoUpdated.tags).to.deep.equal([ 'tagup1', 'tagup2' ])
411 expect(miscsUtils.dateIsValid(videoUpdated.updatedAt, 20000)).to.be.true
412
413 videosUtils.testVideoImage(server.url, 'video_short3.webm', videoUpdated.thumbnailPath, function (err, test) {
414 if (err) throw err
415 expect(test).to.equal(true)
416
417 webtorrent.add(videoUpdated.magnetUri, function (torrent) {
418 expect(torrent.files).to.exist
419 expect(torrent.files.length).to.equal(1)
420 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
421
422 callback()
423 })
424 })
425 })
426 }, done)
427 })
371 428
372 it('Should remove the file 3 and 3-2 by asking pod 3', function (done) { 429 it('Should remove the videos 3 and 3-2 by asking pod 3', function (done) {
373 this.timeout(15000) 430 this.timeout(15000)
374 431
375 series([ 432 series([
376 function (next) { 433 function (next) {
377 videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) 434 videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, next)
378 }, 435 },
379 function (next) { 436 function (next) {
380 videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) 437 videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1].id, next)
381 }], 438 }],
382 function (err) { 439 function (err) {
383 if (err) throw err 440 if (err) throw err
@@ -394,11 +451,11 @@ describe('Test multiple pods', function () {
394 const videos = res.body.data 451 const videos = res.body.data
395 expect(videos).to.be.an('array') 452 expect(videos).to.be.an('array')
396 expect(videos.length).to.equal(2) 453 expect(videos.length).to.equal(2)
397 expect(videos[0].id).not.to.equal(videos[1].id) 454 expect(videos[0].name).not.to.equal(videos[1].name)
398 expect(videos[0].id).not.to.equal(toRemove[0]) 455 expect(videos[0].name).not.to.equal(toRemove[0].name)
399 expect(videos[1].id).not.to.equal(toRemove[0]) 456 expect(videos[1].name).not.to.equal(toRemove[0].name)
400 expect(videos[0].id).not.to.equal(toRemove[1]) 457 expect(videos[0].name).not.to.equal(toRemove[1].name)
401 expect(videos[1].id).not.to.equal(toRemove[1]) 458 expect(videos[1].name).not.to.equal(toRemove[1].name)
402 459
403 callback() 460 callback()
404 }) 461 })
diff --git a/server/tests/api/requests.js b/server/tests/api/requests.js
index af36f6e34..933ed29b4 100644
--- a/server/tests/api/requests.js
+++ b/server/tests/api/requests.js
@@ -69,7 +69,7 @@ describe('Test requests stats', function () {
69 }) 69 })
70 }) 70 })
71 71
72 it('Should have the correct request', function (done) { 72 it('Should have the correct total request', function (done) {
73 this.timeout(15000) 73 this.timeout(15000)
74 74
75 const server = servers[0] 75 const server = servers[0]
@@ -79,40 +79,16 @@ describe('Test requests stats', function () {
79 uploadVideo(server, function (err) { 79 uploadVideo(server, function (err) {
80 if (err) throw err 80 if (err) throw err
81 81
82 getRequestsStats(server, function (err, res) { 82 setTimeout(function () {
83 if (err) throw err 83 getRequestsStats(server, function (err, res) {
84 84 if (err) throw err
85 const body = res.body
86 expect(body.requests).to.have.lengthOf(1)
87 85
88 const request = body.requests[0] 86 const body = res.body
89 expect(request.to).to.have.lengthOf(1) 87 expect(body.totalRequests).to.equal(1)
90 expect(request.request.type).to.equal('add')
91 88
92 // Wait one cycle 89 done()
93 setTimeout(done, 10000) 90 })
94 }) 91 }, 1000)
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 }) 92 })
117 }) 93 })
118 94
diff --git a/server/tests/api/single-pod.js b/server/tests/api/single-pod.js
index 65d1a7a65..2db60448f 100644
--- a/server/tests/api/single-pod.js
+++ b/server/tests/api/single-pod.js
@@ -82,7 +82,8 @@ describe('Test a single pod', function () {
82 expect(video.author).to.equal('root') 82 expect(video.author).to.equal('root')
83 expect(video.isLocal).to.be.true 83 expect(video.isLocal).to.be.true
84 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 84 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
85 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 85 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
86 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
86 87
87 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 88 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
88 if (err) throw err 89 if (err) throw err
@@ -116,7 +117,8 @@ describe('Test a single pod', function () {
116 expect(video.author).to.equal('root') 117 expect(video.author).to.equal('root')
117 expect(video.isLocal).to.be.true 118 expect(video.isLocal).to.be.true
118 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 119 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
119 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 120 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
121 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
120 122
121 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 123 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
122 if (err) throw err 124 if (err) throw err
@@ -142,7 +144,8 @@ describe('Test a single pod', function () {
142 expect(video.author).to.equal('root') 144 expect(video.author).to.equal('root')
143 expect(video.isLocal).to.be.true 145 expect(video.isLocal).to.be.true
144 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 146 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
145 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 147 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
148 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
146 149
147 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 150 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
148 if (err) throw err 151 if (err) throw err
@@ -153,31 +156,33 @@ describe('Test a single pod', function () {
153 }) 156 })
154 }) 157 })
155 158
156 it('Should search the video by podHost', function (done) { 159 // Not implemented yet
157 videosUtils.searchVideo(server.url, '9001', 'podHost', function (err, res) { 160 // it('Should search the video by podHost', function (done) {
158 if (err) throw err 161 // videosUtils.searchVideo(server.url, '9001', 'host', function (err, res) {
159 162 // if (err) throw err
160 expect(res.body.total).to.equal(1) 163
161 expect(res.body.data).to.be.an('array') 164 // expect(res.body.total).to.equal(1)
162 expect(res.body.data.length).to.equal(1) 165 // expect(res.body.data).to.be.an('array')
163 166 // expect(res.body.data.length).to.equal(1)
164 const video = res.body.data[0] 167
165 expect(video.name).to.equal('my super name') 168 // const video = res.body.data[0]
166 expect(video.description).to.equal('my super description') 169 // expect(video.name).to.equal('my super name')
167 expect(video.podHost).to.equal('localhost:9001') 170 // expect(video.description).to.equal('my super description')
168 expect(video.author).to.equal('root') 171 // expect(video.podHost).to.equal('localhost:9001')
169 expect(video.isLocal).to.be.true 172 // expect(video.author).to.equal('root')
170 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 173 // expect(video.isLocal).to.be.true
171 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 174 // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
172 175 // expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
173 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 176 // expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
174 if (err) throw err 177
175 expect(test).to.equal(true) 178 // videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
176 179 // if (err) throw err
177 done() 180 // expect(test).to.equal(true)
178 }) 181
179 }) 182 // done()
180 }) 183 // })
184 // })
185 // })
181 186
182 it('Should search the video by tag', function (done) { 187 it('Should search the video by tag', function (done) {
183 videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { 188 videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) {
@@ -194,7 +199,8 @@ describe('Test a single pod', function () {
194 expect(video.author).to.equal('root') 199 expect(video.author).to.equal('root')
195 expect(video.isLocal).to.be.true 200 expect(video.isLocal).to.be.true
196 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 201 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
197 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 202 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
203 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
198 204
199 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 205 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
200 if (err) throw err 206 if (err) throw err
@@ -230,7 +236,7 @@ describe('Test a single pod', function () {
230 }) 236 })
231 237
232 it('Should not find a search by tag', function (done) { 238 it('Should not find a search by tag', function (done) {
233 videosUtils.searchVideo(server.url, 'tag', 'tags', function (err, res) { 239 videosUtils.searchVideo(server.url, 'hello', 'tags', function (err, res) {
234 if (err) throw err 240 if (err) throw err
235 241
236 expect(res.body.total).to.equal(0) 242 expect(res.body.total).to.equal(0)
@@ -332,69 +338,69 @@ describe('Test a single pod', function () {
332 }) 338 })
333 339
334 it('Should list only the two first videos', function (done) { 340 it('Should list only the two first videos', function (done) {
335 videosUtils.getVideosListPagination(server.url, 0, 2, function (err, res) { 341 videosUtils.getVideosListPagination(server.url, 0, 2, 'name', function (err, res) {
336 if (err) throw err 342 if (err) throw err
337 343
338 const videos = res.body.data 344 const videos = res.body.data
339 expect(res.body.total).to.equal(6) 345 expect(res.body.total).to.equal(6)
340 expect(videos.length).to.equal(2) 346 expect(videos.length).to.equal(2)
341 expect(videos[0].name === videosListBase[0].name) 347 expect(videos[0].name).to.equal(videosListBase[0].name)
342 expect(videos[1].name === videosListBase[1].name) 348 expect(videos[1].name).to.equal(videosListBase[1].name)
343 349
344 done() 350 done()
345 }) 351 })
346 }) 352 })
347 353
348 it('Should list only the next three videos', function (done) { 354 it('Should list only the next three videos', function (done) {
349 videosUtils.getVideosListPagination(server.url, 2, 3, function (err, res) { 355 videosUtils.getVideosListPagination(server.url, 2, 3, 'name', function (err, res) {
350 if (err) throw err 356 if (err) throw err
351 357
352 const videos = res.body.data 358 const videos = res.body.data
353 expect(res.body.total).to.equal(6) 359 expect(res.body.total).to.equal(6)
354 expect(videos.length).to.equal(3) 360 expect(videos.length).to.equal(3)
355 expect(videos[0].name === videosListBase[2].name) 361 expect(videos[0].name).to.equal(videosListBase[2].name)
356 expect(videos[1].name === videosListBase[3].name) 362 expect(videos[1].name).to.equal(videosListBase[3].name)
357 expect(videos[2].name === videosListBase[4].name) 363 expect(videos[2].name).to.equal(videosListBase[4].name)
358 364
359 done() 365 done()
360 }) 366 })
361 }) 367 })
362 368
363 it('Should list the last video', function (done) { 369 it('Should list the last video', function (done) {
364 videosUtils.getVideosListPagination(server.url, 5, 6, function (err, res) { 370 videosUtils.getVideosListPagination(server.url, 5, 6, 'name', function (err, res) {
365 if (err) throw err 371 if (err) throw err
366 372
367 const videos = res.body.data 373 const videos = res.body.data
368 expect(res.body.total).to.equal(6) 374 expect(res.body.total).to.equal(6)
369 expect(videos.length).to.equal(1) 375 expect(videos.length).to.equal(1)
370 expect(videos[0].name === videosListBase[5].name) 376 expect(videos[0].name).to.equal(videosListBase[5].name)
371 377
372 done() 378 done()
373 }) 379 })
374 }) 380 })
375 381
376 it('Should search the first video', function (done) { 382 it('Should search the first video', function (done) {
377 videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { 383 videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, 'name', function (err, res) {
378 if (err) throw err 384 if (err) throw err
379 385
380 const videos = res.body.data 386 const videos = res.body.data
381 expect(res.body.total).to.equal(4) 387 expect(res.body.total).to.equal(4)
382 expect(videos.length).to.equal(1) 388 expect(videos.length).to.equal(1)
383 expect(videos[0].name === 'video_short.webm name') 389 expect(videos[0].name).to.equal('video_short1.webm name')
384 390
385 done() 391 done()
386 }) 392 })
387 }) 393 })
388 394
389 it('Should search the last two videos', function (done) { 395 it('Should search the last two videos', function (done) {
390 videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { 396 videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, 'name', function (err, res) {
391 if (err) throw err 397 if (err) throw err
392 398
393 const videos = res.body.data 399 const videos = res.body.data
394 expect(res.body.total).to.equal(4) 400 expect(res.body.total).to.equal(4)
395 expect(videos.length).to.equal(2) 401 expect(videos.length).to.equal(2)
396 expect(videos[0].name === 'video_short2.webm name') 402 expect(videos[0].name).to.equal('video_short3.webm name')
397 expect(videos[1].name === 'video_short3.webm name') 403 expect(videos[1].name).to.equal('video_short.webm name')
398 404
399 done() 405 done()
400 }) 406 })
@@ -424,29 +430,30 @@ describe('Test a single pod', function () {
424 }) 430 })
425 }) 431 })
426 432
427 it('Should search all the 9001 port videos', function (done) { 433 // Not implemented yet
428 videosUtils.searchVideoWithPagination(server.url, '9001', 'podHost', 0, 15, function (err, res) { 434 // it('Should search all the 9001 port videos', function (done) {
429 if (err) throw err 435 // videosUtils.searchVideoWithPagination(server.url, '9001', 'host', 0, 15, function (err, res) {
436 // if (err) throw err
430 437
431 const videos = res.body.data 438 // const videos = res.body.data
432 expect(res.body.total).to.equal(6) 439 // expect(res.body.total).to.equal(6)
433 expect(videos.length).to.equal(6) 440 // expect(videos.length).to.equal(6)
434 441
435 done() 442 // done()
436 }) 443 // })
437 }) 444 // })
438 445
439 it('Should search all the localhost videos', function (done) { 446 // it('Should search all the localhost videos', function (done) {
440 videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podHost', 0, 15, function (err, res) { 447 // videosUtils.searchVideoWithPagination(server.url, 'localhost', 'host', 0, 15, function (err, res) {
441 if (err) throw err 448 // if (err) throw err
442 449
443 const videos = res.body.data 450 // const videos = res.body.data
444 expect(res.body.total).to.equal(6) 451 // expect(res.body.total).to.equal(6)
445 expect(videos.length).to.equal(6) 452 // expect(videos.length).to.equal(6)
446 453
447 done() 454 // done()
448 }) 455 // })
449 }) 456 // })
450 457
451 it('Should search the good magnetUri video', function (done) { 458 it('Should search the good magnetUri video', function (done) {
452 const video = videosListBase[0] 459 const video = videosListBase[0]
@@ -469,12 +476,12 @@ describe('Test a single pod', function () {
469 const videos = res.body.data 476 const videos = res.body.data
470 expect(res.body.total).to.equal(6) 477 expect(res.body.total).to.equal(6)
471 expect(videos.length).to.equal(6) 478 expect(videos.length).to.equal(6)
472 expect(videos[5].name === 'video_short.mp4 name') 479 expect(videos[0].name).to.equal('video_short.webm name')
473 expect(videos[4].name === 'video_short.ogv name') 480 expect(videos[1].name).to.equal('video_short.ogv name')
474 expect(videos[3].name === 'video_short.webm name') 481 expect(videos[2].name).to.equal('video_short.mp4 name')
475 expect(videos[2].name === 'video_short1.webm name') 482 expect(videos[3].name).to.equal('video_short3.webm name')
476 expect(videos[1].name === 'video_short2.webm name') 483 expect(videos[4].name).to.equal('video_short2.webm name')
477 expect(videos[0].name === 'video_short3.webm name') 484 expect(videos[5].name).to.equal('video_short1.webm name')
478 485
479 done() 486 done()
480 }) 487 })
@@ -488,15 +495,107 @@ describe('Test a single pod', function () {
488 expect(res.body.total).to.equal(4) 495 expect(res.body.total).to.equal(4)
489 expect(videos.length).to.equal(4) 496 expect(videos.length).to.equal(4)
490 497
491 expect(videos[0].name === 'video_short.webm name') 498 expect(videos[0].name).to.equal('video_short1.webm name')
492 expect(videos[1].name === 'video_short1.webm name') 499 expect(videos[1].name).to.equal('video_short2.webm name')
493 expect(videos[2].name === 'video_short2.webm name') 500 expect(videos[2].name).to.equal('video_short3.webm name')
494 expect(videos[3].name === 'video_short3.webm name') 501 expect(videos[3].name).to.equal('video_short.webm name')
502
503 videoId = videos[2].id
495 504
496 done() 505 done()
497 }) 506 })
498 }) 507 })
499 508
509 it('Should update a video', function (done) {
510 const name = 'my super video updated'
511 const description = 'my super description updated'
512 const tags = [ 'tagup1', 'tagup2' ]
513
514 videosUtils.updateVideo(server.url, server.accessToken, videoId, name, description, tags, done)
515 })
516
517 it('Should have the video updated', function (done) {
518 this.timeout(60000)
519
520 videosUtils.getVideo(server.url, videoId, function (err, res) {
521 if (err) throw err
522
523 const video = res.body
524
525 expect(video.name).to.equal('my super video updated')
526 expect(video.description).to.equal('my super description updated')
527 expect(video.podHost).to.equal('localhost:9001')
528 expect(video.author).to.equal('root')
529 expect(video.isLocal).to.be.true
530 expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ])
531 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
532 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
533
534 videosUtils.testVideoImage(server.url, 'video_short3.webm', video.thumbnailPath, function (err, test) {
535 if (err) throw err
536 expect(test).to.equal(true)
537
538 webtorrent.add(video.magnetUri, function (torrent) {
539 expect(torrent.files).to.exist
540 expect(torrent.files.length).to.equal(1)
541 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
542
543 done()
544 })
545 })
546 })
547 })
548
549 it('Should update only the tags of a video', function (done) {
550 const tags = [ 'tag1', 'tag2', 'supertag' ]
551
552 videosUtils.updateVideo(server.url, server.accessToken, videoId, null, null, tags, function (err) {
553 if (err) throw err
554
555 videosUtils.getVideo(server.url, videoId, function (err, res) {
556 if (err) throw err
557
558 const video = res.body
559
560 expect(video.name).to.equal('my super video updated')
561 expect(video.description).to.equal('my super description updated')
562 expect(video.podHost).to.equal('localhost:9001')
563 expect(video.author).to.equal('root')
564 expect(video.isLocal).to.be.true
565 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'supertag' ])
566 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
567 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
568
569 done()
570 })
571 })
572 })
573
574 it('Should update only the description of a video', function (done) {
575 const description = 'hello everybody'
576
577 videosUtils.updateVideo(server.url, server.accessToken, videoId, null, description, null, function (err) {
578 if (err) throw err
579
580 videosUtils.getVideo(server.url, videoId, function (err, res) {
581 if (err) throw err
582
583 const video = res.body
584
585 expect(video.name).to.equal('my super video updated')
586 expect(video.description).to.equal('hello everybody')
587 expect(video.podHost).to.equal('localhost:9001')
588 expect(video.author).to.equal('root')
589 expect(video.isLocal).to.be.true
590 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'supertag' ])
591 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
592 expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true
593
594 done()
595 })
596 })
597 })
598
500 after(function (done) { 599 after(function (done) {
501 process.kill(-server.app.pid) 600 process.kill(-server.app.pid)
502 601
diff --git a/server/tests/api/users.js b/server/tests/api/users.js
index 94267f104..e6d937eb0 100644
--- a/server/tests/api/users.js
+++ b/server/tests/api/users.js
@@ -261,8 +261,8 @@ describe('Test users', function () {
261 }) 261 })
262 }) 262 })
263 263
264 it('Should list only the second user by createdDate desc', function (done) { 264 it('Should list only the second user by createdAt desc', function (done) {
265 usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) { 265 usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdAt', function (err, res) {
266 if (err) throw err 266 if (err) throw err
267 267
268 const result = res.body 268 const result = res.body
@@ -279,8 +279,8 @@ describe('Test users', function () {
279 }) 279 })
280 }) 280 })
281 281
282 it('Should list all the users by createdDate asc', function (done) { 282 it('Should list all the users by createdAt asc', function (done) {
283 usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) { 283 usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdAt', function (err, res) {
284 if (err) throw err 284 if (err) throw err
285 285
286 const result = res.body 286 const result = res.body
diff --git a/server/tests/api/video-abuse.js b/server/tests/api/video-abuse.js
new file mode 100644
index 000000000..58db17c42
--- /dev/null
+++ b/server/tests/api/video-abuse.js
@@ -0,0 +1,191 @@
1'use strict'
2
3const chai = require('chai')
4const each = require('async/each')
5const expect = chai.expect
6const series = require('async/series')
7
8const loginUtils = require('../utils/login')
9const podsUtils = require('../utils/pods')
10const serversUtils = require('../utils/servers')
11const videosUtils = require('../utils/videos')
12const videoAbusesUtils = require('../utils/video-abuses')
13
14describe('Test video abuses', function () {
15 let servers = []
16
17 before(function (done) {
18 this.timeout(30000)
19
20 series([
21 // Run servers
22 function (next) {
23 serversUtils.flushAndRunMultipleServers(2, function (serversRun) {
24 servers = serversRun
25 next()
26 })
27 },
28 // Get the access tokens
29 function (next) {
30 each(servers, function (server, callbackEach) {
31 loginUtils.loginAndGetAccessToken(server, function (err, accessToken) {
32 if (err) return callbackEach(err)
33
34 server.accessToken = accessToken
35 callbackEach()
36 })
37 }, next)
38 },
39 // Pod 1 make friends too
40 function (next) {
41 const server = servers[0]
42 podsUtils.makeFriends(server.url, server.accessToken, next)
43 },
44 // Upload some videos on each pods
45 function (next) {
46 const name = 'my super name for pod 1'
47 const description = 'my super description for pod 1'
48 const tags = [ 'tag' ]
49 const file = 'video_short2.webm'
50 videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next)
51 },
52 function (next) {
53 const name = 'my super name for pod 2'
54 const description = 'my super description for pod 2'
55 const tags = [ 'tag' ]
56 const file = 'video_short2.webm'
57 videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next)
58 },
59 // Wait videos propagation
60 function (next) {
61 setTimeout(next, 11000)
62 },
63 function (next) {
64 videosUtils.getVideosList(servers[0].url, function (err, res) {
65 if (err) throw err
66
67 const videos = res.body.data
68
69 expect(videos.length).to.equal(2)
70
71 servers[0].video = videos.find(function (video) { return video.name === 'my super name for pod 1' })
72 servers[1].video = videos.find(function (video) { return video.name === 'my super name for pod 2' })
73
74 next()
75 })
76 }
77 ], done)
78 })
79
80 it('Should not have video abuses', function (done) {
81 videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
82 if (err) throw err
83
84 expect(res.body.total).to.equal(0)
85 expect(res.body.data).to.be.an('array')
86 expect(res.body.data.length).to.equal(0)
87
88 done()
89 })
90 })
91
92 it('Should report abuse on a local video', function (done) {
93 this.timeout(15000)
94
95 const reason = 'my super bad reason'
96 videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[0].video.id, reason, function (err) {
97 if (err) throw err
98
99 // We wait requests propagation, even if the pod 1 is not supposed to make a request to pod 2
100 setTimeout(done, 11000)
101 })
102 })
103
104 it('Should have 1 video abuses on pod 1 and 0 on pod 2', function (done) {
105 videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
106 if (err) throw err
107
108 expect(res.body.total).to.equal(1)
109 expect(res.body.data).to.be.an('array')
110 expect(res.body.data.length).to.equal(1)
111
112 const abuse = res.body.data[0]
113 expect(abuse.reason).to.equal('my super bad reason')
114 expect(abuse.reporterUsername).to.equal('root')
115 expect(abuse.reporterPodHost).to.equal('localhost:9001')
116 expect(abuse.videoId).to.equal(servers[0].video.id)
117
118 videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) {
119 if (err) throw err
120
121 expect(res.body.total).to.equal(0)
122 expect(res.body.data).to.be.an('array')
123 expect(res.body.data.length).to.equal(0)
124
125 done()
126 })
127 })
128 })
129
130 it('Should report abuse on a remote video', function (done) {
131 this.timeout(15000)
132
133 const reason = 'my super bad reason 2'
134 videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[1].video.id, reason, function (err) {
135 if (err) throw err
136
137 // We wait requests propagation
138 setTimeout(done, 11000)
139 })
140 })
141
142 it('Should have 2 video abuse on pod 1 and 1 on pod 2', function (done) {
143 videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) {
144 if (err) throw err
145
146 expect(res.body.total).to.equal(2)
147 expect(res.body.data).to.be.an('array')
148 expect(res.body.data.length).to.equal(2)
149
150 let abuse = res.body.data[0]
151 expect(abuse.reason).to.equal('my super bad reason')
152 expect(abuse.reporterUsername).to.equal('root')
153 expect(abuse.reporterPodHost).to.equal('localhost:9001')
154 expect(abuse.videoId).to.equal(servers[0].video.id)
155
156 abuse = res.body.data[1]
157 expect(abuse.reason).to.equal('my super bad reason 2')
158 expect(abuse.reporterUsername).to.equal('root')
159 expect(abuse.reporterPodHost).to.equal('localhost:9001')
160 expect(abuse.videoId).to.equal(servers[1].video.id)
161
162 videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) {
163 if (err) throw err
164
165 expect(res.body.total).to.equal(1)
166 expect(res.body.data).to.be.an('array')
167 expect(res.body.data.length).to.equal(1)
168
169 let abuse = res.body.data[0]
170 expect(abuse.reason).to.equal('my super bad reason 2')
171 expect(abuse.reporterUsername).to.equal('root')
172 expect(abuse.reporterPodHost).to.equal('localhost:9001')
173
174 done()
175 })
176 })
177 })
178
179 after(function (done) {
180 servers.forEach(function (server) {
181 process.kill(-server.app.pid)
182 })
183
184 // Keep the logs if the test failed
185 if (this.ok) {
186 serversUtils.flushTests(done)
187 } else {
188 done()
189 }
190 })
191})
diff --git a/server/tests/real-world/real-world.js b/server/tests/real-world/real-world.js
index dba1970c5..12ab06d6d 100644
--- a/server/tests/real-world/real-world.js
+++ b/server/tests/real-world/real-world.js
@@ -2,6 +2,7 @@
2 2
3const each = require('async/each') 3const each = require('async/each')
4const isEqual = require('lodash/isEqual') 4const isEqual = require('lodash/isEqual')
5const differenceWith = require('lodash/differenceWith')
5const program = require('commander') 6const program = require('commander')
6const series = require('async/series') 7const series = require('async/series')
7 8
@@ -16,32 +17,41 @@ const videosUtils = require('../utils/videos')
16program 17program
17 .option('-c, --create [weight]', 'Weight for creating videos') 18 .option('-c, --create [weight]', 'Weight for creating videos')
18 .option('-r, --remove [weight]', 'Weight for removing videos') 19 .option('-r, --remove [weight]', 'Weight for removing videos')
20 .option('-u, --update [weight]', 'Weight for updating videos')
19 .option('-p, --pods [n]', 'Number of pods to run (3 or 6)', /^3|6$/, 3) 21 .option('-p, --pods [n]', 'Number of pods to run (3 or 6)', /^3|6$/, 3)
20 .option('-a, --action [interval]', 'Interval in ms for an action') 22 .option('-a, --action [interval]', 'Interval in ms for an action')
21 .option('-i, --integrity [interval]', 'Interval in ms for an integrity check') 23 .option('-i, --integrity [interval]', 'Interval in ms for an integrity check')
22 .option('-f, --flush', 'Flush datas on exit') 24 .option('-f, --flush', 'Flush datas on exit')
25 .option('-d, --difference', 'Display difference if integrity is not okay')
23 .parse(process.argv) 26 .parse(process.argv)
24 27
25const createWeight = parseInt(program.create) || 5 28const createWeight = program.create !== undefined ? parseInt(program.create) : 5
26const removeWeight = parseInt(program.remove) || 4 29const removeWeight = program.remove !== undefined ? parseInt(program.remove) : 4
30const updateWeight = program.update !== undefined ? parseInt(program.update) : 4
27const flushAtExit = program.flush || false 31const flushAtExit = program.flush || false
28const actionInterval = parseInt(program.action) || 500 32const actionInterval = program.action !== undefined ? parseInt(program.action) : 500
29let integrityInterval = parseInt(program.integrity) || 60000 33const integrityInterval = program.integrity !== undefined ? parseInt(program.integrity) : 60000
34const displayDiffOnFail = program.integrity || false
30 35
31const numberOfPods = 6 36const numberOfPods = 6
32// Wait requests between pods
33const requestsMaxPerInterval = constants.INTERVAL / actionInterval
34const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT)
35const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.INTERVAL) + 1000
36 37
37integrityInterval += waitForBeforeIntegrityCheck 38// Wait requests between pods
39const baseRequestInterval = integrityInterval < constants.REQUESTS_INTERVAL ? constants.REQUESTS_INTERVAL : integrityInterval
40const requestsMaxPerInterval = baseRequestInterval / actionInterval
41const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT_PER_POD)
42const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.REQUESTS_INTERVAL) - integrityInterval + 1000
38 43
39console.log('Create weight: %d, remove weight: %d.', createWeight, removeWeight) 44console.log('Create weight: %d, update weight: %d, remove weight: %d.', createWeight, updateWeight, removeWeight)
40if (flushAtExit) { 45if (flushAtExit) {
41 console.log('Program will flush data on exit.') 46 console.log('Program will flush data on exit.')
42} else { 47} else {
43 console.log('Program will not flush data on exit.') 48 console.log('Program will not flush data on exit.')
44} 49}
50if (displayDiffOnFail) {
51 console.log('Program will display diff on failure.')
52} else {
53 console.log('Program will not display diff on failure')
54}
45console.log('Interval in ms for each action: %d.', actionInterval) 55console.log('Interval in ms for each action: %d.', actionInterval)
46console.log('Interval in ms for each integrity check: %d.', integrityInterval) 56console.log('Interval in ms for each integrity check: %d.', integrityInterval)
47console.log('Will wait %d ms before an integrity check.', waitForBeforeIntegrityCheck) 57console.log('Will wait %d ms before an integrity check.', waitForBeforeIntegrityCheck)
@@ -63,16 +73,20 @@ runServers(numberOfPods, function (err, servers) {
63 setInterval(function () { 73 setInterval(function () {
64 if (checking === true) return 74 if (checking === true) return
65 75
66 const rand = getRandomInt(0, createWeight + removeWeight) 76 const rand = getRandomInt(0, createWeight + updateWeight + removeWeight)
67 77
68 if (rand < createWeight) { 78 if (rand < createWeight) {
69 upload(servers, getRandomNumServer(servers)) 79 upload(servers, getRandomNumServer(servers))
80 } else if (rand < createWeight + updateWeight) {
81 update(servers, getRandomNumServer(servers))
70 } else { 82 } else {
71 remove(servers, getRandomNumServer(servers)) 83 remove(servers, getRandomNumServer(servers))
72 } 84 }
73 }, actionInterval) 85 }, actionInterval)
74 86
75 setInterval(function () { 87 setInterval(function () {
88 if (checking === true) return
89
76 console.log('Checking integrity...') 90 console.log('Checking integrity...')
77 checking = true 91 checking = true
78 92
@@ -160,9 +174,9 @@ function exitServers (servers, callback) {
160function upload (servers, numServer, callback) { 174function upload (servers, numServer, callback) {
161 if (!callback) callback = function () {} 175 if (!callback) callback = function () {}
162 176
163 const name = 'my super name for pod 1' 177 const name = Date.now() + ' name'
164 const description = 'my super description for pod 1' 178 const description = Date.now() + ' description'
165 const tags = [ 'tag1p1', 'tag2p1' ] 179 const tags = [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ]
166 const file = 'video_short1.webm' 180 const file = 'video_short1.webm'
167 181
168 console.log('Upload video to server ' + numServer) 182 console.log('Upload video to server ' + numServer)
@@ -170,6 +184,26 @@ function upload (servers, numServer, callback) {
170 videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) 184 videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback)
171} 185}
172 186
187function update (servers, numServer, callback) {
188 if (!callback) callback = function () {}
189
190 videosUtils.getVideosList(servers[numServer].url, function (err, res) {
191 if (err) throw err
192
193 const videos = res.body.data.filter(function (video) { return video.isLocal })
194 if (videos.length === 0) return callback()
195
196 const toUpdate = videos[getRandomInt(0, videos.length)].id
197 const name = Date.now() + ' name'
198 const description = Date.now() + ' description'
199 const tags = [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ]
200
201 console.log('Updating video of server ' + numServer)
202
203 videosUtils.updateVideo(servers[numServer].url, servers[numServer].accessToken, toUpdate, name, description, tags, callback)
204 })
205}
206
173function remove (servers, numServer, callback) { 207function remove (servers, numServer, callback) {
174 if (!callback) callback = function () {} 208 if (!callback) callback = function () {}
175 209
@@ -196,6 +230,7 @@ function checkIntegrity (servers, callback) {
196 delete serverVideo.id 230 delete serverVideo.id
197 delete serverVideo.isLocal 231 delete serverVideo.isLocal
198 delete serverVideo.thumbnailPath 232 delete serverVideo.thumbnailPath
233 delete serverVideo.updatedAt
199 } 234 }
200 235
201 videos.push(serverVideos) 236 videos.push(serverVideos)
@@ -205,6 +240,11 @@ function checkIntegrity (servers, callback) {
205 for (const video of videos) { 240 for (const video of videos) {
206 if (!isEqual(video, videos[0])) { 241 if (!isEqual(video, videos[0])) {
207 console.error('Integrity not ok!') 242 console.error('Integrity not ok!')
243
244 if (displayDiffOnFail) {
245 console.log(differenceWith(videos[0], video, isEqual))
246 }
247
208 process.exit(-1) 248 process.exit(-1)
209 } 249 }
210 } 250 }
diff --git a/server/tests/utils/login.js b/server/tests/utils/login.js
index 465564e14..c984c0baf 100644
--- a/server/tests/utils/login.js
+++ b/server/tests/utils/login.js
@@ -4,7 +4,8 @@ const request = require('supertest')
4 4
5const loginUtils = { 5const loginUtils = {
6 login, 6 login,
7 loginAndGetAccessToken 7 loginAndGetAccessToken,
8 getUserAccessToken
8} 9}
9 10
10// ---------------------- Export functions -------------------- 11// ---------------------- Export functions --------------------
@@ -43,6 +44,14 @@ function loginAndGetAccessToken (server, callback) {
43 }) 44 })
44} 45}
45 46
47function getUserAccessToken (server, user, callback) {
48 login(server.url, server.client, user, 200, function (err, res) {
49 if (err) return callback(err)
50
51 return callback(null, res.body.access_token)
52 })
53}
54
46// --------------------------------------------------------------------------- 55// ---------------------------------------------------------------------------
47 56
48module.exports = loginUtils 57module.exports = loginUtils
diff --git a/server/tests/utils/miscs.js b/server/tests/utils/miscs.js
index 4ceff65df..c4b661496 100644
--- a/server/tests/utils/miscs.js
+++ b/server/tests/utils/miscs.js
@@ -6,12 +6,14 @@ const miscsUtils = {
6 6
7// ---------------------- Export functions -------------------- 7// ---------------------- Export functions --------------------
8 8
9function dateIsValid (dateString) { 9function dateIsValid (dateString, interval) {
10 const dateToCheck = new Date(dateString) 10 const dateToCheck = new Date(dateString)
11 const now = new Date() 11 const now = new Date()
12 12
13 // Check if the interval is more than 2 minutes 13 // Check if the interval is more than 2 minutes
14 if (now - dateToCheck > 120000) return false 14 if (!interval) interval = 120000
15
16 if (now - dateToCheck > interval) return false
15 17
16 return true 18 return true
17} 19}
diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js
index 01c9a2f39..1946ef49a 100644
--- a/server/tests/utils/servers.js
+++ b/server/tests/utils/servers.js
@@ -34,7 +34,7 @@ function flushAndRunMultipleServers (totalServers, serversRun) {
34 runServer(j, function (app, url) { 34 runServer(j, function (app, url) {
35 anotherServerDone(j, app, url) 35 anotherServerDone(j, app, url)
36 }) 36 })
37 }, 1000 * j) 37 }, 1000 * (j - 1))
38 } 38 }
39 }) 39 })
40} 40}
@@ -60,12 +60,12 @@ function runServer (number, callback) {
60 60
61 // These actions are async so we need to be sure that they have both been done 61 // These actions are async so we need to be sure that they have both been done
62 const serverRunString = { 62 const serverRunString = {
63 'Connected to mongodb': false, 63 'Database is ready': false,
64 'Server listening on port': false 64 'Server listening on port': false
65 } 65 }
66 66
67 const regexps = { 67 const regexps = {
68 client_id: 'Client id: ([a-f0-9]+)', 68 client_id: 'Client id: (.+)',
69 client_secret: 'Client secret: (.+)', 69 client_secret: 'Client secret: (.+)',
70 user_username: 'Username: (.+)', 70 user_username: 'Username: (.+)',
71 user_password: 'User password: (.+)' 71 user_password: 'User password: (.+)'
@@ -103,7 +103,7 @@ function runServer (number, callback) {
103 if (serverRunString[key] === false) dontContinue = true 103 if (serverRunString[key] === false) dontContinue = true
104 } 104 }
105 105
106 // If no, there is maybe one thing not already initialized (mongodb...) 106 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
107 if (dontContinue === true) return 107 if (dontContinue === true) return
108 108
109 server.app.stdout.removeListener('data', onStdout) 109 server.app.stdout.removeListener('data', onStdout)
diff --git a/server/tests/utils/video-abuses.js b/server/tests/utils/video-abuses.js
new file mode 100644
index 000000000..596c824b3
--- /dev/null
+++ b/server/tests/utils/video-abuses.js
@@ -0,0 +1,73 @@
1'use strict'
2
3const request = require('supertest')
4
5const videosUtils = {
6 getVideoAbusesList,
7 getVideoAbusesListPagination,
8 getVideoAbusesListSort,
9 reportVideoAbuse
10}
11
12// ---------------------- Export functions --------------------
13
14function reportVideoAbuse (url, token, videoId, reason, specialStatus, end) {
15 if (!end) {
16 end = specialStatus
17 specialStatus = 204
18 }
19
20 const path = '/api/v1/videos/' + videoId + '/abuse'
21
22 request(url)
23 .post(path)
24 .set('Accept', 'application/json')
25 .set('Authorization', 'Bearer ' + token)
26 .send({ reason })
27 .expect(specialStatus)
28 .end(end)
29}
30
31function getVideoAbusesList (url, token, end) {
32 const path = '/api/v1/videos/abuse'
33
34 request(url)
35 .get(path)
36 .query({ sort: 'createdAt' })
37 .set('Accept', 'application/json')
38 .set('Authorization', 'Bearer ' + token)
39 .expect(200)
40 .expect('Content-Type', /json/)
41 .end(end)
42}
43
44function getVideoAbusesListPagination (url, token, start, count, end) {
45 const path = '/api/v1/videos/abuse'
46
47 request(url)
48 .get(path)
49 .query({ start: start })
50 .query({ count: count })
51 .set('Accept', 'application/json')
52 .set('Authorization', 'Bearer ' + token)
53 .expect(200)
54 .expect('Content-Type', /json/)
55 .end(end)
56}
57
58function getVideoAbusesListSort (url, token, sort, end) {
59 const path = '/api/v1/videos/abuse'
60
61 request(url)
62 .get(path)
63 .query({ sort: sort })
64 .set('Accept', 'application/json')
65 .set('Authorization', 'Bearer ' + token)
66 .expect(200)
67 .expect('Content-Type', /json/)
68 .end(end)
69}
70
71// ---------------------------------------------------------------------------
72
73module.exports = videosUtils
diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js
index 536093db1..f94368437 100644
--- a/server/tests/utils/videos.js
+++ b/server/tests/utils/videos.js
@@ -15,7 +15,8 @@ const videosUtils = {
15 searchVideoWithPagination, 15 searchVideoWithPagination,
16 searchVideoWithSort, 16 searchVideoWithSort,
17 testVideoImage, 17 testVideoImage,
18 uploadVideo 18 uploadVideo,
19 updateVideo
19} 20}
20 21
21// ---------------------- Export functions -------------------- 22// ---------------------- Export functions --------------------
@@ -25,7 +26,7 @@ function getAllVideosListBy (url, end) {
25 26
26 request(url) 27 request(url)
27 .get(path) 28 .get(path)
28 .query({ sort: 'createdDate' }) 29 .query({ sort: 'createdAt' })
29 .query({ start: 0 }) 30 .query({ start: 0 })
30 .query({ count: 10000 }) 31 .query({ count: 10000 })
31 .set('Accept', 'application/json') 32 .set('Accept', 'application/json')
@@ -57,17 +58,25 @@ function getVideosList (url, end) {
57 .end(end) 58 .end(end)
58} 59}
59 60
60function getVideosListPagination (url, start, count, end) { 61function getVideosListPagination (url, start, count, sort, end) {
62 if (!end) {
63 end = sort
64 sort = null
65 }
66
61 const path = '/api/v1/videos' 67 const path = '/api/v1/videos'
62 68
63 request(url) 69 const req = request(url)
64 .get(path) 70 .get(path)
65 .query({ start: start }) 71 .query({ start: start })
66 .query({ count: count }) 72 .query({ count: count })
67 .set('Accept', 'application/json') 73
68 .expect(200) 74 if (sort) req.query({ sort })
69 .expect('Content-Type', /json/) 75
70 .end(end) 76 req.set('Accept', 'application/json')
77 .expect(200)
78 .expect('Content-Type', /json/)
79 .end(end)
71} 80}
72 81
73function getVideosListSort (url, sort, end) { 82function getVideosListSort (url, sort, end) {
@@ -115,18 +124,26 @@ function searchVideo (url, search, field, end) {
115 .end(end) 124 .end(end)
116} 125}
117 126
118function searchVideoWithPagination (url, search, field, start, count, end) { 127function searchVideoWithPagination (url, search, field, start, count, sort, end) {
128 if (!end) {
129 end = sort
130 sort = null
131 }
132
119 const path = '/api/v1/videos' 133 const path = '/api/v1/videos'
120 134
121 request(url) 135 const req = request(url)
122 .get(path + '/search/' + search) 136 .get(path + '/search/' + search)
123 .query({ start: start }) 137 .query({ start: start })
124 .query({ count: count }) 138 .query({ count: count })
125 .query({ field: field }) 139 .query({ field: field })
126 .set('Accept', 'application/json') 140
127 .expect(200) 141 if (sort) req.query({ sort })
128 .expect('Content-Type', /json/) 142
129 .end(end) 143 req.set('Accept', 'application/json')
144 .expect(200)
145 .expect('Content-Type', /json/)
146 .end(end)
130} 147}
131 148
132function searchVideoWithSort (url, search, sort, end) { 149function searchVideoWithSort (url, search, sort, end) {
@@ -194,6 +211,31 @@ function uploadVideo (url, accessToken, name, description, tags, fixture, specia
194 .end(end) 211 .end(end)
195} 212}
196 213
214function updateVideo (url, accessToken, id, name, description, tags, specialStatus, end) {
215 if (!end) {
216 end = specialStatus
217 specialStatus = 204
218 }
219
220 const path = '/api/v1/videos/' + id
221
222 const req = request(url)
223 .put(path)
224 .set('Accept', 'application/json')
225 .set('Authorization', 'Bearer ' + accessToken)
226
227 if (name) req.field('name', name)
228 if (description) req.field('description', description)
229
230 if (tags) {
231 for (let i = 0; i < tags.length; i++) {
232 req.field('tags[' + i + ']', tags[i])
233 }
234 }
235
236 req.expect(specialStatus).end(end)
237}
238
197// --------------------------------------------------------------------------- 239// ---------------------------------------------------------------------------
198 240
199module.exports = videosUtils 241module.exports = videosUtils
diff --git a/support/doc/client/code.md b/support/doc/client/code.md
index 3c2029847..f629af32f 100644
--- a/support/doc/client/code.md
+++ b/support/doc/client/code.md
@@ -54,7 +54,7 @@ Uses [TSLint](https://palantir.github.io/tslint/) for TypeScript linting and [An
54 54
55 * Install [the dependencies](https://github.com/Chocobozzz/PeerTube#dependencies) 55 * Install [the dependencies](https://github.com/Chocobozzz/PeerTube#dependencies)
56 * Run `npm install` at the root directory to install all the dependencies 56 * Run `npm install` at the root directory to install all the dependencies
57 * Run MongoDB 57 * Run PostgreSQL and create the database `peertube_dev`.
58 * Run `npm run dev` to compile the client and automatically run the server. Then the server will watch and compile the client files automatically. You just need to refresh the browser to see your modifications. 58 * Run `npm run dev` to compile the client and automatically run the server. Then the server will watch and compile the client files automatically. You just need to refresh the browser to see your modifications.
59 59
60In a Angular 2 application, we create components that we put together. Each component is defined by an HTML structure, a TypeScript file and optionnaly a SASS file. 60In a Angular 2 application, we create components that we put together. Each component is defined by an HTML structure, a TypeScript file and optionnaly a SASS file.
diff --git a/support/doc/server/code.md b/support/doc/server/code.md
index 652bd53df..c15885c8c 100644
--- a/support/doc/server/code.md
+++ b/support/doc/server/code.md
@@ -6,9 +6,9 @@ The server is a web server developed with [NodeJS](https://nodejs.org)/[Express]
6## Technologies 6## Technologies
7 7
8 * [NodeJS](https://nodejs.org) -> Language 8 * [NodeJS](https://nodejs.org) -> Language
9 * [MongoDB](https://www.mongodb.com/) -> Database 9 * [PostgreSQL](https://www.postgresql.org/) -> Database
10 * [Express](http://expressjs.com) -> Web server framework 10 * [Express](http://expressjs.com) -> Web server framework
11 * [Mongoose](http://mongoosejs.com/) -> MongoDB object modeling 11 * [Sequelize](http://docs.sequelizejs.com/en/v3/) -> SQL ORM
12 * [WebTorrent](https://webtorrent.io/) -> BitTorrent tracker and torrent creation 12 * [WebTorrent](https://webtorrent.io/) -> BitTorrent tracker and torrent creation
13 * [Mocha](https://mochajs.org/) -> Test framework 13 * [Mocha](https://mochajs.org/) -> Test framework
14 14
@@ -28,7 +28,7 @@ All other server files are in the [server](https://github.com/Chocobozzz/PeerTub
28 |__ initializers -> functions used at the server startup (installer, database, constants...) 28 |__ initializers -> functions used at the server startup (installer, database, constants...)
29 |__ lib -> library function (WebTorrent, OAuth2, friends logic...) 29 |__ lib -> library function (WebTorrent, OAuth2, friends logic...)
30 |__ middlewares -> middlewares for controllers (requests validators, requests pagination...) 30 |__ middlewares -> middlewares for controllers (requests validators, requests pagination...)
31 |__ models -> Mongoose models for each MongoDB collection (videos, users, pods...) 31 |__ models -> Sequelize models for each SQL tables (videos, users, pods...)
32 |__ tests -> API tests and real world simulations (to test the decentralized feature...) 32 |__ tests -> API tests and real world simulations (to test the decentralized feature...)
33 33
34 34
@@ -41,7 +41,7 @@ Uses [JavaScript Standard Style](http://standardjs.com/).
41 41
42 * Install [the dependencies](https://github.com/Chocobozzz/PeerTube#dependencies) 42 * Install [the dependencies](https://github.com/Chocobozzz/PeerTube#dependencies)
43 * Run `npm install` at the root directory to install all the dependencies 43 * Run `npm install` at the root directory to install all the dependencies
44 * Run MongoDB 44 * Run PostgreSQL and create the database `peertube_dev`.
45 * Run `npm run dev` to compile the client and automatically run the server. If the client files are already compiled you can simply run `NODE_ENV=test node server` 45 * Run `npm run dev` to compile the client and automatically run the server. If the client files are already compiled you can simply run `NODE_ENV=test node server`
46 46
47The `NODE_ENV=test` is set to speed up communications between pods (see [constants.js](https://github.com/Chocobozzz/PeerTube/blob/master/server/initializers/constants.js)). 47The `NODE_ENV=test` is set to speed up communications between pods (see [constants.js](https://github.com/Chocobozzz/PeerTube/blob/master/server/initializers/constants.js)).
@@ -62,7 +62,7 @@ A video is seeded by the server throught the [WebSeed](http://www.bittorrent.org
62 62
63![Architecture scheme](https://github.com/Chocobozzz/PeerTube/blob/master/support/doc/server/upload-video.png) 63![Architecture scheme](https://github.com/Chocobozzz/PeerTube/blob/master/support/doc/server/upload-video.png)
64 64
65When a user uploads a video, the rest API create the torrent file and then adds it to its Mongo database. 65When a user uploads a video, the rest API create the torrent file and then adds it to its database.
66 66
67If a user wants to watch the video, the tracker will indicate all other users that are watching the video + the HTTP url for the WebSeed. 67If a user wants to watch the video, the tracker will indicate all other users that are watching the video + the HTTP url for the WebSeed.
68 68