From 67ed6552b831df66713bac9e672738796128d33f Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 23 Jun 2020 14:10:17 +0200 Subject: Reorganize client shared modules --- client/src/app/shared/users/index.ts | 3 - .../src/app/shared/users/user-history.service.ts | 45 --- .../app/shared/users/user-notification.model.ts | 184 ----------- .../app/shared/users/user-notification.service.ts | 86 ----- .../shared/users/user-notifications.component.html | 166 ---------- .../shared/users/user-notifications.component.scss | 53 --- .../shared/users/user-notifications.component.ts | 101 ------ client/src/app/shared/users/user.model.ts | 150 --------- client/src/app/shared/users/user.service.ts | 367 --------------------- 9 files changed, 1155 deletions(-) delete mode 100644 client/src/app/shared/users/index.ts delete mode 100644 client/src/app/shared/users/user-history.service.ts delete mode 100644 client/src/app/shared/users/user-notification.model.ts delete mode 100644 client/src/app/shared/users/user-notification.service.ts delete mode 100644 client/src/app/shared/users/user-notifications.component.html delete mode 100644 client/src/app/shared/users/user-notifications.component.scss delete mode 100644 client/src/app/shared/users/user-notifications.component.ts delete mode 100644 client/src/app/shared/users/user.model.ts delete mode 100644 client/src/app/shared/users/user.service.ts (limited to 'client/src/app/shared/users') diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts deleted file mode 100644 index ebd715fb1..000000000 --- a/client/src/app/shared/users/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './user.model' -export * from './user.service' -export * 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 deleted file mode 100644 index b358cdf20..000000000 --- a/client/src/app/shared/users/user-history.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { HttpClient, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { environment } from '../../../environments/environment' -import { RestExtractor } from '../rest/rest-extractor.service' -import { RestService } from '../rest/rest.service' -import { Video } from '../video/video.model' -import { catchError, map, switchMap } from 'rxjs/operators' -import { ComponentPaginationLight } from '@app/shared/rest/component-pagination.model' -import { VideoService } from '@app/shared/video/video.service' -import { ResultList } from '../../../../../shared' - -@Injectable() -export class UserHistoryService { - static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos' - - constructor ( - private authHttp: HttpClient, - private restExtractor: RestExtractor, - private restService: RestService, - private videoService: VideoService - ) {} - - getUserVideosHistory (historyPagination: ComponentPaginationLight) { - const pagination = this.restService.componentPaginationToRestPagination(historyPagination) - - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination) - - return this.authHttp - .get>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params }) - .pipe( - switchMap(res => this.videoService.extractVideos(res)), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - deleteUserVideosHistory () { - return this.authHttp - .post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {}) - .pipe( - map(() => this.restExtractor.extractDataBool()), - catchError(err => this.restExtractor.handleError(err)) - ) - } -} diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts deleted file mode 100644 index 7b8368d87..000000000 --- a/client/src/app/shared/users/user-notification.model.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { ActorInfo, FollowState, UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, Avatar } from '../../../../../shared' -import { Actor } from '@app/shared/actor/actor.model' - -export class UserNotification implements UserNotificationServer { - id: number - type: UserNotificationType - read: boolean - - video?: VideoInfo & { - channel: ActorInfo & { avatarUrl?: string } - } - - videoImport?: { - id: number - video?: VideoInfo - torrentName?: string - magnetUri?: string - targetUrl?: string - } - - comment?: { - id: number - threadId: number - account: ActorInfo & { avatarUrl?: string } - video: VideoInfo - } - - videoAbuse?: { - id: number - video: VideoInfo - } - - videoBlacklist?: { - id: number - video: VideoInfo - } - - account?: ActorInfo & { avatarUrl?: string } - - actorFollow?: { - id: number - state: FollowState - follower: ActorInfo & { avatarUrl?: string } - following: { - type: 'account' | 'channel' | 'instance' - name: string - displayName: string - host: string - } - } - - createdAt: string - updatedAt: string - - // Additional fields - videoUrl?: string - commentUrl?: any[] - videoAbuseUrl?: string - videoAutoBlacklistUrl?: string - accountUrl?: string - videoImportIdentifier?: string - videoImportUrl?: string - instanceFollowUrl?: string - - constructor (hash: UserNotificationServer) { - this.id = hash.id - this.type = hash.type - this.read = hash.read - - // We assume that some fields exist - // To prevent a notification popup crash in case of bug, wrap it inside a try/catch - try { - this.video = hash.video - if (this.video) this.setAvatarUrl(this.video.channel) - - this.videoImport = hash.videoImport - - this.comment = hash.comment - if (this.comment) this.setAvatarUrl(this.comment.account) - - this.videoAbuse = hash.videoAbuse - - this.videoBlacklist = hash.videoBlacklist - - this.account = hash.account - if (this.account) this.setAvatarUrl(this.account) - - this.actorFollow = hash.actorFollow - if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower) - - this.createdAt = hash.createdAt - this.updatedAt = hash.updatedAt - - switch (this.type) { - case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION: - this.videoUrl = this.buildVideoUrl(this.video) - break - - case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO: - this.videoUrl = this.buildVideoUrl(this.video) - break - - case UserNotificationType.NEW_COMMENT_ON_MY_VIDEO: - case UserNotificationType.COMMENT_MENTION: - if (!this.comment) break - this.accountUrl = this.buildAccountUrl(this.comment.account) - this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ] - break - - case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS: - this.videoAbuseUrl = '/admin/moderation/video-abuses/list' - this.videoUrl = this.buildVideoUrl(this.videoAbuse.video) - break - - case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: - this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' - // Backward compatibility where we did not assign videoBlacklist to this type of notification before - if (!this.videoBlacklist) this.videoBlacklist = { id: null, video: this.video } - - this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) - break - - case UserNotificationType.BLACKLIST_ON_MY_VIDEO: - this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) - break - - case UserNotificationType.MY_VIDEO_PUBLISHED: - this.videoUrl = this.buildVideoUrl(this.video) - break - - case UserNotificationType.MY_VIDEO_IMPORT_SUCCESS: - this.videoImportUrl = this.buildVideoImportUrl() - this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport) - - if (this.videoImport.video) this.videoUrl = this.buildVideoUrl(this.videoImport.video) - break - - case UserNotificationType.MY_VIDEO_IMPORT_ERROR: - this.videoImportUrl = this.buildVideoImportUrl() - this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport) - break - - case UserNotificationType.NEW_USER_REGISTRATION: - this.accountUrl = this.buildAccountUrl(this.account) - break - - case UserNotificationType.NEW_FOLLOW: - this.accountUrl = this.buildAccountUrl(this.actorFollow.follower) - break - - case UserNotificationType.NEW_INSTANCE_FOLLOWER: - this.instanceFollowUrl = '/admin/follows/followers-list' - break - - case UserNotificationType.AUTO_INSTANCE_FOLLOWING: - this.instanceFollowUrl = '/admin/follows/following-list' - break - } - } catch (err) { - this.type = null - console.error(err) - } - } - - private buildVideoUrl (video: { uuid: string }) { - return '/videos/watch/' + video.uuid - } - - private buildAccountUrl (account: { name: string, host: string }) { - return '/accounts/' + Actor.CREATE_BY_STRING(account.name, account.host) - } - - private buildVideoImportUrl () { - return '/my-account/video-imports' - } - - private buildVideoImportIdentifier (videoImport: { targetUrl?: string, magnetUri?: string, torrentName?: string }) { - return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName - } - - private setAvatarUrl (actor: { avatarUrl?: string, avatar?: Avatar }) { - actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor) - } -} diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/users/user-notification.service.ts deleted file mode 100644 index e525a1d58..000000000 --- a/client/src/app/shared/users/user-notification.service.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Injectable } from '@angular/core' -import { HttpClient, HttpParams } from '@angular/common/http' -import { RestExtractor, RestService } from '../rest' -import { catchError, map, tap } from 'rxjs/operators' -import { environment } from '../../../environments/environment' -import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared' -import { UserNotification } from './user-notification.model' -import { AuthService } from '../../core' -import { ComponentPaginationLight } from '../rest/component-pagination.model' -import { User } from '../users/user.model' -import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' - -@Injectable() -export class UserNotificationService { - static BASE_NOTIFICATIONS_URL = environment.apiUrl + '/api/v1/users/me/notifications' - static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings' - - constructor ( - private auth: AuthService, - private authHttp: HttpClient, - private restExtractor: RestExtractor, - private restService: RestService, - private userNotificationSocket: UserNotificationSocket - ) {} - - listMyNotifications (pagination: ComponentPaginationLight, unread?: boolean, ignoreLoadingBar = false) { - let params = new HttpParams() - params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination)) - - if (unread) params = params.append('unread', `${unread}`) - - const headers = ignoreLoadingBar ? { ignoreLoadingBar: '' } : undefined - - return this.authHttp.get>(UserNotificationService.BASE_NOTIFICATIONS_URL, { params, headers }) - .pipe( - map(res => this.restExtractor.convertResultListDateToHuman(res)), - map(res => this.restExtractor.applyToResultListData(res, this.formatNotification.bind(this))), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - countUnreadNotifications () { - return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true) - .pipe(map(n => n.total)) - } - - markAsRead (notification: UserNotification) { - const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read' - - const body = { ids: [ notification.id ] } - const headers = { ignoreLoadingBar: '' } - - return this.authHttp.post(url, body, { headers }) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => this.userNotificationSocket.dispatch('read')), - catchError(res => this.restExtractor.handleError(res)) - ) - } - - markAllAsRead () { - const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read-all' - const headers = { ignoreLoadingBar: '' } - - return this.authHttp.post(url, {}, { headers }) - .pipe( - map(this.restExtractor.extractDataBool), - tap(() => this.userNotificationSocket.dispatch('read-all')), - catchError(res => this.restExtractor.handleError(res)) - ) - } - - updateNotificationSettings (user: User, settings: UserNotificationSetting) { - const url = UserNotificationService.BASE_NOTIFICATION_SETTINGS - - return this.authHttp.put(url, settings) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) - } - - private formatNotification (notification: UserNotificationServer) { - return new UserNotification(notification) - } -} diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html deleted file mode 100644 index 08771110d..000000000 --- a/client/src/app/shared/users/user-notifications.component.html +++ /dev/null @@ -1,166 +0,0 @@ -
You don't have notifications.
- -
-
- - - - - - - - - - -
- {{ notification.video.channel.displayName }} published a new video: {{ notification.video.name }} -
-
- - - - -
- The notification concerns a video now unavailable -
-
-
- - - - -
- Your video {{ notification.video.name }} has been unblocked -
-
- - - - -
- Your video {{ notification.videoBlacklist.video.name }} has been blocked -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- The notification concerns a comment now unavailable -
-
-
- - - - -
- Your video {{ notification.video.name }} has been published -
-
- - - - -
- Your video import {{ notification.videoImportIdentifier }} succeeded -
-
- - - - -
- Your video import {{ notification.videoImportIdentifier }} failed -
-
- - - - -
- User {{ notification.account.name }} registered on your instance -
-
- - - - - - -
- {{ notification.actorFollow.follower.displayName }} is following - - your channel {{ notification.actorFollow.following.displayName }} - your account -
-
- - - - - - - - - - - - -
- Your instance has a new follower ({{ notification.actorFollow?.follower.host }}) - awaiting your approval -
-
- - - - -
- Your instance automatically followed {{ notification.actorFollow.following.host }} -
-
- - - - -
- The notification points to a content now unavailable -
-
-
- -
{{ notification.createdAt | myFromNow }}
-
-
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/users/user-notifications.component.scss deleted file mode 100644 index 5166bd559..000000000 --- a/client/src/app/shared/users/user-notifications.component.scss +++ /dev/null @@ -1,53 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.no-notification { - display: flex; - justify-content: center; - align-items: center; - padding: 20px 0; -} - -.notification { - display: flex; - align-items: center; - font-size: inherit; - padding: 15px 5px 15px 10px; - border-bottom: 1px solid $separator-border-color; - word-break: break-word; - - &.unread { - background-color: rgba(0, 0, 0, 0.05); - } - - my-global-icon { - width: 24px; - margin-right: 11px; - margin-left: 3px; - - @include apply-svg-color(#333); - } - - .avatar { - @include avatar(30px); - - margin-right: 10px; - } - - .message { - flex-grow: 1; - - a { - font-weight: $font-semibold; - } - } - - .from-date { - font-size: 0.85em; - color: pvar(--greyForegroundColor); - padding-left: 5px; - min-width: 70px; - text-align: right; - margin-left: auto; - } -} diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/users/user-notifications.component.ts deleted file mode 100644 index 977dd8925..000000000 --- a/client/src/app/shared/users/user-notifications.component.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' -import { UserNotificationService } from '@app/shared/users/user-notification.service' -import { UserNotificationType } from '../../../../../shared' -import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model' -import { Notifier } from '@app/core' -import { UserNotification } from '@app/shared/users/user-notification.model' -import { Subject } from 'rxjs' - -@Component({ - selector: 'my-user-notifications', - templateUrl: 'user-notifications.component.html', - styleUrls: [ 'user-notifications.component.scss' ] -}) -export class UserNotificationsComponent implements OnInit { - @Input() ignoreLoadingBar = false - @Input() infiniteScroll = true - @Input() itemsPerPage = 20 - @Input() markAllAsReadSubject: Subject - - @Output() notificationsLoaded = new EventEmitter() - - notifications: UserNotification[] = [] - - // So we can access it in the template - UserNotificationType = UserNotificationType - - componentPagination: ComponentPagination - - onDataSubject = new Subject() - - constructor ( - private userNotificationService: UserNotificationService, - private notifier: Notifier - ) { } - - ngOnInit () { - this.componentPagination = { - currentPage: 1, - itemsPerPage: this.itemsPerPage, // Reset items per page, because of the @Input() variable - totalItems: null - } - - this.loadMoreNotifications() - - if (this.markAllAsReadSubject) { - this.markAllAsReadSubject.subscribe(() => this.markAllAsRead()) - } - } - - loadMoreNotifications () { - this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar) - .subscribe( - result => { - this.notifications = this.notifications.concat(result.data) - this.componentPagination.totalItems = result.total - - this.notificationsLoaded.emit() - - this.onDataSubject.next(result.data) - }, - - err => this.notifier.error(err.message) - ) - } - - onNearOfBottom () { - if (this.infiniteScroll === false) return - - this.componentPagination.currentPage++ - - if (hasMoreItems(this.componentPagination)) { - this.loadMoreNotifications() - } - } - - markAsRead (notification: UserNotification) { - if (notification.read) return - - this.userNotificationService.markAsRead(notification) - .subscribe( - () => { - notification.read = true - }, - - err => this.notifier.error(err.message) - ) - } - - markAllAsRead () { - this.userNotificationService.markAllAsRead() - .subscribe( - () => { - for (const notification of this.notifications) { - notification.read = true - } - }, - - err => this.notifier.error(err.message) - ) - } -} diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts deleted file mode 100644 index 3348fe75f..000000000 --- a/client/src/app/shared/users/user.model.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { - hasUserRight, - User as UserServerModel, - UserNotificationSetting, - UserRight, - UserRole -} from '../../../../../shared/models/users' -import { VideoChannel } from '../../../../../shared/models/videos' -import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' -import { Account } from '@app/shared/account/account.model' -import { Avatar } from '../../../../../shared/models/avatars/avatar.model' -import { UserAdminFlag } from '@shared/models/users/user-flag.model' - -export class User implements UserServerModel { - static KEYS = { - ID: 'id', - ROLE: 'role', - EMAIL: 'email', - VIDEOS_HISTORY_ENABLED: 'videos-history-enabled', - USERNAME: 'username', - NSFW_POLICY: 'nsfw_policy', - WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled', - AUTO_PLAY_VIDEO: 'auto_play_video', - SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO: 'auto_play_next_video', - AUTO_PLAY_VIDEO_PLAYLIST: 'auto_play_video_playlist', - THEME: 'last_active_theme', - VIDEO_LANGUAGES: 'video_languages' - } - - id: number - username: string - email: string - pendingEmail: string | null - - emailVerified: boolean - nsfwPolicy: NSFWPolicyType - - adminFlags?: UserAdminFlag - - autoPlayVideo: boolean - autoPlayNextVideo: boolean - autoPlayNextVideoPlaylist: boolean - webTorrentEnabled: boolean - videosHistoryEnabled: boolean - videoLanguages: string[] - - role: UserRole - roleLabel: string - - videoQuota: number - videoQuotaDaily: number - videoQuotaUsed?: number - videoQuotaUsedDaily?: number - videosCount?: number - videoAbusesCount?: number - videoAbusesAcceptedCount?: number - videoAbusesCreatedCount?: number - videoCommentsCount?: number - - theme: string - - account: Account - notificationSettings?: UserNotificationSetting - videoChannels?: VideoChannel[] - - blocked: boolean - blockedReason?: string - - noInstanceConfigWarningModal: boolean - noWelcomeModal: boolean - - pluginAuth: string | null - - lastLoginDate: Date | null - - createdAt: Date - - constructor (hash: Partial) { - this.id = hash.id - this.username = hash.username - this.email = hash.email - - this.role = hash.role - - this.videoChannels = hash.videoChannels - - this.videoQuota = hash.videoQuota - this.videoQuotaDaily = hash.videoQuotaDaily - this.videoQuotaUsed = hash.videoQuotaUsed - this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily - this.videosCount = hash.videosCount - this.videoAbusesCount = hash.videoAbusesCount - this.videoAbusesAcceptedCount = hash.videoAbusesAcceptedCount - this.videoAbusesCreatedCount = hash.videoAbusesCreatedCount - this.videoCommentsCount = hash.videoCommentsCount - - this.nsfwPolicy = hash.nsfwPolicy - this.webTorrentEnabled = hash.webTorrentEnabled - this.autoPlayVideo = hash.autoPlayVideo - this.autoPlayNextVideo = hash.autoPlayNextVideo - this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist - this.videosHistoryEnabled = hash.videosHistoryEnabled - this.videoLanguages = hash.videoLanguages - - this.theme = hash.theme - - this.adminFlags = hash.adminFlags - - this.blocked = hash.blocked - this.blockedReason = hash.blockedReason - - this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal - this.noWelcomeModal = hash.noWelcomeModal - - this.notificationSettings = hash.notificationSettings - - this.createdAt = hash.createdAt - - this.pluginAuth = hash.pluginAuth - this.lastLoginDate = hash.lastLoginDate - - if (hash.account !== undefined) { - this.account = new Account(hash.account) - } - } - - get accountAvatarUrl () { - if (!this.account) return '' - - return this.account.avatarUrl - } - - hasRight (right: UserRight) { - return hasUserRight(this.role, right) - } - - patch (obj: UserServerModel) { - for (const key of Object.keys(obj)) { - this[key] = obj[key] - } - - if (obj.account !== undefined) { - this.account = new Account(obj.account) - } - } - - updateAccountAvatar (newAccountAvatar: Avatar) { - this.account.updateAvatar(newAccountAvatar) - } -} diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts deleted file mode 100644 index de1c8ec94..000000000 --- a/client/src/app/shared/users/user.service.ts +++ /dev/null @@ -1,367 +0,0 @@ -import { has } from 'lodash-es' -import { BytesPipe } from 'ngx-pipes' -import { SortMeta } from 'primeng/api' -import { from, Observable, of } from 'rxjs' -import { catchError, concatMap, first, map, shareReplay, toArray, throttleTime, filter } from 'rxjs/operators' -import { HttpClient, HttpParams } from '@angular/common/http' -import { Injectable } from '@angular/core' -import { AuthService } from '@app/core/auth' -import { I18n } from '@ngx-translate/i18n-polyfill' -import { UserRegister } from '@shared/models/users/user-register.model' -import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' -import { ResultList, User as UserServerModel, UserCreate, UserRole, UserUpdate, UserUpdateMe, UserVideoQuota } from '../../../../../shared' -import { Avatar } from '../../../../../shared/models/avatars/avatar.model' -import { environment } from '../../../environments/environment' -import { LocalStorageService, SessionStorageService } from '../misc/storage.service' -import { RestExtractor, RestPagination, RestService } from '../rest' -import { User } from './user.model' - -@Injectable() -export class UserService { - static BASE_USERS_URL = environment.apiUrl + '/api/v1/users/' - - private bytesPipe = new BytesPipe() - - private userCache: { [ id: number ]: Observable } = {} - - constructor ( - private authHttp: HttpClient, - private authService: AuthService, - private restExtractor: RestExtractor, - private restService: RestService, - private localStorageService: LocalStorageService, - private sessionStorageService: SessionStorageService, - private i18n: I18n - ) { } - - changePassword (currentPassword: string, newPassword: string) { - const url = UserService.BASE_USERS_URL + 'me' - const body: UserUpdateMe = { - currentPassword, - password: newPassword - } - - return this.authHttp.put(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - changeEmail (password: string, newEmail: string) { - const url = UserService.BASE_USERS_URL + 'me' - const body: UserUpdateMe = { - currentPassword: password, - email: newEmail - } - - return this.authHttp.put(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateMyProfile (profile: UserUpdateMe) { - const url = UserService.BASE_USERS_URL + 'me' - - return this.authHttp.put(url, profile) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateMyAnonymousProfile (profile: UserUpdateMe) { - const supportedKeys = { - // local storage keys - nsfwPolicy: (val: NSFWPolicyType) => this.localStorageService.setItem(User.KEYS.NSFW_POLICY, val), - webTorrentEnabled: (val: boolean) => this.localStorageService.setItem(User.KEYS.WEBTORRENT_ENABLED, String(val)), - autoPlayVideo: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO, String(val)), - autoPlayNextVideoPlaylist: (val: boolean) => this.localStorageService.setItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, String(val)), - theme: (val: string) => this.localStorageService.setItem(User.KEYS.THEME, val), - videoLanguages: (val: string[]) => this.localStorageService.setItem(User.KEYS.VIDEO_LANGUAGES, JSON.stringify(val)), - - // session storage keys - autoPlayNextVideo: (val: boolean) => - this.sessionStorageService.setItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO, String(val)) - } - - for (const key of Object.keys(profile)) { - try { - if (has(supportedKeys, key)) supportedKeys[key](profile[key]) - } catch (err) { - console.error(`Cannot set item ${key} in localStorage. Likely due to a value impossible to stringify.`, err) - } - } - } - - listenAnonymousUpdate () { - return this.localStorageService.watch([ - User.KEYS.NSFW_POLICY, - User.KEYS.WEBTORRENT_ENABLED, - User.KEYS.AUTO_PLAY_VIDEO, - User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST, - User.KEYS.THEME, - User.KEYS.VIDEO_LANGUAGES - ]).pipe( - throttleTime(200), - filter(() => this.authService.isLoggedIn() !== true), - map(() => this.getAnonymousUser()) - ) - } - - deleteMe () { - const url = UserService.BASE_USERS_URL + 'me' - - return this.authHttp.delete(url) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - changeAvatar (avatarForm: FormData) { - const url = UserService.BASE_USERS_URL + 'me/avatar/pick' - - return this.authHttp.post<{ avatar: Avatar }>(url, avatarForm) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - signup (userCreate: UserRegister) { - return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getMyVideoQuotaUsed () { - const url = UserService.BASE_USERS_URL + 'me/video-quota-used' - - return this.authHttp.get(url) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - askResetPassword (email: string) { - const url = UserService.BASE_USERS_URL + '/ask-reset-password' - - return this.authHttp.post(url, { email }) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - resetPassword (userId: number, verificationString: string, password: string) { - const url = `${UserService.BASE_USERS_URL}/${userId}/reset-password` - const body = { - verificationString, - password - } - - return this.authHttp.post(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) - } - - verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) { - const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` - const body = { - verificationString, - isPendingEmail - } - - return this.authHttp.post(url, body) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(res => this.restExtractor.handleError(res)) - ) - } - - askSendVerifyEmail (email: string) { - const url = UserService.BASE_USERS_URL + '/ask-send-verify-email' - - return this.authHttp.post(url, { email }) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - autocomplete (search: string): Observable { - const url = UserService.BASE_USERS_URL + 'autocomplete' - const params = new HttpParams().append('search', search) - - return this.authHttp - .get(url, { params }) - .pipe(catchError(res => this.restExtractor.handleError(res))) - } - - getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) { - // Don't update display name, the user seems to have changed it - if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername - - return this.displayNameToUsername(newDisplayName) - } - - displayNameToUsername (displayName: string) { - if (!displayName) return '' - - return displayName - .toLowerCase() - .replace(/\s/g, '_') - .replace(/[^a-z0-9_.]/g, '') - } - - /* ###### Admin methods ###### */ - - addUser (userCreate: UserCreate) { - return this.authHttp.post(UserService.BASE_USERS_URL, userCreate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateUser (userId: number, userUpdate: UserUpdate) { - return this.authHttp.put(UserService.BASE_USERS_URL + userId, userUpdate) - .pipe( - map(this.restExtractor.extractDataBool), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - updateUsers (users: UserServerModel[], userUpdate: UserUpdate) { - return from(users) - .pipe( - concatMap(u => this.authHttp.put(UserService.BASE_USERS_URL + u.id, userUpdate)), - toArray(), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getUserWithCache (userId: number) { - if (!this.userCache[userId]) { - this.userCache[ userId ] = this.getUser(userId).pipe(shareReplay()) - } - - return this.userCache[userId] - } - - getUser (userId: number, withStats = false) { - const params = new HttpParams().append('withStats', withStats + '') - return this.authHttp.get(UserService.BASE_USERS_URL + userId, { params }) - .pipe(catchError(err => this.restExtractor.handleError(err))) - } - - getAnonymousUser () { - let videoLanguages: string[] - - try { - videoLanguages = JSON.parse(this.localStorageService.getItem(User.KEYS.VIDEO_LANGUAGES)) - } catch (err) { - videoLanguages = null - console.error('Cannot parse desired video languages from localStorage.', err) - } - - return new User({ - // local storage keys - nsfwPolicy: this.localStorageService.getItem(User.KEYS.NSFW_POLICY) as NSFWPolicyType, - webTorrentEnabled: this.localStorageService.getItem(User.KEYS.WEBTORRENT_ENABLED) !== 'false', - theme: this.localStorageService.getItem(User.KEYS.THEME) || 'instance-default', - videoLanguages, - - autoPlayNextVideoPlaylist: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO_PLAYLIST) !== 'false', - autoPlayVideo: this.localStorageService.getItem(User.KEYS.AUTO_PLAY_VIDEO) === 'true', - - // session storage keys - autoPlayNextVideo: this.sessionStorageService.getItem(User.KEYS.SESSION_STORAGE_AUTO_PLAY_NEXT_VIDEO) === 'true' - }) - } - - getUsers (pagination: RestPagination, sort: SortMeta, search?: string): Observable> { - let params = new HttpParams() - params = this.restService.addRestGetParams(params, pagination, sort) - - if (search) params = params.append('search', search) - - return this.authHttp.get>(UserService.BASE_USERS_URL, { params }) - .pipe( - map(res => this.restExtractor.convertResultListDateToHuman(res)), - map(res => this.restExtractor.applyToResultListData(res, this.formatUser.bind(this))), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - removeUser (usersArg: UserServerModel | UserServerModel[]) { - const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] - - return from(users) - .pipe( - concatMap(u => this.authHttp.delete(UserService.BASE_USERS_URL + u.id)), - toArray(), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - banUsers (usersArg: UserServerModel | UserServerModel[], reason?: string) { - const body = reason ? { reason } : {} - const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] - - return from(users) - .pipe( - concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/block', body)), - toArray(), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - unbanUsers (usersArg: UserServerModel | UserServerModel[]) { - const users = Array.isArray(usersArg) ? usersArg : [ usersArg ] - - return from(users) - .pipe( - concatMap(u => this.authHttp.post(UserService.BASE_USERS_URL + u.id + '/unblock', {})), - toArray(), - catchError(err => this.restExtractor.handleError(err)) - ) - } - - getAnonymousOrLoggedUser () { - if (!this.authService.isLoggedIn()) { - return of(this.getAnonymousUser()) - } - - return this.authService.userInformationLoaded - .pipe( - first(), - map(() => this.authService.getUser()) - ) - } - - private formatUser (user: UserServerModel) { - let videoQuota - if (user.videoQuota === -1) { - videoQuota = this.i18n('Unlimited') - } else { - videoQuota = this.bytesPipe.transform(user.videoQuota, 0) - } - - const videoQuotaUsed = this.bytesPipe.transform(user.videoQuotaUsed, 0) - - const roleLabels: { [ id in UserRole ]: string } = { - [UserRole.USER]: this.i18n('User'), - [UserRole.ADMINISTRATOR]: this.i18n('Administrator'), - [UserRole.MODERATOR]: this.i18n('Moderator') - } - - return Object.assign(user, { - roleLabel: roleLabels[user.role], - videoQuota, - videoQuotaUsed - }) - } -} -- cgit v1.2.3