]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - client/src/app/core/users/user.service.ts
Fix abuse crash on deleted video
[github/Chocobozzz/PeerTube.git] / client / src / app / core / users / user.service.ts
CommitLineData
5c20a455
C
1import { SortMeta } from 'primeng/api'
2import { from, Observable, of } from 'rxjs'
67ed6552 3import { catchError, concatMap, filter, first, map, shareReplay, throttleTime, toArray } from 'rxjs/operators'
74d63469 4import { HttpClient, HttpParams } from '@angular/common/http'
63c4db6d 5import { Injectable } from '@angular/core'
5c20a455 6import { AuthService } from '@app/core/auth'
b4c3c51d 7import { getBytes } from '@root-helpers/bytes'
a4ff3100 8import { UserLocalStorageKeys } from '@root-helpers/users'
67ed6552
C
9import {
10 Avatar,
11 NSFWPolicyType,
12 ResultList,
13 User as UserServerModel,
14 UserCreate,
15 UserRegister,
16 UserRole,
17 UserUpdate,
18 UserUpdateMe,
19 UserVideoQuota
20} from '@shared/models'
5c20a455 21import { environment } from '../../../environments/environment'
5c20a455 22import { RestExtractor, RestPagination, RestService } from '../rest'
67ed6552 23import { LocalStorageService, SessionStorageService } from '../wrappers/storage.service'
5c20a455 24import { User } from './user.model'
629d8d6f
C
25
26@Injectable()
e2a2d6c8 27export class UserService {
63c4db6d 28 static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/'
629d8d6f 29
d3217560 30 private userCache: { [ id: number ]: Observable<UserServerModel> } = {}
218b0874 31
df98563e 32 constructor (
d592e0a9 33 private authHttp: HttpClient,
5c20a455 34 private authService: AuthService,
e724fa93
C
35 private restExtractor: RestExtractor,
36 private restService: RestService,
d3217560 37 private localStorageService: LocalStorageService,
66357162
C
38 private sessionStorageService: SessionStorageService
39 ) { }
629d8d6f 40
a890d1e0 41 changePassword (currentPassword: string, newPassword: string) {
8094a898
C
42 const url = UserService.BASE_USERS_URL + 'me'
43 const body: UserUpdateMe = {
a890d1e0 44 currentPassword,
629d8d6f 45 password: newPassword
df98563e 46 }
629d8d6f 47
de59c48f 48 return this.authHttp.put(url, body)
db400f44
C
49 .pipe(
50 map(this.restExtractor.extractDataBool),
e4f0e92e 51 catchError(err => this.restExtractor.handleError(err))
db400f44 52 )
629d8d6f 53 }
af5e743b 54
0ba5f5ba
C
55 changeEmail (password: string, newEmail: string) {
56 const url = UserService.BASE_USERS_URL + 'me'
57 const body: UserUpdateMe = {
58 currentPassword: password,
59 email: newEmail
60 }
61
62 return this.authHttp.put(url, body)
63 .pipe(
64 map(this.restExtractor.extractDataBool),
65 catchError(err => this.restExtractor.handleError(err))
66 )
67 }
68
ed56ad11 69 updateMyProfile (profile: UserUpdateMe) {
8094a898 70 const url = UserService.BASE_USERS_URL + 'me'
af5e743b 71
ed56ad11 72 return this.authHttp.put(url, profile)
db400f44
C
73 .pipe(
74 map(this.restExtractor.extractDataBool),
e4f0e92e 75 catchError(err => this.restExtractor.handleError(err))
db400f44 76 )
af5e743b 77 }
a184c71b 78
d3217560 79 updateMyAnonymousProfile (profile: UserUpdateMe) {
24d3352c
C
80 const localStorageKeys: { [ id in keyof UserUpdateMe ]: string } = {
81 nsfwPolicy: UserLocalStorageKeys.NSFW_POLICY,
82 webTorrentEnabled: UserLocalStorageKeys.WEBTORRENT_ENABLED,
83 autoPlayNextVideo: UserLocalStorageKeys.AUTO_PLAY_VIDEO,
84 autoPlayNextVideoPlaylist: UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
85 theme: UserLocalStorageKeys.THEME,
86 videoLanguages: UserLocalStorageKeys.VIDEO_LANGUAGES
87 }
d3217560 88
24d3352c
C
89 const obj = Object.keys(localStorageKeys)
90 .filter(key => key in profile)
91 .map(key => ([ localStorageKeys[key], profile[key] ]))
92
93 for (const [ key, value ] of obj) {
94 try {
95 if (!value) {
96 this.localStorageService.removeItem(key)
97 continue
98 }
99
100 const localStorageValue = typeof value === 'string'
101 ? value
102 : JSON.stringify(value)
103
104 this.localStorageService.setItem(key, localStorageValue)
105 } catch (err) {
106 console.error(`Cannot set ${key}->${value} in localStorage. Likely due to a value impossible to stringify.`, err)
107 }
d3217560
RK
108 }
109 }
110
5c20a455
C
111 listenAnonymousUpdate () {
112 return this.localStorageService.watch([
a4ff3100
C
113 UserLocalStorageKeys.NSFW_POLICY,
114 UserLocalStorageKeys.WEBTORRENT_ENABLED,
115 UserLocalStorageKeys.AUTO_PLAY_VIDEO,
116 UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST,
117 UserLocalStorageKeys.THEME,
118 UserLocalStorageKeys.VIDEO_LANGUAGES
5c20a455
C
119 ]).pipe(
120 throttleTime(200),
121 filter(() => this.authService.isLoggedIn() !== true),
122 map(() => this.getAnonymousUser())
123 )
124 }
125
92b9d60c
C
126 deleteMe () {
127 const url = UserService.BASE_USERS_URL + 'me'
128
129 return this.authHttp.delete(url)
130 .pipe(
131 map(this.restExtractor.extractDataBool),
132 catchError(err => this.restExtractor.handleError(err))
133 )
134 }
135
c5911fd3
C
136 changeAvatar (avatarForm: FormData) {
137 const url = UserService.BASE_USERS_URL + 'me/avatar/pick'
138
5fcbd898 139 return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm)
e4f0e92e 140 .pipe(catchError(err => this.restExtractor.handleError(err)))
c5911fd3
C
141 }
142
1ea7da81
RK
143 deleteAvatar () {
144 const url = UserService.BASE_USERS_URL + 'me/avatar'
145
146 return this.authHttp.delete(url)
147 .pipe(
148 map(this.restExtractor.extractDataBool),
149 catchError(err => this.restExtractor.handleError(err))
150 )
151 }
152
1d5342ab 153 signup (userCreate: UserRegister) {
d592e0a9 154 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
db400f44
C
155 .pipe(
156 map(this.restExtractor.extractDataBool),
e4f0e92e 157 catchError(err => this.restExtractor.handleError(err))
db400f44 158 )
a184c71b 159 }
c5911fd3 160
ce5496d6 161 getMyVideoQuotaUsed () {
bf64ed41 162 const url = UserService.BASE_USERS_URL + 'me/video-quota-used'
c5911fd3 163
5fcbd898 164 return this.authHttp.get<UserVideoQuota>(url)
e4f0e92e 165 .pipe(catchError(err => this.restExtractor.handleError(err)))
c5911fd3 166 }
ecb4e35f
C
167
168 askResetPassword (email: string) {
169 const url = UserService.BASE_USERS_URL + '/ask-reset-password'
170
171 return this.authHttp.post(url, { email })
db400f44
C
172 .pipe(
173 map(this.restExtractor.extractDataBool),
e4f0e92e 174 catchError(err => this.restExtractor.handleError(err))
db400f44 175 )
ecb4e35f
C
176 }
177
178 resetPassword (userId: number, verificationString: string, password: string) {
179 const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password`
180 const body = {
181 verificationString,
182 password
183 }
184
185 return this.authHttp.post(url, body)
db400f44
C
186 .pipe(
187 map(this.restExtractor.extractDataBool),
188 catchError(res => this.restExtractor.handleError(res))
189 )
ecb4e35f 190 }
d9eaee39 191
0ba5f5ba 192 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
d9eaee39
JM
193 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
194 const body = {
0ba5f5ba
C
195 verificationString,
196 isPendingEmail
d9eaee39
JM
197 }
198
199 return this.authHttp.post(url, body)
200 .pipe(
201 map(this.restExtractor.extractDataBool),
202 catchError(res => this.restExtractor.handleError(res))
203 )
204 }
205
206 askSendVerifyEmail (email: string) {
207 const url = UserService.BASE_USERS_URL + '/ask-send-verify-email'
208
209 return this.authHttp.post(url, { email })
210 .pipe(
211 map(this.restExtractor.extractDataBool),
212 catchError(err => this.restExtractor.handleError(err))
213 )
214 }
74d63469
GR
215
216 autocomplete (search: string): Observable<string[]> {
217 const url = UserService.BASE_USERS_URL + 'autocomplete'
218 const params = new HttpParams().append('search', search)
219
220 return this.authHttp
221 .get<string[]>(url, { params })
222 .pipe(catchError(res => this.restExtractor.handleError(res)))
223 }
e724fa93 224
1f20622f
C
225 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
226 // Don't update display name, the user seems to have changed it
227 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
228
229 return this.displayNameToUsername(newDisplayName)
230 }
231
232 displayNameToUsername (displayName: string) {
233 if (!displayName) return ''
234
235 return displayName
236 .toLowerCase()
237 .replace(/\s/g, '_')
238 .replace(/[^a-z0-9_.]/g, '')
239 }
240
e724fa93
C
241 /* ###### Admin methods ###### */
242
243 addUser (userCreate: UserCreate) {
244 return this.authHttp.post(UserService.BASE_USERS_URL, userCreate)
245 .pipe(
246 map(this.restExtractor.extractDataBool),
247 catchError(err => this.restExtractor.handleError(err))
248 )
249 }
250
251 updateUser (userId: number, userUpdate: UserUpdate) {
252 return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate)
253 .pipe(
254 map(this.restExtractor.extractDataBool),
255 catchError(err => this.restExtractor.handleError(err))
256 )
257 }
258
d3217560 259 updateUsers (users: UserServerModel[], userUpdate: UserUpdate) {
fc2ec87a
JM
260 return from(users)
261 .pipe(
262 concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)),
263 toArray(),
264 catchError(err => this.restExtractor.handleError(err))
265 )
266 }
267
218b0874
C
268 getUserWithCache (userId: number) {
269 if (!this.userCache[userId]) {
270 this.userCache[ userId ] = this.getUser(userId).pipe(shareReplay())
271 }
272
273 return this.userCache[userId]
274 }
275
76314386
RK
276 getUser (userId: number, withStats = false) {
277 const params = new HttpParams().append('withStats', withStats + '')
278 return this.authHttp.get<UserServerModel>(UserService.BASE_USERS_URL + userId, { params })
e724fa93
C
279 .pipe(catchError(err => this.restExtractor.handleError(err)))
280 }
281
d3217560 282 getAnonymousUser () {
5c20a455 283 let videoLanguages: string[]
9870329f 284
d3217560 285 try {
24d3352c
C
286 const languagesString = this.localStorageService.getItem(UserLocalStorageKeys.VIDEO_LANGUAGES)
287 videoLanguages = languagesString && languagesString !== 'undefined'
288 ? JSON.parse(languagesString)
289 : null
d3217560
RK
290 } catch (err) {
291 videoLanguages = null
292 console.error('Cannot parse desired video languages from localStorage.', err)
293 }
294
295 return new User({
296 // local storage keys
7a8d858e 297 nsfwPolicy: this.localStorageService.getItem(UserLocalStorageKeys.NSFW_POLICY),
a4ff3100
C
298 webTorrentEnabled: this.localStorageService.getItem(UserLocalStorageKeys.WEBTORRENT_ENABLED) !== 'false',
299 theme: this.localStorageService.getItem(UserLocalStorageKeys.THEME) || 'instance-default',
d3217560
RK
300 videoLanguages,
301
a4ff3100
C
302 autoPlayNextVideoPlaylist: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false',
303 autoPlayVideo: this.localStorageService.getItem(UserLocalStorageKeys.AUTO_PLAY_VIDEO) === 'true',
9870329f 304
d3217560 305 // session storage keys
a4ff3100 306 autoPlayNextVideo: this.sessionStorageService.getItem(UserLocalStorageKeys.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true'
d3217560
RK
307 })
308 }
309
8491293b
RK
310 getUsers (parameters: {
311 pagination: RestPagination
312 sort: SortMeta
313 search?: string
314 }): Observable<ResultList<UserServerModel>> {
315 const { pagination, sort, search } = parameters
316
e724fa93
C
317 let params = new HttpParams()
318 params = this.restService.addRestGetParams(params, pagination, sort)
319
8491293b
RK
320 if (search) {
321 const filters = this.restService.parseQueryStringFilter(search, {
322 blocked: {
323 prefix: 'banned:',
324 isBoolean: true,
325 handler: v => {
326 if (v === 'true') return v
327 if (v === 'false') return v
328
329 return undefined
330 }
331 }
332 })
333
334 params = this.restService.addObjectParams(params, filters)
335 }
24b9417c 336
d3217560 337 return this.authHttp.get<ResultList<UserServerModel>>(UserService.BASE_USERS_URL, { params })
e724fa93
C
338 .pipe(
339 map(res => this.restExtractor.convertResultListDateToHuman(res)),
340 map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))),
341 catchError(err => this.restExtractor.handleError(err))
342 )
343 }
344
d3217560 345 removeUser (usersArg: UserServerModel | UserServerModel[]) {
791645e6
C
346 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
347
348 return from(users)
349 .pipe(
350 concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)),
351 toArray(),
352 catchError(err => this.restExtractor.handleError(err))
353 )
e724fa93
C
354 }
355
d3217560 356 banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) {
e724fa93 357 const body = reason ? { reason } : {}
791645e6 358 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
e724fa93 359
791645e6
C
360 return from(users)
361 .pipe(
362 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)),
363 toArray(),
364 catchError(err => this.restExtractor.handleError(err))
365 )
e724fa93
C
366 }
367
d3217560 368 unbanUsers (usersArg: UserServerModel | UserServerModel[]) {
791645e6
C
369 const users = Array.isArray(usersArg) ? usersArg : [ usersArg ]
370
371 return from(users)
372 .pipe(
373 concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})),
374 toArray(),
375 catchError(err => this.restExtractor.handleError(err))
376 )
e724fa93
C
377 }
378
5c20a455
C
379 getAnonymousOrLoggedUser () {
380 if (!this.authService.isLoggedIn()) {
381 return of(this.getAnonymousUser())
382 }
383
384 return this.authService.userInformationLoaded
385 .pipe(
386 first(),
387 map(() => this.authService.getUser())
388 )
389 }
390
d3217560 391 private formatUser (user: UserServerModel) {
e724fa93
C
392 let videoQuota
393 if (user.videoQuota === -1) {
bc99dfe5 394 videoQuota = '∞'
e724fa93 395 } else {
b4c3c51d 396 videoQuota = getBytes(user.videoQuota, 0)
e724fa93
C
397 }
398
b4c3c51d 399 const videoQuotaUsed = getBytes(user.videoQuotaUsed, 0)
e724fa93 400
4f5d0459
RK
401 let videoQuotaDaily: string
402 let videoQuotaUsedDaily: string
bc99dfe5
RK
403 if (user.videoQuotaDaily === -1) {
404 videoQuotaDaily = '∞'
b4c3c51d 405 videoQuotaUsedDaily = getBytes(0, 0) + ''
bc99dfe5 406 } else {
b4c3c51d
C
407 videoQuotaDaily = getBytes(user.videoQuotaDaily, 0) + ''
408 videoQuotaUsedDaily = getBytes(user.videoQuotaUsedDaily || 0, 0) + ''
bc99dfe5
RK
409 }
410
e724fa93 411 const roleLabels: { [ id in UserRole ]: string } = {
66357162
C
412 [UserRole.USER]: $localize`User`,
413 [UserRole.ADMINISTRATOR]: $localize`Administrator`,
414 [UserRole.MODERATOR]: $localize`Moderator`
e724fa93
C
415 }
416
417 return Object.assign(user, {
418 roleLabel: roleLabels[user.role],
419 videoQuota,
bc99dfe5
RK
420 videoQuotaUsed,
421 rawVideoQuota: user.videoQuota,
422 rawVideoQuotaUsed: user.videoQuotaUsed,
423 videoQuotaDaily,
424 videoQuotaUsedDaily,
425 rawVideoQuotaDaily: user.videoQuotaDaily,
426 rawVideoQuotaUsedDaily: user.videoQuotaUsedDaily
e724fa93
C
427 })
428 }
629d8d6f 429}