diff options
Diffstat (limited to 'client/src/app/shared')
7 files changed, 235 insertions, 11 deletions
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 | ||
2 | import { NgModule } from '@angular/core' | 2 | import { NgModule } from '@angular/core' |
3 | import { SharedActorImageModule } from '../shared-actor-image/shared-actor-image.module' | ||
3 | import { SharedFormModule } from '../shared-forms/shared-form.module' | 4 | import { SharedFormModule } from '../shared-forms/shared-form.module' |
4 | import { SharedGlobalIconModule } from '../shared-icons' | 5 | import { SharedGlobalIconModule } from '../shared-icons' |
5 | import { SharedMainModule } from '../shared-main/shared-main.module' | 6 | import { SharedMainModule } from '../shared-main/shared-main.module' |
7 | import { SharedUsersModule } from '../shared-users' | ||
6 | import { SharedVideoCommentModule } from '../shared-video-comment' | 8 | import { SharedVideoCommentModule } from '../shared-video-comment' |
7 | import { AbuseService } from './abuse.service' | 9 | import { AbuseService } from './abuse.service' |
10 | import { AccountBlockBadgesComponent } from './account-block-badges.component' | ||
8 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' | 11 | import { BatchDomainsModalComponent } from './batch-domains-modal.component' |
9 | import { BlocklistService } from './blocklist.service' | 12 | import { BlocklistService } from './blocklist.service' |
10 | import { BulkService } from './bulk.service' | 13 | import { BulkService } from './bulk.service' |
@@ -13,8 +16,6 @@ import { UserBanModalComponent } from './user-ban-modal.component' | |||
13 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' | 16 | import { UserModerationDropdownComponent } from './user-moderation-dropdown.component' |
14 | import { VideoBlockComponent } from './video-block.component' | 17 | import { VideoBlockComponent } from './video-block.component' |
15 | import { VideoBlockService } from './video-block.service' | 18 | import { VideoBlockService } from './video-block.service' |
16 | import { AccountBlockBadgesComponent } from './account-block-badges.component' | ||
17 | import { 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 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier, UserService } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | 3 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' |
4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 4 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 5 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
6 | import { User } from '@shared/models' | 6 | import { User } from '@shared/models' |
7 | import { USER_BAN_REASON_VALIDATOR } from '../form-validators/user-validators' | 7 | import { USER_BAN_REASON_VALIDATOR } from '../form-validators/user-validators' |
8 | import { 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 @@ | |||
1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { AuthService, ConfirmService, Notifier, ServerService, UserService } from '@app/core' | 2 | import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core' |
3 | import { Account, DropdownAction } from '@app/shared/shared-main' | 3 | import { Account, DropdownAction } from '@app/shared/shared-main' |
4 | import { BulkRemoveCommentsOfBody, User, UserRight } from '@shared/models' | 4 | import { BulkRemoveCommentsOfBody, User, UserRight } from '@shared/models' |
5 | import { UserAdminService } from '../shared-users' | ||
5 | import { BlocklistService } from './blocklist.service' | 6 | import { BlocklistService } from './blocklist.service' |
6 | import { BulkService } from './bulk.service' | 7 | import { BulkService } from './bulk.service' |
7 | import { UserBanModalComponent } from './user-ban-modal.component' | 8 | import { 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 @@ | |||
1 | export * from './user-admin.service' | ||
2 | export * from './user-signup.service' | ||
3 | |||
4 | export * 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 | |||
2 | import { NgModule } from '@angular/core' | ||
3 | import { SharedMainModule } from '../shared-main/shared-main.module' | ||
4 | import { UserAdminService } from './user-admin.service' | ||
5 | import { 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 | }) | ||
21 | export 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 @@ | |||
1 | import { SortMeta } from 'primeng/api' | ||
2 | import { from, Observable } from 'rxjs' | ||
3 | import { catchError, concatMap, map, toArray } from 'rxjs/operators' | ||
4 | import { HttpClient, HttpParams } from '@angular/common/http' | ||
5 | import { Injectable } from '@angular/core' | ||
6 | import { RestExtractor, RestPagination, RestService, UserService } from '@app/core' | ||
7 | import { getBytes } from '@root-helpers/bytes' | ||
8 | import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate } from '@shared/models' | ||
9 | |||
10 | @Injectable() | ||
11 | export 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 @@ | |||
1 | import { catchError, tap } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { RestExtractor, UserService } from '@app/core' | ||
5 | import { UserRegister } from '@shared/models' | ||
6 | |||
7 | @Injectable() | ||
8 | export 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 | } | ||