Add welcome modal
This commit is contained in:
parent
bc20aaed46
commit
43d0ea7f4b
20 changed files with 428 additions and 67 deletions
|
@ -54,3 +54,8 @@
|
|||
</div>
|
||||
</ng-template>
|
||||
</p-toast>
|
||||
|
||||
<ng-template [ngIf]="isUserLoggedIn()">
|
||||
<my-welcome-modal #welcomeModal></my-welcome-modal>
|
||||
<my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal>
|
||||
</ng-template>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Warning!</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer inputs">
|
||||
<span i18n class="action-button action-button-cancel" (click)="hide()">Close</span>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
|
@ -0,0 +1,6 @@
|
|||
@import '_mixins';
|
||||
@import '_variables';
|
||||
|
||||
.action-button-cancel {
|
||||
margin-right: 0 !important;
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
66
client/src/app/modal/welcome-modal.component.html
Normal file
66
client/src/app/modal/welcome-modal.component.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
<ng-template #modal let-hide="close">
|
||||
<div class="modal-header">
|
||||
<h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4>
|
||||
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="block-links">
|
||||
<div class="subtitle">Useful links</div>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Official PeerTube website: <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Discover CLI PeerTube tools (to upload or import videos, parse logs, prune storage directories, reset user password...):
|
||||
<a href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/maintain-tools</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Understand how to administer your instance (managing users, following other instances, dealing with spammers...):
|
||||
<a href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/admin-following-instances</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
Learn how to use PeerTube (setup your account, managing video playlists, discover third-party applications...):
|
||||
<a href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">https://docs.joinpeertube.org/#/use-setup-account</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="block-configuration">
|
||||
<div class="subtitle">Configure your instance</div>
|
||||
|
||||
<p>
|
||||
Now it's time to configure your instance! Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>,
|
||||
specifying <strong>who you are</strong> and <strong>how long</strong> you plan to <strong>maintain your instance</strong>
|
||||
is very important for visitors to understand on what type of instance they are.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong>
|
||||
and specify the categories and languages you speak. This way, users that are looking for a PeerTube instance on which they can register
|
||||
will be able to choose <strong>the right one</strong>.
|
||||
</p>
|
||||
|
||||
<div class="configure-instance">
|
||||
<a href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block-instance">
|
||||
<div class="subtitle">Index your instance</div>
|
||||
|
||||
If you want, you can index your PeerTube instance on the public PeerTube instances list:
|
||||
<a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer inputs">
|
||||
<span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span>
|
||||
</div>
|
||||
|
||||
</ng-template>
|
31
client/src/app/modal/welcome-modal.component.scss
Normal file
31
client/src/app/modal/welcome-modal.component.scss
Normal file
|
@ -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;
|
||||
}
|
40
client/src/app/modal/welcome-modal.component.ts
Normal file
40
client/src/app/modal/welcome-modal.component.ts
Normal file
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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<UserServerModel>) {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
40
server/initializers/migrations/0425-user-modals.ts
Normal file
40
server/initializers/migrations/0425-user-modals.ts
Normal file
|
@ -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<void> {
|
||||
{
|
||||
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
|
||||
}
|
|
@ -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') })
|
||||
|
|
|
@ -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<UserModel> {
|
|||
@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<UserModel> {
|
|||
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) {
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -15,4 +15,7 @@ export interface UserUpdateMe {
|
|||
password?: string
|
||||
|
||||
theme?: string
|
||||
|
||||
noInstanceConfigWarningModal?: boolean
|
||||
noWelcomeModal?: boolean
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue