diff options
author | Rigel Kent <sendmemail@rigelk.eu> | 2020-01-02 13:07:18 +0100 |
---|---|---|
committer | Rigel Kent <sendmemail@rigelk.eu> | 2020-01-02 14:50:14 +0100 |
commit | 29128b2f5ce00093ad81b4b72daae0e3444fd5a8 (patch) | |
tree | f1a90ead86c16892255e2c661da3eed5f302a260 | |
parent | cca1e13b96799377f19bcc95110fbf76ff741e20 (diff) | |
download | PeerTube-29128b2f5ce00093ad81b4b72daae0e3444fd5a8.tar.gz PeerTube-29128b2f5ce00093ad81b4b72daae0e3444fd5a8.tar.zst PeerTube-29128b2f5ce00093ad81b4b72daae0e3444fd5a8.zip |
Add miniature quick actions to add video to Watch later playlist
-rw-r--r-- | client/src/app/core/auth/auth-user.model.ts | 2 | ||||
-rw-r--r-- | client/src/app/core/auth/auth.service.ts | 2 | ||||
-rw-r--r-- | client/src/app/login/login.component.ts | 2 | ||||
-rw-r--r-- | client/src/app/shared/images/global-icon.component.ts | 1 | ||||
-rw-r--r-- | client/src/app/shared/video-playlist/video-playlist.service.ts | 1 | ||||
-rw-r--r-- | client/src/app/shared/video/video-thumbnail.component.html | 14 | ||||
-rw-r--r-- | client/src/app/shared/video/video-thumbnail.component.scss | 42 | ||||
-rw-r--r-- | client/src/app/shared/video/video-thumbnail.component.ts | 82 | ||||
-rw-r--r-- | client/src/assets/images/global/clock.svg | 11 | ||||
-rw-r--r-- | server/controllers/api/users/me.ts | 2 | ||||
-rw-r--r-- | server/models/account/user.ts | 31 | ||||
-rw-r--r-- | server/tests/api/users/users.ts | 6 | ||||
-rw-r--r-- | shared/models/users/user.model.ts | 5 |
13 files changed, 184 insertions, 17 deletions
diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index d371a923f..55a5a6dde 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts | |||
@@ -5,6 +5,7 @@ import { User as ServerUserModel } from '../../../../../shared/models/users/user | |||
5 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' | 5 | import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' |
6 | import { User } from '../../shared/users/user.model' | 6 | import { User } from '../../shared/users/user.model' |
7 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' | 7 | import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' |
8 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
8 | 9 | ||
9 | export type TokenOptions = { | 10 | export type TokenOptions = { |
10 | accessToken: string | 11 | accessToken: string |
@@ -79,6 +80,7 @@ export class AuthUser extends User { | |||
79 | } | 80 | } |
80 | 81 | ||
81 | tokens: Tokens | 82 | tokens: Tokens |
83 | specialPlaylists: Partial<VideoPlaylist>[] | ||
82 | 84 | ||
83 | static load () { | 85 | static load () { |
84 | const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) | 86 | const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) |
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index d601cadf5..9ae008e39 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -4,7 +4,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' | |||
4 | import { Injectable } from '@angular/core' | 4 | import { Injectable } from '@angular/core' |
5 | import { Router } from '@angular/router' | 5 | import { Router } from '@angular/router' |
6 | import { Notifier } from '@app/core/notification/notifier.service' | 6 | import { Notifier } from '@app/core/notification/notifier.service' |
7 | import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' | 7 | import { OAuthClientLocal, MyUser as UserServerModel, UserRefreshToken } from '../../../../../shared' |
8 | import { User } from '../../../../../shared/models/users' | 8 | import { User } from '../../../../../shared/models/users' |
9 | import { UserLogin } from '../../../../../shared/models/users/user-login.model' | 9 | import { UserLogin } from '../../../../../shared/models/users/user-login.model' |
10 | import { environment } from '../../../environments/environment' | 10 | import { environment } from '../../../environments/environment' |
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index cf923492a..ffadc9aa4 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts | |||
@@ -28,13 +28,11 @@ export class LoginComponent extends FormReactive implements OnInit { | |||
28 | 28 | ||
29 | constructor ( | 29 | constructor ( |
30 | protected formValidatorService: FormValidatorService, | 30 | protected formValidatorService: FormValidatorService, |
31 | private router: Router, | ||
32 | private route: ActivatedRoute, | 31 | private route: ActivatedRoute, |
33 | private modalService: NgbModal, | 32 | private modalService: NgbModal, |
34 | private loginValidatorsService: LoginValidatorsService, | 33 | private loginValidatorsService: LoginValidatorsService, |
35 | private authService: AuthService, | 34 | private authService: AuthService, |
36 | private userService: UserService, | 35 | private userService: UserService, |
37 | private serverService: ServerService, | ||
38 | private redirectService: RedirectService, | 36 | private redirectService: RedirectService, |
39 | private notifier: Notifier, | 37 | private notifier: Notifier, |
40 | private i18n: I18n | 38 | private i18n: I18n |
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts index 8a4965926..17186cff4 100644 --- a/client/src/app/shared/images/global-icon.component.ts +++ b/client/src/app/shared/images/global-icon.component.ts | |||
@@ -10,6 +10,7 @@ const icons = { | |||
10 | 'sparkle': require('!!raw-loader?!../../../assets/images/global/sparkle.svg'), | 10 | 'sparkle': require('!!raw-loader?!../../../assets/images/global/sparkle.svg'), |
11 | 'alert': require('!!raw-loader?!../../../assets/images/global/alert.svg'), | 11 | 'alert': require('!!raw-loader?!../../../assets/images/global/alert.svg'), |
12 | 'cloud-error': require('!!raw-loader?!../../../assets/images/global/cloud-error.svg'), | 12 | 'cloud-error': require('!!raw-loader?!../../../assets/images/global/cloud-error.svg'), |
13 | 'clock': require('!!raw-loader?!../../../assets/images/global/clock.svg'), | ||
13 | 'user-add': require('!!raw-loader?!../../../assets/images/global/user-add.svg'), | 14 | 'user-add': require('!!raw-loader?!../../../assets/images/global/user-add.svg'), |
14 | 'no': require('!!raw-loader?!../../../assets/images/global/no.svg'), | 15 | 'no': require('!!raw-loader?!../../../assets/images/global/no.svg'), |
15 | 'cloud-download': require('!!raw-loader?!../../../assets/images/global/cloud-download.svg'), | 16 | 'cloud-download': require('!!raw-loader?!../../../assets/images/global/cloud-download.svg'), |
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts index 5f74dcd4c..fc3b77b2a 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts | |||
@@ -30,6 +30,7 @@ export class VideoPlaylistService { | |||
30 | // Use a replay subject because we "next" a value before subscribing | 30 | // Use a replay subject because we "next" a value before subscribing |
31 | private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) | 31 | private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) |
32 | private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> | 32 | private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> |
33 | private cachedWatchLaterPlaylists: VideoPlaylist[] | ||
33 | 34 | ||
34 | constructor ( | 35 | constructor ( |
35 | private authHttp: HttpClient, | 36 | private authHttp: HttpClient, |
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html index b302ebd0f..df15698c0 100644 --- a/client/src/app/shared/video/video-thumbnail.component.html +++ b/client/src/app/shared/video/video-thumbnail.component.html | |||
@@ -1,9 +1,23 @@ | |||
1 | <a | 1 | <a |
2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name" | 2 | [routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name" |
3 | class="video-thumbnail" | 3 | class="video-thumbnail" |
4 | (mouseenter)="load()" | ||
4 | > | 5 | > |
5 | <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> | 6 | <img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" /> |
6 | 7 | ||
8 | <div *ngIf="isUserLoggedIn()" class="video-thumbnail-actions-overlay"> | ||
9 | <ng-container *ngIf="addedToWatchLater !== true"> | ||
10 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="addToWatchLater();$event.stopPropagation();false"> | ||
11 | <my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon> | ||
12 | </div> | ||
13 | </ng-container> | ||
14 | <ng-container *ngIf="addedToWatchLater === true"> | ||
15 | <div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="removeFromWatchLater();$event.stopPropagation();false"> | ||
16 | <my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon> | ||
17 | </div> | ||
18 | </ng-container> | ||
19 | </div> | ||
20 | |||
7 | <div class="video-thumbnail-duration-overlay">{{ video.durationLabel }}</div> | 21 | <div class="video-thumbnail-duration-overlay">{{ video.durationLabel }}</div> |
8 | 22 | ||
9 | <div class="play-overlay"> | 23 | <div class="play-overlay"> |
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss index e48629778..aac50fd1b 100644 --- a/client/src/app/shared/video/video-thumbnail.component.scss +++ b/client/src/app/shared/video/video-thumbnail.component.scss | |||
@@ -18,16 +18,50 @@ | |||
18 | } | 18 | } |
19 | } | 19 | } |
20 | 20 | ||
21 | .video-thumbnail-watch-later-overlay, | ||
21 | .video-thumbnail-duration-overlay { | 22 | .video-thumbnail-duration-overlay { |
22 | @include static-thumbnail-overlay; | 23 | @include static-thumbnail-overlay; |
23 | 24 | ||
24 | position: absolute; | ||
25 | right: 5px; | ||
26 | bottom: 5px; | ||
27 | padding: 0 5px; | ||
28 | border-radius: 3px; | 25 | border-radius: 3px; |
29 | font-size: 12px; | 26 | font-size: 12px; |
30 | font-weight: $font-bold; | 27 | font-weight: $font-bold; |
31 | z-index: 1; | 28 | z-index: 1; |
32 | } | 29 | } |
30 | |||
31 | .video-thumbnail-duration-overlay { | ||
32 | position: absolute; | ||
33 | padding: 0 5px; | ||
34 | right: 5px; | ||
35 | bottom: 5px; | ||
36 | } | ||
37 | |||
38 | &:hover { | ||
39 | .video-thumbnail-actions-overlay { | ||
40 | opacity: 1; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | .video-thumbnail-actions-overlay { | ||
45 | position: absolute; | ||
46 | display: flex; | ||
47 | flex-direction: column; | ||
48 | right: 5px; | ||
49 | top: 5px; | ||
50 | opacity: 0; | ||
51 | |||
52 | div:not(:first-child) { | ||
53 | margin-top: 2px; | ||
54 | } | ||
55 | |||
56 | .video-thumbnail-watch-later-overlay { | ||
57 | padding: 3px; | ||
58 | |||
59 | my-global-icon { | ||
60 | width: 22px; | ||
61 | height: 22px; | ||
62 | |||
63 | @include apply-svg-color(#fff); | ||
64 | } | ||
65 | } | ||
66 | } | ||
33 | } | 67 | } |
diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts index fe65ade94..0f605e425 100644 --- a/client/src/app/shared/video/video-thumbnail.component.ts +++ b/client/src/app/shared/video/video-thumbnail.component.ts | |||
@@ -1,6 +1,14 @@ | |||
1 | import { Component, Input } from '@angular/core' | 1 | import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core' |
2 | import { Video } from './video.model' | 2 | import { Video } from './video.model' |
3 | import { ScreenService } from '@app/shared/misc/screen.service' | 3 | import { ScreenService } from '@app/shared/misc/screen.service' |
4 | import { AuthService, ThemeService } from '@app/core' | ||
5 | import { VideoPlaylistService } from '../video-playlist/video-playlist.service' | ||
6 | import { VideoPlaylistType } from '@shared/models' | ||
7 | import { forkJoin } from 'rxjs' | ||
8 | import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' | ||
9 | import { VideoPlaylist } from '../video-playlist/video-playlist.model' | ||
10 | import { VideoPlaylistElementCreate } from '../../../../../shared' | ||
11 | import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' | ||
4 | 12 | ||
5 | @Component({ | 13 | @Component({ |
6 | selector: 'my-video-thumbnail', | 14 | selector: 'my-video-thumbnail', |
@@ -13,7 +21,44 @@ export class VideoThumbnailComponent { | |||
13 | @Input() routerLink: any[] | 21 | @Input() routerLink: any[] |
14 | @Input() queryParams: any[] | 22 | @Input() queryParams: any[] |
15 | 23 | ||
16 | constructor (private screenService: ScreenService) { | 24 | addToWatchLaterText = 'Add to watch later' |
25 | addedToWatchLaterText = 'Added to watch later' | ||
26 | addedToWatchLater: boolean | ||
27 | |||
28 | watchLaterPlaylist: any | ||
29 | |||
30 | constructor ( | ||
31 | private screenService: ScreenService, | ||
32 | private authService: AuthService, | ||
33 | private videoPlaylistService: VideoPlaylistService, | ||
34 | private cd: ChangeDetectorRef | ||
35 | ) {} | ||
36 | |||
37 | load () { | ||
38 | if (this.addedToWatchLater !== undefined) return | ||
39 | |||
40 | this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id) | ||
41 | .subscribe( | ||
42 | existResult => { | ||
43 | for (const playlist of this.authService.getUser().specialPlaylists) { | ||
44 | const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id) | ||
45 | this.addedToWatchLater = !!existingPlaylist | ||
46 | |||
47 | if (existingPlaylist) { | ||
48 | this.watchLaterPlaylist = { | ||
49 | playlistId: existingPlaylist.playlistId, | ||
50 | playlistElementId: existingPlaylist.playlistElementId | ||
51 | } | ||
52 | } else { | ||
53 | this.watchLaterPlaylist = { | ||
54 | playlistId: playlist.id | ||
55 | } | ||
56 | } | ||
57 | |||
58 | this.cd.markForCheck() | ||
59 | } | ||
60 | } | ||
61 | ) | ||
17 | } | 62 | } |
18 | 63 | ||
19 | getImageUrl () { | 64 | getImageUrl () { |
@@ -39,4 +84,37 @@ export class VideoThumbnailComponent { | |||
39 | 84 | ||
40 | return [ '/videos/watch', this.video.uuid ] | 85 | return [ '/videos/watch', this.video.uuid ] |
41 | } | 86 | } |
87 | |||
88 | isUserLoggedIn () { | ||
89 | return this.authService.isLoggedIn() | ||
90 | } | ||
91 | |||
92 | addToWatchLater () { | ||
93 | if (this.addedToWatchLater === undefined) return | ||
94 | this.addedToWatchLater = true | ||
95 | |||
96 | this.videoPlaylistService.addVideoInPlaylist( | ||
97 | this.watchLaterPlaylist.playlistId, | ||
98 | { videoId: this.video.id } as VideoPlaylistElementCreate | ||
99 | ).subscribe( | ||
100 | res => { | ||
101 | this.addedToWatchLater = true | ||
102 | this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id | ||
103 | } | ||
104 | ) | ||
105 | } | ||
106 | |||
107 | removeFromWatchLater () { | ||
108 | if (this.addedToWatchLater === undefined) return | ||
109 | this.addedToWatchLater = false | ||
110 | |||
111 | this.videoPlaylistService.removeVideoFromPlaylist( | ||
112 | this.watchLaterPlaylist.playlistId, | ||
113 | this.watchLaterPlaylist.playlistElementId | ||
114 | ).subscribe( | ||
115 | _ => { | ||
116 | this.addedToWatchLater = false | ||
117 | } | ||
118 | ) | ||
119 | } | ||
42 | } | 120 | } |
diff --git a/client/src/assets/images/global/clock.svg b/client/src/assets/images/global/clock.svg new file mode 100644 index 000000000..f2d4f0397 --- /dev/null +++ b/client/src/assets/images/global/clock.svg | |||
@@ -0,0 +1,11 @@ | |||
1 | <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | ||
2 | <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> | ||
3 | <g id="Artboard-4" transform="translate(-488.000000, -159.000000)" stroke="#000000" stroke-width="2"> | ||
4 | <g id="31" transform="translate(488.000000, 159.000000)"> | ||
5 | <path d="M12,21 C7.02943725,21 3,16.9705627 3,12 C3,7.02943725 7.02943725,3 12,3 C16.9705627,3 21,7.02943725 21,12 C21,16.9705627 16.9705627,21 12,21 Z" id="Base"/> | ||
6 | <path d="M12,12 L16,12" id="Path-18" stroke-linecap="round"/> | ||
7 | <path d="M12,12 L12,7" id="Path-40" stroke-linecap="round" stroke-linejoin="round"/> | ||
8 | </g> | ||
9 | </g> | ||
10 | </g> | ||
11 | </svg> \ No newline at end of file | ||
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index b1f29f252..2f3efe6aa 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -128,7 +128,7 @@ async function getUserInformation (req: express.Request, res: express.Response) | |||
128 | // We did not load channels in res.locals.user | 128 | // We did not load channels in res.locals.user |
129 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 129 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
130 | 130 | ||
131 | return res.json(user.toFormattedJSON()) | 131 | return res.json(user.toFormattedJSON({ me: true })) |
132 | } | 132 | } |
133 | 133 | ||
134 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { | 134 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 3a339b5c3..8bd41de22 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -19,7 +19,7 @@ import { | |||
19 | Table, | 19 | Table, |
20 | UpdatedAt | 20 | UpdatedAt |
21 | } from 'sequelize-typescript' | 21 | } from 'sequelize-typescript' |
22 | import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy } from '../../../shared' | 22 | import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy, MyUser } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | 25 | isNoInstanceConfigWarningModal, |
@@ -45,6 +45,7 @@ import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | |||
45 | import { OAuthTokenModel } from '../oauth/oauth-token' | 45 | import { OAuthTokenModel } from '../oauth/oauth-token' |
46 | import { getSort, throwIfNotValid } from '../utils' | 46 | import { getSort, throwIfNotValid } from '../utils' |
47 | import { VideoChannelModel } from '../video/video-channel' | 47 | import { VideoChannelModel } from '../video/video-channel' |
48 | import { VideoPlaylistModel } from '../video/video-playlist' | ||
48 | import { AccountModel } from './account' | 49 | import { AccountModel } from './account' |
49 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 50 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
50 | import { values } from 'lodash' | 51 | import { values } from 'lodash' |
@@ -68,7 +69,8 @@ import { | |||
68 | } from '@server/typings/models' | 69 | } from '@server/typings/models' |
69 | 70 | ||
70 | enum ScopeNames { | 71 | enum ScopeNames { |
71 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 72 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL', |
73 | WITH_SPECIAL_PLAYLISTS = 'WITH_SPECIAL_PLAYLISTS' | ||
72 | } | 74 | } |
73 | 75 | ||
74 | @DefaultScope(() => ({ | 76 | @DefaultScope(() => ({ |
@@ -96,6 +98,16 @@ enum ScopeNames { | |||
96 | required: true | 98 | required: true |
97 | } | 99 | } |
98 | ] | 100 | ] |
101 | }, | ||
102 | [ScopeNames.WITH_SPECIAL_PLAYLISTS]: { | ||
103 | attributes: { | ||
104 | include: [ | ||
105 | [ | ||
106 | literal('(select array(select "id" from "videoPlaylist" where "ownerAccountId" in (select id from public.account where "userId" = "UserModel"."id") and name LIKE \'Watch later\'))'), | ||
107 | 'specialPlaylists' | ||
108 | ] | ||
109 | ] | ||
110 | } | ||
99 | } | 111 | } |
100 | })) | 112 | })) |
101 | @Table({ | 113 | @Table({ |
@@ -431,7 +443,10 @@ export class UserModel extends Model<UserModel> { | |||
431 | } | 443 | } |
432 | } | 444 | } |
433 | 445 | ||
434 | return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) | 446 | return UserModel.scope([ |
447 | ScopeNames.WITH_VIDEO_CHANNEL, | ||
448 | ScopeNames.WITH_SPECIAL_PLAYLISTS | ||
449 | ]).findOne(query) | ||
435 | } | 450 | } |
436 | 451 | ||
437 | static loadByEmail (email: string): Bluebird<MUserDefault> { | 452 | static loadByEmail (email: string): Bluebird<MUserDefault> { |
@@ -610,11 +625,11 @@ export class UserModel extends Model<UserModel> { | |||
610 | return comparePassword(password, this.password) | 625 | return comparePassword(password, this.password) |
611 | } | 626 | } |
612 | 627 | ||
613 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { | 628 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean, me?: boolean } = {}): User | MyUser { |
614 | const videoQuotaUsed = this.get('videoQuotaUsed') | 629 | const videoQuotaUsed = this.get('videoQuotaUsed') |
615 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 630 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
616 | 631 | ||
617 | const json: User = { | 632 | const json: User | MyUser = { |
618 | id: this.id, | 633 | id: this.id, |
619 | username: this.username, | 634 | username: this.username, |
620 | email: this.email, | 635 | email: this.email, |
@@ -675,6 +690,12 @@ export class UserModel extends Model<UserModel> { | |||
675 | }) | 690 | }) |
676 | } | 691 | } |
677 | 692 | ||
693 | if (parameters.me) { | ||
694 | Object.assign(json, { | ||
695 | specialPlaylists: (this.get('specialPlaylists') as Array<number>).map(p => ({ id: p })) | ||
696 | }) | ||
697 | } | ||
698 | |||
678 | return json | 699 | return json |
679 | } | 700 | } |
680 | 701 | ||
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 07b7fc747..3c3ee3ed7 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, UserRole, Video } from '../../../../shared/index' | 5 | import { User, UserRole, Video, MyUser } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | blockUser, | 7 | blockUser, |
8 | cleanupTests, | 8 | cleanupTests, |
@@ -251,7 +251,7 @@ describe('Test users', function () { | |||
251 | 251 | ||
252 | it('Should be able to get user information', async function () { | 252 | it('Should be able to get user information', async function () { |
253 | const res1 = await getMyUserInformation(server.url, accessTokenUser) | 253 | const res1 = await getMyUserInformation(server.url, accessTokenUser) |
254 | const userMe: User = res1.body | 254 | const userMe: User & MyUser = res1.body |
255 | 255 | ||
256 | const res2 = await getUserInformation(server.url, server.accessToken, userMe.id) | 256 | const res2 = await getUserInformation(server.url, server.accessToken, userMe.id) |
257 | const userGet: User = res2.body | 257 | const userGet: User = res2.body |
@@ -269,6 +269,8 @@ describe('Test users', function () { | |||
269 | 269 | ||
270 | expect(userMe.adminFlags).to.be.undefined | 270 | expect(userMe.adminFlags).to.be.undefined |
271 | expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST) | 271 | expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST) |
272 | |||
273 | expect(userMe.specialPlaylists).to.have.lengthOf(1) | ||
272 | }) | 274 | }) |
273 | }) | 275 | }) |
274 | 276 | ||
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index 90d59ac56..1434dca81 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { Account } from '../actors' | 1 | import { Account } from '../actors' |
2 | import { VideoChannel } from '../videos/channel/video-channel.model' | 2 | import { VideoChannel } from '../videos/channel/video-channel.model' |
3 | import { VideoPlaylist } from '../videos/playlist/video-playlist.model' | ||
3 | import { UserRole } from './user-role' | 4 | import { UserRole } from './user-role' |
4 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | 5 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' |
5 | import { UserNotificationSetting } from './user-notification-setting.model' | 6 | import { UserNotificationSetting } from './user-notification-setting.model' |
@@ -45,3 +46,7 @@ export interface User { | |||
45 | 46 | ||
46 | createdAt: Date | 47 | createdAt: Date |
47 | } | 48 | } |
49 | |||
50 | export interface MyUser extends User { | ||
51 | specialPlaylists: Partial<VideoPlaylist>[] | ||
52 | } | ||