aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/shared/users
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/shared/users')
-rw-r--r--client/src/app/shared/users/index.ts1
-rw-r--r--client/src/app/shared/users/user-history.service.ts45
-rw-r--r--client/src/app/shared/users/user-notification.model.ts155
-rw-r--r--client/src/app/shared/users/user-notification.service.ts86
-rw-r--r--client/src/app/shared/users/user-notifications.component.html101
-rw-r--r--client/src/app/shared/users/user-notifications.component.scss51
-rw-r--r--client/src/app/shared/users/user-notifications.component.ts87
-rw-r--r--client/src/app/shared/users/user.model.ts38
-rw-r--r--client/src/app/shared/users/user.service.ts52
9 files changed, 579 insertions, 37 deletions
diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts
index 7b5a67bc7..ebd715fb1 100644
--- a/client/src/app/shared/users/index.ts
+++ b/client/src/app/shared/users/index.ts
@@ -1,2 +1,3 @@
1export * from './user.model' 1export * from './user.model'
2export * from './user.service' 2export * from './user.service'
3export * from './user-notifications.component'
diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/users/user-history.service.ts
new file mode 100644
index 000000000..9ed25bfc7
--- /dev/null
+++ b/client/src/app/shared/users/user-history.service.ts
@@ -0,0 +1,45 @@
1import { HttpClient, HttpParams } from '@angular/common/http'
2import { Injectable } from '@angular/core'
3import { environment } from '../../../environments/environment'
4import { RestExtractor } from '../rest/rest-extractor.service'
5import { RestService } from '../rest/rest.service'
6import { Video } from '../video/video.model'
7import { catchError, map, switchMap } from 'rxjs/operators'
8import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
9import { VideoService } from '@app/shared/video/video.service'
10import { ResultList } from '../../../../../shared'
11
12@Injectable()
13export class UserHistoryService {
14 static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos'
15
16 constructor (
17 private authHttp: HttpClient,
18 private restExtractor: RestExtractor,
19 private restService: RestService,
20 private videoService: VideoService
21 ) {}
22
23 getUserVideosHistory (historyPagination: ComponentPagination) {
24 const pagination = this.restService.componentPaginationToRestPagination(historyPagination)
25
26 let params = new HttpParams()
27 params = this.restService.addRestGetParams(params, pagination)
28
29 return this.authHttp
30 .get<ResultList<Video>>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params })
31 .pipe(
32 switchMap(res => this.videoService.extractVideos(res)),
33 catchError(err => this.restExtractor.handleError(err))
34 )
35 }
36
37 deleteUserVideosHistory () {
38 return this.authHttp
39 .post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {})
40 .pipe(
41 map(() => this.restExtractor.extractDataBool()),
42 catchError(err => this.restExtractor.handleError(err))
43 )
44 }
45}
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts
new file mode 100644
index 000000000..125d2120c
--- /dev/null
+++ b/client/src/app/shared/users/user-notification.model.ts
@@ -0,0 +1,155 @@
1import { UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, ActorInfo } from '../../../../../shared'
2import { Actor } from '@app/shared/actor/actor.model'
3
4export class UserNotification implements UserNotificationServer {
5 id: number
6 type: UserNotificationType
7 read: boolean
8
9 video?: VideoInfo & {
10 channel: ActorInfo & { avatarUrl?: string }
11 }
12
13 videoImport?: {
14 id: number
15 video?: VideoInfo
16 torrentName?: string
17 magnetUri?: string
18 targetUrl?: string
19 }
20
21 comment?: {
22 id: number
23 threadId: number
24 account: ActorInfo & { avatarUrl?: string }
25 video: VideoInfo
26 }
27
28 videoAbuse?: {
29 id: number
30 video: VideoInfo
31 }
32
33 videoBlacklist?: {
34 id: number
35 video: VideoInfo
36 }
37
38 account?: ActorInfo & { avatarUrl?: string }
39
40 actorFollow?: {
41 id: number
42 follower: ActorInfo & { avatarUrl?: string }
43 following: {
44 type: 'account' | 'channel'
45 name: string
46 displayName: string
47 }
48 }
49
50 createdAt: string
51 updatedAt: string
52
53 // Additional fields
54 videoUrl?: string
55 commentUrl?: any[]
56 videoAbuseUrl?: string
57 accountUrl?: string
58 videoImportIdentifier?: string
59 videoImportUrl?: string
60
61 constructor (hash: UserNotificationServer) {
62 this.id = hash.id
63 this.type = hash.type
64 this.read = hash.read
65
66 this.video = hash.video
67 if (this.video) this.setAvatarUrl(this.video.channel)
68
69 this.videoImport = hash.videoImport
70
71 this.comment = hash.comment
72 if (this.comment) this.setAvatarUrl(this.comment.account)
73
74 this.videoAbuse = hash.videoAbuse
75
76 this.videoBlacklist = hash.videoBlacklist
77
78 this.account = hash.account
79 if (this.account) this.setAvatarUrl(this.account)
80
81 this.actorFollow = hash.actorFollow
82 if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower)
83
84 this.createdAt = hash.createdAt
85 this.updatedAt = hash.updatedAt
86
87 switch (this.type) {
88 case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION:
89 this.videoUrl = this.buildVideoUrl(this.video)
90 break
91
92 case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO:
93 this.videoUrl = this.buildVideoUrl(this.video)
94 break
95
96 case UserNotificationType.NEW_COMMENT_ON_MY_VIDEO:
97 case UserNotificationType.COMMENT_MENTION:
98 this.accountUrl = this.buildAccountUrl(this.comment.account)
99 this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
100 break
101
102 case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS:
103 this.videoAbuseUrl = '/admin/moderation/video-abuses/list'
104 this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
105 break
106
107 case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
108 this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
109 break
110
111 case UserNotificationType.MY_VIDEO_PUBLISHED:
112 this.videoUrl = this.buildVideoUrl(this.video)
113 break
114
115 case UserNotificationType.MY_VIDEO_IMPORT_SUCCESS:
116 this.videoImportUrl = this.buildVideoImportUrl()
117 this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
118 this.videoUrl = this.buildVideoUrl(this.videoImport.video)
119 break
120
121 case UserNotificationType.MY_VIDEO_IMPORT_ERROR:
122 this.videoImportUrl = this.buildVideoImportUrl()
123 this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
124 break
125
126 case UserNotificationType.NEW_USER_REGISTRATION:
127 this.accountUrl = this.buildAccountUrl(this.account)
128 break
129
130 case UserNotificationType.NEW_FOLLOW:
131 this.accountUrl = this.buildAccountUrl(this.actorFollow.follower)
132 break
133 }
134 }
135
136 private buildVideoUrl (video: { uuid: string }) {
137 return '/videos/watch/' + video.uuid
138 }
139
140 private buildAccountUrl (account: { name: string, host: string }) {
141 return '/accounts/' + Actor.CREATE_BY_STRING(account.name, account.host)
142 }
143
144 private buildVideoImportUrl () {
145 return '/my-account/video-imports'
146 }
147
148 private buildVideoImportIdentifier (videoImport: { targetUrl?: string, magnetUri?: string, torrentName?: string }) {
149 return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
150 }
151
152 private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { path: string } }) {
153 actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
154 }
155}
diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/users/user-notification.service.ts
new file mode 100644
index 000000000..f8a30955d
--- /dev/null
+++ b/client/src/app/shared/users/user-notification.service.ts
@@ -0,0 +1,86 @@
1import { Injectable } from '@angular/core'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { RestExtractor, RestService } from '../rest'
4import { catchError, map, tap } from 'rxjs/operators'
5import { environment } from '../../../environments/environment'
6import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared'
7import { UserNotification } from './user-notification.model'
8import { AuthService } from '../../core'
9import { ComponentPagination } from '../rest/component-pagination.model'
10import { User } from '..'
11import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service'
12
13@Injectable()
14export class UserNotificationService {
15 static BASE_NOTIFICATIONS_URL = environment.apiUrl + '/api/v1/users/me/notifications'
16 static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings'
17
18 constructor (
19 private auth: AuthService,
20 private authHttp: HttpClient,
21 private restExtractor: RestExtractor,
22 private restService: RestService,
23 private userNotificationSocket: UserNotificationSocket
24 ) {}
25
26 listMyNotifications (pagination: ComponentPagination, unread?: boolean, ignoreLoadingBar = false) {
27 let params = new HttpParams()
28 params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination))
29
30 if (unread) params = params.append('unread', `${unread}`)
31
32 const headers = ignoreLoadingBar ? { ignoreLoadingBar: '' } : undefined
33
34 return this.authHttp.get<ResultList<UserNotification>>(UserNotificationService.BASE_NOTIFICATIONS_URL, { params, headers })
35 .pipe(
36 map(res => this.restExtractor.convertResultListDateToHuman(res)),
37 map(res => this.restExtractor.applyToResultListData(res, this.formatNotification.bind(this))),
38 catchError(err => this.restExtractor.handleError(err))
39 )
40 }
41
42 countUnreadNotifications () {
43 return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true)
44 .pipe(map(n => n.total))
45 }
46
47 markAsRead (notification: UserNotification) {
48 const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read'
49
50 const body = { ids: [ notification.id ] }
51 const headers = { ignoreLoadingBar: '' }
52
53 return this.authHttp.post(url, body, { headers })
54 .pipe(
55 map(this.restExtractor.extractDataBool),
56 tap(() => this.userNotificationSocket.dispatch('read')),
57 catchError(res => this.restExtractor.handleError(res))
58 )
59 }
60
61 markAllAsRead () {
62 const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read-all'
63 const headers = { ignoreLoadingBar: '' }
64
65 return this.authHttp.post(url, {}, { headers })
66 .pipe(
67 map(this.restExtractor.extractDataBool),
68 tap(() => this.userNotificationSocket.dispatch('read-all')),
69 catchError(res => this.restExtractor.handleError(res))
70 )
71 }
72
73 updateNotificationSettings (user: User, settings: UserNotificationSetting) {
74 const url = UserNotificationService.BASE_NOTIFICATION_SETTINGS
75
76 return this.authHttp.put(url, settings)
77 .pipe(
78 map(this.restExtractor.extractDataBool),
79 catchError(res => this.restExtractor.handleError(res))
80 )
81 }
82
83 private formatNotification (notification: UserNotificationServer) {
84 return new UserNotification(notification)
85 }
86}
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html
new file mode 100644
index 000000000..0d69e0feb
--- /dev/null
+++ b/client/src/app/shared/users/user-notifications.component.html
@@ -0,0 +1,101 @@
1<div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div>
2
3<div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
4 <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
5
6 <ng-container [ngSwitch]="notification.type">
7 <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION">
8 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
9
10 <div class="message">
11 {{ notification.video.channel.displayName }} published a <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">new video</a>
12 </div>
13 </ng-container>
14
15 <ng-container i18n *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO">
16 <my-global-icon iconName="undo"></my-global-icon>
17
18 <div class="message">
19 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblacklisted
20 </div>
21 </ng-container>
22
23 <ng-container i18n *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO">
24 <my-global-icon iconName="no"></my-global-icon>
25
26 <div class="message">
27 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blacklisted
28 </div>
29 </ng-container>
30
31 <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS">
32 <my-global-icon iconName="alert"></my-global-icon>
33
34 <div class="message">
35 <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a>
36 </div>
37 </ng-container>
38
39 <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
40 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
41
42 <div class="message">
43 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a>
44 </div>
45 </ng-container>
46
47 <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED">
48 <my-global-icon iconName="sparkle"></my-global-icon>
49
50 <div class="message">
51 Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been published
52 </div>
53 </ng-container>
54
55 <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS">
56 <my-global-icon iconName="cloud-download"></my-global-icon>
57
58 <div class="message">
59 <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">Your video import</a> {{ notification.videoImportIdentifier }} succeeded
60 </div>
61 </ng-container>
62
63 <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR">
64 <my-global-icon iconName="cloud-error"></my-global-icon>
65
66 <div class="message">
67 <a (click)="markAsRead(notification)" [routerLink]="notification.videoImportUrl">Your video import</a> {{ notification.videoImportIdentifier }} failed
68 </div>
69 </ng-container>
70
71 <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION">
72 <my-global-icon iconName="user-add"></my-global-icon>
73
74 <div class="message">
75 User <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.account.name }} registered</a> on your instance
76 </div>
77 </ng-container>
78
79 <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_FOLLOW">
80 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" />
81
82 <div class="message">
83 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.actorFollow.follower.displayName }}</a> is following
84
85 <ng-container *ngIf="notification.actorFollow.following.type === 'channel'">your channel {{ notification.actorFollow.following.displayName }}</ng-container>
86 <ng-container *ngIf="notification.actorFollow.following.type === 'account'">your account</ng-container>
87 </div>
88 </ng-container>
89
90 <ng-container i18n *ngSwitchCase="UserNotificationType.COMMENT_MENTION">
91 <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
92
93 <div class="message">
94 <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> mentioned you on <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">video {{ notification.comment.video.name }}</a>
95 </div>
96 </ng-container>
97 </ng-container>
98
99 <div class="from-date">{{ notification.createdAt | myFromNow }}</div>
100 </div>
101</div>
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/users/user-notifications.component.scss
new file mode 100644
index 000000000..315d504c9
--- /dev/null
+++ b/client/src/app/shared/users/user-notifications.component.scss
@@ -0,0 +1,51 @@
1@import '_variables';
2@import '_mixins';
3
4.no-notification {
5 display: flex;
6 justify-content: center;
7 align-items: center;
8 padding: 20px 0;
9}
10
11.notification {
12 display: flex;
13 align-items: center;
14 font-size: inherit;
15 padding: 15px 5px 15px 10px;
16 border-bottom: 1px solid rgba(0, 0, 0, 0.10);
17
18 &.unread {
19 background-color: rgba(0, 0, 0, 0.05);
20 }
21
22 my-global-icon {
23 width: 24px;
24 margin-right: 11px;
25 margin-left: 3px;
26
27 @include apply-svg-color(#333);
28 }
29
30 .avatar {
31 @include avatar(30px);
32
33 margin-right: 10px;
34 }
35
36 .message {
37 flex-grow: 1;
38
39 a {
40 font-weight: $font-semibold;
41 }
42 }
43
44 .from-date {
45 font-size: 0.85em;
46 color: $grey-foreground-color;
47 padding-left: 5px;
48 min-width: 70px;
49 text-align: right;
50 }
51}
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/users/user-notifications.component.ts
new file mode 100644
index 000000000..b5f9fd399
--- /dev/null
+++ b/client/src/app/shared/users/user-notifications.component.ts
@@ -0,0 +1,87 @@
1import { Component, Input, OnInit } from '@angular/core'
2import { UserNotificationService } from '@app/shared/users/user-notification.service'
3import { UserNotificationType } from '../../../../../shared'
4import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
5import { Notifier } from '@app/core'
6import { UserNotification } from '@app/shared/users/user-notification.model'
7
8@Component({
9 selector: 'my-user-notifications',
10 templateUrl: 'user-notifications.component.html',
11 styleUrls: [ 'user-notifications.component.scss' ]
12})
13export class UserNotificationsComponent implements OnInit {
14 @Input() ignoreLoadingBar = false
15 @Input() infiniteScroll = true
16 @Input() itemsPerPage = 20
17
18 notifications: UserNotification[] = []
19
20 // So we can access it in the template
21 UserNotificationType = UserNotificationType
22
23 componentPagination: ComponentPagination
24
25 constructor (
26 private userNotificationService: UserNotificationService,
27 private notifier: Notifier
28 ) { }
29
30 ngOnInit () {
31 this.componentPagination = {
32 currentPage: 1,
33 itemsPerPage: this.itemsPerPage, // Reset items per page, because of the @Input() variable
34 totalItems: null
35 }
36
37 this.loadMoreNotifications()
38 }
39
40 loadMoreNotifications () {
41 this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar)
42 .subscribe(
43 result => {
44 this.notifications = this.notifications.concat(result.data)
45 this.componentPagination.totalItems = result.total
46 },
47
48 err => this.notifier.error(err.message)
49 )
50 }
51
52 onNearOfBottom () {
53 if (this.infiniteScroll === false) return
54
55 this.componentPagination.currentPage++
56
57 if (hasMoreItems(this.componentPagination)) {
58 this.loadMoreNotifications()
59 }
60 }
61
62 markAsRead (notification: UserNotification) {
63 if (notification.read) return
64
65 this.userNotificationService.markAsRead(notification)
66 .subscribe(
67 () => {
68 notification.read = true
69 },
70
71 err => this.notifier.error(err.message)
72 )
73 }
74
75 markAllAsRead () {
76 this.userNotificationService.markAllAsRead()
77 .subscribe(
78 () => {
79 for (const notification of this.notifications) {
80 notification.read = true
81 }
82 },
83
84 err => this.notifier.error(err.message)
85 )
86 }
87}
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index 877f1bf3a..c15f1de8c 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -1,38 +1,20 @@
1import { 1import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared'
2 Account as AccountServerModel,
3 hasUserRight,
4 User as UserServerModel,
5 UserRight,
6 UserRole,
7 VideoChannel
8} from '../../../../../shared'
9import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' 2import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
10import { Account } from '@app/shared/account/account.model' 3import { Account } from '@app/shared/account/account.model'
11import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 4import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
12 5
13export type UserConstructorHash = {
14 id: number,
15 username: string,
16 email: string,
17 role: UserRole,
18 videoQuota?: number,
19 videoQuotaDaily?: number,
20 nsfwPolicy?: NSFWPolicyType,
21 autoPlayVideo?: boolean,
22 createdAt?: Date,
23 account?: AccountServerModel,
24 videoChannels?: VideoChannel[]
25
26 blocked?: boolean
27 blockedReason?: string
28}
29export class User implements UserServerModel { 6export class User implements UserServerModel {
30 id: number 7 id: number
31 username: string 8 username: string
32 email: string 9 email: string
10 emailVerified: boolean
33 role: UserRole 11 role: UserRole
34 nsfwPolicy: NSFWPolicyType 12 nsfwPolicy: NSFWPolicyType
13
14 webTorrentEnabled: boolean
35 autoPlayVideo: boolean 15 autoPlayVideo: boolean
16 videosHistoryEnabled: boolean
17
36 videoQuota: number 18 videoQuota: number
37 videoQuotaDaily: number 19 videoQuotaDaily: number
38 account: Account 20 account: Account
@@ -42,7 +24,9 @@ export class User implements UserServerModel {
42 blocked: boolean 24 blocked: boolean
43 blockedReason?: string 25 blockedReason?: string
44 26
45 constructor (hash: UserConstructorHash) { 27 notificationSettings?: UserNotificationSetting
28
29 constructor (hash: Partial<UserServerModel>) {
46 this.id = hash.id 30 this.id = hash.id
47 this.username = hash.username 31 this.username = hash.username
48 this.email = hash.email 32 this.email = hash.email
@@ -52,11 +36,15 @@ export class User implements UserServerModel {
52 this.videoQuota = hash.videoQuota 36 this.videoQuota = hash.videoQuota
53 this.videoQuotaDaily = hash.videoQuotaDaily 37 this.videoQuotaDaily = hash.videoQuotaDaily
54 this.nsfwPolicy = hash.nsfwPolicy 38 this.nsfwPolicy = hash.nsfwPolicy
39 this.webTorrentEnabled = hash.webTorrentEnabled
40 this.videosHistoryEnabled = hash.videosHistoryEnabled
55 this.autoPlayVideo = hash.autoPlayVideo 41 this.autoPlayVideo = hash.autoPlayVideo
56 this.createdAt = hash.createdAt 42 this.createdAt = hash.createdAt
57 this.blocked = hash.blocked 43 this.blocked = hash.blocked
58 this.blockedReason = hash.blockedReason 44 this.blockedReason = hash.blockedReason
59 45
46 this.notificationSettings = hash.notificationSettings
47
60 if (hash.account !== undefined) { 48 if (hash.account !== undefined) {
61 this.account = new Account(hash.account) 49 this.account = new Account(hash.account)
62 } 50 }
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index d9b81c181..cc5c051f1 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -1,5 +1,5 @@
1import { Observable } from 'rxjs' 1import { from, Observable } from 'rxjs'
2import { catchError, map } from 'rxjs/operators' 2import { catchError, concatMap, map, toArray } from 'rxjs/operators'
3import { HttpClient, HttpParams } from '@angular/common/http' 3import { HttpClient, HttpParams } from '@angular/common/http'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' 5import { ResultList, User, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared'
@@ -153,15 +153,26 @@ export class UserService {
153 ) 153 )
154 } 154 }
155 155
156 updateUsers (users: User[], userUpdate: UserUpdate) {
157 return from(users)
158 .pipe(
159 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
160 toArray(),
161 catchError(err => this.restExtractor.handleError(err))
162 )
163 }
164
156 getUser (userId: number) { 165 getUser (userId: number) {
157 return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId) 166 return this.authHttp.get<User>(UserService.BASE_USERS_URL + userId)
158 .pipe(catchError(err => this.restExtractor.handleError(err))) 167 .pipe(catchError(err => this.restExtractor.handleError(err)))
159 } 168 }
160 169
161 getUsers (pagination: RestPagination, sort: SortMeta): Observable<ResultList<User>> { 170 getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable<ResultList<User>> {
162 let params = new HttpParams() 171 let params = new HttpParams()
163 params = this.restService.addRestGetParams(params, pagination, sort) 172 params = this.restService.addRestGetParams(params, pagination, sort)
164 173
174 if (search) params = params.append('search', search)
175
165 return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params }) 176 return this.authHttp.get<ResultList<User>>(UserService.BASE_USERS_URL, { params })
166 .pipe( 177 .pipe(
167 map(res => this.restExtractor.convertResultListDateToHuman(res)), 178 map(res => this.restExtractor.convertResultListDateToHuman(res)),
@@ -170,21 +181,38 @@ export class UserService {
170 ) 181 )
171 } 182 }
172 183
173 removeUser (user: { id: number }) { 184 removeUser (usersArg: User | User[]) {
174 return this.authHttp.delete(UserService.BASE_USERS_URL + user.id) 185 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
175 .pipe(catchError(err => this.restExtractor.handleError(err))) 186
187 return from(users)
188 .pipe(
189 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
190 toArray(),
191 catchError(err => this.restExtractor.handleError(err))
192 )
176 } 193 }
177 194
178 banUser (user: { id: number }, reason?: string) { 195 banUsers (usersArg: User | User[], reason?: string) {
179 const body = reason ? { reason } : {} 196 const body = reason ? { reason } : {}
197 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
180 198
181 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/block', body) 199 return from(users)
182 .pipe(catchError(err => this.restExtractor.handleError(err))) 200 .pipe(
201 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
202 toArray(),
203 catchError(err => this.restExtractor.handleError(err))
204 )
183 } 205 }
184 206
185 unbanUser (user: { id: number }) { 207 unbanUsers (usersArg: User | User[]) {
186 return this.authHttp.post(UserService.BASE_USERS_URL + user.id + '/unblock', {}) 208 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
187 .pipe(catchError(err => this.restExtractor.handleError(err))) 209
210 return from(users)
211 .pipe(
212 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
213 toArray(),
214 catchError(err => this.restExtractor.handleError(err))
215 )
188 } 216 }
189 217
190 private formatUser (user: User) { 218 private formatUser (user: User) {