diff options
author | kontrollanten <6680299+kontrollanten@users.noreply.github.com> | 2022-02-28 08:34:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-28 08:34:43 +0100 |
commit | d0800f7661f13fabe7bb6f4aa0ea50764f106405 (patch) | |
tree | d43e6b0b6f4a5a32e03487e6464edbcaf288be2a /server/models/video | |
parent | 5cad2ca9db9b9d138f8a33058d10b94a9fd50c69 (diff) | |
download | PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.gz PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.zst PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.zip |
Implement avatar miniatures (#4639)
* client: remove unused file
* refactor(client/my-actor-avatar): size from input
Read size from component input instead of scss, to make it possible to
use smaller avatar images when implemented.
* implement avatar miniatures
close #4560
* fix(test): max file size
* fix(search-index): normalize res acc to avatarMini
* refactor avatars to an array
* client/search: resize channel avatar to 120
* refactor(client/videos): remove unused function
* client(actor-avatar): set default size
* fix tests and avatars full result
When findOne is used only an array containting one avatar is returned.
* update migration version and version notations
* server/search: harmonize normalizing
* Cleanup avatar miniature PR
Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/models/video')
16 files changed, 341 insertions, 211 deletions
diff --git a/server/models/video/sql/video/index.ts b/server/models/video/sql/video/index.ts new file mode 100644 index 000000000..e9132d5e1 --- /dev/null +++ b/server/models/video/sql/video/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './video-model-get-query-builder' | ||
2 | export * from './videos-id-list-query-builder' | ||
3 | export * from './videos-model-list-query-builder' | ||
diff --git a/server/models/video/sql/shared/abstract-run-query.ts b/server/models/video/sql/video/shared/abstract-run-query.ts index 8e7a7642d..8e7a7642d 100644 --- a/server/models/video/sql/shared/abstract-run-query.ts +++ b/server/models/video/sql/video/shared/abstract-run-query.ts | |||
diff --git a/server/models/video/sql/shared/abstract-video-query-builder.ts b/server/models/video/sql/video/shared/abstract-video-query-builder.ts index a6afb04e4..490e5e6e0 100644 --- a/server/models/video/sql/shared/abstract-video-query-builder.ts +++ b/server/models/video/sql/video/shared/abstract-video-query-builder.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { createSafeIn } from '@server/models/utils' | 1 | import { createSafeIn } from '@server/models/utils' |
2 | import { MUserAccountId } from '@server/types/models' | 2 | import { MUserAccountId } from '@server/types/models' |
3 | import { ActorImageType } from '@shared/models' | ||
3 | import validator from 'validator' | 4 | import validator from 'validator' |
4 | import { AbstractRunQuery } from './abstract-run-query' | 5 | import { AbstractRunQuery } from './abstract-run-query' |
5 | import { VideoTableAttributes } from './video-table-attributes' | 6 | import { VideoTableAttributes } from './video-table-attributes' |
@@ -42,8 +43,9 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery { | |||
42 | ) | 43 | ) |
43 | 44 | ||
44 | this.addJoin( | 45 | this.addJoin( |
45 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Actor->Avatar" ' + | 46 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Actor->Avatars" ' + |
46 | 'ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"' | 47 | 'ON "VideoChannel->Actor"."id" = "VideoChannel->Actor->Avatars"."actorId" ' + |
48 | `AND "VideoChannel->Actor->Avatars"."type" = ${ActorImageType.AVATAR}` | ||
47 | ) | 49 | ) |
48 | 50 | ||
49 | this.attributes = { | 51 | this.attributes = { |
@@ -51,7 +53,7 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery { | |||
51 | 53 | ||
52 | ...this.buildAttributesObject('VideoChannel', this.tables.getChannelAttributes()), | 54 | ...this.buildAttributesObject('VideoChannel', this.tables.getChannelAttributes()), |
53 | ...this.buildActorInclude('VideoChannel->Actor'), | 55 | ...this.buildActorInclude('VideoChannel->Actor'), |
54 | ...this.buildAvatarInclude('VideoChannel->Actor->Avatar'), | 56 | ...this.buildAvatarInclude('VideoChannel->Actor->Avatars'), |
55 | ...this.buildServerInclude('VideoChannel->Actor->Server') | 57 | ...this.buildServerInclude('VideoChannel->Actor->Server') |
56 | } | 58 | } |
57 | } | 59 | } |
@@ -68,8 +70,9 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery { | |||
68 | ) | 70 | ) |
69 | 71 | ||
70 | this.addJoin( | 72 | this.addJoin( |
71 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Account->Actor->Avatar" ' + | 73 | 'LEFT OUTER JOIN "actorImage" AS "VideoChannel->Account->Actor->Avatars" ' + |
72 | 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"' | 74 | 'ON "VideoChannel->Account"."actorId"= "VideoChannel->Account->Actor->Avatars"."actorId" ' + |
75 | `AND "VideoChannel->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR}` | ||
73 | ) | 76 | ) |
74 | 77 | ||
75 | this.attributes = { | 78 | this.attributes = { |
@@ -77,7 +80,7 @@ export class AbstractVideoQueryBuilder extends AbstractRunQuery { | |||
77 | 80 | ||
78 | ...this.buildAttributesObject('VideoChannel->Account', this.tables.getAccountAttributes()), | 81 | ...this.buildAttributesObject('VideoChannel->Account', this.tables.getAccountAttributes()), |
79 | ...this.buildActorInclude('VideoChannel->Account->Actor'), | 82 | ...this.buildActorInclude('VideoChannel->Account->Actor'), |
80 | ...this.buildAvatarInclude('VideoChannel->Account->Actor->Avatar'), | 83 | ...this.buildAvatarInclude('VideoChannel->Account->Actor->Avatars'), |
81 | ...this.buildServerInclude('VideoChannel->Account->Actor->Server') | 84 | ...this.buildServerInclude('VideoChannel->Account->Actor->Server') |
82 | } | 85 | } |
83 | } | 86 | } |
diff --git a/server/models/video/sql/shared/video-file-query-builder.ts b/server/models/video/sql/video/shared/video-file-query-builder.ts index 3eb3dc07d..3eb3dc07d 100644 --- a/server/models/video/sql/shared/video-file-query-builder.ts +++ b/server/models/video/sql/video/shared/video-file-query-builder.ts | |||
diff --git a/server/models/video/sql/shared/video-model-builder.ts b/server/models/video/sql/video/shared/video-model-builder.ts index 7751d8e68..b1b47b721 100644 --- a/server/models/video/sql/shared/video-model-builder.ts +++ b/server/models/video/sql/video/shared/video-model-builder.ts | |||
@@ -9,15 +9,15 @@ import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | |||
9 | import { TrackerModel } from '@server/models/server/tracker' | 9 | import { TrackerModel } from '@server/models/server/tracker' |
10 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' | 10 | import { UserVideoHistoryModel } from '@server/models/user/user-video-history' |
11 | import { VideoInclude } from '@shared/models' | 11 | import { VideoInclude } from '@shared/models' |
12 | import { ScheduleVideoUpdateModel } from '../../schedule-video-update' | 12 | import { ScheduleVideoUpdateModel } from '../../../schedule-video-update' |
13 | import { TagModel } from '../../tag' | 13 | import { TagModel } from '../../../tag' |
14 | import { ThumbnailModel } from '../../thumbnail' | 14 | import { ThumbnailModel } from '../../../thumbnail' |
15 | import { VideoModel } from '../../video' | 15 | import { VideoModel } from '../../../video' |
16 | import { VideoBlacklistModel } from '../../video-blacklist' | 16 | import { VideoBlacklistModel } from '../../../video-blacklist' |
17 | import { VideoChannelModel } from '../../video-channel' | 17 | import { VideoChannelModel } from '../../../video-channel' |
18 | import { VideoFileModel } from '../../video-file' | 18 | import { VideoFileModel } from '../../../video-file' |
19 | import { VideoLiveModel } from '../../video-live' | 19 | import { VideoLiveModel } from '../../../video-live' |
20 | import { VideoStreamingPlaylistModel } from '../../video-streaming-playlist' | 20 | import { VideoStreamingPlaylistModel } from '../../../video-streaming-playlist' |
21 | import { VideoTableAttributes } from './video-table-attributes' | 21 | import { VideoTableAttributes } from './video-table-attributes' |
22 | 22 | ||
23 | type SQLRow = { [id: string]: string | number } | 23 | type SQLRow = { [id: string]: string | number } |
@@ -34,6 +34,7 @@ export class VideoModelBuilder { | |||
34 | private videoFileMemo: { [ id: number ]: VideoFileModel } | 34 | private videoFileMemo: { [ id: number ]: VideoFileModel } |
35 | 35 | ||
36 | private thumbnailsDone: Set<any> | 36 | private thumbnailsDone: Set<any> |
37 | private actorImagesDone: Set<any> | ||
37 | private historyDone: Set<any> | 38 | private historyDone: Set<any> |
38 | private blacklistDone: Set<any> | 39 | private blacklistDone: Set<any> |
39 | private accountBlocklistDone: Set<any> | 40 | private accountBlocklistDone: Set<any> |
@@ -69,11 +70,21 @@ export class VideoModelBuilder { | |||
69 | for (const row of rows) { | 70 | for (const row of rows) { |
70 | this.buildVideoAndAccount(row) | 71 | this.buildVideoAndAccount(row) |
71 | 72 | ||
72 | const videoModel = this.videosMemo[row.id] | 73 | const videoModel = this.videosMemo[row.id as number] |
73 | 74 | ||
74 | this.setUserHistory(row, videoModel) | 75 | this.setUserHistory(row, videoModel) |
75 | this.addThumbnail(row, videoModel) | 76 | this.addThumbnail(row, videoModel) |
76 | 77 | ||
78 | const channelActor = videoModel.VideoChannel?.Actor | ||
79 | if (channelActor) { | ||
80 | this.addActorAvatar(row, 'VideoChannel.Actor', channelActor) | ||
81 | } | ||
82 | |||
83 | const accountActor = videoModel.VideoChannel?.Account?.Actor | ||
84 | if (accountActor) { | ||
85 | this.addActorAvatar(row, 'VideoChannel.Account.Actor', accountActor) | ||
86 | } | ||
87 | |||
77 | if (!rowsWebTorrentFiles) { | 88 | if (!rowsWebTorrentFiles) { |
78 | this.addWebTorrentFile(row, videoModel) | 89 | this.addWebTorrentFile(row, videoModel) |
79 | } | 90 | } |
@@ -113,6 +124,7 @@ export class VideoModelBuilder { | |||
113 | this.videoFileMemo = {} | 124 | this.videoFileMemo = {} |
114 | 125 | ||
115 | this.thumbnailsDone = new Set() | 126 | this.thumbnailsDone = new Set() |
127 | this.actorImagesDone = new Set() | ||
116 | this.historyDone = new Set() | 128 | this.historyDone = new Set() |
117 | this.blacklistDone = new Set() | 129 | this.blacklistDone = new Set() |
118 | this.liveDone = new Set() | 130 | this.liveDone = new Set() |
@@ -195,13 +207,8 @@ export class VideoModelBuilder { | |||
195 | 207 | ||
196 | private buildActor (row: SQLRow, prefix: string) { | 208 | private buildActor (row: SQLRow, prefix: string) { |
197 | const actorPrefix = `${prefix}.Actor` | 209 | const actorPrefix = `${prefix}.Actor` |
198 | const avatarPrefix = `${actorPrefix}.Avatar` | ||
199 | const serverPrefix = `${actorPrefix}.Server` | 210 | const serverPrefix = `${actorPrefix}.Server` |
200 | 211 | ||
201 | const avatarModel = row[`${avatarPrefix}.id`] !== null | ||
202 | ? new ActorImageModel(this.grab(row, this.tables.getAvatarAttributes(), avatarPrefix), this.buildOpts) | ||
203 | : null | ||
204 | |||
205 | const serverModel = row[`${serverPrefix}.id`] !== null | 212 | const serverModel = row[`${serverPrefix}.id`] !== null |
206 | ? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts) | 213 | ? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts) |
207 | : null | 214 | : null |
@@ -209,8 +216,8 @@ export class VideoModelBuilder { | |||
209 | if (serverModel) serverModel.BlockedBy = [] | 216 | if (serverModel) serverModel.BlockedBy = [] |
210 | 217 | ||
211 | const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts) | 218 | const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts) |
212 | actorModel.Avatar = avatarModel | ||
213 | actorModel.Server = serverModel | 219 | actorModel.Server = serverModel |
220 | actorModel.Avatars = [] | ||
214 | 221 | ||
215 | return actorModel | 222 | return actorModel |
216 | } | 223 | } |
@@ -226,6 +233,18 @@ export class VideoModelBuilder { | |||
226 | this.historyDone.add(id) | 233 | this.historyDone.add(id) |
227 | } | 234 | } |
228 | 235 | ||
236 | private addActorAvatar (row: SQLRow, actorPrefix: string, actor: ActorModel) { | ||
237 | const avatarPrefix = `${actorPrefix}.Avatar` | ||
238 | const id = row[`${avatarPrefix}.id`] | ||
239 | if (!id || this.actorImagesDone.has(id)) return | ||
240 | |||
241 | const attributes = this.grab(row, this.tables.getAvatarAttributes(), avatarPrefix) | ||
242 | const avatarModel = new ActorImageModel(attributes, this.buildOpts) | ||
243 | actor.Avatars.push(avatarModel) | ||
244 | |||
245 | this.actorImagesDone.add(id) | ||
246 | } | ||
247 | |||
229 | private addThumbnail (row: SQLRow, videoModel: VideoModel) { | 248 | private addThumbnail (row: SQLRow, videoModel: VideoModel) { |
230 | const id = row['Thumbnails.id'] | 249 | const id = row['Thumbnails.id'] |
231 | if (!id || this.thumbnailsDone.has(id)) return | 250 | if (!id || this.thumbnailsDone.has(id)) return |
diff --git a/server/models/video/sql/shared/video-table-attributes.ts b/server/models/video/sql/video/shared/video-table-attributes.ts index 8a8d2073a..df2ed3fb0 100644 --- a/server/models/video/sql/shared/video-table-attributes.ts +++ b/server/models/video/sql/video/shared/video-table-attributes.ts | |||
@@ -186,8 +186,7 @@ export class VideoTableAttributes { | |||
186 | 'id', | 186 | 'id', |
187 | 'preferredUsername', | 187 | 'preferredUsername', |
188 | 'url', | 188 | 'url', |
189 | 'serverId', | 189 | 'serverId' |
190 | 'avatarId' | ||
191 | ] | 190 | ] |
192 | 191 | ||
193 | if (this.mode === 'get') { | 192 | if (this.mode === 'get') { |
@@ -212,6 +211,7 @@ export class VideoTableAttributes { | |||
212 | getAvatarAttributes () { | 211 | getAvatarAttributes () { |
213 | let attributeKeys = [ | 212 | let attributeKeys = [ |
214 | 'id', | 213 | 'id', |
214 | 'width', | ||
215 | 'filename', | 215 | 'filename', |
216 | 'type', | 216 | 'type', |
217 | 'fileUrl', | 217 | 'fileUrl', |
diff --git a/server/models/video/sql/video-model-get-query-builder.ts b/server/models/video/sql/video/video-model-get-query-builder.ts index a65c96097..a65c96097 100644 --- a/server/models/video/sql/video-model-get-query-builder.ts +++ b/server/models/video/sql/video/video-model-get-query-builder.ts | |||
diff --git a/server/models/video/sql/videos-id-list-query-builder.ts b/server/models/video/sql/video/videos-id-list-query-builder.ts index 098e15359..098e15359 100644 --- a/server/models/video/sql/videos-id-list-query-builder.ts +++ b/server/models/video/sql/video/videos-id-list-query-builder.ts | |||
diff --git a/server/models/video/sql/videos-model-list-query-builder.ts b/server/models/video/sql/video/videos-model-list-query-builder.ts index b15b29ec3..b15b29ec3 100644 --- a/server/models/video/sql/videos-model-list-query-builder.ts +++ b/server/models/video/sql/video/videos-model-list-query-builder.ts | |||
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 2c6669bcb..410fd6d3f 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -31,6 +31,7 @@ import { | |||
31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
32 | import { sendDeleteActor } from '../../lib/activitypub/send' | 32 | import { sendDeleteActor } from '../../lib/activitypub/send' |
33 | import { | 33 | import { |
34 | MChannel, | ||
34 | MChannelActor, | 35 | MChannelActor, |
35 | MChannelAP, | 36 | MChannelAP, |
36 | MChannelBannerAccountDefault, | 37 | MChannelBannerAccountDefault, |
@@ -62,6 +63,7 @@ type AvailableForListOptions = { | |||
62 | search?: string | 63 | search?: string |
63 | host?: string | 64 | host?: string |
64 | handles?: string[] | 65 | handles?: string[] |
66 | forCount?: boolean | ||
65 | } | 67 | } |
66 | 68 | ||
67 | type AvailableWithStatsOptions = { | 69 | type AvailableWithStatsOptions = { |
@@ -116,70 +118,91 @@ export type SummaryOptions = { | |||
116 | }) | 118 | }) |
117 | } | 119 | } |
118 | 120 | ||
119 | let rootWhere: WhereOptions | 121 | if (Array.isArray(options.handles) && options.handles.length !== 0) { |
120 | if (options.handles) { | 122 | const or: string[] = [] |
121 | const or: WhereOptions[] = [] | ||
122 | 123 | ||
123 | for (const handle of options.handles || []) { | 124 | for (const handle of options.handles || []) { |
124 | const [ preferredUsername, host ] = handle.split('@') | 125 | const [ preferredUsername, host ] = handle.split('@') |
125 | 126 | ||
126 | if (!host || host === WEBSERVER.HOST) { | 127 | if (!host || host === WEBSERVER.HOST) { |
127 | or.push({ | 128 | or.push(`("preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} AND "serverId" IS NULL)`) |
128 | '$Actor.preferredUsername$': preferredUsername, | ||
129 | '$Actor.serverId$': null | ||
130 | }) | ||
131 | } else { | 129 | } else { |
132 | or.push({ | 130 | or.push( |
133 | '$Actor.preferredUsername$': preferredUsername, | 131 | `(` + |
134 | '$Actor.Server.host$': host | 132 | `"preferredUsername" = ${VideoChannelModel.sequelize.escape(preferredUsername)} ` + |
135 | }) | 133 | `AND "host" = ${VideoChannelModel.sequelize.escape(host)}` + |
134 | `)` | ||
135 | ) | ||
136 | } | 136 | } |
137 | } | 137 | } |
138 | 138 | ||
139 | rootWhere = { | 139 | whereActorAnd.push({ |
140 | [Op.or]: or | 140 | id: { |
141 | } | 141 | [Op.in]: literal(`(SELECT "actor".id FROM actor LEFT JOIN server on server.id = actor."serverId" WHERE ${or.join(' OR ')})`) |
142 | } | ||
143 | }) | ||
144 | } | ||
145 | |||
146 | const channelInclude: Includeable[] = [] | ||
147 | const accountInclude: Includeable[] = [] | ||
148 | |||
149 | if (options.forCount !== true) { | ||
150 | accountInclude.push({ | ||
151 | model: ServerModel, | ||
152 | required: false | ||
153 | }) | ||
154 | |||
155 | accountInclude.push({ | ||
156 | model: ActorImageModel, | ||
157 | as: 'Avatars', | ||
158 | required: false | ||
159 | }) | ||
160 | |||
161 | channelInclude.push({ | ||
162 | model: ActorImageModel, | ||
163 | as: 'Avatars', | ||
164 | required: false | ||
165 | }) | ||
166 | |||
167 | channelInclude.push({ | ||
168 | model: ActorImageModel, | ||
169 | as: 'Banners', | ||
170 | required: false | ||
171 | }) | ||
172 | } | ||
173 | |||
174 | if (options.forCount !== true || serverRequired) { | ||
175 | channelInclude.push({ | ||
176 | model: ServerModel, | ||
177 | duplicating: false, | ||
178 | required: serverRequired, | ||
179 | where: whereServer | ||
180 | }) | ||
142 | } | 181 | } |
143 | 182 | ||
144 | return { | 183 | return { |
145 | where: rootWhere, | ||
146 | include: [ | 184 | include: [ |
147 | { | 185 | { |
148 | attributes: { | 186 | attributes: { |
149 | exclude: unusedActorAttributesForAPI | 187 | exclude: unusedActorAttributesForAPI |
150 | }, | 188 | }, |
151 | model: ActorModel, | 189 | model: ActorModel.unscoped(), |
152 | where: { | 190 | where: { |
153 | [Op.and]: whereActorAnd | 191 | [Op.and]: whereActorAnd |
154 | }, | 192 | }, |
155 | include: [ | 193 | include: channelInclude |
156 | { | ||
157 | model: ServerModel, | ||
158 | required: serverRequired, | ||
159 | where: whereServer | ||
160 | }, | ||
161 | { | ||
162 | model: ActorImageModel, | ||
163 | as: 'Avatar', | ||
164 | required: false | ||
165 | }, | ||
166 | { | ||
167 | model: ActorImageModel, | ||
168 | as: 'Banner', | ||
169 | required: false | ||
170 | } | ||
171 | ] | ||
172 | }, | 194 | }, |
173 | { | 195 | { |
174 | model: AccountModel, | 196 | model: AccountModel.unscoped(), |
175 | required: true, | 197 | required: true, |
176 | include: [ | 198 | include: [ |
177 | { | 199 | { |
178 | attributes: { | 200 | attributes: { |
179 | exclude: unusedActorAttributesForAPI | 201 | exclude: unusedActorAttributesForAPI |
180 | }, | 202 | }, |
181 | model: ActorModel, // Default scope includes avatar and server | 203 | model: ActorModel.unscoped(), |
182 | required: true | 204 | required: true, |
205 | include: accountInclude | ||
183 | } | 206 | } |
184 | ] | 207 | ] |
185 | } | 208 | } |
@@ -189,7 +212,7 @@ export type SummaryOptions = { | |||
189 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { | 212 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { |
190 | const include: Includeable[] = [ | 213 | const include: Includeable[] = [ |
191 | { | 214 | { |
192 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | 215 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId' ], |
193 | model: ActorModel.unscoped(), | 216 | model: ActorModel.unscoped(), |
194 | required: options.actorRequired ?? true, | 217 | required: options.actorRequired ?? true, |
195 | include: [ | 218 | include: [ |
@@ -199,8 +222,8 @@ export type SummaryOptions = { | |||
199 | required: false | 222 | required: false |
200 | }, | 223 | }, |
201 | { | 224 | { |
202 | model: ActorImageModel.unscoped(), | 225 | model: ActorImageModel, |
203 | as: 'Avatar', | 226 | as: 'Avatars', |
204 | required: false | 227 | required: false |
205 | } | 228 | } |
206 | ] | 229 | ] |
@@ -245,7 +268,7 @@ export type SummaryOptions = { | |||
245 | { | 268 | { |
246 | model: ActorImageModel, | 269 | model: ActorImageModel, |
247 | required: false, | 270 | required: false, |
248 | as: 'Banner' | 271 | as: 'Banners' |
249 | } | 272 | } |
250 | ] | 273 | ] |
251 | } | 274 | } |
@@ -474,14 +497,14 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
474 | order: getSort(parameters.sort) | 497 | order: getSort(parameters.sort) |
475 | } | 498 | } |
476 | 499 | ||
477 | return VideoChannelModel | 500 | const getScope = (forCount: boolean) => { |
478 | .scope({ | 501 | return { method: [ ScopeNames.FOR_API, { actorId, forCount } as AvailableForListOptions ] } |
479 | method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ] | 502 | } |
480 | }) | 503 | |
481 | .findAndCountAll(query) | 504 | return Promise.all([ |
482 | .then(({ rows, count }) => { | 505 | VideoChannelModel.scope(getScope(true)).count(), |
483 | return { total: count, data: rows } | 506 | VideoChannelModel.scope(getScope(false)).findAll(query) |
484 | }) | 507 | ]).then(([ total, data ]) => ({ total, data })) |
485 | } | 508 | } |
486 | 509 | ||
487 | static searchForApi (options: Pick<AvailableForListOptions, 'actorId' | 'search' | 'host' | 'handles'> & { | 510 | static searchForApi (options: Pick<AvailableForListOptions, 'actorId' | 'search' | 'host' | 'handles'> & { |
@@ -519,14 +542,22 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
519 | where | 542 | where |
520 | } | 543 | } |
521 | 544 | ||
522 | return VideoChannelModel | 545 | const getScope = (forCount: boolean) => { |
523 | .scope({ | 546 | return { |
524 | method: [ ScopeNames.FOR_API, pick(options, [ 'actorId', 'host', 'handles' ]) as AvailableForListOptions ] | 547 | method: [ |
525 | }) | 548 | ScopeNames.FOR_API, { |
526 | .findAndCountAll(query) | 549 | ...pick(options, [ 'actorId', 'host', 'handles' ]), |
527 | .then(({ rows, count }) => { | 550 | |
528 | return { total: count, data: rows } | 551 | forCount |
529 | }) | 552 | } as AvailableForListOptions |
553 | ] | ||
554 | } | ||
555 | } | ||
556 | |||
557 | return Promise.all([ | ||
558 | VideoChannelModel.scope(getScope(true)).count(query), | ||
559 | VideoChannelModel.scope(getScope(false)).findAll(query) | ||
560 | ]).then(([ total, data ]) => ({ total, data })) | ||
530 | } | 561 | } |
531 | 562 | ||
532 | static listByAccountForAPI (options: { | 563 | static listByAccountForAPI (options: { |
@@ -552,20 +583,26 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
552 | } | 583 | } |
553 | : null | 584 | : null |
554 | 585 | ||
555 | const query = { | 586 | const getQuery = (forCount: boolean) => { |
556 | offset: options.start, | 587 | const accountModel = forCount |
557 | limit: options.count, | 588 | ? AccountModel.unscoped() |
558 | order: getSort(options.sort), | 589 | : AccountModel |
559 | include: [ | 590 | |
560 | { | 591 | return { |
561 | model: AccountModel, | 592 | offset: options.start, |
562 | where: { | 593 | limit: options.count, |
563 | id: options.accountId | 594 | order: getSort(options.sort), |
564 | }, | 595 | include: [ |
565 | required: true | 596 | { |
566 | } | 597 | model: accountModel, |
567 | ], | 598 | where: { |
568 | where | 599 | id: options.accountId |
600 | }, | ||
601 | required: true | ||
602 | } | ||
603 | ], | ||
604 | where | ||
605 | } | ||
569 | } | 606 | } |
570 | 607 | ||
571 | const scopes: string | ScopeOptions | (string | ScopeOptions)[] = [ ScopeNames.WITH_ACTOR_BANNER ] | 608 | const scopes: string | ScopeOptions | (string | ScopeOptions)[] = [ ScopeNames.WITH_ACTOR_BANNER ] |
@@ -576,21 +613,19 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
576 | }) | 613 | }) |
577 | } | 614 | } |
578 | 615 | ||
579 | return VideoChannelModel | 616 | return Promise.all([ |
580 | .scope(scopes) | 617 | VideoChannelModel.scope(scopes).count(getQuery(true)), |
581 | .findAndCountAll(query) | 618 | VideoChannelModel.scope(scopes).findAll(getQuery(false)) |
582 | .then(({ rows, count }) => { | 619 | ]).then(([ total, data ]) => ({ total, data })) |
583 | return { total: count, data: rows } | ||
584 | }) | ||
585 | } | 620 | } |
586 | 621 | ||
587 | static listAllByAccount (accountId: number) { | 622 | static listAllByAccount (accountId: number): Promise<MChannel[]> { |
588 | const query = { | 623 | const query = { |
589 | limit: CONFIG.VIDEO_CHANNELS.MAX_PER_USER, | 624 | limit: CONFIG.VIDEO_CHANNELS.MAX_PER_USER, |
590 | include: [ | 625 | include: [ |
591 | { | 626 | { |
592 | attributes: [], | 627 | attributes: [], |
593 | model: AccountModel, | 628 | model: AccountModel.unscoped(), |
594 | where: { | 629 | where: { |
595 | id: accountId | 630 | id: accountId |
596 | }, | 631 | }, |
@@ -621,7 +656,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
621 | { | 656 | { |
622 | model: ActorImageModel, | 657 | model: ActorImageModel, |
623 | required: false, | 658 | required: false, |
624 | as: 'Banner' | 659 | as: 'Banners' |
625 | } | 660 | } |
626 | ] | 661 | ] |
627 | } | 662 | } |
@@ -655,7 +690,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
655 | { | 690 | { |
656 | model: ActorImageModel, | 691 | model: ActorImageModel, |
657 | required: false, | 692 | required: false, |
658 | as: 'Banner' | 693 | as: 'Banners' |
659 | } | 694 | } |
660 | ] | 695 | ] |
661 | } | 696 | } |
@@ -685,7 +720,7 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
685 | { | 720 | { |
686 | model: ActorImageModel, | 721 | model: ActorImageModel, |
687 | required: false, | 722 | required: false, |
688 | as: 'Banner' | 723 | as: 'Banners' |
689 | } | 724 | } |
690 | ] | 725 | ] |
691 | } | 726 | } |
@@ -706,6 +741,9 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
706 | displayName: this.getDisplayName(), | 741 | displayName: this.getDisplayName(), |
707 | url: actor.url, | 742 | url: actor.url, |
708 | host: actor.host, | 743 | host: actor.host, |
744 | avatars: actor.avatars, | ||
745 | |||
746 | // TODO: remove, deprecated in 4.2 | ||
709 | avatar: actor.avatar | 747 | avatar: actor.avatar |
710 | } | 748 | } |
711 | } | 749 | } |
@@ -736,9 +774,16 @@ ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"` | |||
736 | support: this.support, | 774 | support: this.support, |
737 | isLocal: this.Actor.isOwned(), | 775 | isLocal: this.Actor.isOwned(), |
738 | updatedAt: this.updatedAt, | 776 | updatedAt: this.updatedAt, |
777 | |||
739 | ownerAccount: undefined, | 778 | ownerAccount: undefined, |
779 | |||
740 | videosCount, | 780 | videosCount, |
741 | viewsPerDay | 781 | viewsPerDay, |
782 | |||
783 | avatars: actor.avatars, | ||
784 | |||
785 | // TODO: remove, deprecated in 4.2 | ||
786 | avatar: actor.avatar | ||
742 | } | 787 | } |
743 | 788 | ||
744 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() | 789 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index fa77455bc..2d60c6a30 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { uniq } from 'lodash' | 1 | import { uniq } from 'lodash' |
2 | import { FindAndCountOptions, FindOptions, Op, Order, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' | 2 | import { FindOptions, Op, Order, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
3 | import { | 3 | import { |
4 | AllowNull, | 4 | AllowNull, |
5 | BelongsTo, | 5 | BelongsTo, |
@@ -16,8 +16,8 @@ import { | |||
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { getServerActor } from '@server/models/application/application' | 17 | import { getServerActor } from '@server/models/application/application' |
18 | import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' | 18 | import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' |
19 | import { AttributesOnly } from '@shared/typescript-utils' | ||
20 | import { VideoPrivacy } from '@shared/models' | 19 | import { VideoPrivacy } from '@shared/models' |
20 | import { AttributesOnly } from '@shared/typescript-utils' | ||
21 | import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' | 21 | import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' |
22 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 22 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
23 | import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/comment/video-comment.model' | 23 | import { VideoComment, VideoCommentAdmin } from '../../../shared/models/videos/comment/video-comment.model' |
@@ -363,40 +363,43 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment | |||
363 | Object.assign(whereVideo, searchAttribute(searchVideo, 'name')) | 363 | Object.assign(whereVideo, searchAttribute(searchVideo, 'name')) |
364 | } | 364 | } |
365 | 365 | ||
366 | const query: FindAndCountOptions = { | 366 | const getQuery = (forCount: boolean) => { |
367 | offset: start, | 367 | return { |
368 | limit: count, | 368 | offset: start, |
369 | order: getCommentSort(sort), | 369 | limit: count, |
370 | where, | 370 | order: getCommentSort(sort), |
371 | include: [ | 371 | where, |
372 | { | 372 | include: [ |
373 | model: AccountModel.unscoped(), | 373 | { |
374 | required: true, | 374 | model: AccountModel.unscoped(), |
375 | where: whereAccount, | 375 | required: true, |
376 | include: [ | 376 | where: whereAccount, |
377 | { | 377 | include: [ |
378 | attributes: { | 378 | { |
379 | exclude: unusedActorAttributesForAPI | 379 | attributes: { |
380 | }, | 380 | exclude: unusedActorAttributesForAPI |
381 | model: ActorModel, // Default scope includes avatar and server | 381 | }, |
382 | required: true, | 382 | model: forCount === true |
383 | where: whereActor | 383 | ? ActorModel.unscoped() // Default scope includes avatar and server |
384 | } | 384 | : ActorModel, |
385 | ] | 385 | required: true, |
386 | }, | 386 | where: whereActor |
387 | { | 387 | } |
388 | model: VideoModel.unscoped(), | 388 | ] |
389 | required: true, | 389 | }, |
390 | where: whereVideo | 390 | { |
391 | } | 391 | model: VideoModel.unscoped(), |
392 | ] | 392 | required: true, |
393 | where: whereVideo | ||
394 | } | ||
395 | ] | ||
396 | } | ||
393 | } | 397 | } |
394 | 398 | ||
395 | return VideoCommentModel | 399 | return Promise.all([ |
396 | .findAndCountAll(query) | 400 | VideoCommentModel.count(getQuery(true)), |
397 | .then(({ rows, count }) => { | 401 | VideoCommentModel.findAll(getQuery(false)) |
398 | return { total: count, data: rows } | 402 | ]).then(([ total, data ]) => ({ total, data })) |
399 | }) | ||
400 | } | 403 | } |
401 | 404 | ||
402 | static async listThreadsForApi (parameters: { | 405 | static async listThreadsForApi (parameters: { |
@@ -443,14 +446,20 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment | |||
443 | } | 446 | } |
444 | } | 447 | } |
445 | 448 | ||
446 | const scopesList: (string | ScopeOptions)[] = [ | 449 | const findScopesList: (string | ScopeOptions)[] = [ |
447 | ScopeNames.WITH_ACCOUNT_FOR_API, | 450 | ScopeNames.WITH_ACCOUNT_FOR_API, |
448 | { | 451 | { |
449 | method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] | 452 | method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] |
450 | } | 453 | } |
451 | ] | 454 | ] |
452 | 455 | ||
453 | const queryCount = { | 456 | const countScopesList: ScopeOptions[] = [ |
457 | { | ||
458 | method: [ ScopeNames.ATTRIBUTES_FOR_API, blockerAccountIds ] | ||
459 | } | ||
460 | ] | ||
461 | |||
462 | const notDeletedQueryCount = { | ||
454 | where: { | 463 | where: { |
455 | videoId, | 464 | videoId, |
456 | deletedAt: null, | 465 | deletedAt: null, |
@@ -459,9 +468,10 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment | |||
459 | } | 468 | } |
460 | 469 | ||
461 | return Promise.all([ | 470 | return Promise.all([ |
462 | VideoCommentModel.scope(scopesList).findAndCountAll(queryList), | 471 | VideoCommentModel.scope(findScopesList).findAll(queryList), |
463 | VideoCommentModel.count(queryCount) | 472 | VideoCommentModel.scope(countScopesList).count(queryList), |
464 | ]).then(([ { rows, count }, totalNotDeletedComments ]) => { | 473 | VideoCommentModel.count(notDeletedQueryCount) |
474 | ]).then(([ rows, count, totalNotDeletedComments ]) => { | ||
465 | return { total: count, data: rows, totalNotDeletedComments } | 475 | return { total: count, data: rows, totalNotDeletedComments } |
466 | }) | 476 | }) |
467 | } | 477 | } |
@@ -512,11 +522,10 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment | |||
512 | } | 522 | } |
513 | ] | 523 | ] |
514 | 524 | ||
515 | return VideoCommentModel.scope(scopes) | 525 | return Promise.all([ |
516 | .findAndCountAll(query) | 526 | VideoCommentModel.count(query), |
517 | .then(({ rows, count }) => { | 527 | VideoCommentModel.scope(scopes).findAll(query) |
518 | return { total: count, data: rows } | 528 | ]).then(([ total, data ]) => ({ total, data })) |
519 | }) | ||
520 | } | 529 | } |
521 | 530 | ||
522 | static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Promise<MCommentOwner[]> { | 531 | static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Promise<MCommentOwner[]> { |
@@ -565,7 +574,10 @@ export class VideoCommentModel extends Model<Partial<AttributesOnly<VideoComment | |||
565 | transaction: t | 574 | transaction: t |
566 | } | 575 | } |
567 | 576 | ||
568 | return VideoCommentModel.findAndCountAll<MComment>(query) | 577 | return Promise.all([ |
578 | VideoCommentModel.count(query), | ||
579 | VideoCommentModel.findAll<MComment>(query) | ||
580 | ]).then(([ total, data ]) => ({ total, data })) | ||
569 | } | 581 | } |
570 | 582 | ||
571 | static async listForFeed (parameters: { | 583 | static async listForFeed (parameters: { |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 5d2b230e8..1d8296060 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -155,13 +155,10 @@ export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportMo | |||
155 | where | 155 | where |
156 | } | 156 | } |
157 | 157 | ||
158 | return VideoImportModel.findAndCountAll<MVideoImportDefault>(query) | 158 | return Promise.all([ |
159 | .then(({ rows, count }) => { | 159 | VideoImportModel.unscoped().count(query), |
160 | return { | 160 | VideoImportModel.findAll<MVideoImportDefault>(query) |
161 | data: rows, | 161 | ]).then(([ total, data ]) => ({ total, data })) |
162 | total: count | ||
163 | } | ||
164 | }) | ||
165 | } | 162 | } |
166 | 163 | ||
167 | getTargetIdentifier () { | 164 | getTargetIdentifier () { |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index e20e32f8b..4e4160818 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -23,6 +23,7 @@ import { | |||
23 | MVideoPlaylistElementVideoUrlPlaylistPrivacy, | 23 | MVideoPlaylistElementVideoUrlPlaylistPrivacy, |
24 | MVideoPlaylistVideoThumbnail | 24 | MVideoPlaylistVideoThumbnail |
25 | } from '@server/types/models/video/video-playlist-element' | 25 | } from '@server/types/models/video/video-playlist-element' |
26 | import { AttributesOnly } from '@shared/typescript-utils' | ||
26 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | 27 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' |
27 | import { VideoPrivacy } from '../../../shared/models/videos' | 28 | import { VideoPrivacy } from '../../../shared/models/videos' |
28 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' | 29 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' |
@@ -32,7 +33,6 @@ import { AccountModel } from '../account/account' | |||
32 | import { getSort, throwIfNotValid } from '../utils' | 33 | import { getSort, throwIfNotValid } from '../utils' |
33 | import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' | 34 | import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' |
34 | import { VideoPlaylistModel } from './video-playlist' | 35 | import { VideoPlaylistModel } from './video-playlist' |
35 | import { AttributesOnly } from '@shared/typescript-utils' | ||
36 | 36 | ||
37 | @Table({ | 37 | @Table({ |
38 | tableName: 'videoPlaylistElement', | 38 | tableName: 'videoPlaylistElement', |
@@ -208,22 +208,28 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide | |||
208 | } | 208 | } |
209 | 209 | ||
210 | static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number, t?: Transaction) { | 210 | static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number, t?: Transaction) { |
211 | const query = { | 211 | const getQuery = (forCount: boolean) => { |
212 | attributes: [ 'url' ], | 212 | return { |
213 | offset: start, | 213 | attributes: forCount |
214 | limit: count, | 214 | ? [] |
215 | order: getSort('position'), | 215 | : [ 'url' ], |
216 | where: { | 216 | offset: start, |
217 | videoPlaylistId | 217 | limit: count, |
218 | }, | 218 | order: getSort('position'), |
219 | transaction: t | 219 | where: { |
220 | videoPlaylistId | ||
221 | }, | ||
222 | transaction: t | ||
223 | } | ||
220 | } | 224 | } |
221 | 225 | ||
222 | return VideoPlaylistElementModel | 226 | return Promise.all([ |
223 | .findAndCountAll(query) | 227 | VideoPlaylistElementModel.count(getQuery(true)), |
224 | .then(({ rows, count }) => { | 228 | VideoPlaylistElementModel.findAll(getQuery(false)) |
225 | return { total: count, data: rows.map(e => e.url) } | 229 | ]).then(([ total, rows ]) => ({ |
226 | }) | 230 | total, |
231 | data: rows.map(e => e.url) | ||
232 | })) | ||
227 | } | 233 | } |
228 | 234 | ||
229 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Promise<MVideoPlaylistVideoThumbnail> { | 235 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Promise<MVideoPlaylistVideoThumbnail> { |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index c125db3ff..ae5e237ec 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { FindOptions, literal, Op, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' | 2 | import { FindOptions, Includeable, literal, Op, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
3 | import { | 3 | import { |
4 | AllowNull, | 4 | AllowNull, |
5 | BelongsTo, | 5 | BelongsTo, |
@@ -86,6 +86,7 @@ type AvailableForListOptions = { | |||
86 | host?: string | 86 | host?: string |
87 | uuids?: string[] | 87 | uuids?: string[] |
88 | withVideos?: boolean | 88 | withVideos?: boolean |
89 | forCount?: boolean | ||
89 | } | 90 | } |
90 | 91 | ||
91 | function getVideoLengthSelect () { | 92 | function getVideoLengthSelect () { |
@@ -239,23 +240,28 @@ function getVideoLengthSelect () { | |||
239 | [Op.and]: whereAnd | 240 | [Op.and]: whereAnd |
240 | } | 241 | } |
241 | 242 | ||
243 | const include: Includeable[] = [ | ||
244 | { | ||
245 | model: AccountModel.scope({ | ||
246 | method: [ AccountScopeNames.SUMMARY, { whereActor, whereServer, forCount: options.forCount } as SummaryOptions ] | ||
247 | }), | ||
248 | required: true | ||
249 | } | ||
250 | ] | ||
251 | |||
252 | if (options.forCount !== true) { | ||
253 | include.push({ | ||
254 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | ||
255 | required: false | ||
256 | }) | ||
257 | } | ||
258 | |||
242 | return { | 259 | return { |
243 | attributes: { | 260 | attributes: { |
244 | include: attributesInclude | 261 | include: attributesInclude |
245 | }, | 262 | }, |
246 | where, | 263 | where, |
247 | include: [ | 264 | include |
248 | { | ||
249 | model: AccountModel.scope({ | ||
250 | method: [ AccountScopeNames.SUMMARY, { whereActor, whereServer } as SummaryOptions ] | ||
251 | }), | ||
252 | required: true | ||
253 | }, | ||
254 | { | ||
255 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | ||
256 | required: false | ||
257 | } | ||
258 | ] | ||
259 | } as FindOptions | 265 | } as FindOptions |
260 | } | 266 | } |
261 | })) | 267 | })) |
@@ -369,12 +375,23 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli | |||
369 | order: getPlaylistSort(options.sort) | 375 | order: getPlaylistSort(options.sort) |
370 | } | 376 | } |
371 | 377 | ||
372 | const scopes: (string | ScopeOptions)[] = [ | 378 | const commonAvailableForListOptions = pick(options, [ |
379 | 'type', | ||
380 | 'followerActorId', | ||
381 | 'accountId', | ||
382 | 'videoChannelId', | ||
383 | 'listMyPlaylists', | ||
384 | 'search', | ||
385 | 'host', | ||
386 | 'uuids' | ||
387 | ]) | ||
388 | |||
389 | const scopesFind: (string | ScopeOptions)[] = [ | ||
373 | { | 390 | { |
374 | method: [ | 391 | method: [ |
375 | ScopeNames.AVAILABLE_FOR_LIST, | 392 | ScopeNames.AVAILABLE_FOR_LIST, |
376 | { | 393 | { |
377 | ...pick(options, [ 'type', 'followerActorId', 'accountId', 'videoChannelId', 'listMyPlaylists', 'search', 'host', 'uuids' ]), | 394 | ...commonAvailableForListOptions, |
378 | 395 | ||
379 | withVideos: options.withVideos || false | 396 | withVideos: options.withVideos || false |
380 | } as AvailableForListOptions | 397 | } as AvailableForListOptions |
@@ -384,12 +401,26 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli | |||
384 | ScopeNames.WITH_THUMBNAIL | 401 | ScopeNames.WITH_THUMBNAIL |
385 | ] | 402 | ] |
386 | 403 | ||
387 | return VideoPlaylistModel | 404 | const scopesCount: (string | ScopeOptions)[] = [ |
388 | .scope(scopes) | 405 | { |
389 | .findAndCountAll(query) | 406 | method: [ |
390 | .then(({ rows, count }) => { | 407 | ScopeNames.AVAILABLE_FOR_LIST, |
391 | return { total: count, data: rows } | 408 | |
392 | }) | 409 | { |
410 | ...commonAvailableForListOptions, | ||
411 | |||
412 | withVideos: options.withVideos || false, | ||
413 | forCount: true | ||
414 | } as AvailableForListOptions | ||
415 | ] | ||
416 | }, | ||
417 | ScopeNames.WITH_VIDEOS_LENGTH | ||
418 | ] | ||
419 | |||
420 | return Promise.all([ | ||
421 | VideoPlaylistModel.scope(scopesCount).count(), | ||
422 | VideoPlaylistModel.scope(scopesFind).findAll(query) | ||
423 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) | ||
393 | } | 424 | } |
394 | 425 | ||
395 | static searchForApi (options: Pick<AvailableForListOptions, 'followerActorId' | 'search'| 'host'| 'uuids'> & { | 426 | static searchForApi (options: Pick<AvailableForListOptions, 'followerActorId' | 'search'| 'host'| 'uuids'> & { |
@@ -419,17 +450,24 @@ export class VideoPlaylistModel extends Model<Partial<AttributesOnly<VideoPlayli | |||
419 | Object.assign(where, { videoChannelId: options.channel.id }) | 450 | Object.assign(where, { videoChannelId: options.channel.id }) |
420 | } | 451 | } |
421 | 452 | ||
422 | const query = { | 453 | const getQuery = (forCount: boolean) => { |
423 | attributes: [ 'url' ], | 454 | return { |
424 | offset: start, | 455 | attributes: forCount === true |
425 | limit: count, | 456 | ? [] |
426 | where | 457 | : [ 'url' ], |
458 | offset: start, | ||
459 | limit: count, | ||
460 | where | ||
461 | } | ||
427 | } | 462 | } |
428 | 463 | ||
429 | return VideoPlaylistModel.findAndCountAll(query) | 464 | return Promise.all([ |
430 | .then(({ rows, count }) => { | 465 | VideoPlaylistModel.count(getQuery(true)), |
431 | return { total: count, data: rows.map(p => p.url) } | 466 | VideoPlaylistModel.findAll(getQuery(false)) |
432 | }) | 467 | ]).then(([ total, rows ]) => ({ |
468 | total, | ||
469 | data: rows.map(p => p.url) | ||
470 | })) | ||
433 | } | 471 | } |
434 | 472 | ||
435 | static listPlaylistIdsOf (accountId: number, videoIds: number[]): Promise<MVideoPlaylistIdWithElements[]> { | 473 | static listPlaylistIdsOf (accountId: number, videoIds: number[]): Promise<MVideoPlaylistIdWithElements[]> { |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index f6659b992..ad95dec6e 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -183,7 +183,10 @@ export class VideoShareModel extends Model<Partial<AttributesOnly<VideoShareMode | |||
183 | transaction: t | 183 | transaction: t |
184 | } | 184 | } |
185 | 185 | ||
186 | return VideoShareModel.findAndCountAll(query) | 186 | return Promise.all([ |
187 | VideoShareModel.count(query), | ||
188 | VideoShareModel.findAll(query) | ||
189 | ]).then(([ total, data ]) => ({ total, data })) | ||
187 | } | 190 | } |
188 | 191 | ||
189 | static listRemoteShareUrlsOfLocalVideos () { | 192 | static listRemoteShareUrlsOfLocalVideos () { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 9111c71b0..5536334eb 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -114,9 +114,13 @@ import { | |||
114 | videoModelToFormattedJSON | 114 | videoModelToFormattedJSON |
115 | } from './formatter/video-format-utils' | 115 | } from './formatter/video-format-utils' |
116 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | 116 | import { ScheduleVideoUpdateModel } from './schedule-video-update' |
117 | import { VideoModelGetQueryBuilder } from './sql/video-model-get-query-builder' | 117 | import { |
118 | import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder' | 118 | BuildVideosListQueryOptions, |
119 | import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder' | 119 | DisplayOnlyForFollowerOptions, |
120 | VideoModelGetQueryBuilder, | ||
121 | VideosIdListQueryBuilder, | ||
122 | VideosModelListQueryBuilder | ||
123 | } from './sql/video' | ||
120 | import { TagModel } from './tag' | 124 | import { TagModel } from './tag' |
121 | import { ThumbnailModel } from './thumbnail' | 125 | import { ThumbnailModel } from './thumbnail' |
122 | import { VideoBlacklistModel } from './video-blacklist' | 126 | import { VideoBlacklistModel } from './video-blacklist' |
@@ -229,8 +233,8 @@ export type ForAPIOptions = { | |||
229 | required: false | 233 | required: false |
230 | }, | 234 | }, |
231 | { | 235 | { |
232 | model: ActorImageModel.unscoped(), | 236 | model: ActorImageModel, |
233 | as: 'Avatar', | 237 | as: 'Avatars', |
234 | required: false | 238 | required: false |
235 | } | 239 | } |
236 | ] | 240 | ] |
@@ -252,8 +256,8 @@ export type ForAPIOptions = { | |||
252 | required: false | 256 | required: false |
253 | }, | 257 | }, |
254 | { | 258 | { |
255 | model: ActorImageModel.unscoped(), | 259 | model: ActorImageModel, |
256 | as: 'Avatar', | 260 | as: 'Avatars', |
257 | required: false | 261 | required: false |
258 | } | 262 | } |
259 | ] | 263 | ] |