diff options
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 @@ | |||
1 | language: node_js | 1 | language: node_js |
2 | 2 | ||
3 | node_js: | 3 | node_js: |
4 | - "4.6" | 4 | - "4" |
5 | - "6.9" | 5 | - "6" |
6 | 6 | ||
7 | env: | 7 | env: |
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 | ||
17 | sudo: false | 18 | sudo: false |
18 | 19 | ||
19 | services: | 20 | services: |
20 | - mongodb | 21 | - postgresql |
21 | 22 | ||
22 | before_install: if [[ `npm -v` != 3* ]]; then npm i -g npm@3; fi | 23 | before_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 | ||
33 | after_failure: | 41 | after_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) |
@@ -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'; | |||
2 | import { Observable } from 'rxjs/Observable'; | 2 | import { Observable } from 'rxjs/Observable'; |
3 | 3 | ||
4 | import { Friend } from './friend.model'; | 4 | import { Friend } from './friend.model'; |
5 | import { AuthHttp, RestExtractor } from '../../../shared'; | 5 | import { AuthHttp, RestExtractor, ResultList } from '../../../shared'; |
6 | 6 | ||
7 | @Injectable() | 7 | @Injectable() |
8 | export class FriendService { | 8 | export 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'; | |||
5 | import { RouterModule } from '@angular/router'; | 5 | import { RouterModule } from '@angular/router'; |
6 | 6 | ||
7 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; | 7 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; |
8 | import { DropdownModule } from 'ng2-bootstrap/components/dropdown'; | 8 | import { DropdownModule } from 'ng2-bootstrap/dropdown'; |
9 | import { ProgressbarModule } from 'ng2-bootstrap/components/progressbar'; | 9 | import { ProgressbarModule } from 'ng2-bootstrap/progressbar'; |
10 | import { PaginationModule } from 'ng2-bootstrap/components/pagination'; | 10 | import { PaginationModule } from 'ng2-bootstrap/pagination'; |
11 | import { ModalModule } from 'ng2-bootstrap/components/modal'; | 11 | import { ModalModule } from 'ng2-bootstrap/modal'; |
12 | import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; | 12 | import { FileUploadModule } from 'ng2-file-upload/ng2-file-upload'; |
13 | 13 | ||
14 | import { AUTH_HTTP_PROVIDERS } from './auth'; | 14 | import { 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 @@ | |||
1 | export class User { | 1 | export 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 @@ | |||
1 | export type SortField = "name" | "-name" | 1 | export 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 @@ | |||
1 | export class Video { | 1 | export 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 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core'; | 1 | import { Component, Input, ViewChild } from '@angular/core'; |
2 | 2 | ||
3 | import { ModalDirective } from 'ng2-bootstrap/components/modal'; | 3 | import { ModalDirective } from 'ng2-bootstrap/modal'; |
4 | 4 | ||
5 | import { Video } from '../shared'; | 5 | import { 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 @@ | |||
1 | import { Component, Input, ViewChild } from '@angular/core'; | 1 | import { Component, Input, ViewChild } from '@angular/core'; |
2 | 2 | ||
3 | import { ModalDirective } from 'ng2-bootstrap/components/modal'; | 3 | import { ModalDirective } from 'ng2-bootstrap/modal'; |
4 | 4 | ||
5 | import { Video } from '../shared'; | 5 | import { 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'; | |||
29 | import 'ng2-file-upload'; | 29 | import 'ng2-file-upload'; |
30 | import 'video.js'; | 30 | import 'video.js'; |
31 | import 'ng2-meta'; | 31 | import 'ng2-meta'; |
32 | import 'ng2-bootstrap/components/pagination'; | 32 | import 'ng2-bootstrap/pagination'; |
33 | import 'ng2-bootstrap/components/dropdown'; | 33 | import 'ng2-bootstrap/dropdown'; |
34 | import 'ng2-bootstrap/components/progressbar'; | 34 | import 'ng2-bootstrap/progressbar'; |
35 | import 'ng2-bootstrap/components/modal'; | 35 | import '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 | ||
9 | database: | 9 | database: |
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 |
15 | storage: | 17 | storage: |
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 @@ | |||
1 | listen: | ||
2 | port: 9000 | ||
3 | |||
1 | # Correspond to your reverse proxy "listen" configuration | 4 | # Correspond to your reverse proxy "listen" configuration |
2 | webserver: | 5 | webserver: |
3 | https: false | 6 | https: false |
@@ -5,4 +8,17 @@ webserver: | |||
5 | port: 80 | 8 | port: 80 |
6 | 9 | ||
7 | database: | 10 | database: |
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 | ||
18 | storage: | ||
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test1' | 9 | suffix: '_test1' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test2' | 9 | suffix: '_test2' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test3' | 9 | suffix: '_test3' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test4' | 9 | suffix: '_test4' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test5' | 9 | suffix: '_test5' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
8 | database: | 8 | database: |
9 | suffix: '-test6' | 9 | suffix: '_test6' |
10 | 10 | ||
11 | # From the project root directory | 11 | # From the project root directory |
12 | storage: | 12 | storage: |
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 | ||
7 | database: | 7 | database: |
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 | ||
3 | for i in $(seq 1 6); do | 3 | for 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" | ||
6 | done | 7 | done |
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 @@ | |||
1 | const eachSeries = require('async/eachSeries') | ||
1 | const rimraf = require('rimraf') | 2 | const rimraf = require('rimraf') |
2 | const mongoose = require('mongoose') | ||
3 | mongoose.Promise = global.Promise | ||
4 | 3 | ||
5 | const constants = require('../../../server/initializers/constants') | 4 | const constants = require('../../../server/initializers/constants') |
5 | const db = require('../../../server/initializers/database') | ||
6 | 6 | ||
7 | const mongodbUrl = 'mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME | 7 | db.init(true, function () { |
8 | mongoose.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 | ||
15 | const STORAGE = constants.CONFIG.STORAGE | 11 | console.info('Tables of %s deleted.', db.sequelize.config.database) |
16 | Object.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 | ||
3 | read -p "This will remove all directories and Mongo database. Are you sure? " -n 1 -r | 3 | read -p "This will remove all directories and SQL tables. Are you sure? (y/*) " -n 1 -r |
4 | echo | ||
4 | 5 | ||
5 | if [[ "$REPLY" =~ ^[Yy]$ ]]; then | 6 | if [[ "$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 | ||
3 | read -p "This will remove all directories and Mongo database. Are you sure? " -n 1 -r | 3 | read -p "This will remove all directories and SQL tables. Are you sure? (y/*) " -n 1 -r |
4 | echo | ||
4 | 5 | ||
5 | if [[ "$REPLY" =~ ^[Yy]$ ]]; then | 6 | if [[ "$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 | |||
7 | const program = require('commander') | ||
8 | const eachSeries = require('async/eachSeries') | ||
9 | const series = require('async/series') | ||
10 | const waterfall = require('async/waterfall') | ||
11 | const fs = require('fs') | ||
12 | const path = require('path') | ||
13 | const MongoClient = require('mongodb').MongoClient | ||
14 | |||
15 | const constants = require('../server/initializers/constants') | ||
16 | |||
17 | program | ||
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 | |||
23 | if (!program.mongoDatabase) { | ||
24 | console.error('The mongodb database is mandatory.') | ||
25 | process.exit(-1) | ||
26 | } | ||
27 | |||
28 | const mongoUrl = 'mongodb://' + program.mongoHost + ':' + program.mongoPort + '/' + program.mongoDatabase | ||
29 | const dbSequelize = require('../server/initializers/database') | ||
30 | |||
31 | console.log('Connecting to ' + mongoUrl) | ||
32 | MongoClient.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 | |||
78 | function 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 | |||
99 | function 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 |
6 | fi | 6 | fi |
7 | 7 | ||
8 | for i in 1 2 3; do | 8 | max=${1:-3} |
9 | |||
10 | for 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 |
11 | done | 13 | done |
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 | ||
7 | const fs = require('fs') | 7 | const fs = require('fs') |
8 | const mongoose = require('mongoose') | ||
9 | const parseTorrent = require('parse-torrent') | 8 | const parseTorrent = require('parse-torrent') |
10 | 9 | ||
11 | const constants = require('../server/initializers/constants') | 10 | const constants = require('../server/initializers/constants') |
12 | const database = require('../server/initializers/database') | 11 | const db = require('../server/initializers/database') |
13 | |||
14 | database.connect() | ||
15 | 12 | ||
16 | const friends = require('../server/lib/friends') | 13 | const friends = require('../server/lib/friends') |
17 | const Video = mongoose.model('Video') | ||
18 | |||
19 | friends.hasFriends(function (err, hasFriends) { | ||
20 | if (err) throw err | ||
21 | 14 | ||
22 | if (hasFriends === true) { | 15 | db.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) { |
@@ -17,10 +17,10 @@ const app = express() | |||
17 | 17 | ||
18 | // ----------- Database ----------- | 18 | // ----------- Database ----------- |
19 | const constants = require('./server/initializers/constants') | 19 | const constants = require('./server/initializers/constants') |
20 | const database = require('./server/initializers/database') | ||
21 | const logger = require('./server/helpers/logger') | 20 | const logger = require('./server/helpers/logger') |
22 | 21 | // Initialize database and models | |
23 | database.connect() | 22 | const db = require('./server/initializers/database') |
23 | db.init() | ||
24 | 24 | ||
25 | // ----------- Checker ----------- | 25 | // ----------- Checker ----------- |
26 | const checker = require('./server/initializers/checker') | 26 | const checker = require('./server/initializers/checker') |
@@ -39,9 +39,7 @@ if (errorMessage !== null) { | |||
39 | const customValidators = require('./server/helpers/custom-validators') | 39 | const customValidators = require('./server/helpers/custom-validators') |
40 | const installer = require('./server/initializers/installer') | 40 | const installer = require('./server/initializers/installer') |
41 | const migrator = require('./server/initializers/migrator') | 41 | const migrator = require('./server/initializers/migrator') |
42 | const mongoose = require('mongoose') | ||
43 | const routes = require('./server/controllers') | 42 | const routes = require('./server/controllers') |
44 | const 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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | 8 | ||
9 | const Client = mongoose.model('OAuthClient') | ||
10 | |||
11 | const router = express.Router() | 9 | const router = express.Router() |
12 | 10 | ||
13 | router.get('/local', getLocalClient) | 11 | router.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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | 4 | ||
5 | const utils = require('../../helpers/utils') | ||
6 | |||
5 | const router = express.Router() | 7 | const router = express.Router() |
6 | 8 | ||
7 | const clientsController = require('./clients') | 9 | const clientsController = require('./clients') |
@@ -18,7 +20,7 @@ router.use('/requests', requestsController) | |||
18 | router.use('/users', usersController) | 20 | router.use('/users', usersController) |
19 | router.use('/videos', videosController) | 21 | router.use('/videos', videosController) |
20 | router.use('/ping', pong) | 22 | router.use('/ping', pong) |
21 | router.use('/*', badRequest) | 23 | router.use('/*', utils.badRequest) |
22 | 24 | ||
23 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
24 | 26 | ||
@@ -29,7 +31,3 @@ module.exports = router | |||
29 | function pong (req, res, next) { | 31 | function pong (req, res, next) { |
30 | return res.send('pong').status(200).end() | 32 | return res.send('pong').status(200).end() |
31 | } | 33 | } |
32 | |||
33 | function 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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
6 | 5 | ||
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | const utils = require('../../helpers/utils') | ||
8 | const friends = require('../../lib/friends') | 9 | const friends = require('../../lib/friends') |
9 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
10 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
@@ -15,7 +16,6 @@ const validators = middlewares.validators.pods | |||
15 | const signatureValidator = middlewares.validators.remote.signature | 16 | const signatureValidator = middlewares.validators.remote.signature |
16 | 17 | ||
17 | const router = express.Router() | 18 | const router = express.Router() |
18 | const Pod = mongoose.model('Pod') | ||
19 | 19 | ||
20 | router.get('/', listPods) | 20 | router.get('/', listPods) |
21 | router.post('/', | 21 | router.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 |
39 | router.post('/remove', | 39 | router.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 | ||
86 | function listPods (req, res, next) { | 86 | function 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 | |||
137 | function 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 | |||
3 | const each = require('async/each') | ||
4 | const eachSeries = require('async/eachSeries') | ||
5 | const express = require('express') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const middlewares = require('../../middlewares') | ||
9 | const secureMiddleware = middlewares.secure | ||
10 | const validators = middlewares.validators.remote | ||
11 | const logger = require('../../helpers/logger') | ||
12 | |||
13 | const router = express.Router() | ||
14 | const Video = mongoose.model('Video') | ||
15 | |||
16 | router.post('/videos', | ||
17 | validators.signature, | ||
18 | secureMiddleware.checkSignature, | ||
19 | validators.remoteVideos, | ||
20 | remoteVideos | ||
21 | ) | ||
22 | |||
23 | // --------------------------------------------------------------------------- | ||
24 | |||
25 | module.exports = router | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | function 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 | |||
53 | function 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 | |||
68 | function 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 | |||
3 | const express = require('express') | ||
4 | |||
5 | const utils = require('../../../helpers/utils') | ||
6 | |||
7 | const router = express.Router() | ||
8 | |||
9 | const videosRemoteController = require('./videos') | ||
10 | |||
11 | router.use('/videos', videosRemoteController) | ||
12 | router.use('/*', utils.badRequest) | ||
13 | |||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
16 | module.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 | |||
3 | const eachSeries = require('async/eachSeries') | ||
4 | const express = require('express') | ||
5 | const waterfall = require('async/waterfall') | ||
6 | |||
7 | const db = require('../../../initializers/database') | ||
8 | const middlewares = require('../../../middlewares') | ||
9 | const secureMiddleware = middlewares.secure | ||
10 | const videosValidators = middlewares.validators.remote.videos | ||
11 | const signatureValidators = middlewares.validators.remote.signature | ||
12 | const logger = require('../../../helpers/logger') | ||
13 | const utils = require('../../../helpers/utils') | ||
14 | |||
15 | const router = express.Router() | ||
16 | |||
17 | router.post('/', | ||
18 | signatureValidators.signature, | ||
19 | secureMiddleware.checkSignature, | ||
20 | videosValidators.remoteVideos, | ||
21 | remoteVideos | ||
22 | ) | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | module.exports = router | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | function 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 | ||
68 | function 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 | |||
84 | function 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 | ||
184 | function 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 | |||
200 | function 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 | |||
270 | function 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 | |||
288 | function 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 | |||
317 | function 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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const middlewares = require('../../middlewares') | 7 | const middlewares = require('../../middlewares') |
8 | const admin = middlewares.admin | 8 | const admin = middlewares.admin |
9 | const oAuth = middlewares.oauth | 9 | const oAuth = middlewares.oauth |
10 | 10 | ||
11 | const Request = mongoose.model('Request') | ||
12 | |||
13 | const router = express.Router() | 11 | const router = express.Router() |
14 | 12 | ||
15 | router.get('/stats', | 13 | router.get('/stats', |
@@ -25,13 +23,13 @@ module.exports = router | |||
25 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
26 | 24 | ||
27 | function getStatsRequests (req, res, next) { | 25 | function 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 | ||
3 | const each = require('async/each') | ||
4 | const express = require('express') | 3 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
6 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
7 | 5 | ||
8 | const constants = require('../../initializers/constants') | 6 | const constants = require('../../initializers/constants') |
9 | const friends = require('../../lib/friends') | 7 | const db = require('../../initializers/database') |
10 | const logger = require('../../helpers/logger') | 8 | const logger = require('../../helpers/logger') |
9 | const utils = require('../../helpers/utils') | ||
11 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
12 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
13 | const oAuth = middlewares.oauth | 12 | const oAuth = middlewares.oauth |
@@ -17,9 +16,6 @@ const validatorsPagination = middlewares.validators.pagination | |||
17 | const validatorsSort = middlewares.validators.sort | 16 | const validatorsSort = middlewares.validators.sort |
18 | const validatorsUsers = middlewares.validators.users | 17 | const validatorsUsers = middlewares.validators.users |
19 | 18 | ||
20 | const User = mongoose.model('User') | ||
21 | const Video = mongoose.model('Video') | ||
22 | |||
23 | const router = express.Router() | 19 | const router = express.Router() |
24 | 20 | ||
25 | router.get('/me', oAuth.authenticate, getUserInformation) | 21 | router.get('/me', oAuth.authenticate, getUserInformation) |
@@ -62,13 +58,13 @@ module.exports = router | |||
62 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
63 | 59 | ||
64 | function createUser (req, res, next) { | 60 | function 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 | ||
78 | function getUserInformation (req, res, next) { | 74 | function 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 | ||
86 | function listUsers (req, res, next) { | 82 | function 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 | ||
94 | function removeUser (req, res, next) { | 90 | function 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 | ||
140 | function updateUser (req, res, next) { | 109 | function 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) { | |||
153 | function success (req, res, next) { | 122 | function success (req, res, next) { |
154 | res.end() | 123 | res.end() |
155 | } | 124 | } |
156 | |||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
159 | function getFormatedUsers (users, usersTotal) { | ||
160 | const formatedUsers = [] | ||
161 | |||
162 | users.forEach(function (user) { | ||
163 | formatedUsers.push(user.toFormatedJSON()) | ||
164 | }) | ||
165 | |||
166 | return { | ||
167 | total: usersTotal, | ||
168 | data: formatedUsers | ||
169 | } | ||
170 | } | ||
diff --git a/server/controllers/api/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 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const fs = require('fs') | 4 | const fs = require('fs') |
5 | const mongoose = require('mongoose') | ||
6 | const multer = require('multer') | 5 | const multer = require('multer') |
7 | const path = require('path') | 6 | const path = require('path') |
8 | const waterfall = require('async/waterfall') | 7 | const waterfall = require('async/waterfall') |
9 | 8 | ||
10 | const constants = require('../../initializers/constants') | 9 | const constants = require('../../initializers/constants') |
10 | const db = require('../../initializers/database') | ||
11 | const logger = require('../../helpers/logger') | 11 | const logger = require('../../helpers/logger') |
12 | const friends = require('../../lib/friends') | 12 | const friends = require('../../lib/friends') |
13 | const middlewares = require('../../middlewares') | 13 | const middlewares = require('../../middlewares') |
14 | const admin = middlewares.admin | ||
14 | const oAuth = middlewares.oauth | 15 | const oAuth = middlewares.oauth |
15 | const pagination = middlewares.pagination | 16 | const pagination = middlewares.pagination |
16 | const validators = middlewares.validators | 17 | const validators = middlewares.validators |
@@ -22,7 +23,6 @@ const sort = middlewares.sort | |||
22 | const utils = require('../../helpers/utils') | 23 | const utils = require('../../helpers/utils') |
23 | 24 | ||
24 | const router = express.Router() | 25 | const router = express.Router() |
25 | const Video = mongoose.model('Video') | ||
26 | 26 | ||
27 | // multer configuration | 27 | // multer configuration |
28 | const storage = multer.diskStorage({ | 28 | const storage = multer.diskStorage({ |
@@ -44,6 +44,21 @@ const storage = multer.diskStorage({ | |||
44 | 44 | ||
45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) | 45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) |
46 | 46 | ||
47 | router.get('/abuse', | ||
48 | oAuth.authenticate, | ||
49 | admin.ensureIsAdmin, | ||
50 | validatorsPagination.pagination, | ||
51 | validatorsSort.videoAbusesSort, | ||
52 | sort.setVideoAbusesSort, | ||
53 | pagination.setPagination, | ||
54 | listVideoAbuses | ||
55 | ) | ||
56 | router.post('/:id/abuse', | ||
57 | oAuth.authenticate, | ||
58 | validatorsVideos.videoAbuseReport, | ||
59 | reportVideoAbuseRetryWrapper | ||
60 | ) | ||
61 | |||
47 | router.get('/', | 62 | router.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 | ) |
69 | router.put('/:id', | ||
70 | oAuth.authenticate, | ||
71 | reqFiles, | ||
72 | validatorsVideos.videosUpdate, | ||
73 | updateVideoRetryWrapper | ||
74 | ) | ||
54 | router.post('/', | 75 | router.post('/', |
55 | oAuth.authenticate, | 76 | oAuth.authenticate, |
56 | reqFiles, | 77 | reqFiles, |
57 | validatorsVideos.videosAdd, | 78 | validatorsVideos.videosAdd, |
58 | addVideo | 79 | addVideoRetryWrapper |
59 | ) | 80 | ) |
60 | router.get('/:id', | 81 | router.get('/:id', |
61 | validatorsVideos.videosGet, | 82 | validatorsVideos.videosGet, |
@@ -82,117 +103,264 @@ module.exports = router | |||
82 | 103 | ||
83 | // --------------------------------------------------------------------------- | 104 | // --------------------------------------------------------------------------- |
84 | 105 | ||
85 | function 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 |
108 | function 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 | |||
125 | function 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 | ||
149 | function getVideo (req, res, next) { | 243 | function 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 | ||
161 | function listVideos (req, res, next) { | 260 | function 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 | ||
169 | function 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 | |||
347 | function getVideo (req, res, next) { | ||
348 | const videoInstance = res.locals.video | ||
349 | res.json(videoInstance.toFormatedJSON()) | ||
350 | } | ||
351 | |||
352 | function 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 | |||
360 | function 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 | ||
205 | function searchVideos (req, res, next) { | 373 | function 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 | |||
384 | function 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 | // --------------------------------------------------------------------------- | 392 | function 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 | ||
216 | function getFormatedVideos (videos, videosTotal) { | 403 | return res.type('json').status(204).end() |
217 | const formatedVideos = [] | 404 | } |
405 | ) | ||
406 | } | ||
218 | 407 | ||
219 | videos.forEach(function (video) { | 408 | function 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 @@ | |||
3 | const parallel = require('async/parallel') | 3 | const parallel = require('async/parallel') |
4 | const express = require('express') | 4 | const express = require('express') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const mongoose = require('mongoose') | ||
7 | const path = require('path') | 6 | const path = require('path') |
8 | const validator = require('express-validator').validator | 7 | const validator = require('express-validator').validator |
9 | 8 | ||
10 | const constants = require('../initializers/constants') | 9 | const constants = require('../initializers/constants') |
10 | const db = require('../initializers/database') | ||
11 | 11 | ||
12 | const Video = mongoose.model('Video') | ||
13 | const router = express.Router() | 12 | const router = express.Router() |
14 | 13 | ||
15 | const opengraphComment = '<!-- opengraph tags -->' | 14 | const 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 | ||
3 | const miscValidators = require('./misc') | 3 | const miscValidators = require('./misc') |
4 | const podsValidators = require('./pods') | 4 | const podsValidators = require('./pods') |
5 | const remoteValidators = require('./remote') | ||
5 | const usersValidators = require('./users') | 6 | const usersValidators = require('./users') |
6 | const videosValidators = require('./videos') | 7 | const videosValidators = require('./videos') |
7 | 8 | ||
8 | const validators = { | 9 | const 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 | |||
5 | const miscValidators = require('./misc') | 5 | const miscValidators = require('./misc') |
6 | 6 | ||
7 | const podsValidators = { | 7 | const podsValidators = { |
8 | isEachUniqueHostValid | 8 | isEachUniqueHostValid, |
9 | isHostValid | ||
10 | } | ||
11 | |||
12 | function isHostValid (host) { | ||
13 | return validator.isURL(host) && host.split('://').length === 1 | ||
9 | } | 14 | } |
10 | 15 | ||
11 | function isEachUniqueHostValid (hosts) { | 16 | function 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 | |||
3 | const remoteVideosValidators = require('./videos') | ||
4 | |||
5 | const validators = { | ||
6 | videos: remoteVideosValidators | ||
7 | } | ||
8 | |||
9 | // --------------------------------------------------------------------------- | ||
10 | |||
11 | module.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 | |||
3 | const videosValidators = require('../videos') | ||
4 | const miscValidators = require('../misc') | ||
5 | |||
6 | const remoteVideosValidators = { | ||
7 | isEachRemoteRequestVideosValid | ||
8 | } | ||
9 | |||
10 | function 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 | |||
55 | module.exports = remoteVideosValidators | ||
56 | |||
57 | // --------------------------------------------------------------------------- | ||
58 | |||
59 | function isRequestTypeAddValid (value) { | ||
60 | return value === 'add' | ||
61 | } | ||
62 | |||
63 | function isRequestTypeUpdateValid (value) { | ||
64 | return value === 'update' | ||
65 | } | ||
66 | |||
67 | function isRequestTypeRemoveValid (value) { | ||
68 | return value === 'remove' | ||
69 | } | ||
70 | |||
71 | function 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') | |||
6 | const usersValidators = require('./users') | 6 | const usersValidators = require('./users') |
7 | const miscValidators = require('./misc') | 7 | const miscValidators = require('./misc') |
8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS | 8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS |
9 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEO_ABUSES | ||
9 | 10 | ||
10 | const videosValidators = { | 11 | const 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, | |
24 | function 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 | ||
48 | function isVideoAuthorValid (value) { | 27 | function 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 | ||
64 | function isVideoMagnetValid (value) { | 43 | function 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 | ||
68 | function isVideoNameValid (value) { | 47 | function 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 | ||
72 | function isVideoPodHostValid (value) { | 51 | function 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 | ||
77 | function isVideoTagsValid (tags) { | 55 | function 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 | ||
90 | function isVideoThumbnail64Valid (value) { | 68 | function 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 | ||
95 | function isVideoRemoteIdValid (value) { | 72 | function isVideoRemoteIdValid (value) { |
96 | return validator.isMongoId(value) | 73 | return validator.isUUID(value, 4) |
97 | } | 74 | } |
98 | 75 | ||
99 | // --------------------------------------------------------------------------- | 76 | function isVideoAbuseReasonValid (value) { |
77 | return validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
78 | } | ||
100 | 79 | ||
101 | module.exports = videosValidators | 80 | function isVideoAbuseReporterUsernameValid (value) { |
81 | return usersValidators.isUserUsernameValid(value) | ||
82 | } | ||
102 | 83 | ||
103 | // --------------------------------------------------------------------------- | 84 | // --------------------------------------------------------------------------- |
104 | 85 | ||
105 | function isRequestTypeAddValid (value) { | 86 | module.exports = videosValidators |
106 | return value === 'add' | ||
107 | } | ||
108 | |||
109 | function 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 | ||
3 | const bcrypt = require('bcrypt') | ||
4 | const crypto = require('crypto') | 3 | const crypto = require('crypto') |
4 | const bcrypt = require('bcrypt') | ||
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const openssl = require('openssl-wrapper') | 6 | const openssl = require('openssl-wrapper') |
7 | const ursa = require('ursa') | ||
8 | 7 | ||
9 | const constants = require('../initializers/constants') | 8 | const constants = require('../initializers/constants') |
10 | const logger = require('./logger') | 9 | const logger = require('./logger') |
11 | 10 | ||
12 | const algorithm = 'aes-256-ctr' | ||
13 | |||
14 | const peertubeCrypto = { | 11 | const peertubeCrypto = { |
15 | checkSignature, | 12 | checkSignature, |
16 | comparePassword, | 13 | comparePassword, |
@@ -19,12 +16,51 @@ const peertubeCrypto = { | |||
19 | sign | 16 | sign |
20 | } | 17 | } |
21 | 18 | ||
22 | function checkSignature (publicKey, rawData, hexSignature) { | 19 | function 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 | ||
40 | function 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 | |||
28 | function comparePassword (plainPassword, hashPassword, callback) { | 64 | function 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 | ||
58 | function 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 | ||
67 | module.exports = peertubeCrypto | 96 | module.exports = peertubeCrypto |
@@ -113,11 +142,3 @@ function createCerts (callback) { | |||
113 | }) | 142 | }) |
114 | }) | 143 | }) |
115 | } | 144 | } |
116 | |||
117 | function 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 | ||
3 | const crypto = require('crypto') | 3 | const crypto = require('crypto') |
4 | const retry = require('async/retry') | ||
4 | 5 | ||
5 | const logger = require('./logger') | 6 | const logger = require('./logger') |
6 | 7 | ||
7 | const utils = { | 8 | const utils = { |
9 | badRequest, | ||
8 | cleanForExit, | 10 | cleanForExit, |
9 | generateRandomString, | 11 | generateRandomString, |
10 | isTestInstance | 12 | isTestInstance, |
13 | getFormatedObjects, | ||
14 | transactionRetryer | ||
15 | } | ||
16 | |||
17 | function badRequest (req, res, next) { | ||
18 | res.type('json').status(400).end() | ||
11 | } | 19 | } |
12 | 20 | ||
13 | function generateRandomString (size, callback) { | 21 | function 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 | ||
38 | function 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 | |||
51 | function 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 | ||
32 | module.exports = utils | 65 | module.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 | ||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const Client = mongoose.model('OAuthClient') | 5 | const db = require('./database') |
7 | const User = mongoose.model('User') | ||
8 | 6 | ||
9 | const checker = { | 7 | const checker = { |
10 | checkConfig, | 8 | checkConfig, |
@@ -29,7 +27,7 @@ function checkConfig () { | |||
29 | function checkMissedConfig () { | 27 | function 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 | ||
46 | function clientsExist (callback) { | 44 | function 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 | ||
54 | function usersExist (callback) { | 52 | function 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 | ||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const maxBy = require('lodash/maxBy') | ||
5 | const path = require('path') | 4 | const 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 |
16 | const SEARCHABLE_COLUMNS = { | 15 | const 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 |
21 | const SORTABLE_COLUMNS = { | 20 | const 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 | ||
26 | const OAUTH_LIFETIME = { | 26 | const 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 | ||
91 | const MONGO_MIGRATION_SCRIPTS = [ | 95 | const 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 | ] | ||
125 | const 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 |
139 | const REQUESTS_IN_PARALLEL = 10 | 109 | const REQUESTS_IN_PARALLEL = 10 |
140 | 110 | ||
141 | // How many requests we put in request | 111 | // To how many pods we send requests |
142 | const REQUESTS_LIMIT = 10 | 112 | const REQUESTS_LIMIT_PODS = 10 |
113 | // How many requests we send to a pod per interval | ||
114 | const 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 |
145 | const RETRY_REQUESTS = 5 | 117 | const RETRY_REQUESTS = 5 |
@@ -148,16 +120,21 @@ const REQUEST_ENDPOINTS = { | |||
148 | VIDEOS: 'videos' | 120 | VIDEOS: 'videos' |
149 | } | 121 | } |
150 | 122 | ||
151 | // --------------------------------------------------------------------------- | ||
152 | |||
153 | const REMOTE_SCHEME = { | 123 | const REMOTE_SCHEME = { |
154 | HTTP: 'https', | 124 | HTTP: 'https', |
155 | WS: 'wss' | 125 | WS: 'wss' |
156 | } | 126 | } |
157 | 127 | ||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | const SIGNATURE_ALGORITHM = 'RSA-SHA256' | ||
131 | const SIGNATURE_ENCODING = 'hex' | ||
132 | |||
158 | // Password encryption | 133 | // Password encryption |
159 | const BCRYPT_SALT_SIZE = 10 | 134 | const BCRYPT_SALT_SIZE = 10 |
160 | 135 | ||
136 | // --------------------------------------------------------------------------- | ||
137 | |||
161 | // Express static paths (router) | 138 | // Express static paths (router) |
162 | const STATIC_PATHS = { | 139 | const STATIC_PATHS = { |
163 | PREVIEWS: '/static/previews/', | 140 | PREVIEWS: '/static/previews/', |
@@ -173,6 +150,8 @@ let STATIC_MAX_AGE = '30d' | |||
173 | const THUMBNAILS_SIZE = '200x110' | 150 | const THUMBNAILS_SIZE = '200x110' |
174 | const PREVIEWS_SIZE = '640x480' | 151 | const PREVIEWS_SIZE = '640x480' |
175 | 152 | ||
153 | // --------------------------------------------------------------------------- | ||
154 | |||
176 | const USER_ROLES = { | 155 | const 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 | ||
3 | const mongoose = require('mongoose') | 3 | const fs = require('fs') |
4 | const path = require('path') | ||
5 | const Sequelize = require('sequelize') | ||
4 | 6 | ||
5 | const constants = require('../initializers/constants') | 7 | const constants = require('../initializers/constants') |
6 | const logger = require('../helpers/logger') | 8 | const logger = require('../helpers/logger') |
9 | const utils = require('../helpers/utils') | ||
7 | 10 | ||
8 | // Bootstrap models | 11 | const database = {} |
9 | require('../models/application') | ||
10 | require('../models/oauth-token') | ||
11 | require('../models/user') | ||
12 | require('../models/oauth-client') | ||
13 | require('../models/video') | ||
14 | // Request model needs Video model | ||
15 | require('../models/pods') | ||
16 | // Request model needs Pod model | ||
17 | require('../models/request') | ||
18 | |||
19 | const database = { | ||
20 | connect: connect | ||
21 | } | ||
22 | 12 | ||
23 | function connect () { | 13 | const dbname = constants.CONFIG.DATABASE.DBNAME |
24 | mongoose.Promise = global.Promise | 14 | const username = constants.CONFIG.DATABASE.USERNAME |
25 | mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME) | 15 | const 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 () { | 17 | const 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 | |||
33 | database.sequelize = sequelize | ||
34 | database.Sequelize = Sequelize | ||
35 | database.init = init | ||
34 | 36 | ||
35 | // --------------------------------------------------------------------------- | 37 | // --------------------------------------------------------------------------- |
36 | 38 | ||
37 | module.exports = database | 39 | module.exports = database |
40 | |||
41 | // --------------------------------------------------------------------------- | ||
42 | |||
43 | function 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 @@ | |||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const each = require('async/each') | 4 | const each = require('async/each') |
5 | const mkdirp = require('mkdirp') | 5 | const mkdirp = require('mkdirp') |
6 | const mongoose = require('mongoose') | ||
7 | const passwordGenerator = require('password-generator') | 6 | const passwordGenerator = require('password-generator') |
8 | const path = require('path') | 7 | const path = require('path') |
9 | const series = require('async/series') | 8 | const series = require('async/series') |
10 | 9 | ||
11 | const checker = require('./checker') | 10 | const checker = require('./checker') |
12 | const constants = require('./constants') | 11 | const constants = require('./constants') |
12 | const db = require('./database') | ||
13 | const logger = require('../helpers/logger') | 13 | const logger = require('../helpers/logger') |
14 | const peertubeCrypto = require('../helpers/peertube-crypto') | 14 | const peertubeCrypto = require('../helpers/peertube-crypto') |
15 | 15 | ||
16 | const Application = mongoose.model('Application') | ||
17 | const Client = mongoose.model('OAuthClient') | ||
18 | const User = mongoose.model('User') | ||
19 | |||
20 | const installer = { | 16 | const installer = { |
21 | installApplication | 17 | installApplication |
22 | } | 18 | } |
23 | 19 | ||
24 | function installApplication (callback) { | 20 | function 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 | |||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const Application = mongoose.model('Application') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | const application = new Application() | ||
12 | application.save(callback) | ||
13 | } | ||
14 | |||
15 | exports.down = function (callback) { | ||
16 | throw new Error('Not implemented.') | ||
17 | } | ||
diff --git a/server/initializers/migrations/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 | |||
5 | const eachSeries = require('async/eachSeries') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.list(function (err, users) { | ||
12 | if (err) return callback(err) | ||
13 | |||
14 | eachSeries(users, function (user, callbackEach) { | ||
15 | user.save(callbackEach) | ||
16 | }, callback) | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | exports.down = function (callback) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
diff --git a/server/initializers/migrations/0015-admin-role.js b/server/initializers/migrations/0015-admin-role.js 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 | |||
5 | const constants = require('../constants') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.update({ username: 'root' }, { role: constants.USER_ROLES.ADMIN }, callback) | ||
12 | } | ||
13 | |||
14 | exports.down = function (callback) { | ||
15 | throw new Error('Not implemented.') | ||
16 | } | ||
diff --git a/server/initializers/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 | |||
5 | const mongoose = require('mongoose') | ||
6 | |||
7 | const Request = mongoose.model('Request') | ||
8 | |||
9 | exports.up = function (callback) { | ||
10 | Request.update({ }, { endpoint: 'videos' }, callback) | ||
11 | } | ||
12 | |||
13 | exports.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 | |||
5 | const each = require('async/each') | ||
6 | const fs = require('fs') | ||
7 | const path = require('path') | ||
8 | const mongoose = require('mongoose') | ||
9 | |||
10 | const constants = require('../constants') | ||
11 | const logger = require('../../helpers/logger') | ||
12 | |||
13 | const Video = mongoose.model('Video') | ||
14 | |||
15 | exports.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 | |||
55 | exports.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 | |||
5 | const each = require('async/each') | ||
6 | const magnet = require('magnet-uri') | ||
7 | const mongoose = require('mongoose') | ||
8 | |||
9 | const Video = mongoose.model('Video') | ||
10 | |||
11 | exports.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 | |||
30 | exports.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 | |||
5 | const each = require('async/each') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const Video = mongoose.model('Video') | ||
9 | |||
10 | exports.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 | |||
28 | exports.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 | |||
5 | const map = require('lodash/map') | ||
6 | const mongoose = require('mongoose') | ||
7 | const readline = require('readline') | ||
8 | |||
9 | const rl = readline.createInterface({ | ||
10 | input: process.stdin, | ||
11 | output: process.stdout | ||
12 | }) | ||
13 | |||
14 | const logger = require('../../helpers/logger') | ||
15 | const friends = require('../../lib/friends') | ||
16 | |||
17 | const Pod = mongoose.model('Pod') | ||
18 | const Video = mongoose.model('Video') | ||
19 | |||
20 | exports.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 | |||
49 | exports.down = function (callback) { | ||
50 | throw new Error('Not implemented.') | ||
51 | } | ||
52 | |||
53 | function 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 | ||
3 | const eachSeries = require('async/eachSeries') | 3 | const eachSeries = require('async/eachSeries') |
4 | const mongoose = require('mongoose') | 4 | const fs = require('fs') |
5 | const path = require('path') | 5 | const path = require('path') |
6 | 6 | ||
7 | const constants = require('./constants') | 7 | const constants = require('./constants') |
8 | const db = require('./database') | ||
8 | const logger = require('../helpers/logger') | 9 | const logger = require('../helpers/logger') |
9 | 10 | ||
10 | const Application = mongoose.model('Application') | ||
11 | |||
12 | const migrator = { | 11 | const migrator = { |
13 | migrate: migrate | 12 | migrate: migrate |
14 | } | 13 | } |
15 | 14 | ||
16 | function migrate (callback) { | 15 | function 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 | ||
55 | module.exports = migrator | 43 | module.exports = migrator |
56 | 44 | ||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | function 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 | |||
66 | function 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') | |||
4 | const eachLimit = require('async/eachLimit') | 4 | const eachLimit = require('async/eachLimit') |
5 | const eachSeries = require('async/eachSeries') | 5 | const eachSeries = require('async/eachSeries') |
6 | const fs = require('fs') | 6 | const fs = require('fs') |
7 | const mongoose = require('mongoose') | ||
8 | const request = require('request') | 7 | const request = require('request') |
9 | const waterfall = require('async/waterfall') | 8 | const waterfall = require('async/waterfall') |
10 | 9 | ||
11 | const constants = require('../initializers/constants') | 10 | const constants = require('../initializers/constants') |
11 | const db = require('../initializers/database') | ||
12 | const logger = require('../helpers/logger') | 12 | const logger = require('../helpers/logger') |
13 | const requests = require('../helpers/requests') | 13 | const requests = require('../helpers/requests') |
14 | 14 | ||
15 | const Pod = mongoose.model('Pod') | ||
16 | const Request = mongoose.model('Request') | ||
17 | const Video = mongoose.model('Video') | ||
18 | |||
19 | const friends = { | 15 | const 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 | ||
29 | function addVideoToFriends (video) { | 27 | function 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 | |||
37 | function 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 | |||
47 | function removeVideoToFriends (videoParams) { | ||
48 | const options = { | ||
49 | type: 'remove', | ||
50 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
51 | data: videoParams | ||
52 | } | ||
53 | createRequest(options) | ||
54 | } | ||
55 | |||
56 | function 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 | ||
33 | function hasFriends (callback) { | 66 | function 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 | ||
70 | function quitFriends (callback) { | 103 | function 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 | ||
120 | function removeVideoToFriends (videoParams) { | ||
121 | createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams) | ||
122 | } | ||
123 | |||
124 | function sendOwnedVideosToPod (podId) { | 155 | function 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 | ||
151 | function computeForeignPodsList (host, podsScore, callback) { | 188 | function 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 | ||
201 | function makeRequestsToWinningPods (cert, podsList, callback) { | 238 | function 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 | ||
252 | function 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 } |
291 | function 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 } | ||
308 | function _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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | const db = require('../initializers/database') |
2 | |||
3 | const logger = require('../helpers/logger') | 2 | const logger = require('../helpers/logger') |
4 | 3 | ||
5 | const OAuthClient = mongoose.model('OAuthClient') | ||
6 | const OAuthToken = mongoose.model('OAuthToken') | ||
7 | const 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 |
10 | const OAuthModel = { | 5 | const OAuthModel = { |
11 | getAccessToken, | 6 | getAccessToken, |
@@ -21,27 +16,25 @@ const OAuthModel = { | |||
21 | function getAccessToken (bearerToken) { | 16 | function 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 | ||
27 | function getClient (clientId, clientSecret) { | 22 | function 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 | ||
35 | function getRefreshToken (refreshToken) { | 28 | function 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 | ||
41 | function getUser (username, password) { | 34 | function 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 | ||
62 | function revokeToken (token) { | 55 | function 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) { | |||
80 | function saveToken (token, client, user) { | 73 | function 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 | |||
44 | function getHostWithPort (host) { | 44 | function 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 | ||
3 | const db = require('../initializers/database') | ||
3 | const logger = require('../helpers/logger') | 4 | const logger = require('../helpers/logger') |
4 | const mongoose = require('mongoose') | ||
5 | const peertubeCrypto = require('../helpers/peertube-crypto') | 5 | const peertubeCrypto = require('../helpers/peertube-crypto') |
6 | 6 | ||
7 | const Pod = mongoose.model('Pod') | ||
8 | |||
9 | const secureMiddleware = { | 7 | const secureMiddleware = { |
10 | checkSignature | 8 | checkSignature |
11 | } | 9 | } |
12 | 10 | ||
13 | function checkSignature (req, res, next) { | 11 | function 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 | ||
3 | const sortMiddleware = { | 3 | const sortMiddleware = { |
4 | setUsersSort, | 4 | setUsersSort, |
5 | setVideoAbusesSort, | ||
5 | setVideosSort | 6 | setVideosSort |
6 | } | 7 | } |
7 | 8 | ||
8 | function setUsersSort (req, res, next) { | 9 | function 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 | |||
15 | function 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 | ||
14 | function setVideosSort (req, res, next) { | 21 | function 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 | |||
3 | const checkErrors = require('./utils').checkErrors | ||
4 | const logger = require('../../helpers/logger') | ||
5 | |||
6 | const validatorsRemote = { | ||
7 | remoteVideos, | ||
8 | signature | ||
9 | } | ||
10 | |||
11 | function 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 | |||
19 | function 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 | |||
30 | module.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 | |||
3 | const remoteSignatureValidators = require('./signature') | ||
4 | const remoteVideosValidators = require('./videos') | ||
5 | |||
6 | const validators = { | ||
7 | signature: remoteSignatureValidators, | ||
8 | videos: remoteVideosValidators | ||
9 | } | ||
10 | |||
11 | // --------------------------------------------------------------------------- | ||
12 | |||
13 | module.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 | |||
3 | const checkErrors = require('../utils').checkErrors | ||
4 | const logger = require('../../../helpers/logger') | ||
5 | |||
6 | const validatorsRemoteSignature = { | ||
7 | signature | ||
8 | } | ||
9 | |||
10 | function 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 | |||
21 | module.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 | |||
3 | const checkErrors = require('../utils').checkErrors | ||
4 | const logger = require('../../../helpers/logger') | ||
5 | |||
6 | const validatorsRemoteVideos = { | ||
7 | remoteVideos | ||
8 | } | ||
9 | |||
10 | function 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 | |||
20 | module.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 | ||
7 | const validatorsSort = { | 7 | const validatorsSort = { |
8 | usersSort, | 8 | usersSort, |
9 | videoAbusesSort, | ||
9 | videosSort | 10 | videosSort |
10 | } | 11 | } |
11 | 12 | ||
12 | function usersSort (req, res, next) { | 13 | function 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 }) | 19 | function 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 | ||
22 | function videosSort (req, res, next) { | 25 | function 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 | |||
33 | module.exports = validatorsSort | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | function 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 | |||
34 | module.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 | ||
3 | const mongoose = require('mongoose') | ||
4 | |||
5 | const checkErrors = require('./utils').checkErrors | 3 | const checkErrors = require('./utils').checkErrors |
4 | const db = require('../../initializers/database') | ||
6 | const logger = require('../../helpers/logger') | 5 | const logger = require('../../helpers/logger') |
7 | 6 | ||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | const validatorsUsers = { | 7 | const 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 | ||
36 | function usersRemove (req, res, next) { | 33 | function 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 | ||
57 | function usersUpdate (req, res, next) { | 54 | function 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 | ||
3 | const mongoose = require('mongoose') | ||
4 | |||
5 | const checkErrors = require('./utils').checkErrors | 3 | const checkErrors = require('./utils').checkErrors |
6 | const constants = require('../../initializers/constants') | 4 | const constants = require('../../initializers/constants') |
7 | const customVideosValidators = require('../../helpers/custom-validators').videos | 5 | const customVideosValidators = require('../../helpers/custom-validators').videos |
6 | const db = require('../../initializers/database') | ||
8 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
9 | 8 | ||
10 | const Video = mongoose.model('Video') | ||
11 | |||
12 | const validatorsVideos = { | 9 | const 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 | ||
19 | function videosAdd (req, res, next) { | 19 | function 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 | ||
47 | function videosGet (req, res, next) { | 47 | function 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 | ||
71 | function 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 | |||
66 | function videosRemove (req, res, next) { | 81 | function 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 | ||
113 | function 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 | ||
99 | module.exports = validatorsVideos | 126 | module.exports = validatorsVideos |
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function 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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | |||
3 | module.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 | ||
5 | const ApplicationSchema = mongoose.Schema({ | 28 | function loadMigrationVersion (callback) { |
6 | mongoSchemaVersion: { | 29 | const query = { |
7 | type: Number, | 30 | attributes: [ 'migrationVersion' ] |
8 | default: 0 | ||
9 | } | 31 | } |
10 | }) | ||
11 | |||
12 | ApplicationSchema.statics = { | ||
13 | loadMongoSchemaVersion, | ||
14 | updateMongoSchemaVersion | ||
15 | } | ||
16 | |||
17 | mongoose.model('Application', ApplicationSchema) | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | 32 | ||
21 | function 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 | ||
29 | function updateMongoSchemaVersion (newVersion, callback) { | 40 | function 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 | |||
3 | const customUsersValidators = require('../helpers/custom-validators').users | ||
4 | |||
5 | module.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 | |||
44 | function 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 | |||
62 | function 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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | 2 | ||
3 | // --------------------------------------------------------------------------- | 3 | module.exports = function (sequelize, DataTypes) { |
4 | 4 | const OAuthClient = sequelize.define('OAuthClient', | |
5 | const 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: { | |
11 | OAuthClientSchema.path('clientSecret').required(true) | 11 | type: DataTypes.STRING, |
12 | 12 | allowNull: false | |
13 | OAuthClientSchema.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 | ||
19 | mongoose.model('OAuthClient', OAuthClientSchema) | ||
20 | |||
21 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
22 | 44 | ||
23 | function list (callback) { | 45 | function countTotal (callback) { |
24 | return this.find(callback) | 46 | return this.count().asCallback(callback) |
25 | } | 47 | } |
26 | 48 | ||
27 | function loadFirstClient (callback) { | 49 | function loadFirstClient (callback) { |
28 | return this.findOne({}, callback) | 50 | return this.findOne().asCallback(callback) |
29 | } | 51 | } |
30 | 52 | ||
31 | function getByIdAndSecret (id, clientSecret) { | 53 | function 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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | 2 | ||
3 | const logger = require('../helpers/logger') | 3 | const logger = require('../helpers/logger') |
4 | 4 | ||
5 | // --------------------------------------------------------------------------- | 5 | // --------------------------------------------------------------------------- |
6 | 6 | ||
7 | const OAuthTokenSchema = mongoose.Schema({ | 7 | module.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, | |
16 | OAuthTokenSchema.path('accessToken').required(true) | 16 | allowNull: false |
17 | OAuthTokenSchema.path('client').required(true) | 17 | }, |
18 | OAuthTokenSchema.path('user').required(true) | 18 | refreshToken: { |
19 | 19 | type: DataTypes.STRING, | |
20 | OAuthTokenSchema.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 | ||
27 | mongoose.model('OAuthToken', OAuthTokenSchema) | 55 | return OAuthToken |
56 | } | ||
28 | 57 | ||
29 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
30 | 59 | ||
60 | function 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 | |||
31 | function getByRefreshTokenAndPopulateClient (refreshToken) { | 78 | function 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 | ||
52 | function getByTokenAndPopulateUser (bearerToken) { | 106 | function 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 | ||
56 | function getByRefreshTokenAndPopulateUser (refreshToken) { | 121 | function 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 | ||
60 | function removeByUserId (userId, callback) { | 136 | function 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 | |||
3 | const map = require('lodash/map') | ||
4 | |||
5 | const constants = require('../initializers/constants') | ||
6 | const customPodsValidators = require('../helpers/custom-validators').pods | ||
7 | |||
8 | // --------------------------------------------------------------------------- | ||
9 | |||
10 | module.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 | |||
70 | function 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 | |||
83 | function associate (models) { | ||
84 | this.belongsToMany(models.Request, { | ||
85 | foreignKey: 'podId', | ||
86 | through: models.RequestToPod, | ||
87 | onDelete: 'cascade' | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | function countAll (callback) { | ||
92 | return this.count().asCallback(callback) | ||
93 | } | ||
94 | |||
95 | function 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 | |||
115 | function list (callback) { | ||
116 | return this.findAll().asCallback(callback) | ||
117 | } | ||
118 | |||
119 | function 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 | |||
138 | function 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 | |||
174 | function listBadPods (callback) { | ||
175 | const query = { | ||
176 | where: { | ||
177 | score: { $lte: 0 } | ||
178 | } | ||
179 | } | ||
180 | |||
181 | return this.findAll(query).asCallback(callback) | ||
182 | } | ||
183 | |||
184 | function load (id, callback) { | ||
185 | return this.findById(id).asCallback(callback) | ||
186 | } | ||
187 | |||
188 | function loadByHost (host, callback) { | ||
189 | const query = { | ||
190 | where: { | ||
191 | host: host | ||
192 | } | ||
193 | } | ||
194 | |||
195 | return this.findOne(query).asCallback(callback) | ||
196 | } | ||
197 | |||
198 | function 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 | |||
3 | const each = require('async/each') | ||
4 | const mongoose = require('mongoose') | ||
5 | const map = require('lodash/map') | ||
6 | const validator = require('express-validator').validator | ||
7 | |||
8 | const constants = require('../initializers/constants') | ||
9 | |||
10 | const Video = mongoose.model('Video') | ||
11 | |||
12 | // --------------------------------------------------------------------------- | ||
13 | |||
14 | const 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 | |||
24 | PodSchema.path('host').validate(validator.isURL) | ||
25 | PodSchema.path('publicKey').required(true) | ||
26 | PodSchema.path('score').validate(function (value) { return !isNaN(value) }) | ||
27 | |||
28 | PodSchema.methods = { | ||
29 | toFormatedJSON | ||
30 | } | ||
31 | |||
32 | PodSchema.statics = { | ||
33 | countAll, | ||
34 | incrementScores, | ||
35 | list, | ||
36 | listAllIds, | ||
37 | listBadPods, | ||
38 | load, | ||
39 | loadByHost, | ||
40 | removeAll | ||
41 | } | ||
42 | |||
43 | PodSchema.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 | |||
56 | PodSchema.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 | |||
67 | const Pod = mongoose.model('Pod', PodSchema) | ||
68 | |||
69 | // ------------------------------ METHODS ------------------------------ | ||
70 | |||
71 | function 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 | |||
84 | function countAll (callback) { | ||
85 | return this.count(callback) | ||
86 | } | ||
87 | |||
88 | function incrementScores (ids, value, callback) { | ||
89 | if (!callback) callback = function () {} | ||
90 | return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback) | ||
91 | } | ||
92 | |||
93 | function list (callback) { | ||
94 | return this.find(callback) | ||
95 | } | ||
96 | |||
97 | function 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 | |||
105 | function listBadPods (callback) { | ||
106 | return this.find({ score: 0 }, callback) | ||
107 | } | ||
108 | |||
109 | function load (id, callback) { | ||
110 | return this.findById(id, callback) | ||
111 | } | ||
112 | |||
113 | function loadByHost (host, callback) { | ||
114 | return this.findOne({ host }, callback) | ||
115 | } | ||
116 | |||
117 | function 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 | |||
5 | module.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 | |||
29 | function 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 | ||
3 | const each = require('async/each') | 3 | const each = require('async/each') |
4 | const eachLimit = require('async/eachLimit') | 4 | const eachLimit = require('async/eachLimit') |
5 | const values = require('lodash/values') | ||
6 | const mongoose = require('mongoose') | ||
7 | const waterfall = require('async/waterfall') | 5 | const waterfall = require('async/waterfall') |
6 | const values = require('lodash/values') | ||
8 | 7 | ||
9 | const constants = require('../initializers/constants') | 8 | const constants = require('../initializers/constants') |
10 | const logger = require('../helpers/logger') | 9 | const logger = require('../helpers/logger') |
11 | const requests = require('../helpers/requests') | 10 | const requests = require('../helpers/requests') |
12 | 11 | ||
13 | const Pod = mongoose.model('Pod') | ||
14 | |||
15 | let timer = null | 12 | let timer = null |
16 | let lastRequestTimestamp = 0 | 13 | let lastRequestTimestamp = 0 |
17 | 14 | ||
18 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
19 | 16 | ||
20 | const RequestSchema = mongoose.Schema({ | 17 | module.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 | |||
34 | RequestSchema.statics = { | ||
35 | activate, | ||
36 | deactivate, | ||
37 | flush, | ||
38 | forceSend, | ||
39 | list, | ||
40 | remainingMilliSeconds | ||
41 | } | ||
42 | |||
43 | RequestSchema.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 | |||
61 | mongoose.model('Request', RequestSchema) | ||
62 | 45 | ||
63 | // ------------------------------ STATICS ------------------------------ | 46 | // ------------------------------ STATICS ------------------------------ |
64 | 47 | ||
48 | function 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 | |||
65 | function activate () { | 59 | function 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 | ||
70 | function countTotalRequests (callback) { | ||
71 | const query = { | ||
72 | include: [ this.sequelize.models.Pod ] | ||
73 | } | ||
74 | |||
75 | return this.count(query).asCallback(callback) | ||
76 | } | ||
77 | |||
76 | function deactivate () { | 78 | function 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 | ||
82 | function flush () { | 84 | function 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 | ||
93 | function list (callback) { | ||
94 | this.find({ }, callback) | ||
95 | } | ||
96 | |||
97 | function remainingMilliSeconds () { | 97 | function 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 |
137 | function makeRequests () { | 137 | function 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) |
222 | function removeBadPods () { | 220 | function 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 | ||
255 | function updatePodsScore (goodPods, badPods) { | 253 | function 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 | ||
268 | function listWithLimitAndRandom (limit, callback) { | 273 | function 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 | ||
281 | function removeAll (callback) { | 310 | function groupAndTruncateRequests (requests, limitRequestsPerPod) { |
282 | this.remove({ }, callback) | 311 | const requestsGrouped = {} |
283 | } | ||
284 | 312 | ||
285 | function 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 | |||
329 | function removeAll (callback) { | ||
330 | // Delete all requests | ||
331 | this.truncate({ cascade: true }).asCallback(callback) | ||
289 | } | 332 | } |
290 | 333 | ||
291 | function removeWithEmptyTo (callback) { | 334 | function 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 | |||
3 | const each = require('async/each') | ||
4 | |||
5 | // --------------------------------------------------------------------------- | ||
6 | |||
7 | module.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 | |||
36 | function associate (models) { | ||
37 | this.belongsToMany(models.Video, { | ||
38 | foreignKey: 'tagId', | ||
39 | through: models.VideoTag, | ||
40 | onDelete: 'cascade' | ||
41 | }) | ||
42 | } | ||
43 | |||
44 | function 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 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | |||
3 | const values = require('lodash/values') | ||
2 | 4 | ||
3 | const customUsersValidators = require('../helpers/custom-validators').users | ||
4 | const modelUtils = require('./utils') | 5 | const modelUtils = require('./utils') |
6 | const constants = require('../initializers/constants') | ||
5 | const peertubeCrypto = require('../helpers/peertube-crypto') | 7 | const peertubeCrypto = require('../helpers/peertube-crypto') |
6 | 8 | const customUsersValidators = require('../helpers/custom-validators').users | |
7 | const OAuthToken = mongoose.model('OAuthToken') | ||
8 | 9 | ||
9 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
10 | 11 | ||
11 | const UserSchema = mongoose.Schema({ | 12 | module.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.') | |
21 | UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) | 22 | } |
22 | UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) | 23 | } |
23 | UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) | 24 | }, |
24 | 25 | username: { | |
25 | UserSchema.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) | |
30 | UserSchema.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 | ||
39 | UserSchema.pre('save', function (next) { | 70 | function 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 | |||
51 | UserSchema.pre('remove', function (next) { | ||
52 | const user = this | ||
53 | |||
54 | OAuthToken.removeByUserId(user._id, next) | ||
55 | }) | ||
56 | |||
57 | mongoose.model('User', UserSchema) | ||
58 | 79 | ||
59 | // ------------------------------ METHODS ------------------------------ | 80 | // ------------------------------ METHODS ------------------------------ |
60 | 81 | ||
@@ -64,35 +85,68 @@ function isPasswordMatch (password, callback) { | |||
64 | 85 | ||
65 | function toFormatedJSON () { | 86 | function 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 | ||
96 | function 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 | |||
75 | function countTotal (callback) { | 108 | function countTotal (callback) { |
76 | return this.count(callback) | 109 | return this.count().asCallback(callback) |
77 | } | 110 | } |
78 | 111 | ||
79 | function getByUsername (username) { | 112 | function 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 | ||
83 | function list (callback) { | 122 | function list (callback) { |
84 | return this.find(callback) | 123 | return this.find().asCallback(callback) |
85 | } | 124 | } |
86 | 125 | ||
87 | function listForApi (start, count, sort, callback) { | 126 | function 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 | ||
92 | function loadById (id, callback) { | 140 | function loadById (id, callback) { |
93 | return this.findById(id, callback) | 141 | return this.findById(id).asCallback(callback) |
94 | } | 142 | } |
95 | 143 | ||
96 | function loadByUsername (username, callback) { | 144 | function 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 | ||
3 | const parallel = require('async/parallel') | ||
4 | |||
5 | const utils = { | 3 | const utils = { |
6 | listForApiWithCount | 4 | getSort |
7 | } | 5 | } |
8 | 6 | ||
9 | function listForApiWithCount (query, start, count, sort, callback) { | 7 | // Translate for example "-name" to [ 'name', 'DESC' ] |
10 | const self = this | 8 | function 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 | |||
3 | const constants = require('../initializers/constants') | ||
4 | const modelUtils = require('./utils') | ||
5 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
6 | |||
7 | module.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 | |||
56 | function 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 | |||
74 | function 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 | |||
94 | function 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 | |||
5 | module.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 | ||
3 | const Buffer = require('safe-buffer').Buffer | ||
3 | const createTorrent = require('create-torrent') | 4 | const createTorrent = require('create-torrent') |
4 | const ffmpeg = require('fluent-ffmpeg') | 5 | const ffmpeg = require('fluent-ffmpeg') |
5 | const fs = require('fs') | 6 | const fs = require('fs') |
6 | const magnetUtil = require('magnet-uri') | 7 | const magnetUtil = require('magnet-uri') |
8 | const map = require('lodash/map') | ||
7 | const parallel = require('async/parallel') | 9 | const parallel = require('async/parallel') |
8 | const parseTorrent = require('parse-torrent') | 10 | const parseTorrent = require('parse-torrent') |
9 | const pathUtils = require('path') | 11 | const pathUtils = require('path') |
10 | const mongoose = require('mongoose') | 12 | const values = require('lodash/values') |
11 | 13 | ||
12 | const constants = require('../initializers/constants') | 14 | const constants = require('../initializers/constants') |
13 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
14 | const logger = require('../helpers/logger') | 15 | const logger = require('../helpers/logger') |
16 | const friends = require('../lib/friends') | ||
15 | const modelUtils = require('./utils') | 17 | const modelUtils = require('./utils') |
18 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
16 | 19 | ||
17 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
18 | 21 | ||
19 | // TODO: add indexes on searchable columns | 22 | module.exports = function (sequelize, DataTypes) { |
20 | const 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 | |||
41 | VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) | ||
42 | VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) | ||
43 | VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid) | ||
44 | VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) | ||
45 | VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) | ||
46 | VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) | ||
47 | |||
48 | VideoSchema.methods = { | ||
49 | generateMagnetUri, | ||
50 | getVideoFilename, | ||
51 | getThumbnailName, | ||
52 | getPreviewName, | ||
53 | getTorrentName, | ||
54 | isOwned, | ||
55 | toFormatedJSON, | ||
56 | toRemoteJSON | ||
57 | } | ||
58 | |||
59 | VideoSchema.statics = { | ||
60 | generateThumbnailFromBase64, | ||
61 | getDurationFromFile, | ||
62 | listForApi, | ||
63 | listByHostAndRemoteId, | ||
64 | listByHost, | ||
65 | listOwned, | ||
66 | listOwnedByAuthor, | ||
67 | listRemotes, | ||
68 | load, | ||
69 | search | ||
70 | } | ||
71 | |||
72 | VideoSchema.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 | |||
143 | function 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 | ||
99 | VideoSchema.pre('save', function (next) { | 153 | function 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 | |||
197 | function afterDestroy (video, options, next) { | ||
198 | const tasks = [] | ||
145 | 199 | ||
146 | mongoose.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 | ||
237 | function 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 | |||
150 | function generateMagnetUri () { | 261 | function 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 | ||
176 | function getVideoFilename () { | 287 | function 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 | ||
182 | function getThumbnailName () { | 293 | function 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 | ||
187 | function getPreviewName () { | 298 | function 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 () { | |||
195 | function getTorrentName () { | 306 | function 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 | ||
207 | function toFormatedJSON () { | 318 | function 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 | ||
225 | function toRemoteJSON (callback) { | 346 | function 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 | ||
375 | function 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 | ||
255 | function generateThumbnailFromBase64 (video, thumbnailData, callback) { | 394 | function 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 | ||
275 | function listForApi (start, count, sort, callback) { | 414 | function 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 | ||
280 | function listByHostAndRemoteId (fromHost, remoteId, callback) { | 418 | function 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 | ||
284 | function listByHost (fromHost, callback) { | 441 | function 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 | ||
288 | function listOwned (callback) { | 465 | function 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 | ||
293 | function listOwnedByAuthor (author, callback) { | 477 | function 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 | ||
297 | function listRemotes (callback) { | 492 | return this.findAll(query).asCallback(callback) |
298 | this.find({ remoteId: { $ne: null } }, callback) | ||
299 | } | 493 | } |
300 | 494 | ||
301 | function load (id, callback) { | 495 | function load (id, callback) { |
302 | this.findById(id, callback) | 496 | return this.findById(id).asCallback(callback) |
497 | } | ||
498 | |||
499 | function loadAndPopulateAuthor (id, callback) { | ||
500 | const options = { | ||
501 | include: [ this.sequelize.models.Author ] | ||
502 | } | ||
503 | |||
504 | return this.findById(id, options).asCallback(callback) | ||
505 | } | ||
506 | |||
507 | function 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 | ||
305 | function search (value, field, start, count, sort, callback) { | 521 | function 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 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const loginUtils = require('../utils/login') | ||
10 | const requestsUtils = require('../utils/requests') | ||
11 | const serversUtils = require('../utils/servers') | ||
12 | const usersUtils = require('../utils/users') | ||
13 | |||
14 | describe('Test parameters validator', function () { | ||
15 | let server = null | ||
16 | let userAccessToken = null | ||
17 | |||
18 | // --------------------------------------------------------------- | ||
19 | |||
20 | before(function (done) { | ||
21 | this.timeout(20000) | ||
22 | |||
23 | series([ | ||
24 | function (next) { | ||
25 | serversUtils.flushTests(next) | ||
26 | }, | ||
27 | function (next) { | ||
28 | serversUtils.runServer(1, function (server1) { | ||
29 | server = server1 | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }, | ||
34 | function (next) { | ||
35 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
36 | if (err) throw err | ||
37 | server.accessToken = token | ||
38 | |||
39 | next() | ||
40 | }) | ||
41 | } | ||
42 | ], done) | ||
43 | }) | ||
44 | |||
45 | describe('Of the pods API', function () { | ||
46 | const path = '/api/v1/pods/' | ||
47 | |||
48 | describe('When making friends', function () { | ||
49 | let userAccessToken = null | ||
50 | |||
51 | before(function (done) { | ||
52 | usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { | ||
53 | server.user = { | ||
54 | username: 'user1', | ||
55 | password: 'password' | ||
56 | } | ||
57 | |||
58 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
59 | if (err) throw err | ||
60 | |||
61 | userAccessToken = accessToken | ||
62 | |||
63 | done() | ||
64 | }) | ||
65 | }) | ||
66 | }) | ||
67 | |||
68 | describe('When making friends', function () { | ||
69 | const body = { | ||
70 | 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 | ||
4 | require('./pods') | ||
5 | require('./remotes') | ||
6 | require('./users') | ||
7 | require('./requests') | ||
8 | require('./videos') | ||
9 | require('./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 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | |||
11 | describe('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 | |||
3 | const series = require('async/series') | ||
4 | |||
5 | const loginUtils = require('../../utils/login') | ||
6 | const serversUtils = require('../../utils/servers') | ||
7 | |||
8 | describe('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 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const usersUtils = require('../../utils/users') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | |||
10 | describe('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 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | |||
11 | describe('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 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | const videosUtils = require('../../utils/videos') | ||
11 | |||
12 | describe('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 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const loginUtils = require('../../utils/login') | ||
10 | const requestsUtils = require('../../utils/requests') | ||
11 | const serversUtils = require('../../utils/servers') | ||
12 | const videosUtils = require('../../utils/videos') | ||
13 | |||
14 | describe('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') | |||
4 | const each = require('async/each') | 4 | const each = require('async/each') |
5 | const expect = chai.expect | 5 | const expect = chai.expect |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
7 | const webtorrent = new (require('webtorrent'))() | 7 | const WebTorrent = require('webtorrent') |
8 | const webtorrent = new WebTorrent() | ||
8 | 9 | ||
9 | const loginUtils = require('../utils/login') | 10 | const loginUtils = require('../utils/login') |
10 | const miscsUtils = require('../utils/miscs') | 11 | const 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 | |||
3 | const chai = require('chai') | ||
4 | const each = require('async/each') | ||
5 | const expect = chai.expect | ||
6 | const series = require('async/series') | ||
7 | |||
8 | const loginUtils = require('../utils/login') | ||
9 | const podsUtils = require('../utils/pods') | ||
10 | const serversUtils = require('../utils/servers') | ||
11 | const videosUtils = require('../utils/videos') | ||
12 | const videoAbusesUtils = require('../utils/video-abuses') | ||
13 | |||
14 | describe('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 | ||
3 | const each = require('async/each') | 3 | const each = require('async/each') |
4 | const isEqual = require('lodash/isEqual') | 4 | const isEqual = require('lodash/isEqual') |
5 | const differenceWith = require('lodash/differenceWith') | ||
5 | const program = require('commander') | 6 | const program = require('commander') |
6 | const series = require('async/series') | 7 | const series = require('async/series') |
7 | 8 | ||
@@ -16,32 +17,41 @@ const videosUtils = require('../utils/videos') | |||
16 | program | 17 | program |
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 | ||
25 | const createWeight = parseInt(program.create) || 5 | 28 | const createWeight = program.create !== undefined ? parseInt(program.create) : 5 |
26 | const removeWeight = parseInt(program.remove) || 4 | 29 | const removeWeight = program.remove !== undefined ? parseInt(program.remove) : 4 |
30 | const updateWeight = program.update !== undefined ? parseInt(program.update) : 4 | ||
27 | const flushAtExit = program.flush || false | 31 | const flushAtExit = program.flush || false |
28 | const actionInterval = parseInt(program.action) || 500 | 32 | const actionInterval = program.action !== undefined ? parseInt(program.action) : 500 |
29 | let integrityInterval = parseInt(program.integrity) || 60000 | 33 | const integrityInterval = program.integrity !== undefined ? parseInt(program.integrity) : 60000 |
34 | const displayDiffOnFail = program.integrity || false | ||
30 | 35 | ||
31 | const numberOfPods = 6 | 36 | const numberOfPods = 6 |
32 | // Wait requests between pods | ||
33 | const requestsMaxPerInterval = constants.INTERVAL / actionInterval | ||
34 | const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT) | ||
35 | const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.INTERVAL) + 1000 | ||
36 | 37 | ||
37 | integrityInterval += waitForBeforeIntegrityCheck | 38 | // Wait requests between pods |
39 | const baseRequestInterval = integrityInterval < constants.REQUESTS_INTERVAL ? constants.REQUESTS_INTERVAL : integrityInterval | ||
40 | const requestsMaxPerInterval = baseRequestInterval / actionInterval | ||
41 | const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT_PER_POD) | ||
42 | const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.REQUESTS_INTERVAL) - integrityInterval + 1000 | ||
38 | 43 | ||
39 | console.log('Create weight: %d, remove weight: %d.', createWeight, removeWeight) | 44 | console.log('Create weight: %d, update weight: %d, remove weight: %d.', createWeight, updateWeight, removeWeight) |
40 | if (flushAtExit) { | 45 | if (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 | } |
50 | if (displayDiffOnFail) { | ||
51 | console.log('Program will display diff on failure.') | ||
52 | } else { | ||
53 | console.log('Program will not display diff on failure') | ||
54 | } | ||
45 | console.log('Interval in ms for each action: %d.', actionInterval) | 55 | console.log('Interval in ms for each action: %d.', actionInterval) |
46 | console.log('Interval in ms for each integrity check: %d.', integrityInterval) | 56 | console.log('Interval in ms for each integrity check: %d.', integrityInterval) |
47 | console.log('Will wait %d ms before an integrity check.', waitForBeforeIntegrityCheck) | 57 | console.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) { | |||
160 | function upload (servers, numServer, callback) { | 174 | function 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 | ||
187 | function 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 | |||
173 | function remove (servers, numServer, callback) { | 207 | function 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 | ||
5 | const loginUtils = { | 5 | const 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 | ||
47 | function 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 | ||
48 | module.exports = loginUtils | 57 | module.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 | ||
9 | function dateIsValid (dateString) { | 9 | function 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 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const videosUtils = { | ||
6 | getVideoAbusesList, | ||
7 | getVideoAbusesListPagination, | ||
8 | getVideoAbusesListSort, | ||
9 | reportVideoAbuse | ||
10 | } | ||
11 | |||
12 | // ---------------------- Export functions -------------------- | ||
13 | |||
14 | function 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 | |||
31 | function 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 | |||
44 | function 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 | |||
58 | function 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 | |||
73 | module.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 | ||
60 | function getVideosListPagination (url, start, count, end) { | 61 | function 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 | ||
73 | function getVideosListSort (url, sort, end) { | 82 | function getVideosListSort (url, sort, end) { |
@@ -115,18 +124,26 @@ function searchVideo (url, search, field, end) { | |||
115 | .end(end) | 124 | .end(end) |
116 | } | 125 | } |
117 | 126 | ||
118 | function searchVideoWithPagination (url, search, field, start, count, end) { | 127 | function 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 | ||
132 | function searchVideoWithSort (url, search, sort, end) { | 149 | function 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 | ||
214 | function 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 | ||
199 | module.exports = videosUtils | 241 | module.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 | ||
60 | In 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. | 60 | In 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 | ||
47 | The `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)). | 47 | The `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 | ||
65 | When a user uploads a video, the rest API create the torrent file and then adds it to its Mongo database. | 65 | When a user uploads a video, the rest API create the torrent file and then adds it to its database. |
66 | 66 | ||
67 | If 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. | 67 | If 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 | ||