]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/core/users/user.service.ts
Merge branch 'release/4.0.0' into develop
[github/Chocobozzz/PeerTube.git] / client / src / app / core / users / user.service.ts
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'
8 import {
9 ActorImage,
10 ResultList,
11 User as UserServerModel,
12 UserCreate,
13 UserRegister,
14 UserRole,
15 UserUpdate,
16 UserUpdateMe,
17 UserVideoQuota
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'
23
24 @Injectable()
25 export class UserService {
26 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
27
28 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
29
30 private signupInThisSession = false
31
32 constructor (
33 private authHttp: HttpClient,
34 private authService: AuthService,
35 private restExtractor: RestExtractor,
36 private restService: RestService,
37 private userLocalStorageService: UserLocalStorageService
38 ) { }
39
40 hasSignupInThisSession () {
41 return this.signupInThisSession
42 }
43
44 changePassword (currentPassword: string, newPassword: string) {
45 const url = UserService.BASE_USERS_URL + 'me'
46 const body: UserUpdateMe = {
47 currentPassword,
48 password: newPassword
49 }
50
51 return this.authHttp.put(url, body)
52 .pipe(
53 map(this.restExtractor.extractDataBool),
54 catchError(err => this.restExtractor.handleError(err))
55 )
56 }
57
58 changeEmail (password: string, newEmail: string) {
59 const url = UserService.BASE_USERS_URL + 'me'
60 const body: UserUpdateMe = {
61 currentPassword: password,
62 email: newEmail
63 }
64
65 return this.authHttp.put(url, body)
66 .pipe(
67 map(this.restExtractor.extractDataBool),
68 catchError(err => this.restExtractor.handleError(err))
69 )
70 }
71
72 // ---------------------------------------------------------------------------
73
74 updateMyAnonymousProfile (profile: UserUpdateMe) {
75 this.userLocalStorageService.setUserInfo(profile)
76 }
77
78 listenAnonymousUpdate () {
79 return this.userLocalStorageService.listenUserInfoChange()
80 .pipe(map(() => this.getAnonymousUser()))
81 }
82
83 getAnonymousUser () {
84 return new User(this.userLocalStorageService.getUserInfo())
85 }
86
87 // ---------------------------------------------------------------------------
88
89 updateMyProfile (profile: UserUpdateMe) {
90 const url = UserService.BASE_USERS_URL + 'me'
91
92 return this.authHttp.put(url, profile)
93 .pipe(
94 map(this.restExtractor.extractDataBool),
95 catchError(err => this.restExtractor.handleError(err))
96 )
97 }
98
99 deleteMe () {
100 const url = UserService.BASE_USERS_URL + 'me'
101
102 return this.authHttp.delete(url)
103 .pipe(
104 map(this.restExtractor.extractDataBool),
105 catchError(err => this.restExtractor.handleError(err))
106 )
107 }
108
109 changeAvatar (avatarForm: FormData) {
110 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
111
112 return this.authHttp.post<{ avatar: ActorImage }>(url, avatarForm)
113 .pipe(catchError(err => this.restExtractor.handleError(err)))
114 }
115
116 deleteAvatar () {
117 const url = UserService.BASE_USERS_URL + 'me/avatar'
118
119 return this.authHttp.delete(url)
120 .pipe(
121 map(this.restExtractor.extractDataBool),
122 catchError(err => this.restExtractor.handleError(err))
123 )
124 }
125
126 signup (userCreate: UserRegister) {
127 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
128 .pipe(
129 map(this.restExtractor.extractDataBool),
130 tap(() => this.signupInThisSession = true),
131 catchError(err => this.restExtractor.handleError(err))
132 )
133 }
134
135 getMyVideoQuotaUsed () {
136 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
137
138 return this.authHttp.get<UserVideoQuota>(url)
139 .pipe(catchError(err => this.restExtractor.handleError(err)))
140 }
141
142 askResetPassword (email: string) {
143 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
144
145 return this.authHttp.post(url, { email })
146 .pipe(
147 map(this.restExtractor.extractDataBool),
148 catchError(err => this.restExtractor.handleError(err))
149 )
150 }
151
152 resetPassword (userId: number, verificationString: string, password: string) {
153 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
154 const body = {
155 verificationString,
156 password
157 }
158
159 return this.authHttp.post(url, body)
160 .pipe(
161 map(this.restExtractor.extractDataBool),
162 catchError(res => this.restExtractor.handleError(res))
163 )
164 }
165
166 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
167 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
168 const body = {
169 verificationString,
170 isPendingEmail
171 }
172
173 return this.authHttp.post(url, body)
174 .pipe(
175 map(this.restExtractor.extractDataBool),
176 catchError(res => this.restExtractor.handleError(res))
177 )
178 }
179
180 askSendVerifyEmail (email: string) {
181 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
182
183 return this.authHttp.post(url, { email })
184 .pipe(
185 map(this.restExtractor.extractDataBool),
186 catchError(err => this.restExtractor.handleError(err))
187 )
188 }
189
190 autocomplete (search: string): Observable<string[]> {
191 const url = UserService.BASE_USERS_URL + 'autocomplete'
192 const params = new HttpParams().append('search', search)
193
194 return this.authHttp
195 .get<string[]>(url, { params })
196 .pipe(catchError(res => this.restExtractor.handleError(res)))
197 }
198
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
202
203 return this.displayNameToUsername(newDisplayName)
204 }
205
206 displayNameToUsername (displayName: string) {
207 if (!displayName) return ''
208
209 return displayName
210 .toLowerCase()
211 .replace(/\s/g, '_')
212 .replace(/[^a-z0-9_.]/g, '')
213 }
214
215 /* ###### Admin methods ###### */
216
217 addUser (userCreate: UserCreate) {
218 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
219 .pipe(
220 map(this.restExtractor.extractDataBool),
221 catchError(err => this.restExtractor.handleError(err))
222 )
223 }
224
225 updateUser (userId: number, userUpdate: UserUpdate) {
226 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
227 .pipe(
228 map(this.restExtractor.extractDataBool),
229 catchError(err => this.restExtractor.handleError(err))
230 )
231 }
232
233 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
234 return from(users)
235 .pipe(
236 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
237 toArray(),
238 catchError(err => this.restExtractor.handleError(err))
239 )
240 }
241
242 getUserWithCache (userId: number) {
243 if (!this.userCache[userId]) {
244 this.userCache[userId] = this.getUser(userId).pipe(shareReplay())
245 }
246
247 return this.userCache[userId]
248 }
249
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)))
254 }
255
256 getUsers (parameters: {
257 pagination: RestPagination
258 sort: SortMeta
259 search?: string
260 }): Observable<ResultList<UserServerModel>> {
261 const { pagination, sort, search } = parameters
262
263 let params = new HttpParams()
264 params = this.restService.addRestGetParams(params, pagination, sort)
265
266 if (search) {
267 const filters = this.restService.parseQueryStringFilter(search, {
268 blocked: {
269 prefix: 'banned:',
270 isBoolean: true
271 }
272 })
273
274 params = this.restService.addObjectParams(params, filters)
275 }
276
277 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
278 .pipe(
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))
282 )
283 }
284
285 removeUser (usersArg: UserServerModel | UserServerModel[]) {
286 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
287
288 return from(users)
289 .pipe(
290 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
291 toArray(),
292 catchError(err => this.restExtractor.handleError(err))
293 )
294 }
295
296 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
297 const body = reason ? { reason } : {}
298 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
299
300 return from(users)
301 .pipe(
302 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
303 toArray(),
304 catchError(err => this.restExtractor.handleError(err))
305 )
306 }
307
308 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
309 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
310
311 return from(users)
312 .pipe(
313 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
314 toArray(),
315 catchError(err => this.restExtractor.handleError(err))
316 )
317 }
318
319 getAnonymousOrLoggedUser () {
320 if (!this.authService.isLoggedIn()) {
321 return of(this.getAnonymousUser())
322 }
323
324 return this.authService.userInformationLoaded
325 .pipe(
326 first(),
327 map(() => this.authService.getUser())
328 )
329 }
330
331 private formatUser (user: UserServerModel) {
332 let videoQuota
333 if (user.videoQuota === -1) {
334 videoQuota = '∞'
335 } else {
336 videoQuota = getBytes(user.videoQuota, 0)
337 }
338
339 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
340
341 let videoQuotaDaily: string
342 let videoQuotaUsedDaily: string
343 if (user.videoQuotaDaily === -1) {
344 videoQuotaDaily = '∞'
345 videoQuotaUsedDaily = getBytes(0, 0) + ''
346 } else {
347 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
348 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
349 }
350
351 const roleLabels: { [ id in UserRole ]: string } = {
352 [UserRole.USER]: $localize`User`,
353 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
354 [UserRole.MODERATOR]: $localize`Moderator`
355 }
356
357 return Object.assign(user, {
358 roleLabel: roleLabels[user.role],
359 videoQuota,
360 videoQuotaUsed,
361 rawVideoQuota: user.videoQuota,
362 rawVideoQuotaUsed: user.videoQuotaUsed,
363 videoQuotaDaily,
364 videoQuotaUsedDaily,
365 rawVideoQuotaDaily: user.videoQuotaDaily,
366 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily
367 })
368 }
369 }