diff options
20 files changed, 426 insertions, 65 deletions
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 07a576083..81b4351c5 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -54,3 +54,8 @@ | |||
54 | </div> | 54 | </div> |
55 | </ng-template> | 55 | </ng-template> |
56 | </p-toast> | 56 | </p-toast> |
57 | |||
58 | <ng-template [ngIf]="isUserLoggedIn()"> | ||
59 | <my-welcome-modal #welcomeModal></my-welcome-modal> | ||
60 | <my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal> | ||
61 | </ng-template> | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 64bfb9671..f68641047 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' | 3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' |
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' | 7 | import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { fromEvent } from 'rxjs' | 10 | import { fromEvent } from 'rxjs' |
@@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service' | |||
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | 15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' |
16 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
17 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
18 | import { UserRole } from '@shared/models' | ||
19 | import { User } from '@app/shared' | ||
20 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
16 | 21 | ||
17 | @Component({ | 22 | @Component({ |
18 | selector: 'my-app', | 23 | selector: 'my-app', |
@@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | |||
20 | styleUrls: [ './app.component.scss' ] | 25 | styleUrls: [ './app.component.scss' ] |
21 | }) | 26 | }) |
22 | export class AppComponent implements OnInit { | 27 | export class AppComponent implements OnInit { |
28 | @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent | ||
29 | @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent | ||
30 | |||
23 | isMenuDisplayed = true | 31 | isMenuDisplayed = true |
24 | isMenuChangedByUser = false | 32 | isMenuChangedByUser = false |
25 | 33 | ||
@@ -32,6 +40,7 @@ export class AppComponent implements OnInit { | |||
32 | private authService: AuthService, | 40 | private authService: AuthService, |
33 | private serverService: ServerService, | 41 | private serverService: ServerService, |
34 | private pluginService: PluginService, | 42 | private pluginService: PluginService, |
43 | private instanceService: InstanceService, | ||
35 | private domSanitizer: DomSanitizer, | 44 | private domSanitizer: DomSanitizer, |
36 | private redirectService: RedirectService, | 45 | private redirectService: RedirectService, |
37 | private screenService: ScreenService, | 46 | private screenService: ScreenService, |
@@ -96,6 +105,8 @@ export class AppComponent implements OnInit { | |||
96 | .subscribe(() => this.onResize()) | 105 | .subscribe(() => this.onResize()) |
97 | 106 | ||
98 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) | 107 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) |
108 | |||
109 | this.openModalsIfNeeded() | ||
99 | } | 110 | } |
100 | 111 | ||
101 | isUserLoggedIn () { | 112 | isUserLoggedIn () { |
@@ -220,32 +231,62 @@ export class AppComponent implements OnInit { | |||
220 | this.hooks.runAction('action:application.init', 'common') | 231 | this.hooks.runAction('action:application.init', 'common') |
221 | } | 232 | } |
222 | 233 | ||
234 | private async openModalsIfNeeded () { | ||
235 | this.serverService.configLoaded | ||
236 | .pipe( | ||
237 | switchMap(() => this.authService.userInformationLoaded), | ||
238 | map(() => this.authService.getUser()), | ||
239 | filter(user => user.role === UserRole.ADMINISTRATOR) | ||
240 | ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template | ||
241 | } | ||
242 | |||
243 | private async openAdminModals (user: User) { | ||
244 | if (user.noWelcomeModal !== true) return this.welcomeModal.show() | ||
245 | |||
246 | const config = this.serverService.getConfig() | ||
247 | |||
248 | if (user.noInstanceConfigWarningModal !== true && config.signup.allowed && config.instance.name.toLowerCase() === 'peertube') { | ||
249 | this.instanceService.getAbout() | ||
250 | .subscribe(about => { | ||
251 | if (!about.instance.terms) { | ||
252 | this.instanceConfigWarningModal.show() | ||
253 | } | ||
254 | }) | ||
255 | } | ||
256 | } | ||
257 | |||
223 | private initHotkeys () { | 258 | private initHotkeys () { |
224 | this.hotkeysService.add([ | 259 | this.hotkeysService.add([ |
225 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { | 260 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { |
226 | document.getElementById('search-video').focus() | 261 | document.getElementById('search-video').focus() |
227 | return false | 262 | return false |
228 | }, undefined, this.i18n('Focus the search bar')), | 263 | }, undefined, this.i18n('Focus the search bar')), |
264 | |||
229 | new Hotkey('b', (event: KeyboardEvent): boolean => { | 265 | new Hotkey('b', (event: KeyboardEvent): boolean => { |
230 | this.toggleMenu() | 266 | this.toggleMenu() |
231 | return false | 267 | return false |
232 | }, undefined, this.i18n('Toggle the left menu')), | 268 | }, undefined, this.i18n('Toggle the left menu')), |
269 | |||
233 | new Hotkey('g o', (event: KeyboardEvent): boolean => { | 270 | new Hotkey('g o', (event: KeyboardEvent): boolean => { |
234 | this.router.navigate([ '/videos/overview' ]) | 271 | this.router.navigate([ '/videos/overview' ]) |
235 | return false | 272 | return false |
236 | }, undefined, this.i18n('Go to the discover videos page')), | 273 | }, undefined, this.i18n('Go to the discover videos page')), |
274 | |||
237 | new Hotkey('g t', (event: KeyboardEvent): boolean => { | 275 | new Hotkey('g t', (event: KeyboardEvent): boolean => { |
238 | this.router.navigate([ '/videos/trending' ]) | 276 | this.router.navigate([ '/videos/trending' ]) |
239 | return false | 277 | return false |
240 | }, undefined, this.i18n('Go to the trending videos page')), | 278 | }, undefined, this.i18n('Go to the trending videos page')), |
279 | |||
241 | new Hotkey('g r', (event: KeyboardEvent): boolean => { | 280 | new Hotkey('g r', (event: KeyboardEvent): boolean => { |
242 | this.router.navigate([ '/videos/recently-added' ]) | 281 | this.router.navigate([ '/videos/recently-added' ]) |
243 | return false | 282 | return false |
244 | }, undefined, this.i18n('Go to the recently added videos page')), | 283 | }, undefined, this.i18n('Go to the recently added videos page')), |
284 | |||
245 | new Hotkey('g l', (event: KeyboardEvent): boolean => { | 285 | new Hotkey('g l', (event: KeyboardEvent): boolean => { |
246 | this.router.navigate([ '/videos/local' ]) | 286 | this.router.navigate([ '/videos/local' ]) |
247 | return false | 287 | return false |
248 | }, undefined, this.i18n('Go to the local videos page')), | 288 | }, undefined, this.i18n('Go to the local videos page')), |
289 | |||
249 | new Hotkey('g u', (event: KeyboardEvent): boolean => { | 290 | new Hotkey('g u', (event: KeyboardEvent): boolean => { |
250 | this.router.navigate([ '/videos/upload' ]) | 291 | this.router.navigate([ '/videos/upload' ]) |
251 | return false | 292 | return false |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 1e2936a37..a3ea33ca9 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -18,6 +18,8 @@ import { VideosModule } from './videos' | |||
18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
20 | import { SearchModule } from '@app/search' | 20 | import { SearchModule } from '@app/search' |
21 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
22 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
21 | 23 | ||
22 | export function metaFactory (serverService: ServerService): MetaLoader { | 24 | export function metaFactory (serverService: ServerService): MetaLoader { |
23 | return new MetaStaticLoader({ | 25 | return new MetaStaticLoader({ |
@@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
39 | MenuComponent, | 41 | MenuComponent, |
40 | LanguageChooserComponent, | 42 | LanguageChooserComponent, |
41 | AvatarNotificationComponent, | 43 | AvatarNotificationComponent, |
42 | HeaderComponent | 44 | HeaderComponent, |
45 | |||
46 | WelcomeModalComponent, | ||
47 | InstanceConfigWarningModalComponent | ||
43 | ], | 48 | ], |
44 | imports: [ | 49 | imports: [ |
45 | BrowserModule, | 50 | BrowserModule, |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html new file mode 100644 index 000000000..595afb103 --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.html | |||
@@ -0,0 +1,15 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Warning!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | </div> | ||
10 | |||
11 | <div class="modal-footer inputs"> | ||
12 | <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span> | ||
13 | </div> | ||
14 | |||
15 | </ng-template> | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss new file mode 100644 index 000000000..51834c649 --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.scss | |||
@@ -0,0 +1,6 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .action-button-cancel { | ||
5 | margin-right: 0 !important; | ||
6 | } | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts new file mode 100644 index 000000000..5cc9207cd --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-instance-config-warning-modal', | ||
8 | templateUrl: './instance-config-warning-modal.component.html', | ||
9 | styleUrls: [ './instance-config-warning-modal.component.scss' ] | ||
10 | }) | ||
11 | export class InstanceConfigWarningModalComponent { | ||
12 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
13 | |||
14 | constructor ( | ||
15 | private modalService: NgbModal, | ||
16 | private notifier: Notifier, | ||
17 | private i18n: I18n | ||
18 | ) { } | ||
19 | |||
20 | show () { | ||
21 | this.modalService.open(this.modal) | ||
22 | } | ||
23 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.html b/client/src/app/modal/welcome-modal.component.html new file mode 100644 index 000000000..c83b53c2c --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.html | |||
@@ -0,0 +1,66 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <div class="block-links"> | ||
10 | <div class="subtitle">Useful links</div> | ||
11 | |||
12 | <ul> | ||
13 | <li> | ||
14 | Official PeerTube website: <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a> | ||
15 | </li> | ||
16 | |||
17 | <li> | ||
18 | Discover CLI PeerTube tools (to upload or import videos, parse logs, prune storage directories, reset user password...): | ||
19 | <a href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/maintain-tools</a> | ||
20 | </li> | ||
21 | |||
22 | <li> | ||
23 | Understand how to administer your instance (managing users, following other instances, dealing with spammers...): | ||
24 | <a href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/admin-following-instances</a> | ||
25 | </li> | ||
26 | |||
27 | <li> | ||
28 | Learn how to use PeerTube (setup your account, managing video playlists, discover third-party applications...): | ||
29 | <a href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/use-setup-account</a> | ||
30 | </li> | ||
31 | </ul> | ||
32 | </div> | ||
33 | |||
34 | <div class="block-configuration"> | ||
35 | <div class="subtitle">Configure your instance</div> | ||
36 | |||
37 | <p> | ||
38 | Now it's time to configure your instance! Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, | ||
39 | specifying <strong>who you are</strong> and <strong>how long</strong> you plan to <strong>maintain your instance</strong> | ||
40 | is very important for visitors to understand on what type of instance they are. | ||
41 | </p> | ||
42 | |||
43 | <p> | ||
44 | If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong> | ||
45 | and specify the categories and languages you speak. This way, users that are looking for a PeerTube instance on which they can register | ||
46 | will be able to choose <strong>the right one</strong>. | ||
47 | </p> | ||
48 | |||
49 | <div class="configure-instance"> | ||
50 | <a href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a> | ||
51 | </div> | ||
52 | </div> | ||
53 | |||
54 | <div class="block-instance"> | ||
55 | <div class="subtitle">Index your instance</div> | ||
56 | |||
57 | If you want, you can index your PeerTube instance on the public PeerTube instances list: | ||
58 | <a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a> | ||
59 | </div> | ||
60 | </div> | ||
61 | |||
62 | <div class="modal-footer inputs"> | ||
63 | <span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span> | ||
64 | </div> | ||
65 | |||
66 | </ng-template> | ||
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss new file mode 100644 index 000000000..ab57bb993 --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.scss | |||
@@ -0,0 +1,31 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .action-button-cancel { | ||
9 | margin-right: 0 !important; | ||
10 | } | ||
11 | |||
12 | .subtitle { | ||
13 | font-weight: $font-semibold; | ||
14 | margin-bottom: 10px; | ||
15 | font-size: 16px; | ||
16 | } | ||
17 | |||
18 | .block-configuration, | ||
19 | .block-instance { | ||
20 | margin-top: 30px; | ||
21 | } | ||
22 | |||
23 | li { | ||
24 | margin-bottom: 10px; | ||
25 | } | ||
26 | |||
27 | .configure-instance { | ||
28 | text-align: center; | ||
29 | font-weight: 600; | ||
30 | font-size: 18px; | ||
31 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts new file mode 100644 index 000000000..bff2968d4 --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { UserService } from '@app/shared' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-welcome-modal', | ||
8 | templateUrl: './welcome-modal.component.html', | ||
9 | styleUrls: [ './welcome-modal.component.scss' ] | ||
10 | }) | ||
11 | export class WelcomeModalComponent { | ||
12 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
13 | |||
14 | constructor ( | ||
15 | private userService: UserService, | ||
16 | private modalService: NgbModal, | ||
17 | private notifier: Notifier | ||
18 | ) { } | ||
19 | |||
20 | show () { | ||
21 | const ref = this.modalService.open(this.modal,{ | ||
22 | backdrop: 'static', | ||
23 | keyboard: false, | ||
24 | size: 'lg' | ||
25 | }) | ||
26 | |||
27 | ref.result.finally(() => this.doNotOpenAgain()) | ||
28 | } | ||
29 | |||
30 | private doNotOpenAgain () { | ||
31 | this.userService.updateMyProfile({ noWelcomeModal: true }) | ||
32 | .subscribe( | ||
33 | () => console.log('We will not open the welcome modal again.'), | ||
34 | |||
35 | err => this.notifier.error(err.message) | ||
36 | ) | ||
37 | |||
38 | return true | ||
39 | } | ||
40 | } | ||
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 53809f82c..656b73dd2 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -9,31 +9,38 @@ export class User implements UserServerModel { | |||
9 | username: string | 9 | username: string |
10 | email: string | 10 | email: string |
11 | pendingEmail: string | null | 11 | pendingEmail: string | null |
12 | |||
12 | emailVerified: boolean | 13 | emailVerified: boolean |
13 | nsfwPolicy: NSFWPolicyType | 14 | nsfwPolicy: NSFWPolicyType |
14 | 15 | ||
15 | role: UserRole | 16 | adminFlags?: UserAdminFlag |
16 | roleLabel: string | ||
17 | 17 | ||
18 | webTorrentEnabled: boolean | ||
19 | autoPlayVideo: boolean | 18 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | ||
20 | videosHistoryEnabled: boolean | 20 | videosHistoryEnabled: boolean |
21 | videoLanguages: string[] | 21 | videoLanguages: string[] |
22 | 22 | ||
23 | role: UserRole | ||
24 | roleLabel: string | ||
25 | |||
23 | videoQuota: number | 26 | videoQuota: number |
24 | videoQuotaDaily: number | 27 | videoQuotaDaily: number |
25 | account: Account | 28 | videoQuotaUsed?: number |
26 | videoChannels: VideoChannel[] | 29 | videoQuotaUsedDaily?: number |
27 | createdAt: Date | ||
28 | 30 | ||
29 | theme: string | 31 | theme: string |
30 | 32 | ||
31 | adminFlags?: UserAdminFlag | 33 | account: Account |
34 | notificationSettings?: UserNotificationSetting | ||
35 | videoChannels?: VideoChannel[] | ||
32 | 36 | ||
33 | blocked: boolean | 37 | blocked: boolean |
34 | blockedReason?: string | 38 | blockedReason?: string |
35 | 39 | ||
36 | notificationSettings?: UserNotificationSetting | 40 | noInstanceConfigWarningModal: boolean |
41 | noWelcomeModal: boolean | ||
42 | |||
43 | createdAt: Date | ||
37 | 44 | ||
38 | constructor (hash: Partial<UserServerModel>) { | 45 | constructor (hash: Partial<UserServerModel>) { |
39 | this.id = hash.id | 46 | this.id = hash.id |
@@ -43,13 +50,16 @@ export class User implements UserServerModel { | |||
43 | this.role = hash.role | 50 | this.role = hash.role |
44 | 51 | ||
45 | this.videoChannels = hash.videoChannels | 52 | this.videoChannels = hash.videoChannels |
53 | |||
46 | this.videoQuota = hash.videoQuota | 54 | this.videoQuota = hash.videoQuota |
47 | this.videoQuotaDaily = hash.videoQuotaDaily | 55 | this.videoQuotaDaily = hash.videoQuotaDaily |
56 | this.videoQuotaUsed = hash.videoQuotaUsed | ||
57 | this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily | ||
58 | |||
48 | this.nsfwPolicy = hash.nsfwPolicy | 59 | this.nsfwPolicy = hash.nsfwPolicy |
49 | this.webTorrentEnabled = hash.webTorrentEnabled | 60 | this.webTorrentEnabled = hash.webTorrentEnabled |
50 | this.videosHistoryEnabled = hash.videosHistoryEnabled | 61 | this.videosHistoryEnabled = hash.videosHistoryEnabled |
51 | this.autoPlayVideo = hash.autoPlayVideo | 62 | this.autoPlayVideo = hash.autoPlayVideo |
52 | this.createdAt = hash.createdAt | ||
53 | 63 | ||
54 | this.theme = hash.theme | 64 | this.theme = hash.theme |
55 | 65 | ||
@@ -58,8 +68,13 @@ export class User implements UserServerModel { | |||
58 | this.blocked = hash.blocked | 68 | this.blocked = hash.blocked |
59 | this.blockedReason = hash.blockedReason | 69 | this.blockedReason = hash.blockedReason |
60 | 70 | ||
71 | this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal | ||
72 | this.noWelcomeModal = hash.noWelcomeModal | ||
73 | |||
61 | this.notificationSettings = hash.notificationSettings | 74 | this.notificationSettings = hash.notificationSettings |
62 | 75 | ||
76 | this.createdAt = hash.createdAt | ||
77 | |||
63 | if (hash.account !== undefined) { | 78 | if (hash.account !== undefined) { |
64 | this.account = new Account(hash.account) | 79 | this.account = new Account(hash.account) |
65 | } | 80 | } |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index 78e1e7fa3..fb1ddbc6d 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -127,7 +127,7 @@ async function getUserInformation (req: express.Request, res: express.Response) | |||
127 | // We did not load channels in res.locals.user | 127 | // We did not load channels in res.locals.user |
128 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 128 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
129 | 129 | ||
130 | return res.json(user.toFormattedJSON({})) | 130 | return res.json(user.toFormattedJSON()) |
131 | } | 131 | } |
132 | 132 | ||
133 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { | 133 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { |
@@ -178,6 +178,8 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
178 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 178 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
179 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 179 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
180 | if (body.theme !== undefined) user.theme = body.theme | 180 | if (body.theme !== undefined) user.theme = body.theme |
181 | if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal | ||
182 | if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal | ||
181 | 183 | ||
182 | if (body.email !== undefined) { | 184 | if (body.email !== undefined) { |
183 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 185 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
@@ -188,17 +190,19 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
188 | } | 190 | } |
189 | } | 191 | } |
190 | 192 | ||
191 | await sequelizeTypescript.transaction(async t => { | 193 | if (body.displayName !== undefined || body.description !== undefined) { |
192 | const userAccount = await AccountModel.load(user.Account.id) | 194 | await sequelizeTypescript.transaction(async t => { |
195 | const userAccount = await AccountModel.load(user.Account.id, t) | ||
193 | 196 | ||
194 | await user.save({ transaction: t }) | 197 | await user.save({ transaction: t }) |
195 | 198 | ||
196 | if (body.displayName !== undefined) userAccount.name = body.displayName | 199 | if (body.displayName !== undefined) userAccount.name = body.displayName |
197 | if (body.description !== undefined) userAccount.description = body.description | 200 | if (body.description !== undefined) userAccount.description = body.description |
198 | await userAccount.save({ transaction: t }) | 201 | await userAccount.save({ transaction: t }) |
199 | 202 | ||
200 | await sendUpdateActor(userAccount, t) | 203 | await sendUpdateActor(userAccount, t) |
201 | }) | 204 | }) |
205 | } | ||
202 | 206 | ||
203 | if (sendVerificationEmail === true) { | 207 | if (sendVerificationEmail === true) { |
204 | await sendVerifyUserEmail(user, true) | 208 | await sendVerifyUserEmail(user, true) |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index c56ae14ef..68e84d9eb 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -65,6 +65,14 @@ function isUserBlockedValid (value: any) { | |||
65 | return isBooleanValid(value) | 65 | return isBooleanValid(value) |
66 | } | 66 | } |
67 | 67 | ||
68 | function isNoInstanceConfigWarningModal (value: any) { | ||
69 | return isBooleanValid(value) | ||
70 | } | ||
71 | |||
72 | function isNoWelcomeModal (value: any) { | ||
73 | return isBooleanValid(value) | ||
74 | } | ||
75 | |||
68 | function isUserBlockedReasonValid (value: any) { | 76 | function isUserBlockedReasonValid (value: any) { |
69 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) | 77 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) |
70 | } | 78 | } |
@@ -100,5 +108,7 @@ export { | |||
100 | isUserAutoPlayVideoValid, | 108 | isUserAutoPlayVideoValid, |
101 | isUserDisplayNameValid, | 109 | isUserDisplayNameValid, |
102 | isUserDescriptionValid, | 110 | isUserDescriptionValid, |
111 | isNoInstanceConfigWarningModal, | ||
112 | isNoWelcomeModal, | ||
103 | isAvatarFile | 113 | isAvatarFile |
104 | } | 114 | } |
diff --git a/server/initializers/migrations/0425-user-modals.ts b/server/initializers/migrations/0425-user-modals.ts new file mode 100644 index 000000000..5c2aa85b5 --- /dev/null +++ b/server/initializers/migrations/0425-user-modals.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.BOOLEAN, | ||
12 | allowNull: false, | ||
13 | defaultValue: false | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.addColumn('user', 'noInstanceConfigWarningModal', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const data = { | ||
21 | type: Sequelize.BOOLEAN, | ||
22 | allowNull: false, | ||
23 | defaultValue: true | ||
24 | } | ||
25 | |||
26 | await utils.queryInterface.addColumn('user', 'noWelcomeModal', data) | ||
27 | data.defaultValue = false | ||
28 | |||
29 | await utils.queryInterface.changeColumn('user', 'noWelcomeModal', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 26f43cec7..544db76d7 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -4,6 +4,7 @@ import { body, param } from 'express-validator' | |||
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
7 | isNoInstanceConfigWarningModal, isNoWelcomeModal, | ||
7 | isUserAdminFlagsValid, | 8 | isUserAdminFlagsValid, |
8 | isUserAutoPlayVideoValid, | 9 | isUserAutoPlayVideoValid, |
9 | isUserBlockedReasonValid, | 10 | isUserBlockedReasonValid, |
@@ -216,6 +217,12 @@ const usersUpdateMeValidator = [ | |||
216 | body('theme') | 217 | body('theme') |
217 | .optional() | 218 | .optional() |
218 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), | 219 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), |
220 | body('noInstanceConfigWarningModal') | ||
221 | .optional() | ||
222 | .custom(v => isNoInstanceConfigWarningModal(v)).withMessage('Should have a valid noInstanceConfigWarningModal boolean'), | ||
223 | body('noWelcomeModal') | ||
224 | .optional() | ||
225 | .custom(v => isNoWelcomeModal(v)).withMessage('Should have a valid noWelcomeModal boolean'), | ||
219 | 226 | ||
220 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 227 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
221 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 228 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 616dd603c..451e1fd6b 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -22,6 +22,7 @@ import { | |||
22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | ||
25 | isUserAdminFlagsValid, | 26 | isUserAdminFlagsValid, |
26 | isUserAutoPlayVideoValid, | 27 | isUserAutoPlayVideoValid, |
27 | isUserBlockedReasonValid, | 28 | isUserBlockedReasonValid, |
@@ -35,7 +36,8 @@ import { | |||
35 | isUserVideoQuotaDailyValid, | 36 | isUserVideoQuotaDailyValid, |
36 | isUserVideoQuotaValid, | 37 | isUserVideoQuotaValid, |
37 | isUserVideosHistoryEnabledValid, | 38 | isUserVideosHistoryEnabledValid, |
38 | isUserWebTorrentEnabledValid | 39 | isUserWebTorrentEnabledValid, |
40 | isNoWelcomeModal | ||
39 | } from '../../helpers/custom-validators/users' | 41 | } from '../../helpers/custom-validators/users' |
40 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 42 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
41 | import { OAuthTokenModel } from '../oauth/oauth-token' | 43 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -203,6 +205,24 @@ export class UserModel extends Model<UserModel> { | |||
203 | @Column | 205 | @Column |
204 | theme: string | 206 | theme: string |
205 | 207 | ||
208 | @AllowNull(false) | ||
209 | @Default(false) | ||
210 | @Is( | ||
211 | 'UserNoInstanceConfigWarningModal', | ||
212 | value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal') | ||
213 | ) | ||
214 | @Column | ||
215 | noInstanceConfigWarningModal: boolean | ||
216 | |||
217 | @AllowNull(false) | ||
218 | @Default(false) | ||
219 | @Is( | ||
220 | 'UserNoInstanceConfigWarningModal', | ||
221 | value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal') | ||
222 | ) | ||
223 | @Column | ||
224 | noWelcomeModal: boolean | ||
225 | |||
206 | @CreatedAt | 226 | @CreatedAt |
207 | createdAt: Date | 227 | createdAt: Date |
208 | 228 | ||
@@ -560,40 +580,52 @@ export class UserModel extends Model<UserModel> { | |||
560 | return comparePassword(password, this.password) | 580 | return comparePassword(password, this.password) |
561 | } | 581 | } |
562 | 582 | ||
563 | toSummaryJSON | ||
564 | |||
565 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { | 583 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { |
566 | const videoQuotaUsed = this.get('videoQuotaUsed') | 584 | const videoQuotaUsed = this.get('videoQuotaUsed') |
567 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 585 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
568 | 586 | ||
569 | const json = { | 587 | const json: User = { |
570 | id: this.id, | 588 | id: this.id, |
571 | username: this.username, | 589 | username: this.username, |
572 | email: this.email, | 590 | email: this.email, |
591 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
592 | |||
573 | pendingEmail: this.pendingEmail, | 593 | pendingEmail: this.pendingEmail, |
574 | emailVerified: this.emailVerified, | 594 | emailVerified: this.emailVerified, |
595 | |||
575 | nsfwPolicy: this.nsfwPolicy, | 596 | nsfwPolicy: this.nsfwPolicy, |
576 | webTorrentEnabled: this.webTorrentEnabled, | 597 | webTorrentEnabled: this.webTorrentEnabled, |
577 | videosHistoryEnabled: this.videosHistoryEnabled, | 598 | videosHistoryEnabled: this.videosHistoryEnabled, |
578 | autoPlayVideo: this.autoPlayVideo, | 599 | autoPlayVideo: this.autoPlayVideo, |
579 | videoLanguages: this.videoLanguages, | 600 | videoLanguages: this.videoLanguages, |
601 | |||
580 | role: this.role, | 602 | role: this.role, |
581 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
582 | roleLabel: USER_ROLE_LABELS[ this.role ], | 603 | roleLabel: USER_ROLE_LABELS[ this.role ], |
604 | |||
583 | videoQuota: this.videoQuota, | 605 | videoQuota: this.videoQuota, |
584 | videoQuotaDaily: this.videoQuotaDaily, | 606 | videoQuotaDaily: this.videoQuotaDaily, |
585 | createdAt: this.createdAt, | 607 | videoQuotaUsed: videoQuotaUsed !== undefined |
608 | ? parseInt(videoQuotaUsed + '', 10) | ||
609 | : undefined, | ||
610 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
611 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
612 | : undefined, | ||
613 | |||
614 | noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, | ||
615 | noWelcomeModal: this.noWelcomeModal, | ||
616 | |||
586 | blocked: this.blocked, | 617 | blocked: this.blocked, |
587 | blockedReason: this.blockedReason, | 618 | blockedReason: this.blockedReason, |
619 | |||
588 | account: this.Account.toFormattedJSON(), | 620 | account: this.Account.toFormattedJSON(), |
589 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, | 621 | |
622 | notificationSettings: this.NotificationSetting | ||
623 | ? this.NotificationSetting.toFormattedJSON() | ||
624 | : undefined, | ||
625 | |||
590 | videoChannels: [], | 626 | videoChannels: [], |
591 | videoQuotaUsed: videoQuotaUsed !== undefined | 627 | |
592 | ? parseInt(videoQuotaUsed + '', 10) | 628 | createdAt: this.createdAt |
593 | : undefined, | ||
594 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
595 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
596 | : undefined | ||
597 | } | 629 | } |
598 | 630 | ||
599 | if (parameters.withAdminFlags) { | 631 | if (parameters.withAdminFlags) { |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 939b919ed..55094795c 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -476,6 +476,22 @@ describe('Test users API validators', function () { | |||
476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
477 | }) | 477 | }) |
478 | 478 | ||
479 | it('Should fail with an invalid noInstanceConfigWarningModal attribute', async function () { | ||
480 | const fields = { | ||
481 | noInstanceConfigWarningModal: -1 | ||
482 | } | ||
483 | |||
484 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
485 | }) | ||
486 | |||
487 | it('Should fail with an invalid noWelcomeModal attribute', async function () { | ||
488 | const fields = { | ||
489 | noWelcomeModal: -1 | ||
490 | } | ||
491 | |||
492 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
493 | }) | ||
494 | |||
479 | it('Should succeed to change password with the correct params', async function () { | 495 | it('Should succeed to change password with the correct params', async function () { |
480 | const fields = { | 496 | const fields = { |
481 | currentPassword: 'my super password', | 497 | currentPassword: 'my super password', |
@@ -483,7 +499,9 @@ describe('Test users API validators', function () { | |||
483 | nsfwPolicy: 'blur', | 499 | nsfwPolicy: 'blur', |
484 | autoPlayVideo: false, | 500 | autoPlayVideo: false, |
485 | email: 'super_email@example.com', | 501 | email: 'super_email@example.com', |
486 | theme: 'default' | 502 | theme: 'default', |
503 | noInstanceConfigWarningModal: true, | ||
504 | noWelcomeModal: true | ||
487 | } | 505 | } |
488 | 506 | ||
489 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) | 507 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 3a3fabb4c..95b1bb626 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -442,7 +442,7 @@ describe('Test users', function () { | |||
442 | url: server.url, | 442 | url: server.url, |
443 | accessToken: accessTokenUser, | 443 | accessToken: accessTokenUser, |
444 | currentPassword: 'super password', | 444 | currentPassword: 'super password', |
445 | newPassword: 'new password' | 445 | password: 'new password' |
446 | }) | 446 | }) |
447 | user.password = 'new password' | 447 | user.password = 'new password' |
448 | 448 | ||
@@ -543,7 +543,7 @@ describe('Test users', function () { | |||
543 | }) | 543 | }) |
544 | 544 | ||
545 | const res = await getMyUserInformation(server.url, accessTokenUser) | 545 | const res = await getMyUserInformation(server.url, accessTokenUser) |
546 | const user = res.body | 546 | const user: User = res.body |
547 | 547 | ||
548 | expect(user.username).to.equal('user_1') | 548 | expect(user.username).to.equal('user_1') |
549 | expect(user.email).to.equal('updated@example.com') | 549 | expect(user.email).to.equal('updated@example.com') |
@@ -552,6 +552,8 @@ describe('Test users', function () { | |||
552 | expect(user.id).to.be.a('number') | 552 | expect(user.id).to.be.a('number') |
553 | expect(user.account.displayName).to.equal('new display name') | 553 | expect(user.account.displayName).to.equal('new display name') |
554 | expect(user.account.description).to.equal('my super description updated') | 554 | expect(user.account.description).to.equal('my super description updated') |
555 | expect(user.noWelcomeModal).to.be.false | ||
556 | expect(user.noInstanceConfigWarningModal).to.be.false | ||
555 | }) | 557 | }) |
556 | 558 | ||
557 | it('Should be able to update my theme', async function () { | 559 | it('Should be able to update my theme', async function () { |
@@ -568,6 +570,21 @@ describe('Test users', function () { | |||
568 | expect(body.theme).to.equal(theme) | 570 | expect(body.theme).to.equal(theme) |
569 | } | 571 | } |
570 | }) | 572 | }) |
573 | |||
574 | it('Should be able to update my modal preferences', async function () { | ||
575 | await updateMyUser({ | ||
576 | url: server.url, | ||
577 | accessToken: accessTokenUser, | ||
578 | noInstanceConfigWarningModal: true, | ||
579 | noWelcomeModal: true | ||
580 | }) | ||
581 | |||
582 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
583 | const user: User = res.body | ||
584 | |||
585 | expect(user.noWelcomeModal).to.be.true | ||
586 | expect(user.noInstanceConfigWarningModal).to.be.true | ||
587 | }) | ||
571 | }) | 588 | }) |
572 | 589 | ||
573 | describe('Updating another user', function () { | 590 | describe('Updating another user', function () { |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 30ed1bf4a..9959fd074 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' | 2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' |
3 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' | ||
4 | import { UserAdminFlag } from '../../models/users/user-flag.model' | 3 | import { UserAdminFlag } from '../../models/users/user-flag.model' |
5 | import { UserRegister } from '../../models/users/user-register.model' | 4 | import { UserRegister } from '../../models/users/user-register.model' |
6 | import { UserRole } from '../../models/users/user-role' | 5 | import { UserRole } from '../../models/users/user-role' |
7 | import { ServerInfo } from '../server/servers' | 6 | import { ServerInfo } from '../server/servers' |
8 | import { userLogin } from './login' | 7 | import { userLogin } from './login' |
9 | import { UserUpdateMe } from '../../models/users' | 8 | import { UserUpdateMe } from '../../models/users' |
9 | import { omit } from 'lodash' | ||
10 | 10 | ||
11 | type CreateUserArgs = { url: string, | 11 | type CreateUserArgs = { url: string, |
12 | accessToken: string, | 12 | accessToken: string, |
@@ -214,33 +214,10 @@ function unblockUser (url: string, userId: number | string, accessToken: string, | |||
214 | .expect(expectedStatus) | 214 | .expect(expectedStatus) |
215 | } | 215 | } |
216 | 216 | ||
217 | function updateMyUser (options: { | 217 | function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) { |
218 | url: string | ||
219 | accessToken: string | ||
220 | currentPassword?: string | ||
221 | newPassword?: string | ||
222 | nsfwPolicy?: NSFWPolicyType | ||
223 | email?: string | ||
224 | autoPlayVideo?: boolean | ||
225 | displayName?: string | ||
226 | description?: string | ||
227 | videosHistoryEnabled?: boolean | ||
228 | theme?: string | ||
229 | }) { | ||
230 | const path = '/api/v1/users/me' | 218 | const path = '/api/v1/users/me' |
231 | 219 | ||
232 | const toSend: UserUpdateMe = {} | 220 | const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') |
233 | if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword | ||
234 | if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword | ||
235 | if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy | ||
236 | if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo | ||
237 | if (options.email !== undefined && options.email !== null) toSend.email = options.email | ||
238 | if (options.description !== undefined && options.description !== null) toSend.description = options.description | ||
239 | if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName | ||
240 | if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme | ||
241 | if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { | ||
242 | toSend.videosHistoryEnabled = options.videosHistoryEnabled | ||
243 | } | ||
244 | 221 | ||
245 | return makePutBodyRequest({ | 222 | return makePutBodyRequest({ |
246 | url: options.url, | 223 | url: options.url, |
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index b6c0002e5..99b9a65bd 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts | |||
@@ -15,4 +15,7 @@ export interface UserUpdateMe { | |||
15 | password?: string | 15 | password?: string |
16 | 16 | ||
17 | theme?: string | 17 | theme?: string |
18 | |||
19 | noInstanceConfigWarningModal?: boolean | ||
20 | noWelcomeModal?: boolean | ||
18 | } | 21 | } |
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index de9825e1f..f67d262b0 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -10,6 +10,7 @@ export interface User { | |||
10 | username: string | 10 | username: string |
11 | email: string | 11 | email: string |
12 | pendingEmail: string | null | 12 | pendingEmail: string | null |
13 | |||
13 | emailVerified: boolean | 14 | emailVerified: boolean |
14 | nsfwPolicy: NSFWPolicyType | 15 | nsfwPolicy: NSFWPolicyType |
15 | 16 | ||
@@ -18,13 +19,15 @@ export interface User { | |||
18 | autoPlayVideo: boolean | 19 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | 20 | webTorrentEnabled: boolean |
20 | videosHistoryEnabled: boolean | 21 | videosHistoryEnabled: boolean |
22 | videoLanguages: string[] | ||
21 | 23 | ||
22 | role: UserRole | 24 | role: UserRole |
23 | roleLabel: string | 25 | roleLabel: string |
24 | 26 | ||
25 | videoQuota: number | 27 | videoQuota: number |
26 | videoQuotaDaily: number | 28 | videoQuotaDaily: number |
27 | createdAt: Date | 29 | videoQuotaUsed?: number |
30 | videoQuotaUsedDaily?: number | ||
28 | 31 | ||
29 | theme: string | 32 | theme: string |
30 | 33 | ||
@@ -35,5 +38,8 @@ export interface User { | |||
35 | blocked: boolean | 38 | blocked: boolean |
36 | blockedReason?: string | 39 | blockedReason?: string |
37 | 40 | ||
38 | videoQuotaUsed?: number | 41 | noInstanceConfigWarningModal: boolean |
42 | noWelcomeModal: boolean | ||
43 | |||
44 | createdAt: Date | ||
39 | } | 45 | } |