From 43d0ea7f4b88d52097172cc0c1831edd7e492503 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 28 Aug 2019 14:40:06 +0200 Subject: [PATCH] Add welcome modal --- client/src/app/app.component.html | 5 ++ client/src/app/app.component.ts | 45 ++++++++++++- client/src/app/app.module.ts | 7 +- ...stance-config-warning-modal.component.html | 15 +++++ ...stance-config-warning-modal.component.scss | 6 ++ ...instance-config-warning-modal.component.ts | 23 +++++++ .../app/modal/welcome-modal.component.html | 66 +++++++++++++++++++ .../app/modal/welcome-modal.component.scss | 31 +++++++++ .../src/app/modal/welcome-modal.component.ts | 40 +++++++++++ client/src/app/shared/users/user.model.ts | 37 +++++++---- server/controllers/api/users/me.ts | 22 ++++--- server/helpers/custom-validators/users.ts | 10 +++ .../migrations/0425-user-modals.ts | 40 +++++++++++ server/middlewares/validators/users.ts | 7 ++ server/models/account/user.ts | 58 ++++++++++++---- server/tests/api/check-params/users.ts | 20 +++++- server/tests/api/users/users.ts | 21 +++++- shared/extra-utils/users/users.ts | 29 +------- shared/models/users/user-update-me.model.ts | 3 + shared/models/users/user.model.ts | 10 ++- 20 files changed, 428 insertions(+), 67 deletions(-) create mode 100644 client/src/app/modal/instance-config-warning-modal.component.html create mode 100644 client/src/app/modal/instance-config-warning-modal.component.scss create mode 100644 client/src/app/modal/instance-config-warning-modal.component.ts create mode 100644 client/src/app/modal/welcome-modal.component.html create mode 100644 client/src/app/modal/welcome-modal.component.scss create mode 100644 client/src/app/modal/welcome-modal.component.ts create mode 100644 server/initializers/migrations/0425-user-modals.ts 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 @@ + + + + + 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 @@ -import { Component, OnInit } from '@angular/core' +import { Component, OnInit, ViewChild } from '@angular/core' import { DomSanitizer, SafeHtml } from '@angular/platform-browser' import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' import { is18nPath } from '../../../shared/models/i18n' import { ScreenService } from '@app/shared/misc/screen.service' -import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' +import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators' import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { I18n } from '@ngx-translate/i18n-polyfill' import { fromEvent } from 'rxjs' @@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service' import { HooksService } from '@app/core/plugins/hooks.service' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' +import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' +import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' +import { UserRole } from '@shared/models' +import { User } from '@app/shared' +import { InstanceService } from '@app/shared/instance/instance.service' @Component({ selector: 'my-app', @@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' styleUrls: [ './app.component.scss' ] }) export class AppComponent implements OnInit { + @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent + @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent + isMenuDisplayed = true isMenuChangedByUser = false @@ -32,6 +40,7 @@ export class AppComponent implements OnInit { private authService: AuthService, private serverService: ServerService, private pluginService: PluginService, + private instanceService: InstanceService, private domSanitizer: DomSanitizer, private redirectService: RedirectService, private screenService: ScreenService, @@ -96,6 +105,8 @@ export class AppComponent implements OnInit { .subscribe(() => this.onResize()) this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) + + this.openModalsIfNeeded() } isUserLoggedIn () { @@ -220,32 +231,62 @@ export class AppComponent implements OnInit { this.hooks.runAction('action:application.init', 'common') } + private async openModalsIfNeeded () { + this.serverService.configLoaded + .pipe( + switchMap(() => this.authService.userInformationLoaded), + map(() => this.authService.getUser()), + filter(user => user.role === UserRole.ADMINISTRATOR) + ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template + } + + private async openAdminModals (user: User) { + if (user.noWelcomeModal !== true) return this.welcomeModal.show() + + const config = this.serverService.getConfig() + + if (user.noInstanceConfigWarningModal !== true && config.signup.allowed && config.instance.name.toLowerCase() === 'peertube') { + this.instanceService.getAbout() + .subscribe(about => { + if (!about.instance.terms) { + this.instanceConfigWarningModal.show() + } + }) + } + } + private initHotkeys () { this.hotkeysService.add([ new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { document.getElementById('search-video').focus() return false }, undefined, this.i18n('Focus the search bar')), + new Hotkey('b', (event: KeyboardEvent): boolean => { this.toggleMenu() return false }, undefined, this.i18n('Toggle the left menu')), + new Hotkey('g o', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/overview' ]) return false }, undefined, this.i18n('Go to the discover videos page')), + new Hotkey('g t', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/trending' ]) return false }, undefined, this.i18n('Go to the trending videos page')), + new Hotkey('g r', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/recently-added' ]) return false }, undefined, this.i18n('Go to the recently added videos page')), + new Hotkey('g l', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/local' ]) return false }, undefined, this.i18n('Go to the local videos page')), + new Hotkey('g u', (event: KeyboardEvent): boolean => { this.router.navigate([ '/videos/upload' ]) 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' import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' import { SearchModule } from '@app/search' +import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' +import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' export function metaFactory (serverService: ServerService): MetaLoader { return new MetaStaticLoader({ @@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { MenuComponent, LanguageChooserComponent, AvatarNotificationComponent, - HeaderComponent + HeaderComponent, + + WelcomeModalComponent, + InstanceConfigWarningModalComponent ], imports: [ 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 @@ + + + + + + + + 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 @@ +@import '_mixins'; +@import '_variables'; + +.action-button-cancel { + margin-right: 0 !important; +} 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 @@ +import { Component, ElementRef, ViewChild } from '@angular/core' +import { Notifier } from '@app/core' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' + +@Component({ + selector: 'my-instance-config-warning-modal', + templateUrl: './instance-config-warning-modal.component.html', + styleUrls: [ './instance-config-warning-modal.component.scss' ] +}) +export class InstanceConfigWarningModalComponent { + @ViewChild('modal', { static: true }) modal: ElementRef + + constructor ( + private modalService: NgbModal, + private notifier: Notifier, + private i18n: I18n + ) { } + + show () { + this.modalService.open(this.modal) + } +} 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 @@ + + + + + + + + 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 @@ +@import '_mixins'; +@import '_variables'; + +.modal-body { + font-size: 15px; +} + +.action-button-cancel { + margin-right: 0 !important; +} + +.subtitle { + font-weight: $font-semibold; + margin-bottom: 10px; + font-size: 16px; +} + +.block-configuration, +.block-instance { + margin-top: 30px; +} + +li { + margin-bottom: 10px; +} + +.configure-instance { + text-align: center; + font-weight: 600; + font-size: 18px; +} 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 @@ +import { Component, ElementRef, ViewChild } from '@angular/core' +import { Notifier } from '@app/core' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { UserService } from '@app/shared' + +@Component({ + selector: 'my-welcome-modal', + templateUrl: './welcome-modal.component.html', + styleUrls: [ './welcome-modal.component.scss' ] +}) +export class WelcomeModalComponent { + @ViewChild('modal', { static: true }) modal: ElementRef + + constructor ( + private userService: UserService, + private modalService: NgbModal, + private notifier: Notifier + ) { } + + show () { + const ref = this.modalService.open(this.modal,{ + backdrop: 'static', + keyboard: false, + size: 'lg' + }) + + ref.result.finally(() => this.doNotOpenAgain()) + } + + private doNotOpenAgain () { + this.userService.updateMyProfile({ noWelcomeModal: true }) + .subscribe( + () => console.log('We will not open the welcome modal again.'), + + err => this.notifier.error(err.message) + ) + + return true + } +} 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 { username: string email: string pendingEmail: string | null + emailVerified: boolean nsfwPolicy: NSFWPolicyType + adminFlags?: UserAdminFlag + + autoPlayVideo: boolean + webTorrentEnabled: boolean + videosHistoryEnabled: boolean + videoLanguages: string[] + role: UserRole roleLabel: string - webTorrentEnabled: boolean - autoPlayVideo: boolean - videosHistoryEnabled: boolean - videoLanguages: string[] - videoQuota: number videoQuotaDaily: number - account: Account - videoChannels: VideoChannel[] - createdAt: Date + videoQuotaUsed?: number + videoQuotaUsedDaily?: number theme: string - adminFlags?: UserAdminFlag + account: Account + notificationSettings?: UserNotificationSetting + videoChannels?: VideoChannel[] blocked: boolean blockedReason?: string - notificationSettings?: UserNotificationSetting + noInstanceConfigWarningModal: boolean + noWelcomeModal: boolean + + createdAt: Date constructor (hash: Partial) { this.id = hash.id @@ -43,13 +50,16 @@ export class User implements UserServerModel { this.role = hash.role this.videoChannels = hash.videoChannels + this.videoQuota = hash.videoQuota this.videoQuotaDaily = hash.videoQuotaDaily + this.videoQuotaUsed = hash.videoQuotaUsed + this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily + this.nsfwPolicy = hash.nsfwPolicy this.webTorrentEnabled = hash.webTorrentEnabled this.videosHistoryEnabled = hash.videosHistoryEnabled this.autoPlayVideo = hash.autoPlayVideo - this.createdAt = hash.createdAt this.theme = hash.theme @@ -58,8 +68,13 @@ export class User implements UserServerModel { this.blocked = hash.blocked this.blockedReason = hash.blockedReason + this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal + this.noWelcomeModal = hash.noWelcomeModal + this.notificationSettings = hash.notificationSettings + this.createdAt = hash.createdAt + if (hash.account !== undefined) { this.account = new Account(hash.account) } 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) // We did not load channels in res.locals.user const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) - return res.json(user.toFormattedJSON({})) + return res.json(user.toFormattedJSON()) } async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { @@ -178,6 +178,8 @@ async function updateMe (req: express.Request, res: express.Response) { if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages if (body.theme !== undefined) user.theme = body.theme + if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal + if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal if (body.email !== undefined) { if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { @@ -188,17 +190,19 @@ async function updateMe (req: express.Request, res: express.Response) { } } - await sequelizeTypescript.transaction(async t => { - const userAccount = await AccountModel.load(user.Account.id) + if (body.displayName !== undefined || body.description !== undefined) { + await sequelizeTypescript.transaction(async t => { + const userAccount = await AccountModel.load(user.Account.id, t) - await user.save({ transaction: t }) + await user.save({ transaction: t }) - if (body.displayName !== undefined) userAccount.name = body.displayName - if (body.description !== undefined) userAccount.description = body.description - await userAccount.save({ transaction: t }) + if (body.displayName !== undefined) userAccount.name = body.displayName + if (body.description !== undefined) userAccount.description = body.description + await userAccount.save({ transaction: t }) - await sendUpdateActor(userAccount, t) - }) + await sendUpdateActor(userAccount, t) + }) + } if (sendVerificationEmail === true) { 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) { return isBooleanValid(value) } +function isNoInstanceConfigWarningModal (value: any) { + return isBooleanValid(value) +} + +function isNoWelcomeModal (value: any) { + return isBooleanValid(value) +} + function isUserBlockedReasonValid (value: any) { return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) } @@ -100,5 +108,7 @@ export { isUserAutoPlayVideoValid, isUserDisplayNameValid, isUserDescriptionValid, + isNoInstanceConfigWarningModal, + isNoWelcomeModal, isAvatarFile } 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 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize, + db: any +}): Promise { + { + const data = { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + } + + await utils.queryInterface.addColumn('user', 'noInstanceConfigWarningModal', data) + } + + { + const data = { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: true + } + + await utils.queryInterface.addColumn('user', 'noWelcomeModal', data) + data.defaultValue = false + + await utils.queryInterface.changeColumn('user', 'noWelcomeModal', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} 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' import { omit } from 'lodash' import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' import { + isNoInstanceConfigWarningModal, isNoWelcomeModal, isUserAdminFlagsValid, isUserAutoPlayVideoValid, isUserBlockedReasonValid, @@ -216,6 +217,12 @@ const usersUpdateMeValidator = [ body('theme') .optional() .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), + body('noInstanceConfigWarningModal') + .optional() + .custom(v => isNoInstanceConfigWarningModal(v)).withMessage('Should have a valid noInstanceConfigWarningModal boolean'), + body('noWelcomeModal') + .optional() + .custom(v => isNoWelcomeModal(v)).withMessage('Should have a valid noWelcomeModal boolean'), async (req: express.Request, res: express.Response, next: express.NextFunction) => { 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 { import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' import { User, UserRole } from '../../../shared/models/users' import { + isNoInstanceConfigWarningModal, isUserAdminFlagsValid, isUserAutoPlayVideoValid, isUserBlockedReasonValid, @@ -35,7 +36,8 @@ import { isUserVideoQuotaDailyValid, isUserVideoQuotaValid, isUserVideosHistoryEnabledValid, - isUserWebTorrentEnabledValid + isUserWebTorrentEnabledValid, + isNoWelcomeModal } from '../../helpers/custom-validators/users' import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' import { OAuthTokenModel } from '../oauth/oauth-token' @@ -203,6 +205,24 @@ export class UserModel extends Model { @Column theme: string + @AllowNull(false) + @Default(false) + @Is( + 'UserNoInstanceConfigWarningModal', + value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal') + ) + @Column + noInstanceConfigWarningModal: boolean + + @AllowNull(false) + @Default(false) + @Is( + 'UserNoInstanceConfigWarningModal', + value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal') + ) + @Column + noWelcomeModal: boolean + @CreatedAt createdAt: Date @@ -560,40 +580,52 @@ export class UserModel extends Model { return comparePassword(password, this.password) } - toSummaryJSON - toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { const videoQuotaUsed = this.get('videoQuotaUsed') const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') - const json = { + const json: User = { id: this.id, username: this.username, email: this.email, + theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), + pendingEmail: this.pendingEmail, emailVerified: this.emailVerified, + nsfwPolicy: this.nsfwPolicy, webTorrentEnabled: this.webTorrentEnabled, videosHistoryEnabled: this.videosHistoryEnabled, autoPlayVideo: this.autoPlayVideo, videoLanguages: this.videoLanguages, + role: this.role, - theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), roleLabel: USER_ROLE_LABELS[ this.role ], + videoQuota: this.videoQuota, videoQuotaDaily: this.videoQuotaDaily, - createdAt: this.createdAt, + videoQuotaUsed: videoQuotaUsed !== undefined + ? parseInt(videoQuotaUsed + '', 10) + : undefined, + videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined + ? parseInt(videoQuotaUsedDaily + '', 10) + : undefined, + + noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, + noWelcomeModal: this.noWelcomeModal, + blocked: this.blocked, blockedReason: this.blockedReason, + account: this.Account.toFormattedJSON(), - notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, + + notificationSettings: this.NotificationSetting + ? this.NotificationSetting.toFormattedJSON() + : undefined, + videoChannels: [], - videoQuotaUsed: videoQuotaUsed !== undefined - ? parseInt(videoQuotaUsed + '', 10) - : undefined, - videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined - ? parseInt(videoQuotaUsedDaily + '', 10) - : undefined + + createdAt: this.createdAt } 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 () { await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) }) + it('Should fail with an invalid noInstanceConfigWarningModal attribute', async function () { + const fields = { + noInstanceConfigWarningModal: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) + }) + + it('Should fail with an invalid noWelcomeModal attribute', async function () { + const fields = { + noWelcomeModal: -1 + } + + await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) + }) + it('Should succeed to change password with the correct params', async function () { const fields = { currentPassword: 'my super password', @@ -483,7 +499,9 @@ describe('Test users API validators', function () { nsfwPolicy: 'blur', autoPlayVideo: false, email: 'super_email@example.com', - theme: 'default' + theme: 'default', + noInstanceConfigWarningModal: true, + noWelcomeModal: true } 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 () { url: server.url, accessToken: accessTokenUser, currentPassword: 'super password', - newPassword: 'new password' + password: 'new password' }) user.password = 'new password' @@ -543,7 +543,7 @@ describe('Test users', function () { }) const res = await getMyUserInformation(server.url, accessTokenUser) - const user = res.body + const user: User = res.body expect(user.username).to.equal('user_1') expect(user.email).to.equal('updated@example.com') @@ -552,6 +552,8 @@ describe('Test users', function () { expect(user.id).to.be.a('number') expect(user.account.displayName).to.equal('new display name') expect(user.account.description).to.equal('my super description updated') + expect(user.noWelcomeModal).to.be.false + expect(user.noInstanceConfigWarningModal).to.be.false }) it('Should be able to update my theme', async function () { @@ -568,6 +570,21 @@ describe('Test users', function () { expect(body.theme).to.equal(theme) } }) + + it('Should be able to update my modal preferences', async function () { + await updateMyUser({ + url: server.url, + accessToken: accessTokenUser, + noInstanceConfigWarningModal: true, + noWelcomeModal: true + }) + + const res = await getMyUserInformation(server.url, accessTokenUser) + const user: User = res.body + + expect(user.noWelcomeModal).to.be.true + expect(user.noInstanceConfigWarningModal).to.be.true + }) }) 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 @@ import * as request from 'supertest' import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' -import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' import { UserAdminFlag } from '../../models/users/user-flag.model' import { UserRegister } from '../../models/users/user-register.model' import { UserRole } from '../../models/users/user-role' import { ServerInfo } from '../server/servers' import { userLogin } from './login' import { UserUpdateMe } from '../../models/users' +import { omit } from 'lodash' type CreateUserArgs = { url: string, accessToken: string, @@ -214,33 +214,10 @@ function unblockUser (url: string, userId: number | string, accessToken: string, .expect(expectedStatus) } -function updateMyUser (options: { - url: string - accessToken: string - currentPassword?: string - newPassword?: string - nsfwPolicy?: NSFWPolicyType - email?: string - autoPlayVideo?: boolean - displayName?: string - description?: string - videosHistoryEnabled?: boolean - theme?: string -}) { +function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) { const path = '/api/v1/users/me' - const toSend: UserUpdateMe = {} - if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword - if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword - if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy - if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo - if (options.email !== undefined && options.email !== null) toSend.email = options.email - if (options.description !== undefined && options.description !== null) toSend.description = options.description - if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName - if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme - if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { - toSend.videosHistoryEnabled = options.videosHistoryEnabled - } + const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') return makePutBodyRequest({ 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 { password?: string theme?: string + + noInstanceConfigWarningModal?: boolean + noWelcomeModal?: boolean } 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 { username: string email: string pendingEmail: string | null + emailVerified: boolean nsfwPolicy: NSFWPolicyType @@ -18,13 +19,15 @@ export interface User { autoPlayVideo: boolean webTorrentEnabled: boolean videosHistoryEnabled: boolean + videoLanguages: string[] role: UserRole roleLabel: string videoQuota: number videoQuotaDaily: number - createdAt: Date + videoQuotaUsed?: number + videoQuotaUsedDaily?: number theme: string @@ -35,5 +38,8 @@ export interface User { blocked: boolean blockedReason?: string - videoQuotaUsed?: number + noInstanceConfigWarningModal: boolean + noWelcomeModal: boolean + + createdAt: Date }