diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-09-04 20:07:54 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-09-04 20:07:54 +0200 |
commit | b0f9f39ed70299a208d1b388c72de8b7f3510cb7 (patch) | |
tree | 4b7d388125265533ac2f6d4bf457d018617e1db6 | |
parent | e7dbeae8d915cdf4470ceb51c2724b04148b30b5 (diff) | |
download | PeerTube-b0f9f39ed70299a208d1b388c72de8b7f3510cb7.tar.gz PeerTube-b0f9f39ed70299a208d1b388c72de8b7f3510cb7.tar.zst PeerTube-b0f9f39ed70299a208d1b388c72de8b7f3510cb7.zip |
Begin user quota
29 files changed, 274 insertions, 55 deletions
diff --git a/client/package.json b/client/package.json index 27246027b..f1c7e8799 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -80,9 +80,9 @@ | |||
80 | "string-replace-loader": "^1.0.3", | 80 | "string-replace-loader": "^1.0.3", |
81 | "style-loader": "^0.18.2", | 81 | "style-loader": "^0.18.2", |
82 | "tslib": "^1.5.0", | 82 | "tslib": "^1.5.0", |
83 | "tslint": "^5.4.3", | 83 | "tslint": "^5.7.0", |
84 | "tslint-loader": "^3.3.0", | 84 | "tslint-loader": "^3.3.0", |
85 | "typescript": "~2.4.0", | 85 | "typescript": "^2.5.2", |
86 | "url-loader": "^0.5.7", | 86 | "url-loader": "^0.5.7", |
87 | "video.js": "^6.2.0", | 87 | "video.js": "^6.2.0", |
88 | "videojs-dock": "^2.0.2", | 88 | "videojs-dock": "^2.0.2", |
diff --git a/client/src/app/+admin/users/shared/user.service.ts b/client/src/app/+admin/users/shared/user.service.ts index 1c1cd575e..ffd7ba7da 100644 --- a/client/src/app/+admin/users/shared/user.service.ts +++ b/client/src/app/+admin/users/shared/user.service.ts | |||
@@ -2,12 +2,15 @@ import { Injectable } from '@angular/core' | |||
2 | import 'rxjs/add/operator/catch' | 2 | import 'rxjs/add/operator/catch' |
3 | import 'rxjs/add/operator/map' | 3 | import 'rxjs/add/operator/map' |
4 | 4 | ||
5 | import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe' | ||
6 | |||
5 | import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' | 7 | import { AuthHttp, RestExtractor, RestDataSource, User } from '../../../shared' |
6 | import { UserCreate } from '../../../../../../shared' | 8 | import { UserCreate } from '../../../../../../shared' |
7 | 9 | ||
8 | @Injectable() | 10 | @Injectable() |
9 | export class UserService { | 11 | export class UserService { |
10 | private static BASE_USERS_URL = API_URL + '/api/v1/users/' | 12 | private static BASE_USERS_URL = API_URL + '/api/v1/users/' |
13 | private bytesPipe = new BytesPipe() | ||
11 | 14 | ||
12 | constructor ( | 15 | constructor ( |
13 | private authHttp: AuthHttp, | 16 | private authHttp: AuthHttp, |
@@ -21,10 +24,30 @@ export class UserService { | |||
21 | } | 24 | } |
22 | 25 | ||
23 | getDataSource () { | 26 | getDataSource () { |
24 | return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL) | 27 | return new RestDataSource(this.authHttp, UserService.BASE_USERS_URL, this.formatDataSource.bind(this)) |
25 | } | 28 | } |
26 | 29 | ||
27 | removeUser (user: User) { | 30 | removeUser (user: User) { |
28 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) | 31 | return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) |
29 | } | 32 | } |
33 | |||
34 | private formatDataSource (users: User[]) { | ||
35 | const newUsers = [] | ||
36 | |||
37 | users.forEach(user => { | ||
38 | let videoQuota | ||
39 | if (user.videoQuota === -1) { | ||
40 | videoQuota = 'Unlimited' | ||
41 | } else { | ||
42 | videoQuota = this.bytesPipe.transform(user.videoQuota) | ||
43 | } | ||
44 | |||
45 | const newUser = Object.assign(user, { | ||
46 | videoQuota | ||
47 | }) | ||
48 | newUsers.push(newUser) | ||
49 | }) | ||
50 | |||
51 | return newUsers | ||
52 | } | ||
30 | } | 53 | } |
diff --git a/client/src/app/+admin/users/user-add/user-add.component.html b/client/src/app/+admin/users/user-add/user-add.component.html index 9b487aa75..f84d72c7c 100644 --- a/client/src/app/+admin/users/user-add/user-add.component.html +++ b/client/src/app/+admin/users/user-add/user-add.component.html | |||
@@ -9,7 +9,7 @@ | |||
9 | <div class="form-group"> | 9 | <div class="form-group"> |
10 | <label for="username">Username</label> | 10 | <label for="username">Username</label> |
11 | <input | 11 | <input |
12 | type="text" class="form-control" id="username" placeholder="Username" | 12 | type="text" class="form-control" id="username" placeholder="john" |
13 | formControlName="username" | 13 | formControlName="username" |
14 | > | 14 | > |
15 | <div *ngIf="formErrors.username" class="alert alert-danger"> | 15 | <div *ngIf="formErrors.username" class="alert alert-danger"> |
@@ -20,7 +20,7 @@ | |||
20 | <div class="form-group"> | 20 | <div class="form-group"> |
21 | <label for="email">Email</label> | 21 | <label for="email">Email</label> |
22 | <input | 22 | <input |
23 | type="text" class="form-control" id="email" placeholder="Email" | 23 | type="text" class="form-control" id="email" placeholder="mail@example.com" |
24 | formControlName="email" | 24 | formControlName="email" |
25 | > | 25 | > |
26 | <div *ngIf="formErrors.email" class="alert alert-danger"> | 26 | <div *ngIf="formErrors.email" class="alert alert-danger"> |
@@ -31,7 +31,7 @@ | |||
31 | <div class="form-group"> | 31 | <div class="form-group"> |
32 | <label for="password">Password</label> | 32 | <label for="password">Password</label> |
33 | <input | 33 | <input |
34 | type="password" class="form-control" id="password" placeholder="Password" | 34 | type="password" class="form-control" id="password" |
35 | formControlName="password" | 35 | formControlName="password" |
36 | > | 36 | > |
37 | <div *ngIf="formErrors.password" class="alert alert-danger"> | 37 | <div *ngIf="formErrors.password" class="alert alert-danger"> |
@@ -39,6 +39,19 @@ | |||
39 | </div> | 39 | </div> |
40 | </div> | 40 | </div> |
41 | 41 | ||
42 | <div class="form-group"> | ||
43 | <label for="videoQuota">Video quota</label> | ||
44 | <select class="form-control" id="videoQuota" formControlName="videoQuota"> | ||
45 | <option value="-1">Unlimited</option> | ||
46 | <option value="100000000">100MB</option> | ||
47 | <option value="500000000">500MB</option> | ||
48 | <option value="1000000000">1GB</option> | ||
49 | <option value="5000000000">5GB</option> | ||
50 | <option value="20000000000">20GB</option> | ||
51 | <option value="50000000000">50GB</option> | ||
52 | </select> | ||
53 | </div> | ||
54 | |||
42 | <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid"> | 55 | <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid"> |
43 | </form> | 56 | </form> |
44 | </div> | 57 | </div> |
diff --git a/client/src/app/+admin/users/user-add/user-add.component.ts b/client/src/app/+admin/users/user-add/user-add.component.ts index 0dd99eccd..91377a933 100644 --- a/client/src/app/+admin/users/user-add/user-add.component.ts +++ b/client/src/app/+admin/users/user-add/user-add.component.ts | |||
@@ -9,7 +9,8 @@ import { | |||
9 | FormReactive, | 9 | FormReactive, |
10 | USER_USERNAME, | 10 | USER_USERNAME, |
11 | USER_EMAIL, | 11 | USER_EMAIL, |
12 | USER_PASSWORD | 12 | USER_PASSWORD, |
13 | USER_VIDEO_QUOTA | ||
13 | } from '../../../shared' | 14 | } from '../../../shared' |
14 | import { UserCreate } from '../../../../../../shared' | 15 | import { UserCreate } from '../../../../../../shared' |
15 | 16 | ||
@@ -24,12 +25,14 @@ export class UserAddComponent extends FormReactive implements OnInit { | |||
24 | formErrors = { | 25 | formErrors = { |
25 | 'username': '', | 26 | 'username': '', |
26 | 'email': '', | 27 | 'email': '', |
27 | 'password': '' | 28 | 'password': '', |
29 | 'videoQuota': '' | ||
28 | } | 30 | } |
29 | validationMessages = { | 31 | validationMessages = { |
30 | 'username': USER_USERNAME.MESSAGES, | 32 | 'username': USER_USERNAME.MESSAGES, |
31 | 'email': USER_EMAIL.MESSAGES, | 33 | 'email': USER_EMAIL.MESSAGES, |
32 | 'password': USER_PASSWORD.MESSAGES | 34 | 'password': USER_PASSWORD.MESSAGES, |
35 | 'videoQuota': USER_VIDEO_QUOTA.MESSAGES | ||
33 | } | 36 | } |
34 | 37 | ||
35 | constructor ( | 38 | constructor ( |
@@ -45,7 +48,8 @@ export class UserAddComponent extends FormReactive implements OnInit { | |||
45 | this.form = this.formBuilder.group({ | 48 | this.form = this.formBuilder.group({ |
46 | username: [ '', USER_USERNAME.VALIDATORS ], | 49 | username: [ '', USER_USERNAME.VALIDATORS ], |
47 | email: [ '', USER_EMAIL.VALIDATORS ], | 50 | email: [ '', USER_EMAIL.VALIDATORS ], |
48 | password: [ '', USER_PASSWORD.VALIDATORS ] | 51 | password: [ '', USER_PASSWORD.VALIDATORS ], |
52 | videoQuota: [ '-1', USER_VIDEO_QUOTA.VALIDATORS ] | ||
49 | }) | 53 | }) |
50 | 54 | ||
51 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | 55 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) |
@@ -60,6 +64,9 @@ export class UserAddComponent extends FormReactive implements OnInit { | |||
60 | 64 | ||
61 | const userCreate: UserCreate = this.form.value | 65 | const userCreate: UserCreate = this.form.value |
62 | 66 | ||
67 | // A select in HTML is always mapped as a string, we convert it to number | ||
68 | userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10) | ||
69 | |||
63 | this.userService.addUser(userCreate).subscribe( | 70 | this.userService.addUser(userCreate).subscribe( |
64 | () => { | 71 | () => { |
65 | this.notificationsService.success('Success', `User ${userCreate.username} created.`) | 72 | this.notificationsService.success('Success', `User ${userCreate.username} created.`) |
diff --git a/client/src/app/+admin/users/user-list/user-list.component.ts b/client/src/app/+admin/users/user-list/user-list.component.ts index 12826741c..dbb85cedd 100644 --- a/client/src/app/+admin/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/users/user-list/user-list.component.ts | |||
@@ -30,7 +30,7 @@ export class UserListComponent { | |||
30 | }, | 30 | }, |
31 | pager: { | 31 | pager: { |
32 | display: true, | 32 | display: true, |
33 | perPage: 10 | 33 | perPage: 1 |
34 | }, | 34 | }, |
35 | columns: { | 35 | columns: { |
36 | id: { | 36 | id: { |
@@ -43,6 +43,9 @@ export class UserListComponent { | |||
43 | email: { | 43 | email: { |
44 | title: 'Email' | 44 | title: 'Email' |
45 | }, | 45 | }, |
46 | videoQuota: { | ||
47 | title: 'Video quota' | ||
48 | }, | ||
46 | role: { | 49 | role: { |
47 | title: 'Role', | 50 | title: 'Role', |
48 | sort: false | 51 | sort: false |
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts index fd316583e..087a99760 100644 --- a/client/src/app/shared/forms/form-validators/user.ts +++ b/client/src/app/shared/forms/form-validators/user.ts | |||
@@ -22,3 +22,10 @@ export const USER_PASSWORD = { | |||
22 | 'minlength': 'Password must be at least 6 characters long.' | 22 | 'minlength': 'Password must be at least 6 characters long.' |
23 | } | 23 | } |
24 | } | 24 | } |
25 | export const USER_VIDEO_QUOTA = { | ||
26 | VALIDATORS: [ Validators.required, Validators.min(-1) ], | ||
27 | MESSAGES: { | ||
28 | 'required': 'Video quota is required.', | ||
29 | 'min': 'Quota must be greater than -1.' | ||
30 | } | ||
31 | } \ No newline at end of file | ||
diff --git a/client/src/app/shared/rest/rest-data-source.ts b/client/src/app/shared/rest/rest-data-source.ts index 7956637e0..5c205d280 100644 --- a/client/src/app/shared/rest/rest-data-source.ts +++ b/client/src/app/shared/rest/rest-data-source.ts | |||
@@ -3,14 +3,31 @@ import { Http, RequestOptionsArgs, URLSearchParams, Response } from '@angular/ht | |||
3 | import { ServerDataSource } from 'ng2-smart-table' | 3 | import { ServerDataSource } from 'ng2-smart-table' |
4 | 4 | ||
5 | export class RestDataSource extends ServerDataSource { | 5 | export class RestDataSource extends ServerDataSource { |
6 | constructor (http: Http, endpoint: string) { | 6 | private updateResponse: (input: any[]) => any[] |
7 | |||
8 | constructor (http: Http, endpoint: string, updateResponse?: (input: any[]) => any[]) { | ||
7 | const options = { | 9 | const options = { |
8 | endPoint: endpoint, | 10 | endPoint: endpoint, |
9 | sortFieldKey: 'sort', | 11 | sortFieldKey: 'sort', |
10 | dataKey: 'data' | 12 | dataKey: 'data' |
11 | } | 13 | } |
12 | |||
13 | super(http, options) | 14 | super(http, options) |
15 | |||
16 | if (updateResponse) { | ||
17 | this.updateResponse = updateResponse | ||
18 | } | ||
19 | } | ||
20 | |||
21 | protected extractDataFromResponse (res: Response) { | ||
22 | const json = res.json() | ||
23 | if (!json) return [] | ||
24 | let data = json.data | ||
25 | |||
26 | if (this.updateResponse !== undefined) { | ||
27 | data = this.updateResponse(data) | ||
28 | } | ||
29 | |||
30 | return data | ||
14 | } | 31 | } |
15 | 32 | ||
16 | protected extractTotalFromResponse (res: Response) { | 33 | protected extractTotalFromResponse (res: Response) { |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 1c2b481e3..bf12876c7 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -6,6 +6,7 @@ export class User implements UserServerModel { | |||
6 | email: string | 6 | email: string |
7 | role: UserRole | 7 | role: UserRole |
8 | displayNSFW: boolean | 8 | displayNSFW: boolean |
9 | videoQuota: number | ||
9 | createdAt: Date | 10 | createdAt: Date |
10 | 11 | ||
11 | constructor (hash: { | 12 | constructor (hash: { |
@@ -13,6 +14,7 @@ export class User implements UserServerModel { | |||
13 | username: string, | 14 | username: string, |
14 | email: string, | 15 | email: string, |
15 | role: UserRole, | 16 | role: UserRole, |
17 | videoQuota?: number, | ||
16 | displayNSFW?: boolean, | 18 | displayNSFW?: boolean, |
17 | createdAt?: Date | 19 | createdAt?: Date |
18 | }) { | 20 | }) { |
@@ -20,9 +22,16 @@ export class User implements UserServerModel { | |||
20 | this.username = hash.username | 22 | this.username = hash.username |
21 | this.email = hash.email | 23 | this.email = hash.email |
22 | this.role = hash.role | 24 | this.role = hash.role |
23 | this.displayNSFW = hash.displayNSFW | ||
24 | 25 | ||
25 | if (hash.createdAt) { | 26 | if (hash.videoQuota !== undefined) { |
27 | this.videoQuota = hash.videoQuota | ||
28 | } | ||
29 | |||
30 | if (hash.displayNSFW !== undefined) { | ||
31 | this.displayNSFW = hash.displayNSFW | ||
32 | } | ||
33 | |||
34 | if (hash.createdAt !== undefined) { | ||
26 | this.createdAt = hash.createdAt | 35 | this.createdAt = hash.createdAt |
27 | } | 36 | } |
28 | } | 37 | } |
diff --git a/client/tslint.json b/client/tslint.json index cfad2a5d9..b1e211ee9 100644 --- a/client/tslint.json +++ b/client/tslint.json | |||
@@ -4,7 +4,6 @@ | |||
4 | "rules": { | 4 | "rules": { |
5 | "no-inferrable-types": true, | 5 | "no-inferrable-types": true, |
6 | "eofline": true, | 6 | "eofline": true, |
7 | "indent": ["spaces"], | ||
8 | "max-line-length": [true, 140], | 7 | "max-line-length": [true, 140], |
9 | "no-floating-promises": false, | 8 | "no-floating-promises": false, |
10 | "no-unused-variable": false, // Bug, wait TypeScript 2.4 | 9 | "no-unused-variable": false, // Bug, wait TypeScript 2.4 |
diff --git a/client/yarn.lock b/client/yarn.lock index 0fc5ec418..9478e23b2 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -6740,9 +6740,9 @@ tslint-loader@^3.3.0: | |||
6740 | rimraf "^2.4.4" | 6740 | rimraf "^2.4.4" |
6741 | semver "^5.3.0" | 6741 | semver "^5.3.0" |
6742 | 6742 | ||
6743 | tslint@^5.4.3: | 6743 | tslint@^5.7.0: |
6744 | version "5.6.0" | 6744 | version "5.7.0" |
6745 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.6.0.tgz#088aa6c6026623338650b2900828ab3edf59f6cf" | 6745 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.7.0.tgz#c25e0d0c92fa1201c2bc30e844e08e682b4f3552" |
6746 | dependencies: | 6746 | dependencies: |
6747 | babel-code-frame "^6.22.0" | 6747 | babel-code-frame "^6.22.0" |
6748 | colors "^1.1.2" | 6748 | colors "^1.1.2" |
@@ -6753,7 +6753,7 @@ tslint@^5.4.3: | |||
6753 | resolve "^1.3.2" | 6753 | resolve "^1.3.2" |
6754 | semver "^5.3.0" | 6754 | semver "^5.3.0" |
6755 | tslib "^1.7.1" | 6755 | tslib "^1.7.1" |
6756 | tsutils "^2.7.1" | 6756 | tsutils "^2.8.1" |
6757 | 6757 | ||
6758 | tsml@1.0.1: | 6758 | tsml@1.0.1: |
6759 | version "1.0.1" | 6759 | version "1.0.1" |
@@ -6763,9 +6763,9 @@ tsutils@^1.4.0: | |||
6763 | version "1.9.1" | 6763 | version "1.9.1" |
6764 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" | 6764 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" |
6765 | 6765 | ||
6766 | tsutils@^2.7.1: | 6766 | tsutils@^2.8.1: |
6767 | version "2.8.1" | 6767 | version "2.8.2" |
6768 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.1.tgz#3771404e7ca9f0bedf5d919a47a4b1890a68efff" | 6768 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a" |
6769 | dependencies: | 6769 | dependencies: |
6770 | tslib "^1.7.1" | 6770 | tslib "^1.7.1" |
6771 | 6771 | ||
@@ -6806,9 +6806,9 @@ typedarray@^0.0.6: | |||
6806 | version "0.0.6" | 6806 | version "0.0.6" |
6807 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" | 6807 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" |
6808 | 6808 | ||
6809 | typescript@~2.4.0: | 6809 | typescript@^2.5.2: |
6810 | version "2.4.2" | 6810 | version "2.5.2" |
6811 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.2.tgz#f8395f85d459276067c988aa41837a8f82870844" | 6811 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" |
6812 | 6812 | ||
6813 | uglify-js@3.0.x, uglify-js@^3.0.6: | 6813 | uglify-js@3.0.x, uglify-js@^3.0.6: |
6814 | version "3.0.28" | 6814 | version "3.0.28" |
diff --git a/config/default.yaml b/config/default.yaml index a97d3ff78..4c19a5b2d 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -35,6 +35,11 @@ signup: | |||
35 | enabled: false | 35 | enabled: false |
36 | limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited | 36 | limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited |
37 | 37 | ||
38 | user: | ||
39 | # Default value of maximum video BYTES the user can upload (does not take into account transcoded files). | ||
40 | # -1 == unlimited | ||
41 | video_quota: -1 | ||
42 | |||
38 | # If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag | 43 | # If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag |
39 | # Uses a lot of CPU! | 44 | # Uses a lot of CPU! |
40 | transcoding: | 45 | transcoding: |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 90e07f577..987da12cc 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -36,6 +36,11 @@ signup: | |||
36 | enabled: false | 36 | enabled: false |
37 | limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited | 37 | limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited |
38 | 38 | ||
39 | user: | ||
40 | # Default value of maximum video BYTES the user can upload (does not take into account transcoded files). | ||
41 | # -1 == unlimited | ||
42 | video_quota: -1 | ||
43 | |||
39 | # If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag | 44 | # If enabled, the video will be transcoded to mp4 (x264) with "faststart" flag |
40 | # Uses a lot of CPU! | 45 | # Uses a lot of CPU! |
41 | transcoding: | 46 | transcoding: |
diff --git a/package.json b/package.json index 2a1b0bde3..900d04052 100644 --- a/package.json +++ b/package.json | |||
@@ -79,7 +79,7 @@ | |||
79 | "scripty": "^1.5.0", | 79 | "scripty": "^1.5.0", |
80 | "sequelize": "^4.7.5", | 80 | "sequelize": "^4.7.5", |
81 | "ts-node": "^3.0.6", | 81 | "ts-node": "^3.0.6", |
82 | "typescript": "^2.4.1", | 82 | "typescript": "^2.5.2", |
83 | "validator": "^8.1.0", | 83 | "validator": "^8.1.0", |
84 | "winston": "^2.1.1", | 84 | "winston": "^2.1.1", |
85 | "ws": "^3.1.0" | 85 | "ws": "^3.1.0" |
@@ -109,7 +109,7 @@ | |||
109 | "source-map-support": "^0.4.15", | 109 | "source-map-support": "^0.4.15", |
110 | "standard": "^10.0.0", | 110 | "standard": "^10.0.0", |
111 | "supertest": "^3.0.0", | 111 | "supertest": "^3.0.0", |
112 | "tslint": "^5.2.0", | 112 | "tslint": "^5.7.0", |
113 | "tslint-config-standard": "^6.0.0", | 113 | "tslint-config-standard": "^6.0.0", |
114 | "webtorrent": "^0.98.0" | 114 | "webtorrent": "^0.98.0" |
115 | }, | 115 | }, |
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index 04d885185..1b5b7f903 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | 2 | ||
3 | import { database as db } from '../../initializers/database' | 3 | import { database as db } from '../../initializers/database' |
4 | import { USER_ROLES } from '../../initializers' | 4 | import { USER_ROLES, CONFIG } from '../../initializers' |
5 | import { logger, getFormattedObjects } from '../../helpers' | 5 | import { logger, getFormattedObjects } from '../../helpers' |
6 | import { | 6 | import { |
7 | authenticate, | 7 | authenticate, |
@@ -80,12 +80,18 @@ export { | |||
80 | function createUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 80 | function createUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
81 | const body: UserCreate = req.body | 81 | const body: UserCreate = req.body |
82 | 82 | ||
83 | // On registration, we set the user video quota | ||
84 | if (body.videoQuota === undefined) { | ||
85 | body.videoQuota = CONFIG.USER.VIDEO_QUOTA | ||
86 | } | ||
87 | |||
83 | const user = db.User.build({ | 88 | const user = db.User.build({ |
84 | username: body.username, | 89 | username: body.username, |
85 | password: body.password, | 90 | password: body.password, |
86 | email: body.email, | 91 | email: body.email, |
87 | displayNSFW: false, | 92 | displayNSFW: false, |
88 | role: USER_ROLES.USER | 93 | role: USER_ROLES.USER, |
94 | videoQuota: body.videoQuota | ||
89 | }) | 95 | }) |
90 | 96 | ||
91 | user.save() | 97 | user.save() |
@@ -140,6 +146,7 @@ function updateUser (req: express.Request, res: express.Response, next: express. | |||
140 | .then(user => { | 146 | .then(user => { |
141 | if (body.password) user.password = body.password | 147 | if (body.password) user.password = body.password |
142 | if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW | 148 | if (body.displayNSFW !== undefined) user.displayNSFW = body.displayNSFW |
149 | if (body.videoQuota !== undefined) user.videoQuota = body.videoQuota | ||
143 | 150 | ||
144 | return user.save() | 151 | return user.save() |
145 | }) | 152 | }) |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 2b37bdde8..00061f9df 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -15,6 +15,10 @@ function isUserRoleValid (value: string) { | |||
15 | return values(USER_ROLES).indexOf(value as UserRole) !== -1 | 15 | return values(USER_ROLES).indexOf(value as UserRole) !== -1 |
16 | } | 16 | } |
17 | 17 | ||
18 | function isUserVideoQuotaValid (value: string) { | ||
19 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA) | ||
20 | } | ||
21 | |||
18 | function isUserUsernameValid (value: string) { | 22 | function isUserUsernameValid (value: string) { |
19 | const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max | 23 | const max = USERS_CONSTRAINTS_FIELDS.USERNAME.max |
20 | const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min | 24 | const min = USERS_CONSTRAINTS_FIELDS.USERNAME.min |
@@ -30,6 +34,7 @@ function isUserDisplayNSFWValid (value: any) { | |||
30 | export { | 34 | export { |
31 | isUserPasswordValid, | 35 | isUserPasswordValid, |
32 | isUserRoleValid, | 36 | isUserRoleValid, |
37 | isUserVideoQuotaValid, | ||
33 | isUserUsernameValid, | 38 | isUserUsernameValid, |
34 | isUserDisplayNSFWValid | 39 | isUserDisplayNSFWValid |
35 | } | 40 | } |
@@ -39,6 +44,7 @@ declare module 'express-validator' { | |||
39 | isUserPasswordValid, | 44 | isUserPasswordValid, |
40 | isUserRoleValid, | 45 | isUserRoleValid, |
41 | isUserUsernameValid, | 46 | isUserUsernameValid, |
42 | isUserDisplayNSFWValid | 47 | isUserDisplayNSFWValid, |
48 | isUserVideoQuotaValid | ||
43 | } | 49 | } |
44 | } | 50 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 50a939083..b93a85859 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -15,7 +15,7 @@ import { | |||
15 | 15 | ||
16 | // --------------------------------------------------------------------------- | 16 | // --------------------------------------------------------------------------- |
17 | 17 | ||
18 | const LAST_MIGRATION_VERSION = 65 | 18 | const LAST_MIGRATION_VERSION = 70 |
19 | 19 | ||
20 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
21 | 21 | ||
@@ -77,7 +77,10 @@ const CONFIG = { | |||
77 | }, | 77 | }, |
78 | SIGNUP: { | 78 | SIGNUP: { |
79 | ENABLED: config.get<boolean>('signup.enabled'), | 79 | ENABLED: config.get<boolean>('signup.enabled'), |
80 | LIMIT: config.get<number>('signup.limit') | 80 | LIMIT: config.get<number>('signup.limit'), |
81 | }, | ||
82 | USER: { | ||
83 | VIDEO_QUOTA: config.get<number>('user.video_quota') | ||
81 | }, | 84 | }, |
82 | TRANSCODING: { | 85 | TRANSCODING: { |
83 | ENABLED: config.get<boolean>('transcoding.enabled'), | 86 | ENABLED: config.get<boolean>('transcoding.enabled'), |
@@ -97,7 +100,8 @@ CONFIG.WEBSERVER.HOST = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | |||
97 | const CONSTRAINTS_FIELDS = { | 100 | const CONSTRAINTS_FIELDS = { |
98 | USERS: { | 101 | USERS: { |
99 | USERNAME: { min: 3, max: 20 }, // Length | 102 | USERNAME: { min: 3, max: 20 }, // Length |
100 | PASSWORD: { min: 6, max: 255 } // Length | 103 | PASSWORD: { min: 6, max: 255 }, // Length |
104 | VIDEO_QUOTA: { min: -1 } | ||
101 | }, | 105 | }, |
102 | VIDEO_ABUSES: { | 106 | VIDEO_ABUSES: { |
103 | REASON: { min: 2, max: 300 } // Length | 107 | REASON: { min: 2, max: 300 } // Length |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index c0df2b63a..d04c8db1b 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { flattenDepth } from 'lodash' | 2 | import { flattenDepth } from 'lodash' |
3 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | ||
3 | import * as Sequelize from 'sequelize' | 4 | import * as Sequelize from 'sequelize' |
4 | import * as Promise from 'bluebird' | 5 | import * as Promise from 'bluebird' |
5 | 6 | ||
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index 43b5adfed..10b74b85f 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -38,12 +38,12 @@ function removeCacheDirectories () { | |||
38 | } | 38 | } |
39 | 39 | ||
40 | function createDirectoriesIfNotExist () { | 40 | function createDirectoriesIfNotExist () { |
41 | const storages = CONFIG.STORAGE | 41 | const storage = CONFIG.STORAGE |
42 | const cacheDirectories = CACHE.DIRECTORIES | 42 | const cacheDirectories = CACHE.DIRECTORIES |
43 | 43 | ||
44 | const tasks = [] | 44 | const tasks = [] |
45 | Object.keys(storages).forEach(key => { | 45 | Object.keys(storage).forEach(key => { |
46 | const dir = storages[key] | 46 | const dir = storage[key] |
47 | tasks.push(mkdirpPromise(dir)) | 47 | tasks.push(mkdirpPromise(dir)) |
48 | }) | 48 | }) |
49 | 49 | ||
@@ -112,7 +112,8 @@ function createOAuthAdminIfNotExist () { | |||
112 | username, | 112 | username, |
113 | email, | 113 | email, |
114 | password, | 114 | password, |
115 | role | 115 | role, |
116 | videoQuota: -1 | ||
116 | } | 117 | } |
117 | 118 | ||
118 | return db.User.create(userData, createOptions).then(createdUser => { | 119 | return db.User.create(userData, createOptions).then(createdUser => { |
diff --git a/server/initializers/migrations/0070-user-video-quota.ts b/server/initializers/migrations/0070-user-video-quota.ts new file mode 100644 index 000000000..dec4d46dd --- /dev/null +++ b/server/initializers/migrations/0070-user-video-quota.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import * as Promise from 'bluebird' | ||
3 | |||
4 | function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize, | ||
8 | db: any | ||
9 | }): Promise<void> { | ||
10 | const q = utils.queryInterface | ||
11 | |||
12 | const data = { | ||
13 | type: Sequelize.BIGINT, | ||
14 | allowNull: false, | ||
15 | defaultValue: -1 | ||
16 | } | ||
17 | |||
18 | return q.addColumn('Users', 'videoQuota', data) | ||
19 | .then(() => { | ||
20 | data.defaultValue = null | ||
21 | return q.changeColumn('Users', 'videoQuota', data) | ||
22 | }) | ||
23 | } | ||
24 | |||
25 | function down (options) { | ||
26 | throw new Error('Not implemented.') | ||
27 | } | ||
28 | |||
29 | export { | ||
30 | up, | ||
31 | down | ||
32 | } | ||
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 71e529872..eeb0e3557 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -12,6 +12,7 @@ function usersAddValidator (req: express.Request, res: express.Response, next: e | |||
12 | req.checkBody('username', 'Should have a valid username').isUserUsernameValid() | 12 | req.checkBody('username', 'Should have a valid username').isUserUsernameValid() |
13 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() | 13 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() |
14 | req.checkBody('email', 'Should have a valid email').isEmail() | 14 | req.checkBody('email', 'Should have a valid email').isEmail() |
15 | req.checkBody('videoQuota', 'Should have a valid user quota').isUserVideoQuotaValid() | ||
15 | 16 | ||
16 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) | 17 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) |
17 | 18 | ||
@@ -55,6 +56,7 @@ function usersUpdateValidator (req: express.Request, res: express.Response, next | |||
55 | // Add old password verification | 56 | // Add old password verification |
56 | req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid() | 57 | req.checkBody('password', 'Should have a valid password').optional().isUserPasswordValid() |
57 | req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid() | 58 | req.checkBody('displayNSFW', 'Should have a valid display Not Safe For Work attribute').optional().isUserDisplayNSFWValid() |
59 | req.checkBody('videoQuota', 'Should have a valid user quota').optional().isUserVideoQuotaValid() | ||
58 | 60 | ||
59 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) | 61 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) |
60 | 62 | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 29c1ee0ef..1d19ebfd9 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -24,10 +24,23 @@ function videosAddValidator (req: express.Request, res: express.Response, next: | |||
24 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) | 24 | logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files }) |
25 | 25 | ||
26 | checkErrors(req, res, () => { | 26 | checkErrors(req, res, () => { |
27 | const videoFile = req.files['videofile'][0] | 27 | const videoFile: Express.Multer.File = req.files['videofile'][0] |
28 | const user = res.locals.oauth.token.User | ||
28 | 29 | ||
29 | db.Video.getDurationFromFile(videoFile.path) | 30 | user.isAbleToUploadVideo(videoFile) |
31 | .then(isAble => { | ||
32 | if (isAble === false) { | ||
33 | res.status(403).send('The user video quota is exceeded with this video.') | ||
34 | |||
35 | return undefined | ||
36 | } | ||
37 | |||
38 | return db.Video.getDurationFromFile(videoFile.path) | ||
39 | }) | ||
30 | .then(duration => { | 40 | .then(duration => { |
41 | // Previous test failed, abort | ||
42 | if (duration === undefined) return | ||
43 | |||
31 | if (!isVideoDurationValid('' + duration)) { | 44 | if (!isVideoDurationValid('' + duration)) { |
32 | return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') | 45 | return res.status(400).send('Duration of the video file is too big (max: ' + CONSTRAINTS_FIELDS.VIDEOS.DURATION.max + 's).') |
33 | } | 46 | } |
diff --git a/server/models/user/user-interface.ts b/server/models/user/user-interface.ts index 0b97a8f6d..8974a9a97 100644 --- a/server/models/user/user-interface.ts +++ b/server/models/user/user-interface.ts | |||
@@ -11,6 +11,7 @@ export namespace UserMethods { | |||
11 | 11 | ||
12 | export type ToFormattedJSON = (this: UserInstance) => FormattedUser | 12 | export type ToFormattedJSON = (this: UserInstance) => FormattedUser |
13 | export type IsAdmin = (this: UserInstance) => boolean | 13 | export type IsAdmin = (this: UserInstance) => boolean |
14 | export type IsAbleToUploadVideo = (this: UserInstance, videoFile: Express.Multer.File) => Promise<boolean> | ||
14 | 15 | ||
15 | export type CountTotal = () => Promise<number> | 16 | export type CountTotal = () => Promise<number> |
16 | 17 | ||
@@ -31,6 +32,7 @@ export interface UserClass { | |||
31 | isPasswordMatch: UserMethods.IsPasswordMatch, | 32 | isPasswordMatch: UserMethods.IsPasswordMatch, |
32 | toFormattedJSON: UserMethods.ToFormattedJSON, | 33 | toFormattedJSON: UserMethods.ToFormattedJSON, |
33 | isAdmin: UserMethods.IsAdmin, | 34 | isAdmin: UserMethods.IsAdmin, |
35 | isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo, | ||
34 | 36 | ||
35 | countTotal: UserMethods.CountTotal, | 37 | countTotal: UserMethods.CountTotal, |
36 | getByUsername: UserMethods.GetByUsername, | 38 | getByUsername: UserMethods.GetByUsername, |
@@ -42,11 +44,13 @@ export interface UserClass { | |||
42 | } | 44 | } |
43 | 45 | ||
44 | export interface UserAttributes { | 46 | export interface UserAttributes { |
47 | id?: number | ||
45 | password: string | 48 | password: string |
46 | username: string | 49 | username: string |
47 | email: string | 50 | email: string |
48 | displayNSFW?: boolean | 51 | displayNSFW?: boolean |
49 | role: UserRole | 52 | role: UserRole |
53 | videoQuota: number | ||
50 | } | 54 | } |
51 | 55 | ||
52 | export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { | 56 | export interface UserInstance extends UserClass, UserAttributes, Sequelize.Instance<UserAttributes> { |
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index d481fa13c..12a7547f5 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import * as Sequelize from 'sequelize' | 2 | import * as Sequelize from 'sequelize' |
3 | import * as Promise from 'bluebird' | ||
3 | 4 | ||
4 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
5 | import { USER_ROLES } from '../../initializers' | 6 | import { USER_ROLES } from '../../initializers' |
@@ -8,7 +9,8 @@ import { | |||
8 | comparePassword, | 9 | comparePassword, |
9 | isUserPasswordValid, | 10 | isUserPasswordValid, |
10 | isUserUsernameValid, | 11 | isUserUsernameValid, |
11 | isUserDisplayNSFWValid | 12 | isUserDisplayNSFWValid, |
13 | isUserVideoQuotaValid | ||
12 | } from '../../helpers' | 14 | } from '../../helpers' |
13 | 15 | ||
14 | import { addMethodsToModel } from '../utils' | 16 | import { addMethodsToModel } from '../utils' |
@@ -30,6 +32,7 @@ let listForApi: UserMethods.ListForApi | |||
30 | let loadById: UserMethods.LoadById | 32 | let loadById: UserMethods.LoadById |
31 | let loadByUsername: UserMethods.LoadByUsername | 33 | let loadByUsername: UserMethods.LoadByUsername |
32 | let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail | 34 | let loadByUsernameOrEmail: UserMethods.LoadByUsernameOrEmail |
35 | let isAbleToUploadVideo: UserMethods.IsAbleToUploadVideo | ||
33 | 36 | ||
34 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { | 37 | export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { |
35 | User = sequelize.define<UserInstance, UserAttributes>('User', | 38 | User = sequelize.define<UserInstance, UserAttributes>('User', |
@@ -75,6 +78,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
75 | role: { | 78 | role: { |
76 | type: DataTypes.ENUM(values(USER_ROLES)), | 79 | type: DataTypes.ENUM(values(USER_ROLES)), |
77 | allowNull: false | 80 | allowNull: false |
81 | }, | ||
82 | videoQuota: { | ||
83 | type: DataTypes.BIGINT, | ||
84 | allowNull: false, | ||
85 | validate: { | ||
86 | videoQuotaValid: value => { | ||
87 | const res = isUserVideoQuotaValid(value) | ||
88 | if (res === false) throw new Error('Video quota is not valid.') | ||
89 | } | ||
90 | } | ||
78 | } | 91 | } |
79 | }, | 92 | }, |
80 | { | 93 | { |
@@ -109,7 +122,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
109 | const instanceMethods = [ | 122 | const instanceMethods = [ |
110 | isPasswordMatch, | 123 | isPasswordMatch, |
111 | toFormattedJSON, | 124 | toFormattedJSON, |
112 | isAdmin | 125 | isAdmin, |
126 | isAbleToUploadVideo | ||
113 | ] | 127 | ] |
114 | addMethodsToModel(User, classMethods, instanceMethods) | 128 | addMethodsToModel(User, classMethods, instanceMethods) |
115 | 129 | ||
@@ -136,6 +150,7 @@ toFormattedJSON = function (this: UserInstance) { | |||
136 | email: this.email, | 150 | email: this.email, |
137 | displayNSFW: this.displayNSFW, | 151 | displayNSFW: this.displayNSFW, |
138 | role: this.role, | 152 | role: this.role, |
153 | videoQuota: this.videoQuota, | ||
139 | createdAt: this.createdAt | 154 | createdAt: this.createdAt |
140 | } | 155 | } |
141 | } | 156 | } |
@@ -144,6 +159,14 @@ isAdmin = function (this: UserInstance) { | |||
144 | return this.role === USER_ROLES.ADMIN | 159 | return this.role === USER_ROLES.ADMIN |
145 | } | 160 | } |
146 | 161 | ||
162 | isAbleToUploadVideo = function (this: UserInstance, videoFile: Express.Multer.File) { | ||
163 | if (this.videoQuota === -1) return Promise.resolve(true) | ||
164 | |||
165 | return getOriginalVideoFileTotalFromUser(this).then(totalBytes => { | ||
166 | return (videoFile.size + totalBytes) < this.videoQuota | ||
167 | }) | ||
168 | } | ||
169 | |||
147 | // ------------------------------ STATICS ------------------------------ | 170 | // ------------------------------ STATICS ------------------------------ |
148 | 171 | ||
149 | function associate (models) { | 172 | function associate (models) { |
@@ -215,3 +238,36 @@ loadByUsernameOrEmail = function (username: string, email: string) { | |||
215 | // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 | 238 | // FIXME: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18387 |
216 | return (User as any).findOne(query) | 239 | return (User as any).findOne(query) |
217 | } | 240 | } |
241 | |||
242 | // --------------------------------------------------------------------------- | ||
243 | |||
244 | function getOriginalVideoFileTotalFromUser (user: UserInstance) { | ||
245 | const query = { | ||
246 | attributes: [ | ||
247 | Sequelize.fn('COUNT', Sequelize.col('VideoFile.size'), 'totalVideoBytes') | ||
248 | ], | ||
249 | where: { | ||
250 | id: user.id | ||
251 | }, | ||
252 | include: [ | ||
253 | { | ||
254 | model: User['sequelize'].models.Author, | ||
255 | include: [ | ||
256 | { | ||
257 | model: User['sequelize'].models.Video, | ||
258 | include: [ | ||
259 | { | ||
260 | model: User['sequelize'].models.VideoFile | ||
261 | } | ||
262 | ] | ||
263 | } | ||
264 | ] | ||
265 | } | ||
266 | ] | ||
267 | } | ||
268 | |||
269 | // FIXME: cast to any because of bad typing... | ||
270 | return User.findAll(query).then((res: any) => { | ||
271 | return res.totalVideoBytes | ||
272 | }) | ||
273 | } | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 7dfea8ac9..4fb4485d8 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -9,6 +9,7 @@ import * as Sequelize from 'sequelize' | |||
9 | import * as Promise from 'bluebird' | 9 | import * as Promise from 'bluebird' |
10 | 10 | ||
11 | import { TagInstance } from './tag-interface' | 11 | import { TagInstance } from './tag-interface' |
12 | import { UserInstance } from '../user/user-interface' | ||
12 | import { | 13 | import { |
13 | logger, | 14 | logger, |
14 | isVideoNameValid, | 15 | isVideoNameValid, |
@@ -582,7 +583,7 @@ transcodeVideofile = function (this: VideoInstance, inputVideoFile: VideoFileIns | |||
582 | return res() | 583 | return res() |
583 | }) | 584 | }) |
584 | .catch(err => { | 585 | .catch(err => { |
585 | // Autodestruction... | 586 | // Auto destruction... |
586 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) | 587 | this.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', err)) |
587 | 588 | ||
588 | return rej(err) | 589 | return rej(err) |
@@ -608,8 +609,8 @@ removeFile = function (this: VideoInstance, videoFile: VideoFileInstance) { | |||
608 | } | 609 | } |
609 | 610 | ||
610 | removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) { | 611 | removeTorrent = function (this: VideoInstance, videoFile: VideoFileInstance) { |
611 | const torrenPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 612 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
612 | return unlinkPromise(torrenPath) | 613 | return unlinkPromise(torrentPath) |
613 | } | 614 | } |
614 | 615 | ||
615 | // ------------------------------ STATICS ------------------------------ | 616 | // ------------------------------ STATICS ------------------------------ |
diff --git a/shared/models/users/user-create.model.ts b/shared/models/users/user-create.model.ts index 2cddcdcb0..49fa2549d 100644 --- a/shared/models/users/user-create.model.ts +++ b/shared/models/users/user-create.model.ts | |||
@@ -2,4 +2,5 @@ export interface UserCreate { | |||
2 | username: string | 2 | username: string |
3 | password: string | 3 | password: string |
4 | email: string | 4 | email: string |
5 | videoQuota: number | ||
5 | } | 6 | } |
diff --git a/shared/models/users/user-update.model.ts b/shared/models/users/user-update.model.ts index 8b9abfb15..895ec0681 100644 --- a/shared/models/users/user-update.model.ts +++ b/shared/models/users/user-update.model.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export interface UserUpdate { | 1 | export interface UserUpdate { |
2 | displayNSFW?: boolean | 2 | displayNSFW?: boolean |
3 | password?: string | 3 | password?: string |
4 | videoQuota?: number | ||
4 | } | 5 | } |
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index 5c48a17b2..867a6dde5 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -6,5 +6,6 @@ export interface User { | |||
6 | email: string | 6 | email: string |
7 | displayNSFW: boolean | 7 | displayNSFW: boolean |
8 | role: UserRole | 8 | role: UserRole |
9 | videoQuota: number | ||
9 | createdAt: Date | 10 | createdAt: Date |
10 | } | 11 | } |
diff --git a/tslint.json b/tslint.json index 70e5d9bb4..6e982ca85 100644 --- a/tslint.json +++ b/tslint.json | |||
@@ -4,6 +4,7 @@ | |||
4 | "no-inferrable-types": true, | 4 | "no-inferrable-types": true, |
5 | "eofline": true, | 5 | "eofline": true, |
6 | "indent": ["spaces"], | 6 | "indent": ["spaces"], |
7 | "ter-indent": [true, 2], | ||
7 | "max-line-length": [true, 140], | 8 | "max-line-length": [true, 140], |
8 | "no-floating-promises": false | 9 | "no-floating-promises": false |
9 | } | 10 | } |
@@ -3755,9 +3755,9 @@ tslint-eslint-rules@^4.0.0: | |||
3755 | tslib "^1.0.0" | 3755 | tslib "^1.0.0" |
3756 | tsutils "^1.4.0" | 3756 | tsutils "^1.4.0" |
3757 | 3757 | ||
3758 | tslint@^5.2.0: | 3758 | tslint@^5.7.0: |
3759 | version "5.6.0" | 3759 | version "5.7.0" |
3760 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.6.0.tgz#088aa6c6026623338650b2900828ab3edf59f6cf" | 3760 | resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.7.0.tgz#c25e0d0c92fa1201c2bc30e844e08e682b4f3552" |
3761 | dependencies: | 3761 | dependencies: |
3762 | babel-code-frame "^6.22.0" | 3762 | babel-code-frame "^6.22.0" |
3763 | colors "^1.1.2" | 3763 | colors "^1.1.2" |
@@ -3768,15 +3768,15 @@ tslint@^5.2.0: | |||
3768 | resolve "^1.3.2" | 3768 | resolve "^1.3.2" |
3769 | semver "^5.3.0" | 3769 | semver "^5.3.0" |
3770 | tslib "^1.7.1" | 3770 | tslib "^1.7.1" |
3771 | tsutils "^2.7.1" | 3771 | tsutils "^2.8.1" |
3772 | 3772 | ||
3773 | tsutils@^1.4.0: | 3773 | tsutils@^1.4.0: |
3774 | version "1.9.1" | 3774 | version "1.9.1" |
3775 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" | 3775 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" |
3776 | 3776 | ||
3777 | tsutils@^2.7.1: | 3777 | tsutils@^2.8.1: |
3778 | version "2.8.1" | 3778 | version "2.8.2" |
3779 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.1.tgz#3771404e7ca9f0bedf5d919a47a4b1890a68efff" | 3779 | resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a" |
3780 | dependencies: | 3780 | dependencies: |
3781 | tslib "^1.7.1" | 3781 | tslib "^1.7.1" |
3782 | 3782 | ||
@@ -3821,9 +3821,9 @@ typedarray@^0.0.6: | |||
3821 | version "0.0.6" | 3821 | version "0.0.6" |
3822 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" | 3822 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" |
3823 | 3823 | ||
3824 | typescript@^2.4.1: | 3824 | typescript@^2.5.2: |
3825 | version "2.5.1" | 3825 | version "2.5.2" |
3826 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.1.tgz#ce7cc93ada3de19475cc9d17e3adea7aee1832aa" | 3826 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34" |
3827 | 3827 | ||
3828 | uid-number@^0.0.6: | 3828 | uid-number@^0.0.6: |
3829 | version "0.0.6" | 3829 | version "0.0.6" |