aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+admin/admin.module.ts2
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-create.component.ts7
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-password.component.ts7
-rw-r--r--client/src/app/+admin/overview/users/user-edit/user-update.component.ts6
-rw-r--r--client/src/app/+admin/overview/users/user-list/user-list.component.ts13
-rw-r--r--client/src/app/+signup/+register/register-step-channel.component.ts6
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.ts6
-rw-r--r--client/src/app/+signup/+register/register.component.ts9
-rw-r--r--client/src/app/+signup/+register/register.module.ts4
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts7
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts7
-rw-r--r--client/src/app/+signup/+verify-account/verify-account.module.ts4
-rw-r--r--client/src/app/+signup/shared/shared-signup.module.ts (renamed from client/src/app/+signup/shared/signup-shared.module.ts)10
-rw-r--r--client/src/app/core/users/user.service.ts284
-rw-r--r--client/src/app/shared/shared-moderation/shared-moderation.module.ts8
-rw-r--r--client/src/app/shared/shared-moderation/user-ban-modal.component.ts7
-rw-r--r--client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts11
-rw-r--r--client/src/app/shared/shared-users/index.ts4
-rw-r--r--client/src/app/shared/shared-users/shared-users.module.ts21
-rw-r--r--client/src/app/shared/shared-users/user-admin.service.ts139
-rw-r--r--client/src/app/shared/shared-users/user-signup.service.ts56
21 files changed, 342 insertions, 276 deletions
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index e0dea3ba5..c672fa280 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -10,6 +10,7 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons'
10import { SharedMainModule } from '@app/shared/shared-main' 10import { SharedMainModule } from '@app/shared/shared-main'
11import { SharedModerationModule } from '@app/shared/shared-moderation' 11import { SharedModerationModule } from '@app/shared/shared-moderation'
12import { SharedTablesModule } from '@app/shared/shared-tables' 12import { SharedTablesModule } from '@app/shared/shared-tables'
13import { SharedUsersModule } from '@app/shared/shared-users'
13import { SharedVideoCommentModule } from '@app/shared/shared-video-comment' 14import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
14import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' 15import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
15import { AdminRoutingModule } from './admin-routing.module' 16import { AdminRoutingModule } from './admin-routing.module'
@@ -67,6 +68,7 @@ import { JobsComponent } from './system/jobs/jobs.component'
67 SharedCustomMarkupModule, 68 SharedCustomMarkupModule,
68 SharedVideoMiniatureModule, 69 SharedVideoMiniatureModule,
69 SharedTablesModule, 70 SharedTablesModule,
71 SharedUsersModule,
70 72
71 TableModule, 73 TableModule,
72 ChartModule 74 ChartModule
diff --git a/client/src/app/+admin/overview/users/user-edit/user-create.component.ts b/client/src/app/+admin/overview/users/user-edit/user-create.component.ts
index b61b22fd0..1713e06ce 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-create.component.ts
+++ b/client/src/app/+admin/overview/users/user-edit/user-create.component.ts
@@ -1,7 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router' 2import { Router } from '@angular/router'
3import { ConfigService } from '@app/+admin/config/shared/config.service' 3import { ConfigService } from '@app/+admin/config/shared/config.service'
4import { AuthService, Notifier, ScreenService, ServerService, UserService } from '@app/core' 4import { AuthService, Notifier, ScreenService, ServerService } from '@app/core'
5import { 5import {
6 USER_CHANNEL_NAME_VALIDATOR, 6 USER_CHANNEL_NAME_VALIDATOR,
7 USER_EMAIL_VALIDATOR, 7 USER_EMAIL_VALIDATOR,
@@ -13,6 +13,7 @@ import {
13 USER_VIDEO_QUOTA_VALIDATOR 13 USER_VIDEO_QUOTA_VALIDATOR
14} from '@app/shared/form-validators/user-validators' 14} from '@app/shared/form-validators/user-validators'
15import { FormValidatorService } from '@app/shared/shared-forms' 15import { FormValidatorService } from '@app/shared/shared-forms'
16import { UserAdminService } from '@app/shared/shared-users'
16import { UserCreate, UserRole } from '@shared/models' 17import { UserCreate, UserRole } from '@shared/models'
17import { UserEdit } from './user-edit' 18import { UserEdit } from './user-edit'
18 19
@@ -32,7 +33,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
32 protected auth: AuthService, 33 protected auth: AuthService,
33 private router: Router, 34 private router: Router,
34 private notifier: Notifier, 35 private notifier: Notifier,
35 private userService: UserService 36 private userAdminService: UserAdminService
36 ) { 37 ) {
37 super() 38 super()
38 39
@@ -71,7 +72,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
71 userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10) 72 userCreate.videoQuota = parseInt(this.form.value['videoQuota'], 10)
72 userCreate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10) 73 userCreate.videoQuotaDaily = parseInt(this.form.value['videoQuotaDaily'], 10)
73 74
74 this.userService.addUser(userCreate) 75 this.userAdminService.addUser(userCreate)
75 .subscribe({ 76 .subscribe({
76 next: () => { 77 next: () => {
77 this.notifier.success($localize`User ${userCreate.username} created.`) 78 this.notifier.success($localize`User ${userCreate.username} created.`)
diff --git a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
index 42bf20de1..8999d1f00 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
+++ b/client/src/app/+admin/overview/users/user-edit/user-password.component.ts
@@ -1,7 +1,8 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { Notifier, UserService } from '@app/core' 2import { Notifier } from '@app/core'
3import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators' 3import { USER_PASSWORD_VALIDATOR } from '@app/shared/form-validators/user-validators'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { UserAdminService } from '@app/shared/shared-users'
5import { UserUpdate } from '@shared/models' 6import { UserUpdate } from '@shared/models'
6 7
7@Component({ 8@Component({
@@ -19,7 +20,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
19 constructor ( 20 constructor (
20 protected formValidatorService: FormValidatorService, 21 protected formValidatorService: FormValidatorService,
21 private notifier: Notifier, 22 private notifier: Notifier,
22 private userService: UserService 23 private userAdminService: UserAdminService
23 ) { 24 ) {
24 super() 25 super()
25 } 26 }
@@ -35,7 +36,7 @@ export class UserPasswordComponent extends FormReactive implements OnInit {
35 36
36 const userUpdate: UserUpdate = this.form.value 37 const userUpdate: UserUpdate = this.form.value
37 38
38 this.userService.updateUser(this.userId, userUpdate) 39 this.userAdminService.updateUser(this.userId, userUpdate)
39 .subscribe({ 40 .subscribe({
40 next: () => this.notifier.success($localize`Password changed for user ${this.username}.`), 41 next: () => this.notifier.success($localize`Password changed for user ${this.username}.`),
41 42
diff --git a/client/src/app/+admin/overview/users/user-edit/user-update.component.ts b/client/src/app/+admin/overview/users/user-edit/user-update.component.ts
index 42599a17e..bab288a67 100644
--- a/client/src/app/+admin/overview/users/user-edit/user-update.component.ts
+++ b/client/src/app/+admin/overview/users/user-edit/user-update.component.ts
@@ -10,6 +10,7 @@ import {
10 USER_VIDEO_QUOTA_VALIDATOR 10 USER_VIDEO_QUOTA_VALIDATOR
11} from '@app/shared/form-validators/user-validators' 11} from '@app/shared/form-validators/user-validators'
12import { FormValidatorService } from '@app/shared/shared-forms' 12import { FormValidatorService } from '@app/shared/shared-forms'
13import { UserAdminService } from '@app/shared/shared-users'
13import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@shared/models' 14import { User as UserType, UserAdminFlag, UserRole, UserUpdate } from '@shared/models'
14import { UserEdit } from './user-edit' 15import { UserEdit } from './user-edit'
15 16
@@ -32,7 +33,8 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
32 private route: ActivatedRoute, 33 private route: ActivatedRoute,
33 private router: Router, 34 private router: Router,
34 private notifier: Notifier, 35 private notifier: Notifier,
35 private userService: UserService 36 private userService: UserService,
37 private userAdminService: UserAdminService
36 ) { 38 ) {
37 super() 39 super()
38 40
@@ -86,7 +88,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
86 88
87 if (userUpdate.pluginAuth === 'null') userUpdate.pluginAuth = null 89 if (userUpdate.pluginAuth === 'null') userUpdate.pluginAuth = null
88 90
89 this.userService.updateUser(this.user.id, userUpdate) 91 this.userAdminService.updateUser(this.user.id, userUpdate)
90 .subscribe({ 92 .subscribe({
91 next: () => { 93 next: () => {
92 this.notifier.success($localize`User ${this.user.username} updated.`) 94 this.notifier.success($localize`User ${this.user.username} updated.`)
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
index 9fba11cbd..9a9d0f5c6 100644
--- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts
+++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts
@@ -1,10 +1,11 @@
1import { SortMeta } from 'primeng/api' 1import { SortMeta } from 'primeng/api'
2import { Component, OnInit, ViewChild } from '@angular/core' 2import { Component, OnInit, ViewChild } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService, UserService } from '@app/core' 4import { AuthService, ConfirmService, Notifier, RestPagination, RestTable, ServerService } from '@app/core'
5import { AdvancedInputFilter } from '@app/shared/shared-forms' 5import { AdvancedInputFilter } from '@app/shared/shared-forms'
6import { DropdownAction } from '@app/shared/shared-main' 6import { DropdownAction } from '@app/shared/shared-main'
7import { UserBanModalComponent } from '@app/shared/shared-moderation' 7import { UserBanModalComponent } from '@app/shared/shared-moderation'
8import { UserAdminService } from '@app/shared/shared-users'
8import { User, UserRole } from '@shared/models' 9import { User, UserRole } from '@shared/models'
9 10
10type UserForList = User & { 11type UserForList = User & {
@@ -57,7 +58,7 @@ export class UserListComponent extends RestTable implements OnInit {
57 private confirmService: ConfirmService, 58 private confirmService: ConfirmService,
58 private serverService: ServerService, 59 private serverService: ServerService,
59 private auth: AuthService, 60 private auth: AuthService,
60 private userService: UserService 61 private userAdminService: UserAdminService
61 ) { 62 ) {
62 super() 63 super()
63 } 64 }
@@ -177,7 +178,7 @@ export class UserListComponent extends RestTable implements OnInit {
177 const res = await this.confirmService.confirm($localize`Do you really want to unban ${users.length} users?`, $localize`Unban`) 178 const res = await this.confirmService.confirm($localize`Do you really want to unban ${users.length} users?`, $localize`Unban`)
178 if (res === false) return 179 if (res === false) return
179 180
180 this.userService.unbanUsers(users) 181 this.userAdminService.unbanUsers(users)
181 .subscribe({ 182 .subscribe({
182 next: () => { 183 next: () => {
183 this.notifier.success($localize`${users.length} users unbanned.`) 184 this.notifier.success($localize`${users.length} users unbanned.`)
@@ -200,7 +201,7 @@ export class UserListComponent extends RestTable implements OnInit {
200 const res = await this.confirmService.confirm(message, $localize`Delete`) 201 const res = await this.confirmService.confirm(message, $localize`Delete`)
201 if (res === false) return 202 if (res === false) return
202 203
203 this.userService.removeUser(users) 204 this.userAdminService.removeUser(users)
204 .subscribe({ 205 .subscribe({
205 next: () => { 206 next: () => {
206 this.notifier.success($localize`${users.length} users deleted.`) 207 this.notifier.success($localize`${users.length} users deleted.`)
@@ -212,7 +213,7 @@ export class UserListComponent extends RestTable implements OnInit {
212 } 213 }
213 214
214 setEmailsAsVerified (users: User[]) { 215 setEmailsAsVerified (users: User[]) {
215 this.userService.updateUsers(users, { emailVerified: true }) 216 this.userAdminService.updateUsers(users, { emailVerified: true })
216 .subscribe({ 217 .subscribe({
217 next: () => { 218 next: () => {
218 this.notifier.success($localize`${users.length} users email set as verified.`) 219 this.notifier.success($localize`${users.length} users email set as verified.`)
@@ -230,7 +231,7 @@ export class UserListComponent extends RestTable implements OnInit {
230 protected reloadData () { 231 protected reloadData () {
231 this.selectedUsers = [] 232 this.selectedUsers = []
232 233
233 this.userService.getUsers({ 234 this.userAdminService.getUsers({
234 pagination: this.pagination, 235 pagination: this.pagination,
235 sort: this.sort, 236 sort: this.sort,
236 search: this.search 237 search: this.search
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/register-step-channel.component.ts
index d965a7865..1bc0ccfd3 100644
--- a/client/src/app/+signup/+register/register-step-channel.component.ts
+++ b/client/src/app/+signup/+register/register-step-channel.component.ts
@@ -2,9 +2,9 @@ import { concat, of } from 'rxjs'
2import { pairwise } from 'rxjs/operators' 2import { pairwise } from 'rxjs/operators'
3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
4import { FormGroup } from '@angular/forms' 4import { FormGroup } from '@angular/forms'
5import { UserService } from '@app/core'
6import { VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_NAME_VALIDATOR } from '@app/shared/form-validators/video-channel-validators' 5import { VIDEO_CHANNEL_DISPLAY_NAME_VALIDATOR, VIDEO_CHANNEL_NAME_VALIDATOR } from '@app/shared/form-validators/video-channel-validators'
7import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 6import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7import { UserSignupService } from '@app/shared/shared-users'
8 8
9@Component({ 9@Component({
10 selector: 'my-register-step-channel', 10 selector: 'my-register-step-channel',
@@ -17,7 +17,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
17 17
18 constructor ( 18 constructor (
19 protected formValidatorService: FormValidatorService, 19 protected formValidatorService: FormValidatorService,
20 private userService: UserService 20 private userSignupService: UserSignupService
21 ) { 21 ) {
22 super() 22 super()
23 } 23 }
@@ -48,7 +48,7 @@ export class RegisterStepChannelComponent extends FormReactive implements OnInit
48 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 48 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
49 const name = this.form.value['name'] || '' 49 const name = this.form.value['name'] || ''
50 50
51 const newName = this.userService.getNewUsername(oldDisplayName, newDisplayName, name) 51 const newName = this.userSignupService.getNewUsername(oldDisplayName, newDisplayName, name)
52 this.form.patchValue({ name: newName }) 52 this.form.patchValue({ name: newName })
53 } 53 }
54} 54}
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts
index 716cd8c78..92ddfca2e 100644
--- a/client/src/app/+signup/+register/register-step-user.component.ts
+++ b/client/src/app/+signup/+register/register-step-user.component.ts
@@ -2,7 +2,6 @@ import { concat, of } from 'rxjs'
2import { pairwise } from 'rxjs/operators' 2import { pairwise } from 'rxjs/operators'
3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' 3import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
4import { FormGroup } from '@angular/forms' 4import { FormGroup } from '@angular/forms'
5import { UserService } from '@app/core'
6import { 5import {
7 USER_DISPLAY_NAME_REQUIRED_VALIDATOR, 6 USER_DISPLAY_NAME_REQUIRED_VALIDATOR,
8 USER_EMAIL_VALIDATOR, 7 USER_EMAIL_VALIDATOR,
@@ -10,6 +9,7 @@ import {
10 USER_USERNAME_VALIDATOR 9 USER_USERNAME_VALIDATOR
11} from '@app/shared/form-validators/user-validators' 10} from '@app/shared/form-validators/user-validators'
12import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 11import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
12import { UserSignupService } from '@app/shared/shared-users'
13 13
14@Component({ 14@Component({
15 selector: 'my-register-step-user', 15 selector: 'my-register-step-user',
@@ -23,7 +23,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
23 23
24 constructor ( 24 constructor (
25 protected formValidatorService: FormValidatorService, 25 protected formValidatorService: FormValidatorService,
26 private userService: UserService 26 private userSignupService: UserSignupService
27 ) { 27 ) {
28 super() 28 super()
29 } 29 }
@@ -52,7 +52,7 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit {
52 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { 52 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
53 const username = this.form.value['username'] || '' 53 const username = this.form.value['username'] || ''
54 54
55 const newUsername = this.userService.getNewUsername(oldDisplayName, newDisplayName, username) 55 const newUsername = this.userSignupService.getNewUsername(oldDisplayName, newDisplayName, username)
56 this.form.patchValue({ username: newUsername }) 56 this.form.patchValue({ username: newUsername })
57 } 57 }
58} 58}
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
index bb7276459..b4a7c0d0e 100644
--- a/client/src/app/+signup/+register/register.component.ts
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -1,12 +1,13 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { FormGroup } from '@angular/forms' 2import { FormGroup } from '@angular/forms'
3import { ActivatedRoute } from '@angular/router' 3import { ActivatedRoute } from '@angular/router'
4import { AuthService, UserService } from '@app/core' 4import { AuthService } from '@app/core'
5import { HooksService } from '@app/core/plugins/hooks.service' 5import { HooksService } from '@app/core/plugins/hooks.service'
6import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
7import { UserSignupService } from '@app/shared/shared-users'
6import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' 8import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap'
7import { UserRegister } from '@shared/models' 9import { UserRegister } from '@shared/models'
8import { ServerConfig } from '@shared/models/server' 10import { ServerConfig } from '@shared/models/server'
9import { InstanceAboutAccordionComponent } from '@app/shared/shared-instance'
10 11
11@Component({ 12@Component({
12 selector: 'my-register', 13 selector: 'my-register',
@@ -49,7 +50,7 @@ export class RegisterComponent implements OnInit {
49 constructor ( 50 constructor (
50 private route: ActivatedRoute, 51 private route: ActivatedRoute,
51 private authService: AuthService, 52 private authService: AuthService,
52 private userService: UserService, 53 private userSignupService: UserSignupService,
53 private hooks: HooksService 54 private hooks: HooksService
54 ) { } 55 ) { }
55 56
@@ -128,7 +129,7 @@ export class RegisterComponent implements OnInit {
128 'filter:api.signup.registration.create.params' 129 'filter:api.signup.registration.create.params'
129 ) 130 )
130 131
131 this.userService.signup(body).subscribe({ 132 this.userSignupService.signup(body).subscribe({
132 next: () => { 133 next: () => {
133 this.signupDone = true 134 this.signupDone = true
134 135
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts
index c36da53d5..52cdb33bc 100644
--- a/client/src/app/+signup/+register/register.module.ts
+++ b/client/src/app/+signup/+register/register.module.ts
@@ -1,6 +1,6 @@
1import { CdkStepperModule } from '@angular/cdk/stepper' 1import { CdkStepperModule } from '@angular/cdk/stepper'
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' 3import { SharedSignupModule } from '@app/+signup/shared/shared-signup.module'
4import { SharedInstanceModule } from '@app/shared/shared-instance' 4import { SharedInstanceModule } from '@app/shared/shared-instance'
5import { CustomStepperComponent } from './custom-stepper.component' 5import { CustomStepperComponent } from './custom-stepper.component'
6import { RegisterRoutingModule } from './register-routing.module' 6import { RegisterRoutingModule } from './register-routing.module'
@@ -15,7 +15,7 @@ import { RegisterComponent } from './register.component'
15 15
16 CdkStepperModule, 16 CdkStepperModule,
17 17
18 SignupSharedModule, 18 SharedSignupModule,
19 19
20 SharedInstanceModule 20 SharedInstanceModule
21 ], 21 ],
diff --git a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
index 83c24a251..a0ed66a3a 100644
--- a/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
@@ -1,7 +1,8 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { Notifier, RedirectService, ServerService, UserService } from '@app/core' 2import { Notifier, RedirectService, ServerService } from '@app/core'
3import { USER_EMAIL_VALIDATOR } from '@app/shared/form-validators/user-validators' 3import { USER_EMAIL_VALIDATOR } from '@app/shared/form-validators/user-validators'
4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 4import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
5import { UserSignupService } from '@app/shared/shared-users'
5 6
6@Component({ 7@Component({
7 selector: 'my-verify-account-ask-send-email', 8 selector: 'my-verify-account-ask-send-email',
@@ -14,7 +15,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
14 15
15 constructor ( 16 constructor (
16 protected formValidatorService: FormValidatorService, 17 protected formValidatorService: FormValidatorService,
17 private userService: UserService, 18 private userSignupService: UserSignupService,
18 private serverService: ServerService, 19 private serverService: ServerService,
19 private notifier: Notifier, 20 private notifier: Notifier,
20 private redirectService: RedirectService 21 private redirectService: RedirectService
@@ -33,7 +34,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
33 34
34 askSendVerifyEmail () { 35 askSendVerifyEmail () {
35 const email = this.form.value['verify-email-email'] 36 const email = this.form.value['verify-email-email']
36 this.userService.askSendVerifyEmail(email) 37 this.userSignupService.askSendVerifyEmail(email)
37 .subscribe({ 38 .subscribe({
38 next: () => { 39 next: () => {
39 this.notifier.success($localize`An email with verification link will be sent to ${email}.`) 40 this.notifier.success($localize`An email with verification link will be sent to ${email}.`)
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
index 827ec7652..88efce4a1 100644
--- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
@@ -1,6 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { ActivatedRoute } from '@angular/router' 2import { ActivatedRoute } from '@angular/router'
3import { AuthService, Notifier, UserService } from '@app/core' 3import { AuthService, Notifier } from '@app/core'
4import { UserSignupService } from '@app/shared/shared-users'
4 5
5@Component({ 6@Component({
6 selector: 'my-verify-account-email', 7 selector: 'my-verify-account-email',
@@ -16,7 +17,7 @@ export class VerifyAccountEmailComponent implements OnInit {
16 private verificationString: string 17 private verificationString: string
17 18
18 constructor ( 19 constructor (
19 private userService: UserService, 20 private userSignupService: UserSignupService,
20 private authService: AuthService, 21 private authService: AuthService,
21 private notifier: Notifier, 22 private notifier: Notifier,
22 private route: ActivatedRoute 23 private route: ActivatedRoute
@@ -37,7 +38,7 @@ export class VerifyAccountEmailComponent implements OnInit {
37 } 38 }
38 39
39 verifyEmail () { 40 verifyEmail () {
40 this.userService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail) 41 this.userSignupService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail)
41 .subscribe({ 42 .subscribe({
42 next: () => { 43 next: () => {
43 if (this.authService.isLoggedIn()) { 44 if (this.authService.isLoggedIn()) {
diff --git a/client/src/app/+signup/+verify-account/verify-account.module.ts b/client/src/app/+signup/+verify-account/verify-account.module.ts
index 7255605d4..ec342df8d 100644
--- a/client/src/app/+signup/+verify-account/verify-account.module.ts
+++ b/client/src/app/+signup/+verify-account/verify-account.module.ts
@@ -1,5 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { SignupSharedModule } from '../shared/signup-shared.module' 2import { SharedSignupModule } from '../shared/shared-signup.module'
3import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component' 3import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component'
4import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component' 4import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component'
5import { VerifyAccountRoutingModule } from './verify-account-routing.module' 5import { VerifyAccountRoutingModule } from './verify-account-routing.module'
@@ -8,7 +8,7 @@ import { VerifyAccountRoutingModule } from './verify-account-routing.module'
8 imports: [ 8 imports: [
9 VerifyAccountRoutingModule, 9 VerifyAccountRoutingModule,
10 10
11 SignupSharedModule 11 SharedSignupModule
12 ], 12 ],
13 13
14 declarations: [ 14 declarations: [
diff --git a/client/src/app/+signup/shared/signup-shared.module.ts b/client/src/app/+signup/shared/shared-signup.module.ts
index 56b0b3bae..f8b224c71 100644
--- a/client/src/app/+signup/shared/signup-shared.module.ts
+++ b/client/src/app/+signup/shared/shared-signup.module.ts
@@ -1,14 +1,16 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { SharedMainModule } from '@app/shared/shared-main'
3import { SignupSuccessComponent } from './signup-success.component'
4import { SharedFormModule } from '@app/shared/shared-forms' 2import { SharedFormModule } from '@app/shared/shared-forms'
5import { SharedGlobalIconModule } from '@app/shared/shared-icons' 3import { SharedGlobalIconModule } from '@app/shared/shared-icons'
4import { SharedMainModule } from '@app/shared/shared-main'
5import { SharedUsersModule } from '@app/shared/shared-users'
6import { SignupSuccessComponent } from './signup-success.component'
6 7
7@NgModule({ 8@NgModule({
8 imports: [ 9 imports: [
9 SharedMainModule, 10 SharedMainModule,
10 SharedFormModule, 11 SharedFormModule,
11 SharedGlobalIconModule 12 SharedGlobalIconModule,
13 SharedUsersModule
12 ], 14 ],
13 15
14 declarations: [ 16 declarations: [
@@ -26,4 +28,4 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons'
26 providers: [ 28 providers: [
27 ] 29 ]
28}) 30})
29export class SignupSharedModule { } 31export class SharedSignupModule { }
diff --git a/client/src/app/core/users/user.service.ts b/client/src/app/core/users/user.service.ts
index f173c356a..b14bff193 100644
--- a/client/src/app/core/users/user.service.ts
+++ b/client/src/app/core/users/user.service.ts
@@ -1,24 +1,12 @@
1import { SortMeta } from 'primeng/api' 1import { Observable, of } from 'rxjs'
2import { from, Observable, of } from 'rxjs' 2import { catchError, first, map, shareReplay } from 'rxjs/operators'
3import { catchError, concatMap, first, map, shareReplay, tap, toArray } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
6import { AuthService } from '@app/core/auth' 5import { AuthService } from '@app/core/auth'
7import { getBytes } from '@root-helpers/bytes' 6import { ActorImage, User as UserServerModel, UserUpdateMe, UserVideoQuota } from '@shared/models'
8import {
9 ActorImage,
10 ResultList,
11 User as UserServerModel,
12 UserCreate,
13 UserRegister,
14 UserRole,
15 UserUpdate,
16 UserUpdateMe,
17 UserVideoQuota
18} from '@shared/models'
19import { environment } from '../../../environments/environment' 7import { environment } from '../../../environments/environment'
20import { RestExtractor, RestPagination, RestService } from '../rest' 8import { RestExtractor } from '../rest'
21import { UserLocalStorageService } from './' 9import { UserLocalStorageService } from './user-local-storage.service'
22import { User } from './user.model' 10import { User } from './user.model'
23 11
24@Injectable() 12@Injectable()
@@ -26,45 +14,44 @@ export class UserService {
26 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' 14 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
27 15
28 private userCache: { [ id: number ]: Observable<UserServerModel> } = {} 16 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
29
30 private signupInThisSession = false 17 private signupInThisSession = false
31 18
32 constructor ( 19 constructor (
33 private authHttp: HttpClient, 20 private authHttp: HttpClient,
34 private authService: AuthService, 21 private authService: AuthService,
35 private restExtractor: RestExtractor, 22 private restExtractor: RestExtractor,
36 private restService: RestService,
37 private userLocalStorageService: UserLocalStorageService 23 private userLocalStorageService: UserLocalStorageService
38 ) { } 24 ) { }
39 25
40 hasSignupInThisSession () { 26 // ---------------------------------------------------------------------------
41 return this.signupInThisSession
42 }
43 27
44 changePassword (currentPassword: string, newPassword: string) { 28 getUserWithCache (userId: number) {
45 const url = UserService.BASE_USERS_URL + 'me' 29 if (!this.userCache[userId]) {
46 const body: UserUpdateMe = { 30 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
47 currentPassword,
48 password: newPassword
49 } 31 }
50 32
51 return this.authHttp.put(url, body) 33 return this.userCache[userId]
52 .pipe(catchError(err => this.restExtractor.handleError(err)))
53 } 34 }
54 35
55 changeEmail (password: string, newEmail: string) { 36 getUser (userId: number, withStats = false) {
56 const url = UserService.BASE_USERS_URL + 'me' 37 const params = new HttpParams().append('withStats', withStats + '')
57 const body: UserUpdateMe = {
58 currentPassword: password,
59 email: newEmail
60 }
61 38
62 return this.authHttp.put(url, body) 39 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
63 .pipe(catchError(err => this.restExtractor.handleError(err))) 40 .pipe(catchError(err => this.restExtractor.handleError(err)))
64 } 41 }
65 42
66 // --------------------------------------------------------------------------- 43 // ---------------------------------------------------------------------------
67 44
45 setSignupInThisSession (value: boolean) {
46 this.signupInThisSession = value
47 }
48
49 hasSignupInThisSession () {
50 return this.signupInThisSession
51 }
52
53 // ---------------------------------------------------------------------------
54
68 updateMyAnonymousProfile (profile: UserUpdateMe) { 55 updateMyAnonymousProfile (profile: UserUpdateMe) {
69 this.userLocalStorageService.setUserInfo(profile) 56 this.userLocalStorageService.setUserInfo(profile)
70 } 57 }
@@ -78,8 +65,42 @@ export class UserService {
78 return new User(this.userLocalStorageService.getUserInfo()) 65 return new User(this.userLocalStorageService.getUserInfo())
79 } 66 }
80 67
68 getAnonymousOrLoggedUser () {
69 if (!this.authService.isLoggedIn()) {
70 return of(this.getAnonymousUser())
71 }
72
73 return this.authService.userInformationLoaded
74 .pipe(
75 first(),
76 map(() => this.authService.getUser())
77 )
78 }
79
81 // --------------------------------------------------------------------------- 80 // ---------------------------------------------------------------------------
82 81
82 changePassword (currentPassword: string, newPassword: string) {
83 const url = UserService.BASE_USERS_URL + 'me'
84 const body: UserUpdateMe = {
85 currentPassword,
86 password: newPassword
87 }
88
89 return this.authHttp.put(url, body)
90 .pipe(catchError(err => this.restExtractor.handleError(err)))
91 }
92
93 changeEmail (password: string, newEmail: string) {
94 const url = UserService.BASE_USERS_URL + 'me'
95 const body: UserUpdateMe = {
96 currentPassword: password,
97 email: newEmail
98 }
99
100 return this.authHttp.put(url, body)
101 .pipe(catchError(err => this.restExtractor.handleError(err)))
102 }
103
83 updateMyProfile (profile: UserUpdateMe) { 104 updateMyProfile (profile: UserUpdateMe) {
84 const url = UserService.BASE_USERS_URL + 'me' 105 const url = UserService.BASE_USERS_URL + 'me'
85 106
@@ -108,14 +129,6 @@ export class UserService {
108 .pipe(catchError(err => this.restExtractor.handleError(err))) 129 .pipe(catchError(err => this.restExtractor.handleError(err)))
109 } 130 }
110 131
111 signup (userCreate: UserRegister) {
112 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
113 .pipe(
114 tap(() => this.signupInThisSession = true),
115 catchError(err => this.restExtractor.handleError(err))
116 )
117 }
118
119 getMyVideoQuotaUsed () { 132 getMyVideoQuotaUsed () {
120 const url = UserService.BASE_USERS_URL + 'me/video-quota-used' 133 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
121 134
@@ -141,24 +154,6 @@ export class UserService {
141 .pipe(catchError(res => this.restExtractor.handleError(res))) 154 .pipe(catchError(res => this.restExtractor.handleError(res)))
142 } 155 }
143 156
144 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
145 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
146 const body = {
147 verificationString,
148 isPendingEmail
149 }
150
151 return this.authHttp.post(url, body)
152 .pipe(catchError(res => this.restExtractor.handleError(res)))
153 }
154
155 askSendVerifyEmail (email: string) {
156 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
157
158 return this.authHttp.post(url, { email })
159 .pipe(catchError(err => this.restExtractor.handleError(err)))
160 }
161
162 autocomplete (search: string): Observable<string[]> { 157 autocomplete (search: string): Observable<string[]> {
163 const url = UserService.BASE_USERS_URL + 'autocomplete' 158 const url = UserService.BASE_USERS_URL + 'autocomplete'
164 const params = new HttpParams().append('search', search) 159 const params = new HttpParams().append('search', search)
@@ -167,169 +162,4 @@ export class UserService {
167 .get<string[]>(url, { params }) 162 .get<string[]>(url, { params })
168 .pipe(catchError(res => this.restExtractor.handleError(res))) 163 .pipe(catchError(res => this.restExtractor.handleError(res)))
169 } 164 }
170
171 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
172 // Don't update display name, the user seems to have changed it
173 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
174
175 return this.displayNameToUsername(newDisplayName)
176 }
177
178 displayNameToUsername (displayName: string) {
179 if (!displayName) return ''
180
181 return displayName
182 .toLowerCase()
183 .replace(/\s/g, '_')
184 .replace(/[^a-z0-9_.]/g, '')
185 }
186
187 /* ###### Admin methods ###### */
188
189 addUser (userCreate: UserCreate) {
190 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
191 .pipe(catchError(err => this.restExtractor.handleError(err)))
192 }
193
194 updateUser (userId: number, userUpdate: UserUpdate) {
195 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
196 .pipe(catchError(err => this.restExtractor.handleError(err)))
197 }
198
199 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
200 return from(users)
201 .pipe(
202 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
203 toArray(),
204 catchError(err => this.restExtractor.handleError(err))
205 )
206 }
207
208 getUserWithCache (userId: number) {
209 if (!this.userCache[userId]) {
210 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
211 }
212
213 return this.userCache[userId]
214 }
215
216 getUser (userId: number, withStats = false) {
217 const params = new HttpParams().append('withStats', withStats + '')
218 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
219 .pipe(catchError(err => this.restExtractor.handleError(err)))
220 }
221
222 getUsers (parameters: {
223 pagination: RestPagination
224 sort: SortMeta
225 search?: string
226 }): Observable<ResultList<UserServerModel>> {
227 const { pagination, sort, search } = parameters
228
229 let params = new HttpParams()
230 params = this.restService.addRestGetParams(params, pagination, sort)
231
232 if (search) {
233 const filters = this.restService.parseQueryStringFilter(search, {
234 blocked: {
235 prefix: 'banned:',
236 isBoolean: true
237 }
238 })
239
240 params = this.restService.addObjectParams(params, filters)
241 }
242
243 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
244 .pipe(
245 map(res => this.restExtractor.convertResultListDateToHuman(res)),
246 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
247 catchError(err => this.restExtractor.handleError(err))
248 )
249 }
250
251 removeUser (usersArg: UserServerModel | UserServerModel[]) {
252 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
253
254 return from(users)
255 .pipe(
256 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
257 toArray(),
258 catchError(err => this.restExtractor.handleError(err))
259 )
260 }
261
262 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
263 const body = reason ? { reason } : {}
264 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
265
266 return from(users)
267 .pipe(
268 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
269 toArray(),
270 catchError(err => this.restExtractor.handleError(err))
271 )
272 }
273
274 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
275 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
276
277 return from(users)
278 .pipe(
279 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
280 toArray(),
281 catchError(err => this.restExtractor.handleError(err))
282 )
283 }
284
285 getAnonymousOrLoggedUser () {
286 if (!this.authService.isLoggedIn()) {
287 return of(this.getAnonymousUser())
288 }
289
290 return this.authService.userInformationLoaded
291 .pipe(
292 first(),
293 map(() => this.authService.getUser())
294 )
295 }
296
297 private formatUser (user: UserServerModel) {
298 let videoQuota
299 if (user.videoQuota === -1) {
300 videoQuota = '∞'
301 } else {
302 videoQuota = getBytes(user.videoQuota, 0)
303 }
304
305 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
306
307 let videoQuotaDaily: string
308 let videoQuotaUsedDaily: string
309 if (user.videoQuotaDaily === -1) {
310 videoQuotaDaily = '∞'
311 videoQuotaUsedDaily = getBytes(0, 0) + ''
312 } else {
313 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
314 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
315 }
316
317 const roleLabels: { [ id in UserRole ]: string } = {
318 [UserRole.USER]: $localize`User`,
319 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
320 [UserRole.MODERATOR]: $localize`Moderator`
321 }
322
323 return Object.assign(user, {
324 roleLabel: roleLabels[user.role],
325 videoQuota,
326 videoQuotaUsed,
327 rawVideoQuota: user.videoQuota,
328 rawVideoQuotaUsed: user.videoQuotaUsed,
329 videoQuotaDaily,
330 videoQuotaUsedDaily,
331 rawVideoQuotaDaily: user.videoQuotaDaily,
332 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily
333 })
334 }
335} 165}
diff --git a/client/src/app/shared/shared-moderation/shared-moderation.module.ts b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
index 7cadda67c..dac4ae951 100644
--- a/client/src/app/shared/shared-moderation/shared-moderation.module.ts
+++ b/client/src/app/shared/shared-moderation/shared-moderation.module.ts
@@ -1,10 +1,13 @@
1 1
2import { NgModule } from '@angular/core' 2import { NgModule } from '@angular/core'
3import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
3import { SharedFormModule } from '../shared-forms/shared-form.module' 4import { SharedFormModule } from '../shared-forms/shared-form.module'
4import { SharedGlobalIconModule } from '../shared-icons' 5import { SharedGlobalIconModule } from '../shared-icons'
5import { SharedMainModule } from '../shared-main/shared-main.module' 6import { SharedMainModule } from '../shared-main/shared-main.module'
7import { SharedUsersModule } from '../shared-users'
6import { SharedVideoCommentModule } from '../shared-video-comment' 8import { SharedVideoCommentModule } from '../shared-video-comment'
7import { AbuseService } from './abuse.service' 9import { AbuseService } from './abuse.service'
10import { AccountBlockBadgesComponent } from './account-block-badges.component'
8import { BatchDomainsModalComponent } from './batch-domains-modal.component' 11import { BatchDomainsModalComponent } from './batch-domains-modal.component'
9import { BlocklistService } from './blocklist.service' 12import { BlocklistService } from './blocklist.service'
10import { BulkService } from './bulk.service' 13import { BulkService } from './bulk.service'
@@ -13,8 +16,6 @@ import { UserBanModalComponent } from './user-ban-modal.component'
13import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' 16import { UserModerationDropdownComponent } from './user-moderation-dropdown.component'
14import { VideoBlockComponent } from './video-block.component' 17import { VideoBlockComponent } from './video-block.component'
15import { VideoBlockService } from './video-block.service' 18import { VideoBlockService } from './video-block.service'
16import { AccountBlockBadgesComponent } from './account-block-badges.component'
17import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module'
18 19
19@NgModule({ 20@NgModule({
20 imports: [ 21 imports: [
@@ -22,7 +23,8 @@ import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image
22 SharedFormModule, 23 SharedFormModule,
23 SharedGlobalIconModule, 24 SharedGlobalIconModule,
24 SharedVideoCommentModule, 25 SharedVideoCommentModule,
25 SharedActorImageModule 26 SharedActorImageModule,
27 SharedUsersModule
26 ], 28 ],
27 29
28 declarations: [ 30 declarations: [
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
index b2ce019c5..17cad18ec 100644
--- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
+++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts
@@ -1,10 +1,11 @@
1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
2import { Notifier, UserService } from '@app/core' 2import { Notifier } from '@app/core'
3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' 3import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
4import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 4import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' 5import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
6import { User } from '@shared/models' 6import { User } from '@shared/models'
7import { USER_BAN_REASON_VALIDATOR } from '../form-validators/user-validators' 7import { USER_BAN_REASON_VALIDATOR } from '../form-validators/user-validators'
8import { UserAdminService } from '../shared-users'
8 9
9@Component({ 10@Component({
10 selector: 'my-user-ban-modal', 11 selector: 'my-user-ban-modal',
@@ -23,7 +24,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
23 protected formValidatorService: FormValidatorService, 24 protected formValidatorService: FormValidatorService,
24 private modalService: NgbModal, 25 private modalService: NgbModal,
25 private notifier: Notifier, 26 private notifier: Notifier,
26 private userService: UserService 27 private userAdminService: UserAdminService
27 ) { 28 ) {
28 super() 29 super()
29 } 30 }
@@ -50,7 +51,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
50 banUser () { 51 banUser () {
51 const reason = this.form.value['reason'] || undefined 52 const reason = this.form.value['reason'] || undefined
52 53
53 this.userService.banUsers(this.usersToBan, reason) 54 this.userAdminService.banUsers(this.usersToBan, reason)
54 .subscribe({ 55 .subscribe({
55 next: () => { 56 next: () => {
56 const message = Array.isArray(this.usersToBan) 57 const message = Array.isArray(this.usersToBan)
diff --git a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
index e2cd2cdc1..0d19565ef 100644
--- a/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
+++ b/client/src/app/shared/shared-moderation/user-moderation-dropdown.component.ts
@@ -1,7 +1,8 @@
1import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' 1import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'
2import { AuthService, ConfirmService, Notifier, ServerService, UserService } from '@app/core' 2import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
3import { Account, DropdownAction } from '@app/shared/shared-main' 3import { Account, DropdownAction } from '@app/shared/shared-main'
4import { BulkRemoveCommentsOfBody, User, UserRight } from '@shared/models' 4import { BulkRemoveCommentsOfBody, User, UserRight } from '@shared/models'
5import { UserAdminService } from '../shared-users'
5import { BlocklistService } from './blocklist.service' 6import { BlocklistService } from './blocklist.service'
6import { BulkService } from './bulk.service' 7import { BulkService } from './bulk.service'
7import { UserBanModalComponent } from './user-ban-modal.component' 8import { UserBanModalComponent } from './user-ban-modal.component'
@@ -35,7 +36,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
35 private notifier: Notifier, 36 private notifier: Notifier,
36 private confirmService: ConfirmService, 37 private confirmService: ConfirmService,
37 private serverService: ServerService, 38 private serverService: ServerService,
38 private userService: UserService, 39 private userAdminService: UserAdminService,
39 private blocklistService: BlocklistService, 40 private blocklistService: BlocklistService,
40 private bulkService: BulkService 41 private bulkService: BulkService
41 ) { } 42 ) { }
@@ -66,7 +67,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
66 const res = await this.confirmService.confirm($localize`Do you really want to unban ${user.username}?`, $localize`Unban`) 67 const res = await this.confirmService.confirm($localize`Do you really want to unban ${user.username}?`, $localize`Unban`)
67 if (res === false) return 68 if (res === false) return
68 69
69 this.userService.unbanUsers(user) 70 this.userAdminService.unbanUsers(user)
70 .subscribe({ 71 .subscribe({
71 next: () => { 72 next: () => {
72 this.notifier.success($localize`User ${user.username} unbanned.`) 73 this.notifier.success($localize`User ${user.username} unbanned.`)
@@ -87,7 +88,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
87 const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`) 88 const res = await this.confirmService.confirm(message, $localize`Delete ${user.username}`)
88 if (res === false) return 89 if (res === false) return
89 90
90 this.userService.removeUser(user) 91 this.userAdminService.removeUser(user)
91 .subscribe({ 92 .subscribe({
92 next: () => { 93 next: () => {
93 this.notifier.success($localize`User ${user.username} deleted.`) 94 this.notifier.success($localize`User ${user.username} deleted.`)
@@ -99,7 +100,7 @@ export class UserModerationDropdownComponent implements OnInit, OnChanges {
99 } 100 }
100 101
101 setEmailAsVerified (user: User) { 102 setEmailAsVerified (user: User) {
102 this.userService.updateUser(user.id, { emailVerified: true }) 103 this.userAdminService.updateUser(user.id, { emailVerified: true })
103 .subscribe({ 104 .subscribe({
104 next: () => { 105 next: () => {
105 this.notifier.success($localize`User ${user.username} email set as verified`) 106 this.notifier.success($localize`User ${user.username} email set as verified`)
diff --git a/client/src/app/shared/shared-users/index.ts b/client/src/app/shared/shared-users/index.ts
new file mode 100644
index 000000000..8f90f2515
--- /dev/null
+++ b/client/src/app/shared/shared-users/index.ts
@@ -0,0 +1,4 @@
1export * from './user-admin.service'
2export * from './user-signup.service'
3
4export * from './shared-users.module'
diff --git a/client/src/app/shared/shared-users/shared-users.module.ts b/client/src/app/shared/shared-users/shared-users.module.ts
new file mode 100644
index 000000000..2a1dadf20
--- /dev/null
+++ b/client/src/app/shared/shared-users/shared-users.module.ts
@@ -0,0 +1,21 @@
1
2import { NgModule } from '@angular/core'
3import { SharedMainModule } from '../shared-main/shared-main.module'
4import { UserAdminService } from './user-admin.service'
5import { UserSignupService } from './user-signup.service'
6
7@NgModule({
8 imports: [
9 SharedMainModule
10 ],
11
12 declarations: [ ],
13
14 exports: [],
15
16 providers: [
17 UserSignupService,
18 UserAdminService
19 ]
20})
21export class SharedUsersModule { }
diff --git a/client/src/app/shared/shared-users/user-admin.service.ts b/client/src/app/shared/shared-users/user-admin.service.ts
new file mode 100644
index 000000000..3db271c4a
--- /dev/null
+++ b/client/src/app/shared/shared-users/user-admin.service.ts
@@ -0,0 +1,139 @@
1import { SortMeta } from 'primeng/api'
2import { from, Observable } from 'rxjs'
3import { catchError, concatMap, map, toArray } from 'rxjs/operators'
4import { HttpClient, HttpParams } from '@angular/common/http'
5import { Injectable } from '@angular/core'
6import { RestExtractor, RestPagination, RestService, UserService } from '@app/core'
7import { getBytes } from '@root-helpers/bytes'
8import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models'
9
10@Injectable()
11export class UserAdminService {
12
13 constructor (
14 private authHttp: HttpClient,
15 private restExtractor: RestExtractor,
16 private restService: RestService
17 ) { }
18
19 addUser (userCreate: UserCreate) {
20 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
21 .pipe(catchError(err => this.restExtractor.handleError(err)))
22 }
23
24 updateUser (userId: number, userUpdate: UserUpdate) {
25 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
26 .pipe(catchError(err => this.restExtractor.handleError(err)))
27 }
28
29 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
30 return from(users)
31 .pipe(
32 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
33 toArray(),
34 catchError(err => this.restExtractor.handleError(err))
35 )
36 }
37
38 getUsers (parameters: {
39 pagination: RestPagination
40 sort: SortMeta
41 search?: string
42 }): Observable<ResultList<UserServerModel>> {
43 const { pagination, sort, search } = parameters
44
45 let params = new HttpParams()
46 params = this.restService.addRestGetParams(params, pagination, sort)
47
48 if (search) {
49 const filters = this.restService.parseQueryStringFilter(search, {
50 blocked: {
51 prefix: 'banned:',
52 isBoolean: true
53 }
54 })
55
56 params = this.restService.addObjectParams(params, filters)
57 }
58
59 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
60 .pipe(
61 map(res => this.restExtractor.convertResultListDateToHuman(res)),
62 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
63 catchError(err => this.restExtractor.handleError(err))
64 )
65 }
66
67 removeUser (usersArg: UserServerModel | UserServerModel[]) {
68 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
69
70 return from(users)
71 .pipe(
72 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
73 toArray(),
74 catchError(err => this.restExtractor.handleError(err))
75 )
76 }
77
78 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
79 const body = reason ? { reason } : {}
80 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
81
82 return from(users)
83 .pipe(
84 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
85 toArray(),
86 catchError(err => this.restExtractor.handleError(err))
87 )
88 }
89
90 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
91 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
92
93 return from(users)
94 .pipe(
95 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
96 toArray(),
97 catchError(err => this.restExtractor.handleError(err))
98 )
99 }
100
101 private formatUser (user: UserServerModel) {
102 let videoQuota
103 if (user.videoQuota === -1) {
104 videoQuota = '∞'
105 } else {
106 videoQuota = getBytes(user.videoQuota, 0)
107 }
108
109 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
110
111 let videoQuotaDaily: string
112 let videoQuotaUsedDaily: string
113 if (user.videoQuotaDaily === -1) {
114 videoQuotaDaily = '∞'
115 videoQuotaUsedDaily = getBytes(0, 0) + ''
116 } else {
117 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
118 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
119 }
120
121 const roleLabels: { [ id in UserRole ]: string } = {
122 [UserRole.USER]: $localize`User`,
123 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
124 [UserRole.MODERATOR]: $localize`Moderator`
125 }
126
127 return Object.assign(user, {
128 roleLabel: roleLabels[user.role],
129 videoQuota,
130 videoQuotaUsed,
131 rawVideoQuota: user.videoQuota,
132 rawVideoQuotaUsed: user.videoQuotaUsed,
133 videoQuotaDaily,
134 videoQuotaUsedDaily,
135 rawVideoQuotaDaily: user.videoQuotaDaily,
136 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily
137 })
138 }
139}
diff --git a/client/src/app/shared/shared-users/user-signup.service.ts b/client/src/app/shared/shared-users/user-signup.service.ts
new file mode 100644
index 000000000..46fe34af1
--- /dev/null
+++ b/client/src/app/shared/shared-users/user-signup.service.ts
@@ -0,0 +1,56 @@
1import { catchError, tap } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { RestExtractor, UserService } from '@app/core'
5import { UserRegister } from '@shared/models'
6
7@Injectable()
8export class UserSignupService {
9 constructor (
10 private authHttp: HttpClient,
11 private restExtractor: RestExtractor,
12 private userService: UserService
13 ) { }
14
15 signup (userCreate: UserRegister) {
16 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
17 .pipe(
18 tap(() => this.userService.setSignupInThisSession(true)),
19 catchError(err => this.restExtractor.handleError(err))
20 )
21 }
22
23 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
24 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
25 const body = {
26 verificationString,
27 isPendingEmail
28 }
29
30 return this.authHttp.post(url, body)
31 .pipe(catchError(res => this.restExtractor.handleError(res)))
32 }
33
34 askSendVerifyEmail (email: string) {
35 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
36
37 return this.authHttp.post(url, { email })
38 .pipe(catchError(err => this.restExtractor.handleError(err)))
39 }
40
41 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
42 // Don't update display name, the user seems to have changed it
43 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
44
45 return this.displayNameToUsername(newDisplayName)
46 }
47
48 private displayNameToUsername (displayName: string) {
49 if (!displayName) return ''
50
51 return displayName
52 .toLowerCase()
53 .replace(/\s/g, '_')
54 .replace(/[^a-z0-9_.]/g, '')
55 }
56}