1 import { SortMeta } from 'primeng/api'
2 import { from, Observable, of } from 'rxjs'
3 import { catchError, concatMap, first, map, shareReplay, tap, toArray } from 'rxjs/operators'
4 import { HttpClient, HttpParams } from '@angular/common/http'
5 import { Injectable } from '@angular/core'
6 import { AuthService } from '@app/core/auth'
7 import { getBytes } from '@root-helpers/bytes'
11 User as UserServerModel,
18 } from '@shared/models'
19 import { environment } from '../../../environments/environment'
20 import { RestExtractor, RestPagination, RestService } from '../rest'
21 import { UserLocalStorageService } from './'
22 import { User } from './user.model'
25 export class UserService {
26 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
28 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
30 private signupInThisSession = false
33 private authHttp: HttpClient,
34 private authService: AuthService,
35 private restExtractor: RestExtractor,
36 private restService: RestService,
37 private userLocalStorageService: UserLocalStorageService
40 hasSignupInThisSession () {
41 return this.signupInThisSession
44 changePassword (currentPassword: string, newPassword: string) {
45 const url = UserService.BASE_USERS_URL + 'me'
46 const body: UserUpdateMe = {
51 return this.authHttp.put(url, body)
53 map(this.restExtractor.extractDataBool),
54 catchError(err => this.restExtractor.handleError(err))
58 changeEmail (password: string, newEmail: string) {
59 const url = UserService.BASE_USERS_URL + 'me'
60 const body: UserUpdateMe = {
61 currentPassword: password,
65 return this.authHttp.put(url, body)
67 map(this.restExtractor.extractDataBool),
68 catchError(err => this.restExtractor.handleError(err))
72 // ---------------------------------------------------------------------------
74 updateMyAnonymousProfile (profile: UserUpdateMe) {
75 this.userLocalStorageService.setUserInfo(profile)
78 listenAnonymousUpdate () {
79 return this.userLocalStorageService.listenUserInfoChange()
80 .pipe(map(() => this.getAnonymousUser()))
84 return new User(this.userLocalStorageService.getUserInfo())
87 // ---------------------------------------------------------------------------
89 updateMyProfile (profile: UserUpdateMe) {
90 const url = UserService.BASE_USERS_URL + 'me'
92 return this.authHttp.put(url, profile)
94 map(this.restExtractor.extractDataBool),
95 catchError(err => this.restExtractor.handleError(err))
100 const url = UserService.BASE_USERS_URL + 'me'
102 return this.authHttp.delete(url)
104 map(this.restExtractor.extractDataBool),
105 catchError(err => this.restExtractor.handleError(err))
109 changeAvatar (avatarForm: FormData) {
110 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
112 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm)
113 .pipe(catchError(err => this.restExtractor.handleError(err)))
117 const url = UserService.BASE_USERS_URL + 'me/avatar'
119 return this.authHttp.delete(url)
121 map(this.restExtractor.extractDataBool),
122 catchError(err => this.restExtractor.handleError(err))
126 signup (userCreate: UserRegister) {
127 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
129 map(this.restExtractor.extractDataBool),
130 tap(() => this.signupInThisSession = true),
131 catchError(err => this.restExtractor.handleError(err))
135 getMyVideoQuotaUsed () {
136 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
138 return this.authHttp.get<UserVideoQuota>(url)
139 .pipe(catchError(err => this.restExtractor.handleError(err)))
142 askResetPassword (email: string) {
143 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
145 return this.authHttp.post(url, { email })
147 map(this.restExtractor.extractDataBool),
148 catchError(err => this.restExtractor.handleError(err))
152 resetPassword (userId: number, verificationString: string, password: string) {
153 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
159 return this.authHttp.post(url, body)
161 map(this.restExtractor.extractDataBool),
162 catchError(res => this.restExtractor.handleError(res))
166 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
167 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
173 return this.authHttp.post(url, body)
175 map(this.restExtractor.extractDataBool),
176 catchError(res => this.restExtractor.handleError(res))
180 askSendVerifyEmail (email: string) {
181 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
183 return this.authHttp.post(url, { email })
185 map(this.restExtractor.extractDataBool),
186 catchError(err => this.restExtractor.handleError(err))
190 autocomplete (search: string): Observable<string[]> {
191 const url = UserService.BASE_USERS_URL + 'autocomplete'
192 const params = new HttpParams().append('search', search)
195 .get<string[]>(url, { params })
196 .pipe(catchError(res => this.restExtractor.handleError(res)))
199 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
200 // Don't update display name, the user seems to have changed it
201 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
203 return this.displayNameToUsername(newDisplayName)
206 displayNameToUsername (displayName: string) {
207 if (!displayName) return ''
212 .replace(/[^a-z0-9_.]/g, '')
215 /* ###### Admin methods ###### */
217 addUser (userCreate: UserCreate) {
218 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
220 map(this.restExtractor.extractDataBool),
221 catchError(err => this.restExtractor.handleError(err))
225 updateUser (userId: number, userUpdate: UserUpdate) {
226 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
228 map(this.restExtractor.extractDataBool),
229 catchError(err => this.restExtractor.handleError(err))
233 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
236 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
238 catchError(err => this.restExtractor.handleError(err))
242 getUserWithCache (userId: number) {
243 if (!this.userCache[userId]) {
244 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
247 return this.userCache[userId]
250 getUser (userId: number, withStats = false) {
251 const params = new HttpParams().append('withStats', withStats + '')
252 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
253 .pipe(catchError(err => this.restExtractor.handleError(err)))
256 getUsers (parameters: {
257 pagination: RestPagination
260 }): Observable<ResultList<UserServerModel>> {
261 const { pagination, sort, search } = parameters
263 let params = new HttpParams()
264 params = this.restService.addRestGetParams(params, pagination, sort)
267 const filters = this.restService.parseQueryStringFilter(search, {
274 params = this.restService.addObjectParams(params, filters)
277 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
279 map(res => this.restExtractor.convertResultListDateToHuman(res)),
280 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
281 catchError(err => this.restExtractor.handleError(err))
285 removeUser (usersArg: UserServerModel | UserServerModel[]) {
286 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
290 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
292 catchError(err => this.restExtractor.handleError(err))
296 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
297 const body = reason ? { reason } : {}
298 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
302 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
304 catchError(err => this.restExtractor.handleError(err))
308 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
309 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
313 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
315 catchError(err => this.restExtractor.handleError(err))
319 getAnonymousOrLoggedUser () {
320 if (!this.authService.isLoggedIn()) {
321 return of(this.getAnonymousUser())
324 return this.authService.userInformationLoaded
327 map(() => this.authService.getUser())
331 private formatUser (user: UserServerModel) {
333 if (user.videoQuota === -1) {
336 videoQuota = getBytes(user.videoQuota, 0)
339 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
341 let videoQuotaDaily: string
342 let videoQuotaUsedDaily: string
343 if (user.videoQuotaDaily === -1) {
344 videoQuotaDaily = '∞'
345 videoQuotaUsedDaily = getBytes(0, 0) + ''
347 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
348 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
351 const roleLabels: { [ id in UserRole ]: string } = {
352 [UserRole.USER]: $localize`User`,
353 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
354 [UserRole.MODERATOR]: $localize`Moderator`
357 return Object.assign(user, {
358 roleLabel: roleLabels[user.role],
361 rawVideoQuota: user.videoQuota,
362 rawVideoQuotaUsed: user.videoQuotaUsed,
365 rawVideoQuotaDaily: user.videoQuotaDaily,
366 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily