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)
52 .pipe(catchError(err => this.restExtractor.handleError(err)))
55 changeEmail (password: string, newEmail: string) {
56 const url = UserService.BASE_USERS_URL + 'me'
57 const body: UserUpdateMe = {
58 currentPassword: password,
62 return this.authHttp.put(url, body)
63 .pipe(catchError(err => this.restExtractor.handleError(err)))
66 // ---------------------------------------------------------------------------
68 updateMyAnonymousProfile (profile: UserUpdateMe) {
69 this.userLocalStorageService.setUserInfo(profile)
72 listenAnonymousUpdate () {
73 return this.userLocalStorageService.listenUserInfoChange()
74 .pipe(map(() => this.getAnonymousUser()))
78 return new User(this.userLocalStorageService.getUserInfo())
81 // ---------------------------------------------------------------------------
83 updateMyProfile (profile: UserUpdateMe) {
84 const url = UserService.BASE_USERS_URL + 'me'
86 return this.authHttp.put(url, profile)
87 .pipe(catchError(err => this.restExtractor.handleError(err)))
91 const url = UserService.BASE_USERS_URL + 'me'
93 return this.authHttp.delete(url)
94 .pipe(catchError(err => this.restExtractor.handleError(err)))
97 changeAvatar (avatarForm: FormData) {
98 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
100 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm)
101 .pipe(catchError(err => this.restExtractor.handleError(err)))
105 const url = UserService.BASE_USERS_URL + 'me/avatar'
107 return this.authHttp.delete(url)
108 .pipe(catchError(err => this.restExtractor.handleError(err)))
111 signup (userCreate: UserRegister) {
112 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
114 tap(() => this.signupInThisSession = true),
115 catchError(err => this.restExtractor.handleError(err))
119 getMyVideoQuotaUsed () {
120 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
122 return this.authHttp.get<UserVideoQuota>(url)
123 .pipe(catchError(err => this.restExtractor.handleError(err)))
126 askResetPassword (email: string) {
127 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
129 return this.authHttp.post(url, { email })
130 .pipe(catchError(err => this.restExtractor.handleError(err)))
133 resetPassword (userId: number, verificationString: string, password: string) {
134 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
140 return this.authHttp.post(url, body)
141 .pipe(catchError(res => this.restExtractor.handleError(res)))
144 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
145 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
151 return this.authHttp.post(url, body)
152 .pipe(catchError(res => this.restExtractor.handleError(res)))
155 askSendVerifyEmail (email: string) {
156 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
158 return this.authHttp.post(url, { email })
159 .pipe(catchError(err => this.restExtractor.handleError(err)))
162 autocomplete (search: string): Observable<string[]> {
163 const url = UserService.BASE_USERS_URL + 'autocomplete'
164 const params = new HttpParams().append('search', search)
167 .get<string[]>(url, { params })
168 .pipe(catchError(res => this.restExtractor.handleError(res)))
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
175 return this.displayNameToUsername(newDisplayName)
178 displayNameToUsername (displayName: string) {
179 if (!displayName) return ''
184 .replace(/[^a-z0-9_.]/g, '')
187 /* ###### Admin methods ###### */
189 addUser (userCreate: UserCreate) {
190 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
191 .pipe(catchError(err => this.restExtractor.handleError(err)))
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)))
199 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
202 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
204 catchError(err => this.restExtractor.handleError(err))
208 getUserWithCache (userId: number) {
209 if (!this.userCache[userId]) {
210 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
213 return this.userCache[userId]
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)))
222 getUsers (parameters: {
223 pagination: RestPagination
226 }): Observable<ResultList<UserServerModel>> {
227 const { pagination, sort, search } = parameters
229 let params = new HttpParams()
230 params = this.restService.addRestGetParams(params, pagination, sort)
233 const filters = this.restService.parseQueryStringFilter(search, {
240 params = this.restService.addObjectParams(params, filters)
243 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
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))
251 removeUser (usersArg: UserServerModel | UserServerModel[]) {
252 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
256 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
258 catchError(err => this.restExtractor.handleError(err))
262 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
263 const body = reason ? { reason } : {}
264 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
268 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
270 catchError(err => this.restExtractor.handleError(err))
274 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
275 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
279 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
281 catchError(err => this.restExtractor.handleError(err))
285 getAnonymousOrLoggedUser () {
286 if (!this.authService.isLoggedIn()) {
287 return of(this.getAnonymousUser())
290 return this.authService.userInformationLoaded
293 map(() => this.authService.getUser())
297 private formatUser (user: UserServerModel) {
299 if (user.videoQuota === -1) {
302 videoQuota = getBytes(user.videoQuota, 0)
305 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
307 let videoQuotaDaily: string
308 let videoQuotaUsedDaily: string
309 if (user.videoQuotaDaily === -1) {
310 videoQuotaDaily = '∞'
311 videoQuotaUsedDaily = getBytes(0, 0) + ''
313 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
314 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
317 const roleLabels: { [ id in UserRole ]: string } = {
318 [UserRole.USER]: $localize`User`,
319 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
320 [UserRole.MODERATOR]: $localize`Moderator`
323 return Object.assign(user, {
324 roleLabel: roleLabels[user.role],
327 rawVideoQuota: user.videoQuota,
328 rawVideoQuotaUsed: user.videoQuotaUsed,
331 rawVideoQuotaDaily: user.videoQuotaDaily,
332 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily