mirror of https://github.com/Chocobozzz/PeerTube
Add overview of a user's actions in user-edit (#2558)
parent
56d72521ec
commit
76314386ae
|
@ -579,7 +579,7 @@
|
||||||
i18n-labelText labelText="Allow additional extensions"
|
i18n-labelText labelText="Allow additional extensions"
|
||||||
>
|
>
|
||||||
<ng-container ngProjectAs="description">
|
<ng-container ngProjectAs="description">
|
||||||
<span i18n>Allow your users to upload .mkv, .mov, .avi and .flv videos.</span>
|
<span i18n>Allows users to upload .mkv, .mov, .avi and .flv videos.</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</my-peertube-checkbox>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
@ -590,7 +590,7 @@
|
||||||
i18n-labelText labelText="Allow audio files upload"
|
i18n-labelText labelText="Allow audio files upload"
|
||||||
>
|
>
|
||||||
<ng-container ngProjectAs="description">
|
<ng-container ngProjectAs="description">
|
||||||
<span i18n>Allow your users to upload audio files that will be merged with the preview file on upload.</span>
|
<span i18n>Allows users to upload audio files that will be merged with the preview file on upload.</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</my-peertube-checkbox>
|
</my-peertube-checkbox>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -50,6 +50,7 @@ input[type=submit] {
|
||||||
textarea {
|
textarea {
|
||||||
@include peertube-textarea(500px, 150px);
|
@include peertube-textarea(500px, 150px);
|
||||||
|
|
||||||
|
max-width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
&.small {
|
&.small {
|
||||||
|
@ -72,6 +73,10 @@ my-markdown-textarea ::ng-deep {
|
||||||
@media screen and (max-width: 1400px) {
|
@media screen and (max-width: 1400px) {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
|
||||||
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
|
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
|
||||||
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
||||||
import { UserService } from '@app/shared'
|
import { UserService } from '@app/shared'
|
||||||
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-user-create',
|
selector: 'my-user-create',
|
||||||
|
@ -21,6 +22,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
|
||||||
protected serverService: ServerService,
|
protected serverService: ServerService,
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
protected configService: ConfigService,
|
protected configService: ConfigService,
|
||||||
|
protected screenService: ScreenService,
|
||||||
protected auth: AuthService,
|
protected auth: AuthService,
|
||||||
private userValidatorsService: UserValidatorsService,
|
private userValidatorsService: UserValidatorsService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
|
|
@ -1,112 +1,204 @@
|
||||||
<div i18n class="form-sub-title" *ngIf="isCreation() === true">Create user</div>
|
<nav aria-label="breadcrumb">
|
||||||
<div i18n class="form-sub-title" *ngIf="isCreation() === false">Edit user {{ username }}</div>
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a routerLink="/admin/users" i18n>Users</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<ng-container *ngIf="isCreation()">
|
||||||
|
<li class="breadcrumb-item active" i18n>Create</li>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="!isCreation()">
|
||||||
|
<li class="breadcrumb-item active" i18n>Edit</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">
|
||||||
|
<a *ngIf="user" [routerLink]="[ '/accounts', user?.username ]">{{ user?.username }}</a>
|
||||||
|
</li>
|
||||||
|
</ng-container>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<ng-template #dashboard>
|
||||||
|
<div *ngIf="!isCreation() && user" class="dashboard">
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ user.videosCount }} ({{ user.videoQuotaUsed | bytes: 0 }})</div>
|
||||||
|
<div class="dashboard-label" i18n>{user.videosCount, plural, =1 {Video} other {Videos}}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ user.videoChannels.length || 0 }}</div>
|
||||||
|
<div class="dashboard-label" i18n>{user.videoChannels.length, plural, =1 {Channel} other {Channels}}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ subscribersCount }}</div>
|
||||||
|
<div class="dashboard-label" i18n>{subscribersCount, plural, =1 {Subscriber} other {Subscribers}}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ user.videoAbusesCount }}</div>
|
||||||
|
<div class="dashboard-label" i18n>Incriminated in reports</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ user.videoAbusesAcceptedCount }} / {{ user.videoAbusesCreatedCount }}</div>
|
||||||
|
<div class="dashboard-label" i18n>Authored reports accepted</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a>
|
||||||
|
<div class="dashboard-num">{{ user.videoCommentsCount }}</div>
|
||||||
|
<div class="dashboard-label" i18n>{user.videoCommentsCount, plural, =1 {Comment} other {Comments}}</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="form-row" *ngIf="!isInBigView()"> <!-- hidden on large screens, as it is then displayed on the right side of the form -->
|
||||||
|
<div class="col-12 col-xl-3"></div>
|
||||||
|
|
||||||
|
<div class="form-group-right col-12 col-xl-9">
|
||||||
|
<ng-template *ngTemplateOutlet="dashboard"></ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
|
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
|
||||||
|
|
||||||
<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
|
<div class="form-row mt-4"> <!-- user grid -->
|
||||||
<div class="form-group" *ngIf="isCreation()">
|
<div class="form-group col-12 col-lg-4 col-xl-3">
|
||||||
<label i18n for="username">Username</label>
|
<div class="anchor" id="user"></div> <!-- user anchor -->
|
||||||
<input
|
<div *ngIf="isCreation()" class="account-title" i18n>NEW USER</div>
|
||||||
type="text" id="username" i18n-placeholder placeholder="john"
|
<div *ngIf="!isCreation() && user" class="account-title">
|
||||||
formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
|
<my-actor-avatar-info [actor]="user.account"></my-actor-avatar-info>
|
||||||
>
|
|
||||||
<div *ngIf="formErrors.username" class="form-error">
|
|
||||||
{{ formErrors.username }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group form-group-right col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }">
|
||||||
<label i18n for="email">Email</label>
|
|
||||||
<input
|
|
||||||
type="text" id="email" i18n-placeholder placeholder="mail@example.com"
|
|
||||||
formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
|
|
||||||
autocomplete="off"
|
|
||||||
>
|
|
||||||
<div *ngIf="formErrors.email" class="form-error">
|
|
||||||
{{ formErrors.email }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" *ngIf="isCreation()">
|
<form role="form" (ngSubmit)="formValidated()" [formGroup]="form" [ngClass]="{ 'col-5': isInBigView() }">
|
||||||
<label i18n for="password">Password</label>
|
<div class="form-group" *ngIf="isCreation()">
|
||||||
<my-help *ngIf="isPasswordOptional()">
|
<label i18n for="username">Username</label>
|
||||||
<ng-template ptTemplate="customHtml">
|
<input
|
||||||
<ng-container i18n>
|
type="text" id="username" i18n-placeholder placeholder="john"
|
||||||
If you leave the password empty, an email will be sent to the user.
|
formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
|
||||||
</ng-container>
|
>
|
||||||
</ng-template>
|
<div *ngIf="formErrors.username" class="form-error">
|
||||||
</my-help>
|
{{ formErrors.username }}
|
||||||
<input
|
</div>
|
||||||
type="password" id="password" autocomplete="new-password"
|
</div>
|
||||||
formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
|
|
||||||
>
|
|
||||||
<div *ngIf="formErrors.password" class="form-error">
|
|
||||||
{{ formErrors.password }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label i18n for="role">Role</label>
|
<label i18n for="email">Email</label>
|
||||||
<div class="peertube-select-container">
|
<input
|
||||||
<select id="role" formControlName="role">
|
type="text" id="email" i18n-placeholder placeholder="mail@example.com"
|
||||||
<option *ngFor="let role of roles" [value]="role.value">
|
formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
|
||||||
{{ role.label }}
|
autocomplete="off"
|
||||||
</option>
|
>
|
||||||
</select>
|
<div *ngIf="formErrors.email" class="form-error">
|
||||||
|
{{ formErrors.email }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" *ngIf="isCreation()">
|
||||||
|
<label i18n for="password">Password</label>
|
||||||
|
<my-help *ngIf="isPasswordOptional()">
|
||||||
|
<ng-template ptTemplate="customHtml">
|
||||||
|
<ng-container i18n>
|
||||||
|
If you leave the password empty, an email will be sent to the user.
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</my-help>
|
||||||
|
<input
|
||||||
|
type="password" id="password" autocomplete="new-password"
|
||||||
|
formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
|
||||||
|
>
|
||||||
|
<div *ngIf="formErrors.password" class="form-error">
|
||||||
|
{{ formErrors.password }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label i18n for="role">Role</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="role" formControlName="role">
|
||||||
|
<option *ngFor="let role of roles" [value]="role.value">
|
||||||
|
{{ role.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="formErrors.role" class="form-error">
|
||||||
|
{{ formErrors.role }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label i18n for="videoQuota">Video quota</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="videoQuota" formControlName="videoQuota">
|
||||||
|
<option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
|
||||||
|
{{ videoQuotaOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
|
||||||
|
Transcoding is enabled. The video quota only takes into account <strong>original</strong> video size. <br />
|
||||||
|
At most, this user could upload ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label i18n for="videoQuotaDaily">Daily video quota</label>
|
||||||
|
<div class="peertube-select-container">
|
||||||
|
<select id="videoQuotaDaily" formControlName="videoQuotaDaily">
|
||||||
|
<option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
|
||||||
|
{{ videoQuotaDailyOption.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
inputName="byPassAutoBlacklist" formControlName="byPassAutoBlacklist"
|
||||||
|
i18n-labelText labelText="Doesn't need review before a video goes public"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div *ngIf="isInBigView()" class="col-7">
|
||||||
|
<ng-template *ngTemplateOutlet="dashboard"></ng-template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div *ngIf="!isCreation() && user" class="form-row mt-4"> <!-- danger zone grid -->
|
||||||
|
<div class="form-group col-12 col-lg-4 col-xl-3">
|
||||||
|
<div class="anchor" id="danger"></div> <!-- danger zone anchor -->
|
||||||
|
<div i18n class="account-title">DANGER ZONE</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-group-right col-12 col-lg-8 col-xl-9" [ngClass]="{ 'form-row': isInBigView() }">
|
||||||
|
|
||||||
|
<div class="danger-zone">
|
||||||
|
<div class="form-group reset-password-email">
|
||||||
|
<label i18n>Send a link to reset the password by email to the user</label>
|
||||||
|
<button (click)="resetPassword()" i18n>Ask for new password</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label i18n>Manually set the user password</label>
|
||||||
|
<my-user-password [userId]="user.id"></my-user-password>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="formErrors.role" class="form-error">
|
|
||||||
{{ formErrors.role }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label i18n for="videoQuota">Video quota</label>
|
|
||||||
<div class="peertube-select-container">
|
|
||||||
<select id="videoQuota" formControlName="videoQuota">
|
|
||||||
<option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
|
|
||||||
{{ videoQuotaOption.label }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div i18n class="transcoding-information" *ngIf="isTranscodingInformationDisplayed()">
|
|
||||||
Transcoding is enabled on server. The video quota only take in account <strong>original</strong> video. <br />
|
|
||||||
At most, this user could use ~ {{ computeQuotaWithTranscoding() | bytes: 0 }}.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label i18n for="videoQuotaDaily">Daily video quota</label>
|
|
||||||
<div class="peertube-select-container">
|
|
||||||
<select id="videoQuotaDaily" formControlName="videoQuotaDaily">
|
|
||||||
<option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
|
|
||||||
{{ videoQuotaDailyOption.label }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<my-peertube-checkbox
|
|
||||||
inputName="byPassAutoBlacklist" formControlName="byPassAutoBlacklist"
|
|
||||||
i18n-labelText labelText="Bypass video auto blacklist"
|
|
||||||
></my-peertube-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div *ngIf="!isCreation()" class="danger-zone">
|
|
||||||
<div class="account-title" i18n>Danger Zone</div>
|
|
||||||
|
|
||||||
<div class="form-group reset-password-email">
|
|
||||||
<label i18n>Send a link to reset the password by email to the user</label>
|
|
||||||
<button (click)="resetPassword()" i18n>Ask for new password</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label i18n>Manually set the user password</label>
|
|
||||||
<my-user-password [userId]="userId"></my-user-password>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
@import '_variables';
|
@import '_variables';
|
||||||
@import '_mixins';
|
@import '_mixins';
|
||||||
|
|
||||||
.form-sub-title {
|
label {
|
||||||
margin-bottom: 30px;
|
font-weight: $font-regular;
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-title {
|
||||||
|
@include settings-big-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:not([type=submit]) {
|
input:not([type=submit]) {
|
||||||
|
@ -26,18 +31,9 @@ input[type=submit], button {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-title {
|
|
||||||
@include in-content-small-title;
|
|
||||||
|
|
||||||
margin-top: 55px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.danger-zone {
|
.danger-zone {
|
||||||
.reset-password-email {
|
.reset-password-email {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
padding-bottom: 30px;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -45,3 +41,20 @@ input[type=submit], button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
@include breadcrumb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
@include dashboard;
|
||||||
|
max-width: 900px;
|
||||||
|
}
|
||||||
|
|
||||||
|
my-actor-avatar-info ::ng-deep {
|
||||||
|
.actor-img-edit-container,
|
||||||
|
.actor-info-followers,
|
||||||
|
.actor-info-username {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import { ServerConfig, USER_ROLE_LABELS, UserRole, VideoResolution } from '../..
|
||||||
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
||||||
import { UserAdminFlag } from '@shared/models/users/user-flag.model'
|
import { UserAdminFlag } from '@shared/models/users/user-flag.model'
|
||||||
import { OnInit } from '@angular/core'
|
import { OnInit } from '@angular/core'
|
||||||
|
import { User } from '@app/shared/users/user.model'
|
||||||
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
|
|
||||||
export abstract class UserEdit extends FormReactive implements OnInit {
|
export abstract class UserEdit extends FormReactive implements OnInit {
|
||||||
videoQuotaOptions: { value: string, label: string }[] = []
|
videoQuotaOptions: { value: string, label: string }[] = []
|
||||||
videoQuotaDailyOptions: { value: string, label: string }[] = []
|
videoQuotaDailyOptions: { value: string, label: string }[] = []
|
||||||
username: string
|
username: string
|
||||||
userId: number
|
user: User
|
||||||
|
|
||||||
roles: { value: string, label: string }[] = []
|
roles: { value: string, label: string }[] = []
|
||||||
|
|
||||||
|
@ -17,6 +19,7 @@ export abstract class UserEdit extends FormReactive implements OnInit {
|
||||||
|
|
||||||
protected abstract serverService: ServerService
|
protected abstract serverService: ServerService
|
||||||
protected abstract configService: ConfigService
|
protected abstract configService: ConfigService
|
||||||
|
protected abstract screenService: ScreenService
|
||||||
protected abstract auth: AuthService
|
protected abstract auth: AuthService
|
||||||
abstract isCreation (): boolean
|
abstract isCreation (): boolean
|
||||||
abstract getFormButtonTitle (): string
|
abstract getFormButtonTitle (): string
|
||||||
|
@ -29,6 +32,20 @@ export abstract class UserEdit extends FormReactive implements OnInit {
|
||||||
this.buildRoles()
|
this.buildRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get subscribersCount () {
|
||||||
|
const forAccount = this.user
|
||||||
|
? this.user.account.followersCount
|
||||||
|
: 0
|
||||||
|
const forChannels = this.user
|
||||||
|
? this.user.videoChannels.map(c => c.followersCount).reduce((a, b) => a + b, 0)
|
||||||
|
: 0
|
||||||
|
return forAccount + forChannels
|
||||||
|
}
|
||||||
|
|
||||||
|
isInBigView () {
|
||||||
|
return this.screenService.getWindowInnerWidth() > 1600
|
||||||
|
}
|
||||||
|
|
||||||
buildRoles () {
|
buildRoles () {
|
||||||
const authUser = this.auth.getUser()
|
const authUser = this.auth.getUser()
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,6 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
|
||||||
constructor (
|
constructor (
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
private userValidatorsService: UserValidatorsService,
|
private userValidatorsService: UserValidatorsService,
|
||||||
private route: ActivatedRoute,
|
|
||||||
private router: Router,
|
|
||||||
private notifier: Notifier,
|
private notifier: Notifier,
|
||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
private i18n: I18n
|
private i18n: I18n
|
||||||
|
|
|
@ -4,13 +4,15 @@ import { Subscription } from 'rxjs'
|
||||||
import { AuthService, Notifier } from '@app/core'
|
import { AuthService, Notifier } from '@app/core'
|
||||||
import { ServerService } from '../../../core'
|
import { ServerService } from '../../../core'
|
||||||
import { UserEdit } from './user-edit'
|
import { UserEdit } from './user-edit'
|
||||||
import { User, UserUpdate } from '../../../../../../shared'
|
import { User as UserType, UserUpdate, UserRole } from '../../../../../../shared'
|
||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||||
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
|
||||||
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
|
import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
|
||||||
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
import { ConfigService } from '@app/+admin/config/shared/config.service'
|
||||||
import { UserService } from '@app/shared'
|
import { UserService } from '@app/shared'
|
||||||
import { UserAdminFlag } from '@shared/models/users/user-flag.model'
|
import { UserAdminFlag } from '@shared/models/users/user-flag.model'
|
||||||
|
import { User } from '@app/shared/users/user.model'
|
||||||
|
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-user-update',
|
selector: 'my-user-update',
|
||||||
|
@ -19,9 +21,6 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
|
||||||
})
|
})
|
||||||
export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
error: string
|
error: string
|
||||||
userId: number
|
|
||||||
userEmail: string
|
|
||||||
username: string
|
|
||||||
|
|
||||||
private paramsSub: Subscription
|
private paramsSub: Subscription
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
protected serverService: ServerService,
|
protected serverService: ServerService,
|
||||||
protected configService: ConfigService,
|
protected configService: ConfigService,
|
||||||
|
protected screenService: ScreenService,
|
||||||
protected auth: AuthService,
|
protected auth: AuthService,
|
||||||
private userValidatorsService: UserValidatorsService,
|
private userValidatorsService: UserValidatorsService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -45,7 +45,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
super.ngOnInit()
|
super.ngOnInit()
|
||||||
|
|
||||||
const defaultValues = { videoQuota: '-1', videoQuotaDaily: '-1' }
|
const defaultValues = {
|
||||||
|
role: UserRole.USER.toString(),
|
||||||
|
videoQuota: '-1',
|
||||||
|
videoQuotaDaily: '-1'
|
||||||
|
}
|
||||||
|
|
||||||
this.buildForm({
|
this.buildForm({
|
||||||
email: this.userValidatorsService.USER_EMAIL,
|
email: this.userValidatorsService.USER_EMAIL,
|
||||||
role: this.userValidatorsService.USER_ROLE,
|
role: this.userValidatorsService.USER_ROLE,
|
||||||
|
@ -56,7 +61,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.paramsSub = this.route.params.subscribe(routeParams => {
|
this.paramsSub = this.route.params.subscribe(routeParams => {
|
||||||
const userId = routeParams['id']
|
const userId = routeParams['id']
|
||||||
this.userService.getUser(userId).subscribe(
|
this.userService.getUser(userId, true).subscribe(
|
||||||
user => this.onUserFetched(user),
|
user => this.onUserFetched(user),
|
||||||
|
|
||||||
err => this.error = err.message
|
err => this.error = err.message
|
||||||
|
@ -78,9 +83,9 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
|
userUpdate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
|
||||||
userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
|
userUpdate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
|
||||||
|
|
||||||
this.userService.updateUser(this.userId, userUpdate).subscribe(
|
this.userService.updateUser(this.user.id, userUpdate).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username }))
|
this.notifier.success(this.i18n('User {{user.username}} updated.', { username: this.user.username }))
|
||||||
this.router.navigate([ '/admin/users/list' ])
|
this.router.navigate([ '/admin/users/list' ])
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -101,10 +106,10 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPassword () {
|
resetPassword () {
|
||||||
this.userService.askResetPassword(this.userEmail).subscribe(
|
this.userService.askResetPassword(this.user.email).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.notifier.success(
|
this.notifier.success(
|
||||||
this.i18n('An email asking for password reset has been sent to {{username}}.', { username: this.username })
|
this.i18n('An email asking for password reset has been sent to {{username}}.', { username: this.user.username })
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -112,14 +117,12 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private onUserFetched (userJson: User) {
|
private onUserFetched (userJson: UserType) {
|
||||||
this.userId = userJson.id
|
this.user = new User(userJson)
|
||||||
this.username = userJson.username
|
|
||||||
this.userEmail = userJson.email
|
|
||||||
|
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
email: userJson.email,
|
email: userJson.email,
|
||||||
role: userJson.role,
|
role: userJson.role.toString(),
|
||||||
videoQuota: userJson.videoQuota,
|
videoQuota: userJson.videoQuota,
|
||||||
videoQuotaDaily: userJson.videoQuotaDaily,
|
videoQuotaDaily: userJson.videoQuotaDaily,
|
||||||
byPassAutoBlacklist: userJson.adminFlags & UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
|
byPassAutoBlacklist: userJson.adminFlags & UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST
|
||||||
|
|
|
@ -15,7 +15,6 @@ import { MyAccountProfileComponent } from '@app/+my-account/my-account-settings/
|
||||||
import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
|
import { MyAccountVideoChannelsComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channels.component'
|
||||||
import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
|
import { MyAccountVideoChannelCreateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-create.component'
|
||||||
import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
|
import { MyAccountVideoChannelUpdateComponent } from '@app/+my-account/my-account-video-channels/my-account-video-channel-update.component'
|
||||||
import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
|
|
||||||
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
|
import { MyAccountVideoImportsComponent } from '@app/+my-account/my-account-video-imports/my-account-video-imports.component'
|
||||||
import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
|
import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settings/my-account-danger-zone'
|
||||||
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
|
import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
|
||||||
|
@ -63,7 +62,6 @@ import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-setti
|
||||||
MyAccountVideoChannelsComponent,
|
MyAccountVideoChannelsComponent,
|
||||||
MyAccountVideoChannelCreateComponent,
|
MyAccountVideoChannelCreateComponent,
|
||||||
MyAccountVideoChannelUpdateComponent,
|
MyAccountVideoChannelUpdateComponent,
|
||||||
ActorAvatarInfoComponent,
|
|
||||||
MyAccountVideoImportsComponent,
|
MyAccountVideoImportsComponent,
|
||||||
MyAccountDangerZoneComponent,
|
MyAccountDangerZoneComponent,
|
||||||
MyAccountSubscriptionsComponent,
|
MyAccountSubscriptionsComponent,
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
<ng-container *ngIf="actor">
|
<ng-container *ngIf="actor">
|
||||||
<div class="actor">
|
<div class="actor">
|
||||||
<img [src]="actor.avatarUrl" alt="Avatar" />
|
<div class="d-flex">
|
||||||
|
<img [src]="actor.avatarUrl" alt="Avatar" />
|
||||||
|
|
||||||
<div class="actor-img-edit-container">
|
<div class="actor-img-edit-container">
|
||||||
<div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body">
|
<div class="actor-img-edit-button" [ngbTooltip]="'(extensions: '+ avatarExtensions +', '+ maxSizeText +': '+ maxAvatarSizeInBytes +')'" placement="right" container="body">
|
||||||
<my-global-icon iconName="edit"></my-global-icon>
|
<my-global-icon iconName="edit"></my-global-icon>
|
||||||
<input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
|
<input #avatarfileInput type="file" title=" " name="avatarfile" id="avatarfile" [accept]="avatarExtensions" (change)="onAvatarChange()"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="actor-info">
|
<div class="actor-info">
|
||||||
<div class="actor-info-names">
|
<div class="actor-info-names">
|
||||||
<div class="actor-info-display-name">{{ actor.displayName }}</div>
|
<div class="actor-info-display-name">{{ actor.displayName }}</div>
|
||||||
|
|
|
@ -106,6 +106,7 @@ import { InputSwitchModule } from 'primeng/inputswitch'
|
||||||
|
|
||||||
import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
|
import { MyAccountVideoSettingsComponent } from '@app/+my-account/my-account-settings/my-account-video-settings'
|
||||||
import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
|
import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface'
|
||||||
|
import { ActorAvatarInfoComponent } from '@app/+my-account/shared/actor-avatar-info.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -189,7 +190,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
|
||||||
PreviewUploadComponent,
|
PreviewUploadComponent,
|
||||||
|
|
||||||
MyAccountVideoSettingsComponent,
|
MyAccountVideoSettingsComponent,
|
||||||
MyAccountInterfaceSettingsComponent
|
MyAccountInterfaceSettingsComponent,
|
||||||
|
ActorAvatarInfoComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -270,7 +272,8 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account
|
||||||
VideoDurationPipe,
|
VideoDurationPipe,
|
||||||
|
|
||||||
MyAccountVideoSettingsComponent,
|
MyAccountVideoSettingsComponent,
|
||||||
MyAccountInterfaceSettingsComponent
|
MyAccountInterfaceSettingsComponent,
|
||||||
|
ActorAvatarInfoComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
@ -51,6 +51,11 @@ export class User implements UserServerModel {
|
||||||
videoQuotaDaily: number
|
videoQuotaDaily: number
|
||||||
videoQuotaUsed?: number
|
videoQuotaUsed?: number
|
||||||
videoQuotaUsedDaily?: number
|
videoQuotaUsedDaily?: number
|
||||||
|
videosCount?: number
|
||||||
|
videoAbusesCount?: number
|
||||||
|
videoAbusesAcceptedCount?: number
|
||||||
|
videoAbusesCreatedCount?: number
|
||||||
|
videoCommentsCount?: number
|
||||||
|
|
||||||
theme: string
|
theme: string
|
||||||
|
|
||||||
|
@ -79,6 +84,11 @@ export class User implements UserServerModel {
|
||||||
this.videoQuotaDaily = hash.videoQuotaDaily
|
this.videoQuotaDaily = hash.videoQuotaDaily
|
||||||
this.videoQuotaUsed = hash.videoQuotaUsed
|
this.videoQuotaUsed = hash.videoQuotaUsed
|
||||||
this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
|
this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
|
||||||
|
this.videosCount = hash.videosCount
|
||||||
|
this.videoAbusesCount = hash.videoAbusesCount
|
||||||
|
this.videoAbusesAcceptedCount = hash.videoAbusesAcceptedCount
|
||||||
|
this.videoAbusesCreatedCount = hash.videoAbusesCreatedCount
|
||||||
|
this.videoCommentsCount = hash.videoCommentsCount
|
||||||
|
|
||||||
this.nsfwPolicy = hash.nsfwPolicy
|
this.nsfwPolicy = hash.nsfwPolicy
|
||||||
this.webTorrentEnabled = hash.webTorrentEnabled
|
this.webTorrentEnabled = hash.webTorrentEnabled
|
||||||
|
|
|
@ -234,8 +234,9 @@ export class UserService {
|
||||||
return this.userCache[userId]
|
return this.userCache[userId]
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser (userId: number) {
|
getUser (userId: number, withStats = false) {
|
||||||
return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId)
|
const params = new HttpParams().append('withStats', withStats + '')
|
||||||
|
return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
|
||||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,7 @@
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<div i18n class="information">
|
<div i18n class="information">
|
||||||
Your report will be sent to moderators of {{ currentHost }}.
|
Your report will be sent to moderators of {{ currentHost }}<ng-container *ngIf="isRemoteVideo()"> and will be forwarded to the video origin ({{ originHost }}) too</ng-container>.
|
||||||
<ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form novalidate [formGroup]="form" (ngSubmit)="report()">
|
<form novalidate [formGroup]="form" (ngSubmit)="report()">
|
||||||
|
|
|
@ -621,3 +621,85 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
list-style: none;
|
||||||
|
background-color: var(--submenuColor);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
|
||||||
|
.breadcrumb-item {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--mainColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
& + .breadcrumb-item {
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
color: #6c757d;
|
||||||
|
content: "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dashboard {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0 -5px;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex: 0 0 percentage(1/3);
|
||||||
|
padding: 0 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
opacity: .8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a,
|
||||||
|
& > div {
|
||||||
|
padding: 20px;
|
||||||
|
background: var(--submenuColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-num, .dashboard-text {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 130%;
|
||||||
|
line-height: 21px;
|
||||||
|
color: var(--mainForegroundColor);
|
||||||
|
line-height: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-label {
|
||||||
|
font-size: 90%;
|
||||||
|
color: var(--inputPlaceholderColor);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as Bluebird from 'bluebird'
|
import * as Bluebird from 'bluebird'
|
||||||
import * as express from 'express'
|
import * as express from 'express'
|
||||||
import { body, param } from 'express-validator'
|
import { body, param, query } from 'express-validator'
|
||||||
import { omit } from 'lodash'
|
import { omit } from 'lodash'
|
||||||
import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
|
import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
|
||||||
import {
|
import {
|
||||||
|
@ -256,12 +256,13 @@ const usersUpdateMeValidator = [
|
||||||
|
|
||||||
const usersGetValidator = [
|
const usersGetValidator = [
|
||||||
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
|
||||||
|
query('withStats').optional().isBoolean().withMessage('Should have a valid stats flag'),
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
logger.debug('Checking usersGet parameters', { parameters: req.params })
|
logger.debug('Checking usersGet parameters', { parameters: req.params })
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await checkUserIdExist(req.params.id, res)) return
|
if (!await checkUserIdExist(req.params.id, res, req.query.withStats)) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
@ -460,9 +461,9 @@ export {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function checkUserIdExist (idArg: number | string, res: express.Response) {
|
function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) {
|
||||||
const id = parseInt(idArg + '', 10)
|
const id = parseInt(idArg + '', 10)
|
||||||
return checkUserExist(() => UserModel.loadById(id), res)
|
return checkUserExist(() => UserModel.loadById(id, withStats), res)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
|
function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared'
|
import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoPlaylistType, VideoPrivacy, VideoAbuseState } from '../../../shared'
|
||||||
import { User, UserRole } from '../../../shared/models/users'
|
import { User, UserRole } from '../../../shared/models/users'
|
||||||
import {
|
import {
|
||||||
isNoInstanceConfigWarningModal,
|
isNoInstanceConfigWarningModal,
|
||||||
|
@ -70,8 +70,26 @@ import {
|
||||||
MVideoFullLight
|
MVideoFullLight
|
||||||
} from '@server/typings/models'
|
} from '@server/typings/models'
|
||||||
|
|
||||||
|
const literalVideoQuotaUsed: any = [
|
||||||
|
literal(
|
||||||
|
'(' +
|
||||||
|
'SELECT COALESCE(SUM("size"), 0) ' +
|
||||||
|
'FROM (' +
|
||||||
|
'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
|
||||||
|
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
|
||||||
|
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
||||||
|
'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
|
||||||
|
'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' +
|
||||||
|
') t' +
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
'videoQuotaUsed'
|
||||||
|
]
|
||||||
|
|
||||||
enum ScopeNames {
|
enum ScopeNames {
|
||||||
FOR_ME_API = 'FOR_ME_API'
|
FOR_ME_API = 'FOR_ME_API',
|
||||||
|
WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS',
|
||||||
|
WITH_STATS = 'WITH_STATS'
|
||||||
}
|
}
|
||||||
|
|
||||||
@DefaultScope(() => ({
|
@DefaultScope(() => ({
|
||||||
|
@ -112,6 +130,86 @@ enum ScopeNames {
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
[ScopeNames.WITH_VIDEOCHANNELS]: {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: AccountModel,
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: VideoChannelModel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attributes: [ 'id', 'name', 'type' ],
|
||||||
|
model: VideoPlaylistModel.unscoped(),
|
||||||
|
required: true,
|
||||||
|
where: {
|
||||||
|
type: {
|
||||||
|
[Op.ne]: VideoPlaylistType.REGULAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
[ScopeNames.WITH_STATS]: {
|
||||||
|
attributes: {
|
||||||
|
include: [
|
||||||
|
literalVideoQuotaUsed,
|
||||||
|
[
|
||||||
|
literal(
|
||||||
|
'(' +
|
||||||
|
'SELECT COUNT("video"."id") ' +
|
||||||
|
'FROM "video" ' +
|
||||||
|
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
||||||
|
'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
|
||||||
|
'WHERE "account"."userId" = "UserModel"."id"' +
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
'videosCount'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
literal(
|
||||||
|
'(' +
|
||||||
|
`SELECT concat_ws(':', "abuses", "acceptedAbuses") ` +
|
||||||
|
'FROM (' +
|
||||||
|
'SELECT COUNT("videoAbuse"."id") AS "abuses", ' +
|
||||||
|
`COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${VideoAbuseState.ACCEPTED}) AS "acceptedAbuses" ` +
|
||||||
|
'FROM "videoAbuse" ' +
|
||||||
|
'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' +
|
||||||
|
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
||||||
|
'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
|
||||||
|
'WHERE "account"."userId" = "UserModel"."id"' +
|
||||||
|
') t' +
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
'videoAbusesCount'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
literal(
|
||||||
|
'(' +
|
||||||
|
'SELECT COUNT("videoAbuse"."id") ' +
|
||||||
|
'FROM "videoAbuse" ' +
|
||||||
|
'INNER JOIN "account" ON "account"."id" = "videoAbuse"."reporterAccountId" ' +
|
||||||
|
'WHERE "account"."userId" = "UserModel"."id"' +
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
'videoAbusesCreatedCount'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
literal(
|
||||||
|
'(' +
|
||||||
|
'SELECT COUNT("videoComment"."id") ' +
|
||||||
|
'FROM "videoComment" ' +
|
||||||
|
'INNER JOIN "account" ON "account"."id" = "videoComment"."accountId" ' +
|
||||||
|
'WHERE "account"."userId" = "UserModel"."id"' +
|
||||||
|
')'
|
||||||
|
),
|
||||||
|
'videoCommentsCount'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@Table({
|
@Table({
|
||||||
|
@ -332,23 +430,7 @@ export class UserModel extends Model<UserModel> {
|
||||||
|
|
||||||
const query: FindOptions = {
|
const query: FindOptions = {
|
||||||
attributes: {
|
attributes: {
|
||||||
include: [
|
include: [ literalVideoQuotaUsed ]
|
||||||
[
|
|
||||||
literal(
|
|
||||||
'(' +
|
|
||||||
'SELECT COALESCE(SUM("size"), 0) ' +
|
|
||||||
'FROM (' +
|
|
||||||
'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
|
|
||||||
'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
|
|
||||||
'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
|
|
||||||
'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
|
|
||||||
'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' +
|
|
||||||
') t' +
|
|
||||||
')'
|
|
||||||
),
|
|
||||||
'videoQuotaUsed'
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
offset: start,
|
offset: start,
|
||||||
limit: count,
|
limit: count,
|
||||||
|
@ -430,8 +512,14 @@ export class UserModel extends Model<UserModel> {
|
||||||
return UserModel.findAll(query)
|
return UserModel.findAll(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadById (id: number): Bluebird<MUserDefault> {
|
static loadById (id: number, withStats = false): Bluebird<MUserDefault> {
|
||||||
return UserModel.findByPk(id)
|
const scopes = [
|
||||||
|
ScopeNames.WITH_VIDEOCHANNELS
|
||||||
|
]
|
||||||
|
|
||||||
|
if (withStats) scopes.push(ScopeNames.WITH_STATS)
|
||||||
|
|
||||||
|
return UserModel.scope(scopes).findByPk(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
static loadByUsername (username: string): Bluebird<MUserDefault> {
|
static loadByUsername (username: string): Bluebird<MUserDefault> {
|
||||||
|
@ -637,6 +725,10 @@ export class UserModel extends Model<UserModel> {
|
||||||
toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
|
toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
|
||||||
const videoQuotaUsed = this.get('videoQuotaUsed')
|
const videoQuotaUsed = this.get('videoQuotaUsed')
|
||||||
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
|
const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
|
||||||
|
const videosCount = this.get('videosCount')
|
||||||
|
const [ videoAbusesCount, videoAbusesAcceptedCount ] = (this.get('videoAbusesCount') as string || ':').split(':')
|
||||||
|
const videoAbusesCreatedCount = this.get('videoAbusesCreatedCount')
|
||||||
|
const videoCommentsCount = this.get('videoCommentsCount')
|
||||||
|
|
||||||
const json: User = {
|
const json: User = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
@ -666,6 +758,21 @@ export class UserModel extends Model<UserModel> {
|
||||||
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
|
videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
|
||||||
? parseInt(videoQuotaUsedDaily + '', 10)
|
? parseInt(videoQuotaUsedDaily + '', 10)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
videosCount: videosCount !== undefined
|
||||||
|
? parseInt(videosCount + '', 10)
|
||||||
|
: undefined,
|
||||||
|
videoAbusesCount: videoAbusesCount
|
||||||
|
? parseInt(videoAbusesCount, 10)
|
||||||
|
: undefined,
|
||||||
|
videoAbusesAcceptedCount: videoAbusesAcceptedCount
|
||||||
|
? parseInt(videoAbusesAcceptedCount, 10)
|
||||||
|
: undefined,
|
||||||
|
videoAbusesCreatedCount: videoAbusesCreatedCount !== undefined
|
||||||
|
? parseInt(videoAbusesCreatedCount + '', 10)
|
||||||
|
: undefined,
|
||||||
|
videoCommentsCount: videoCommentsCount !== undefined
|
||||||
|
? parseInt(videoCommentsCount + '', 10)
|
||||||
|
: undefined,
|
||||||
|
|
||||||
noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
|
noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
|
||||||
noWelcomeModal: this.noWelcomeModal,
|
noWelcomeModal: this.noWelcomeModal,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import * as chai from 'chai'
|
import * as chai from 'chai'
|
||||||
import 'mocha'
|
import 'mocha'
|
||||||
import { MyUser, User, UserRole, Video, VideoPlaylistType } from '../../../../shared/index'
|
import { MyUser, User, UserRole, Video, VideoPlaylistType, VideoAbuseState, VideoAbuseUpdate } from '../../../../shared/index'
|
||||||
import {
|
import {
|
||||||
blockUser,
|
blockUser,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
|
@ -33,7 +33,11 @@ import {
|
||||||
updateMyUser,
|
updateMyUser,
|
||||||
updateUser,
|
updateUser,
|
||||||
uploadVideo,
|
uploadVideo,
|
||||||
userLogin
|
userLogin,
|
||||||
|
reportVideoAbuse,
|
||||||
|
addVideoCommentThread,
|
||||||
|
updateVideoAbuse,
|
||||||
|
getVideoAbusesList
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
import { follow } from '../../../../shared/extra-utils/server/follows'
|
import { follow } from '../../../../shared/extra-utils/server/follows'
|
||||||
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
|
import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
|
||||||
|
@ -254,7 +258,7 @@ describe('Test users', function () {
|
||||||
const res1 = await getMyUserInformation(server.url, accessTokenUser)
|
const res1 = await getMyUserInformation(server.url, accessTokenUser)
|
||||||
const userMe: MyUser = res1.body
|
const userMe: MyUser = res1.body
|
||||||
|
|
||||||
const res2 = await getUserInformation(server.url, server.accessToken, userMe.id)
|
const res2 = await getUserInformation(server.url, server.accessToken, userMe.id, true)
|
||||||
const userGet: User = res2.body
|
const userGet: User = res2.body
|
||||||
|
|
||||||
for (const user of [ userMe, userGet ]) {
|
for (const user of [ userMe, userGet ]) {
|
||||||
|
@ -273,6 +277,16 @@ describe('Test users', function () {
|
||||||
|
|
||||||
expect(userMe.specialPlaylists).to.have.lengthOf(1)
|
expect(userMe.specialPlaylists).to.have.lengthOf(1)
|
||||||
expect(userMe.specialPlaylists[0].type).to.equal(VideoPlaylistType.WATCH_LATER)
|
expect(userMe.specialPlaylists[0].type).to.equal(VideoPlaylistType.WATCH_LATER)
|
||||||
|
|
||||||
|
// Check stats are included with withStats
|
||||||
|
expect(userGet.videosCount).to.be.a('number')
|
||||||
|
expect(userGet.videosCount).to.equal(0)
|
||||||
|
expect(userGet.videoCommentsCount).to.be.a('number')
|
||||||
|
expect(userGet.videoCommentsCount).to.equal(0)
|
||||||
|
expect(userGet.videoAbusesCount).to.be.a('number')
|
||||||
|
expect(userGet.videoAbusesCount).to.equal(0)
|
||||||
|
expect(userGet.videoAbusesAcceptedCount).to.be.a('number')
|
||||||
|
expect(userGet.videoAbusesAcceptedCount).to.equal(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -623,7 +637,6 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Updating another user', function () {
|
describe('Updating another user', function () {
|
||||||
|
|
||||||
it('Should be able to update another user', async function () {
|
it('Should be able to update another user', async function () {
|
||||||
await updateUser({
|
await updateUser({
|
||||||
url: server.url,
|
url: server.url,
|
||||||
|
@ -698,6 +711,8 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Registering a new user', function () {
|
describe('Registering a new user', function () {
|
||||||
|
let user15AccessToken
|
||||||
|
|
||||||
it('Should register a new user', async function () {
|
it('Should register a new user', async function () {
|
||||||
const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' }
|
const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' }
|
||||||
const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
|
const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
|
||||||
|
@ -711,18 +726,18 @@ describe('Test users', function () {
|
||||||
password: 'my super password'
|
password: 'my super password'
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken = await userLogin(server, user15)
|
user15AccessToken = await userLogin(server, user15)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have the correct display name', async function () {
|
it('Should have the correct display name', async function () {
|
||||||
const res = await getMyUserInformation(server.url, accessToken)
|
const res = await getMyUserInformation(server.url, user15AccessToken)
|
||||||
const user: User = res.body
|
const user: User = res.body
|
||||||
|
|
||||||
expect(user.account.displayName).to.equal('super user 15')
|
expect(user.account.displayName).to.equal('super user 15')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should have the correct video quota', async function () {
|
it('Should have the correct video quota', async function () {
|
||||||
const res = await getMyUserInformation(server.url, accessToken)
|
const res = await getMyUserInformation(server.url, user15AccessToken)
|
||||||
const user = res.body
|
const user = res.body
|
||||||
|
|
||||||
expect(user.videoQuota).to.equal(5 * 1024 * 1024)
|
expect(user.videoQuota).to.equal(5 * 1024 * 1024)
|
||||||
|
@ -740,7 +755,7 @@ describe('Test users', function () {
|
||||||
expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
|
expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await deleteMe(server.url, accessToken)
|
await deleteMe(server.url, user15AccessToken)
|
||||||
|
|
||||||
{
|
{
|
||||||
const res = await getUsersList(server.url, server.accessToken)
|
const res = await getUsersList(server.url, server.accessToken)
|
||||||
|
@ -750,6 +765,9 @@ describe('Test users', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('User blocking', function () {
|
describe('User blocking', function () {
|
||||||
|
let user16Id
|
||||||
|
let user16AccessToken
|
||||||
|
|
||||||
it('Should block and unblock a user', async function () {
|
it('Should block and unblock a user', async function () {
|
||||||
const user16 = {
|
const user16 = {
|
||||||
username: 'user_16',
|
username: 'user_16',
|
||||||
|
@ -761,19 +779,95 @@ describe('Test users', function () {
|
||||||
username: user16.username,
|
username: user16.username,
|
||||||
password: user16.password
|
password: user16.password
|
||||||
})
|
})
|
||||||
const user16Id = resUser.body.user.id
|
user16Id = resUser.body.user.id
|
||||||
|
|
||||||
accessToken = await userLogin(server, user16)
|
user16AccessToken = await userLogin(server, user16)
|
||||||
|
|
||||||
await getMyUserInformation(server.url, accessToken, 200)
|
await getMyUserInformation(server.url, user16AccessToken, 200)
|
||||||
await blockUser(server.url, user16Id, server.accessToken)
|
await blockUser(server.url, user16Id, server.accessToken)
|
||||||
|
|
||||||
await getMyUserInformation(server.url, accessToken, 401)
|
await getMyUserInformation(server.url, user16AccessToken, 401)
|
||||||
await userLogin(server, user16, 400)
|
await userLogin(server, user16, 400)
|
||||||
|
|
||||||
await unblockUser(server.url, user16Id, server.accessToken)
|
await unblockUser(server.url, user16Id, server.accessToken)
|
||||||
accessToken = await userLogin(server, user16)
|
user16AccessToken = await userLogin(server, user16)
|
||||||
await getMyUserInformation(server.url, accessToken, 200)
|
await getMyUserInformation(server.url, user16AccessToken, 200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('User stats', function () {
|
||||||
|
let user17Id
|
||||||
|
let user17AccessToken
|
||||||
|
|
||||||
|
it('Should report correct initial statistics about a user', async function () {
|
||||||
|
const user17 = {
|
||||||
|
username: 'user_17',
|
||||||
|
password: 'my super password'
|
||||||
|
}
|
||||||
|
const resUser = await createUser({
|
||||||
|
url: server.url,
|
||||||
|
accessToken: server.accessToken,
|
||||||
|
username: user17.username,
|
||||||
|
password: user17.password
|
||||||
|
})
|
||||||
|
|
||||||
|
user17Id = resUser.body.user.id
|
||||||
|
user17AccessToken = await userLogin(server, user17)
|
||||||
|
|
||||||
|
const res = await getUserInformation(server.url, server.accessToken, user17Id, true)
|
||||||
|
const user: User = res.body
|
||||||
|
|
||||||
|
expect(user.videosCount).to.equal(0)
|
||||||
|
expect(user.videoCommentsCount).to.equal(0)
|
||||||
|
expect(user.videoAbusesCount).to.equal(0)
|
||||||
|
expect(user.videoAbusesCreatedCount).to.equal(0)
|
||||||
|
expect(user.videoAbusesAcceptedCount).to.equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should report correct videos count', async function () {
|
||||||
|
const videoAttributes = {
|
||||||
|
name: 'video to test user stats'
|
||||||
|
}
|
||||||
|
await uploadVideo(server.url, user17AccessToken, videoAttributes)
|
||||||
|
const res1 = await getVideosList(server.url)
|
||||||
|
videoId = res1.body.data.find(video => video.name === videoAttributes.name).id
|
||||||
|
|
||||||
|
const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)
|
||||||
|
const user: User = res2.body
|
||||||
|
|
||||||
|
expect(user.videosCount).to.equal(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should report correct video comments for user', async function () {
|
||||||
|
const text = 'super comment'
|
||||||
|
await addVideoCommentThread(server.url, user17AccessToken, videoId, text)
|
||||||
|
|
||||||
|
const res = await getUserInformation(server.url, server.accessToken, user17Id, true)
|
||||||
|
const user: User = res.body
|
||||||
|
|
||||||
|
expect(user.videoCommentsCount).to.equal(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should report correct video abuses counts', async function () {
|
||||||
|
const reason = 'my super bad reason'
|
||||||
|
await reportVideoAbuse(server.url, user17AccessToken, videoId, reason)
|
||||||
|
|
||||||
|
const res1 = await getVideoAbusesList(server.url, server.accessToken)
|
||||||
|
const abuseId = res1.body.data[0].id
|
||||||
|
|
||||||
|
const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true)
|
||||||
|
const user2: User = res2.body
|
||||||
|
|
||||||
|
expect(user2.videoAbusesCount).to.equal(1) // number of incriminations
|
||||||
|
expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created
|
||||||
|
|
||||||
|
const body: VideoAbuseUpdate = { state: VideoAbuseState.ACCEPTED }
|
||||||
|
await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body)
|
||||||
|
|
||||||
|
const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true)
|
||||||
|
const user3: User = res3.body
|
||||||
|
|
||||||
|
expect(user3.videoAbusesAcceptedCount).to.equal(1) // number of reports created accepted
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -130,11 +130,12 @@ function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatu
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserInformation (url: string, accessToken: string, userId: number) {
|
function getUserInformation (url: string, accessToken: string, userId: number, withStats = false) {
|
||||||
const path = '/api/v1/users/' + userId
|
const path = '/api/v1/users/' + userId
|
||||||
|
|
||||||
return request(url)
|
return request(url)
|
||||||
.get(path)
|
.get(path)
|
||||||
|
.query({ withStats })
|
||||||
.set('Accept', 'application/json')
|
.set('Accept', 'application/json')
|
||||||
.set('Authorization', 'Bearer ' + accessToken)
|
.set('Authorization', 'Bearer ' + accessToken)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
|
|
@ -31,6 +31,11 @@ export interface User {
|
||||||
videoQuotaDaily: number
|
videoQuotaDaily: number
|
||||||
videoQuotaUsed?: number
|
videoQuotaUsed?: number
|
||||||
videoQuotaUsedDaily?: number
|
videoQuotaUsedDaily?: number
|
||||||
|
videosCount?: number
|
||||||
|
videoAbusesCount?: number
|
||||||
|
videoAbusesAcceptedCount?: number
|
||||||
|
videoAbusesCreatedCount?: number
|
||||||
|
videoCommentsCount? : number
|
||||||
|
|
||||||
theme: string
|
theme: string
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue