diff options
author | Chocobozzz <me@florianbigard.com> | 2020-01-03 14:17:57 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2020-01-03 14:17:57 +0100 |
commit | ac0868bcc0259d4ff14265d9ae403e10869a13aa (patch) | |
tree | 7a86bc8f4d881360d5b613dc906be1a0d99aa0df | |
parent | 35f28e94c763370616d25d5820f4b9ef70cedca9 (diff) | |
download | PeerTube-ac0868bcc0259d4ff14265d9ae403e10869a13aa.tar.gz PeerTube-ac0868bcc0259d4ff14265d9ae403e10869a13aa.tar.zst PeerTube-ac0868bcc0259d4ff14265d9ae403e10869a13aa.zip |
Improve SQL query for my special playlists
-rw-r--r-- | server/controllers/api/users/me.ts | 7 | ||||
-rw-r--r-- | server/models/account/user.ts | 72 | ||||
-rw-r--r-- | server/tests/api/users/users.ts | 5 | ||||
-rw-r--r-- | server/typings/models/user/user.ts | 10 | ||||
-rw-r--r-- | shared/models/users/user.model.ts | 11 |
5 files changed, 63 insertions, 42 deletions
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index 2f3efe6aa..ac7c62aab 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -126,14 +126,13 @@ async function getUserVideoImports (req: express.Request, res: express.Response) | |||
126 | 126 | ||
127 | async function getUserInformation (req: express.Request, res: express.Response) { | 127 | 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.loadForMeAPI(res.locals.oauth.token.user.username) |
130 | 130 | ||
131 | return res.json(user.toFormattedJSON({ me: true })) | 131 | return res.json(user.toMeFormattedJSON()) |
132 | } | 132 | } |
133 | 133 | ||
134 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { | 134 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { |
135 | // We did not load channels in res.locals.user | 135 | const user = res.locals.oauth.token.user |
136 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | ||
137 | const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) | 136 | const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) |
138 | const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user) | 137 | const videoQuotaUsedDaily = await UserModel.getOriginalVideoFileTotalDailyFromUser(user) |
139 | 138 | ||
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 8bd41de22..27262af42 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -19,14 +19,15 @@ 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, MyUser } from '../../../shared' | 22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoPlaylistType, VideoPrivacy } 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, |
26 | isNoWelcomeModal, | ||
26 | isUserAdminFlagsValid, | 27 | isUserAdminFlagsValid, |
27 | isUserAutoPlayVideoValid, | ||
28 | isUserAutoPlayNextVideoValid, | ||
29 | isUserAutoPlayNextVideoPlaylistValid, | 28 | isUserAutoPlayNextVideoPlaylistValid, |
29 | isUserAutoPlayNextVideoValid, | ||
30 | isUserAutoPlayVideoValid, | ||
30 | isUserBlockedReasonValid, | 31 | isUserBlockedReasonValid, |
31 | isUserBlockedValid, | 32 | isUserBlockedValid, |
32 | isUserEmailVerifiedValid, | 33 | isUserEmailVerifiedValid, |
@@ -38,8 +39,7 @@ import { | |||
38 | isUserVideoQuotaDailyValid, | 39 | isUserVideoQuotaDailyValid, |
39 | isUserVideoQuotaValid, | 40 | isUserVideoQuotaValid, |
40 | isUserVideosHistoryEnabledValid, | 41 | isUserVideosHistoryEnabledValid, |
41 | isUserWebTorrentEnabledValid, | 42 | isUserWebTorrentEnabledValid |
42 | isNoWelcomeModal | ||
43 | } from '../../helpers/custom-validators/users' | 43 | } from '../../helpers/custom-validators/users' |
44 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 44 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
45 | import { OAuthTokenModel } from '../oauth/oauth-token' | 45 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -61,16 +61,17 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | |||
61 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 61 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
62 | import * as Bluebird from 'bluebird' | 62 | import * as Bluebird from 'bluebird' |
63 | import { | 63 | import { |
64 | MMyUserFormattable, | ||
64 | MUserDefault, | 65 | MUserDefault, |
65 | MUserFormattable, | 66 | MUserFormattable, |
66 | MUserId, | 67 | MUserId, |
67 | MUserNotifSettingChannelDefault, | 68 | MUserNotifSettingChannelDefault, |
68 | MUserWithNotificationSetting, MVideoFullLight | 69 | MUserWithNotificationSetting, |
70 | MVideoFullLight | ||
69 | } from '@server/typings/models' | 71 | } from '@server/typings/models' |
70 | 72 | ||
71 | enum ScopeNames { | 73 | enum ScopeNames { |
72 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL', | 74 | FOR_ME_API = 'FOR_ME_API' |
73 | WITH_SPECIAL_PLAYLISTS = 'WITH_SPECIAL_PLAYLISTS' | ||
74 | } | 75 | } |
75 | 76 | ||
76 | @DefaultScope(() => ({ | 77 | @DefaultScope(() => ({ |
@@ -86,28 +87,31 @@ enum ScopeNames { | |||
86 | ] | 87 | ] |
87 | })) | 88 | })) |
88 | @Scopes(() => ({ | 89 | @Scopes(() => ({ |
89 | [ScopeNames.WITH_VIDEO_CHANNEL]: { | 90 | [ScopeNames.FOR_ME_API]: { |
90 | include: [ | 91 | include: [ |
91 | { | 92 | { |
92 | model: AccountModel, | 93 | model: AccountModel, |
93 | required: true, | 94 | include: [ |
94 | include: [ VideoChannelModel ] | 95 | { |
96 | model: VideoChannelModel | ||
97 | }, | ||
98 | { | ||
99 | attributes: [ 'id', 'name', 'type' ], | ||
100 | model: VideoPlaylistModel.unscoped(), | ||
101 | required: true, | ||
102 | where: { | ||
103 | type: { | ||
104 | [ Op.ne ]: VideoPlaylistType.REGULAR | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | ] | ||
95 | }, | 109 | }, |
96 | { | 110 | { |
97 | model: UserNotificationSettingModel, | 111 | model: UserNotificationSettingModel, |
98 | required: true | 112 | required: true |
99 | } | 113 | } |
100 | ] | 114 | ] |
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 | } | ||
111 | } | 115 | } |
112 | })) | 116 | })) |
113 | @Table({ | 117 | @Table({ |
@@ -436,17 +440,14 @@ export class UserModel extends Model<UserModel> { | |||
436 | return UserModel.findOne(query) | 440 | return UserModel.findOne(query) |
437 | } | 441 | } |
438 | 442 | ||
439 | static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> { | 443 | static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> { |
440 | const query = { | 444 | const query = { |
441 | where: { | 445 | where: { |
442 | username: { [ Op.iLike ]: username } | 446 | username: { [ Op.iLike ]: username } |
443 | } | 447 | } |
444 | } | 448 | } |
445 | 449 | ||
446 | return UserModel.scope([ | 450 | return UserModel.scope(ScopeNames.FOR_ME_API).findOne(query) |
447 | ScopeNames.WITH_VIDEO_CHANNEL, | ||
448 | ScopeNames.WITH_SPECIAL_PLAYLISTS | ||
449 | ]).findOne(query) | ||
450 | } | 451 | } |
451 | 452 | ||
452 | static loadByEmail (email: string): Bluebird<MUserDefault> { | 453 | static loadByEmail (email: string): Bluebird<MUserDefault> { |
@@ -625,11 +626,11 @@ export class UserModel extends Model<UserModel> { | |||
625 | return comparePassword(password, this.password) | 626 | return comparePassword(password, this.password) |
626 | } | 627 | } |
627 | 628 | ||
628 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean, me?: boolean } = {}): User | MyUser { | 629 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { |
629 | const videoQuotaUsed = this.get('videoQuotaUsed') | 630 | const videoQuotaUsed = this.get('videoQuotaUsed') |
630 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 631 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
631 | 632 | ||
632 | const json: User | MyUser = { | 633 | const json: User = { |
633 | id: this.id, | 634 | id: this.id, |
634 | username: this.username, | 635 | username: this.username, |
635 | email: this.email, | 636 | email: this.email, |
@@ -690,15 +691,18 @@ export class UserModel extends Model<UserModel> { | |||
690 | }) | 691 | }) |
691 | } | 692 | } |
692 | 693 | ||
693 | if (parameters.me) { | ||
694 | Object.assign(json, { | ||
695 | specialPlaylists: (this.get('specialPlaylists') as Array<number>).map(p => ({ id: p })) | ||
696 | }) | ||
697 | } | ||
698 | |||
699 | return json | 694 | return json |
700 | } | 695 | } |
701 | 696 | ||
697 | toMeFormattedJSON (this: MMyUserFormattable): MyUser { | ||
698 | const formatted = this.toFormattedJSON() | ||
699 | |||
700 | const specialPlaylists = this.Account.VideoPlaylists | ||
701 | .map(p => ({ id: p.id, name: p.name, type: p.type })) | ||
702 | |||
703 | return Object.assign(formatted, { specialPlaylists }) | ||
704 | } | ||
705 | |||
702 | async isAbleToUploadVideo (videoFile: { size: number }) { | 706 | async isAbleToUploadVideo (videoFile: { size: number }) { |
703 | if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true) | 707 | if (this.videoQuota === -1 && this.videoQuotaDaily === -1) return Promise.resolve(true) |
704 | 708 | ||
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 3c3ee3ed7..24203a731 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, MyUser } from '../../../../shared/index' | 5 | import { User, UserRole, Video, MyUser, VideoPlaylistType } 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 & MyUser = res1.body | 254 | const userMe: 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 |
@@ -271,6 +271,7 @@ describe('Test users', function () { | |||
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 | 272 | ||
273 | expect(userMe.specialPlaylists).to.have.lengthOf(1) | 273 | expect(userMe.specialPlaylists).to.have.lengthOf(1) |
274 | expect(userMe.specialPlaylists[0].type).to.equal(VideoPlaylistType.WATCH_LATER) | ||
274 | }) | 275 | }) |
275 | }) | 276 | }) |
276 | 277 | ||
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts index a2750adc7..6ac19c20b 100644 --- a/server/typings/models/user/user.ts +++ b/server/typings/models/user/user.ts | |||
@@ -12,6 +12,7 @@ import { | |||
12 | import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting' | 12 | import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting' |
13 | import { AccountModel } from '@server/models/account/account' | 13 | import { AccountModel } from '@server/models/account/account' |
14 | import { MChannelFormattable } from '../video/video-channels' | 14 | import { MChannelFormattable } from '../video/video-channels' |
15 | import { MVideoPlaylist } from '@server/typings/models' | ||
15 | 16 | ||
16 | type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M> | 17 | type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M> |
17 | 18 | ||
@@ -65,6 +66,13 @@ export type MUserDefault = MUser & | |||
65 | 66 | ||
66 | // Format for API or AP object | 67 | // Format for API or AP object |
67 | 68 | ||
69 | type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]> | ||
70 | type MAccountWithChannelsAndSpecialPlaylists = MAccountWithChannels & | ||
71 | PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]> | ||
72 | |||
68 | export type MUserFormattable = MUserQuotaUsed & | 73 | export type MUserFormattable = MUserQuotaUsed & |
69 | Use<'Account', MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>> & | 74 | Use<'Account', MAccountWithChannels> & |
70 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> | 75 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> |
76 | |||
77 | export type MMyUserFormattable = MUserFormattable & | ||
78 | Use<'Account', MAccountWithChannelsAndSpecialPlaylists> | ||
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index 1434dca81..328b69df6 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -5,6 +5,7 @@ import { UserRole } from './user-role' | |||
5 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | 5 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' |
6 | import { UserNotificationSetting } from './user-notification-setting.model' | 6 | import { UserNotificationSetting } from './user-notification-setting.model' |
7 | import { UserAdminFlag } from './user-flag.model' | 7 | import { UserAdminFlag } from './user-flag.model' |
8 | import { VideoPlaylistType } from '@shared/models' | ||
8 | 9 | ||
9 | export interface User { | 10 | export interface User { |
10 | id: number | 11 | id: number |
@@ -47,6 +48,14 @@ export interface User { | |||
47 | createdAt: Date | 48 | createdAt: Date |
48 | } | 49 | } |
49 | 50 | ||
51 | export interface MyUserSpecialPlaylist { | ||
52 | id: number | ||
53 | name: string | ||
54 | type: VideoPlaylistType | ||
55 | } | ||
56 | |||
50 | export interface MyUser extends User { | 57 | export interface MyUser extends User { |
51 | specialPlaylists: Partial<VideoPlaylist>[] | 58 | specialPlaylists: MyUserSpecialPlaylist[] |
52 | } | 59 | } |
60 | |||
61 | |||