aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/core/auth/auth-user.model.ts2
-rw-r--r--client/src/app/core/auth/auth.service.ts2
-rw-r--r--client/src/app/login/login.component.ts2
-rw-r--r--client/src/app/shared/images/global-icon.component.ts1
-rw-r--r--client/src/app/shared/video-playlist/video-playlist.service.ts1
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.html14
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.scss42
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.ts82
-rw-r--r--client/src/assets/images/global/clock.svg11
-rw-r--r--server/controllers/api/users/me.ts2
-rw-r--r--server/models/account/user.ts31
-rw-r--r--server/tests/api/users/users.ts6
-rw-r--r--shared/models/users/user.model.ts5
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
5import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role' 5import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
6import { User } from '../../shared/users/user.model' 6import { User } from '../../shared/users/user.model'
7import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type' 7import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
8import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
8 9
9export type TokenOptions = { 10export 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'
4import { Injectable } from '@angular/core' 4import { Injectable } from '@angular/core'
5import { Router } from '@angular/router' 5import { Router } from '@angular/router'
6import { Notifier } from '@app/core/notification/notifier.service' 6import { Notifier } from '@app/core/notification/notifier.service'
7import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' 7import { OAuthClientLocal, MyUser as UserServerModel, UserRefreshToken } from '../../../../../shared'
8import { User } from '../../../../../shared/models/users' 8import { User } from '../../../../../shared/models/users'
9import { UserLogin } from '../../../../../shared/models/users/user-login.model' 9import { UserLogin } from '../../../../../shared/models/users/user-login.model'
10import { environment } from '../../../environments/environment' 10import { 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 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'
2import { Video } from './video.model' 2import { Video } from './video.model'
3import { ScreenService } from '@app/shared/misc/screen.service' 3import { ScreenService } from '@app/shared/misc/screen.service'
4import { AuthService, ThemeService } from '@app/core'
5import { VideoPlaylistService } from '../video-playlist/video-playlist.service'
6import { VideoPlaylistType } from '@shared/models'
7import { forkJoin } from 'rxjs'
8import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model'
9import { VideoPlaylist } from '../video-playlist/video-playlist.model'
10import { VideoPlaylistElementCreate } from '../../../../../shared'
11import { 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
134async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { 134async 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'
22import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy } from '../../../shared' 22import { hasUserRight, USER_ROLE_LABELS, UserRight, VideoPrivacy, MyUser } from '../../../shared'
23import { User, UserRole } from '../../../shared/models/users' 23import { User, UserRole } from '../../../shared/models/users'
24import { 24import {
25 isNoInstanceConfigWarningModal, 25 isNoInstanceConfigWarningModal,
@@ -45,6 +45,7 @@ import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
45import { OAuthTokenModel } from '../oauth/oauth-token' 45import { OAuthTokenModel } from '../oauth/oauth-token'
46import { getSort, throwIfNotValid } from '../utils' 46import { getSort, throwIfNotValid } from '../utils'
47import { VideoChannelModel } from '../video/video-channel' 47import { VideoChannelModel } from '../video/video-channel'
48import { VideoPlaylistModel } from '../video/video-playlist'
48import { AccountModel } from './account' 49import { AccountModel } from './account'
49import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' 50import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
50import { values } from 'lodash' 51import { values } from 'lodash'
@@ -68,7 +69,8 @@ import {
68} from '@server/typings/models' 69} from '@server/typings/models'
69 70
70enum ScopeNames { 71enum 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
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { User, UserRole, Video } from '../../../../shared/index' 5import { User, UserRole, Video, MyUser } from '../../../../shared/index'
6import { 6import {
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 @@
1import { Account } from '../actors' 1import { Account } from '../actors'
2import { VideoChannel } from '../videos/channel/video-channel.model' 2import { VideoChannel } from '../videos/channel/video-channel.model'
3import { VideoPlaylist } from '../videos/playlist/video-playlist.model'
3import { UserRole } from './user-role' 4import { UserRole } from './user-role'
4import { NSFWPolicyType } from '../videos/nsfw-policy.type' 5import { NSFWPolicyType } from '../videos/nsfw-policy.type'
5import { UserNotificationSetting } from './user-notification-setting.model' 6import { UserNotificationSetting } from './user-notification-setting.model'
@@ -45,3 +46,7 @@ export interface User {
45 46
46 createdAt: Date 47 createdAt: Date
47} 48}
49
50export interface MyUser extends User {
51 specialPlaylists: Partial<VideoPlaylist>[]
52}