diff options
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/account/account-blocklist.ts | 111 | ||||
-rw-r--r-- | server/models/account/account.ts | 3 | ||||
-rw-r--r-- | server/models/account/user-video-history.ts | 55 | ||||
-rw-r--r-- | server/models/account/user.ts | 33 | ||||
-rw-r--r-- | server/models/activitypub/actor-follow.ts | 90 | ||||
-rw-r--r-- | server/models/redundancy/video-redundancy.ts | 1 | ||||
-rw-r--r-- | server/models/server/server-blocklist.ts | 121 | ||||
-rw-r--r-- | server/models/server/server.ts | 6 | ||||
-rw-r--r-- | server/models/utils.ts | 16 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 95 | ||||
-rw-r--r-- | server/models/video/video-format-utils.ts | 13 | ||||
-rw-r--r-- | server/models/video/video.ts | 137 |
12 files changed, 598 insertions, 83 deletions
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts new file mode 100644 index 000000000..fa2819235 --- /dev/null +++ b/server/models/account/account-blocklist.ts | |||
@@ -0,0 +1,111 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { AccountModel } from './account' | ||
3 | import { getSort } from '../utils' | ||
4 | import { AccountBlock } from '../../../shared/models/blocklist' | ||
5 | |||
6 | enum ScopeNames { | ||
7 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | ||
8 | } | ||
9 | |||
10 | @Scopes({ | ||
11 | [ScopeNames.WITH_ACCOUNTS]: { | ||
12 | include: [ | ||
13 | { | ||
14 | model: () => AccountModel, | ||
15 | required: true, | ||
16 | as: 'ByAccount' | ||
17 | }, | ||
18 | { | ||
19 | model: () => AccountModel, | ||
20 | required: true, | ||
21 | as: 'BlockedAccount' | ||
22 | } | ||
23 | ] | ||
24 | } | ||
25 | }) | ||
26 | |||
27 | @Table({ | ||
28 | tableName: 'accountBlocklist', | ||
29 | indexes: [ | ||
30 | { | ||
31 | fields: [ 'accountId', 'targetAccountId' ], | ||
32 | unique: true | ||
33 | }, | ||
34 | { | ||
35 | fields: [ 'targetAccountId' ] | ||
36 | } | ||
37 | ] | ||
38 | }) | ||
39 | export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | ||
40 | |||
41 | @CreatedAt | ||
42 | createdAt: Date | ||
43 | |||
44 | @UpdatedAt | ||
45 | updatedAt: Date | ||
46 | |||
47 | @ForeignKey(() => AccountModel) | ||
48 | @Column | ||
49 | accountId: number | ||
50 | |||
51 | @BelongsTo(() => AccountModel, { | ||
52 | foreignKey: { | ||
53 | name: 'accountId', | ||
54 | allowNull: false | ||
55 | }, | ||
56 | as: 'ByAccount', | ||
57 | onDelete: 'CASCADE' | ||
58 | }) | ||
59 | ByAccount: AccountModel | ||
60 | |||
61 | @ForeignKey(() => AccountModel) | ||
62 | @Column | ||
63 | targetAccountId: number | ||
64 | |||
65 | @BelongsTo(() => AccountModel, { | ||
66 | foreignKey: { | ||
67 | name: 'targetAccountId', | ||
68 | allowNull: false | ||
69 | }, | ||
70 | as: 'BlockedAccount', | ||
71 | onDelete: 'CASCADE' | ||
72 | }) | ||
73 | BlockedAccount: AccountModel | ||
74 | |||
75 | static loadByAccountAndTarget (accountId: number, targetAccountId: number) { | ||
76 | const query = { | ||
77 | where: { | ||
78 | accountId, | ||
79 | targetAccountId | ||
80 | } | ||
81 | } | ||
82 | |||
83 | return AccountBlocklistModel.findOne(query) | ||
84 | } | ||
85 | |||
86 | static listForApi (accountId: number, start: number, count: number, sort: string) { | ||
87 | const query = { | ||
88 | offset: start, | ||
89 | limit: count, | ||
90 | order: getSort(sort), | ||
91 | where: { | ||
92 | accountId | ||
93 | } | ||
94 | } | ||
95 | |||
96 | return AccountBlocklistModel | ||
97 | .scope([ ScopeNames.WITH_ACCOUNTS ]) | ||
98 | .findAndCountAll(query) | ||
99 | .then(({ rows, count }) => { | ||
100 | return { total: count, data: rows } | ||
101 | }) | ||
102 | } | ||
103 | |||
104 | toFormattedJSON (): AccountBlock { | ||
105 | return { | ||
106 | byAccount: this.ByAccount.toFormattedJSON(), | ||
107 | blockedAccount: this.BlockedAccount.toFormattedJSON(), | ||
108 | createdAt: this.createdAt | ||
109 | } | ||
110 | } | ||
111 | } | ||
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 27c75d886..5a237d733 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -248,7 +248,8 @@ export class AccountModel extends Model<AccountModel> { | |||
248 | displayName: this.getDisplayName(), | 248 | displayName: this.getDisplayName(), |
249 | description: this.description, | 249 | description: this.description, |
250 | createdAt: this.createdAt, | 250 | createdAt: this.createdAt, |
251 | updatedAt: this.updatedAt | 251 | updatedAt: this.updatedAt, |
252 | userId: this.userId ? this.userId : undefined | ||
252 | } | 253 | } |
253 | 254 | ||
254 | return Object.assign(actor, account) | 255 | return Object.assign(actor, account) |
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts new file mode 100644 index 000000000..0476cad9d --- /dev/null +++ b/server/models/account/user-video-history.ts | |||
@@ -0,0 +1,55 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Min, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { VideoModel } from '../video/video' | ||
3 | import { UserModel } from './user' | ||
4 | |||
5 | @Table({ | ||
6 | tableName: 'userVideoHistory', | ||
7 | indexes: [ | ||
8 | { | ||
9 | fields: [ 'userId', 'videoId' ], | ||
10 | unique: true | ||
11 | }, | ||
12 | { | ||
13 | fields: [ 'userId' ] | ||
14 | }, | ||
15 | { | ||
16 | fields: [ 'videoId' ] | ||
17 | } | ||
18 | ] | ||
19 | }) | ||
20 | export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | ||
21 | @CreatedAt | ||
22 | createdAt: Date | ||
23 | |||
24 | @UpdatedAt | ||
25 | updatedAt: Date | ||
26 | |||
27 | @AllowNull(false) | ||
28 | @IsInt | ||
29 | @Column | ||
30 | currentTime: number | ||
31 | |||
32 | @ForeignKey(() => VideoModel) | ||
33 | @Column | ||
34 | videoId: number | ||
35 | |||
36 | @BelongsTo(() => VideoModel, { | ||
37 | foreignKey: { | ||
38 | allowNull: false | ||
39 | }, | ||
40 | onDelete: 'CASCADE' | ||
41 | }) | ||
42 | Video: VideoModel | ||
43 | |||
44 | @ForeignKey(() => UserModel) | ||
45 | @Column | ||
46 | userId: number | ||
47 | |||
48 | @BelongsTo(() => UserModel, { | ||
49 | foreignKey: { | ||
50 | allowNull: false | ||
51 | }, | ||
52 | onDelete: 'CASCADE' | ||
53 | }) | ||
54 | User: UserModel | ||
55 | } | ||
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index e56b0bf40..34aafa1a7 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -31,7 +31,8 @@ import { | |||
31 | isUserRoleValid, | 31 | isUserRoleValid, |
32 | isUserUsernameValid, | 32 | isUserUsernameValid, |
33 | isUserVideoQuotaDailyValid, | 33 | isUserVideoQuotaDailyValid, |
34 | isUserVideoQuotaValid | 34 | isUserVideoQuotaValid, |
35 | isUserWebTorrentEnabledValid | ||
35 | } from '../../helpers/custom-validators/users' | 36 | } from '../../helpers/custom-validators/users' |
36 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 37 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
37 | import { OAuthTokenModel } from '../oauth/oauth-token' | 38 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -109,6 +110,12 @@ export class UserModel extends Model<UserModel> { | |||
109 | 110 | ||
110 | @AllowNull(false) | 111 | @AllowNull(false) |
111 | @Default(true) | 112 | @Default(true) |
113 | @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled')) | ||
114 | @Column | ||
115 | webTorrentEnabled: boolean | ||
116 | |||
117 | @AllowNull(false) | ||
118 | @Default(true) | ||
112 | @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean')) | 119 | @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean')) |
113 | @Column | 120 | @Column |
114 | autoPlayVideo: boolean | 121 | autoPlayVideo: boolean |
@@ -181,7 +188,25 @@ export class UserModel extends Model<UserModel> { | |||
181 | return this.count() | 188 | return this.count() |
182 | } | 189 | } |
183 | 190 | ||
184 | static listForApi (start: number, count: number, sort: string) { | 191 | static listForApi (start: number, count: number, sort: string, search?: string) { |
192 | let where = undefined | ||
193 | if (search) { | ||
194 | where = { | ||
195 | [Sequelize.Op.or]: [ | ||
196 | { | ||
197 | email: { | ||
198 | [Sequelize.Op.iLike]: '%' + search + '%' | ||
199 | } | ||
200 | }, | ||
201 | { | ||
202 | username: { | ||
203 | [ Sequelize.Op.iLike ]: '%' + search + '%' | ||
204 | } | ||
205 | } | ||
206 | ] | ||
207 | } | ||
208 | } | ||
209 | |||
185 | const query = { | 210 | const query = { |
186 | attributes: { | 211 | attributes: { |
187 | include: [ | 212 | include: [ |
@@ -204,7 +229,8 @@ export class UserModel extends Model<UserModel> { | |||
204 | }, | 229 | }, |
205 | offset: start, | 230 | offset: start, |
206 | limit: count, | 231 | limit: count, |
207 | order: getSort(sort) | 232 | order: getSort(sort), |
233 | where | ||
208 | } | 234 | } |
209 | 235 | ||
210 | return UserModel.findAndCountAll(query) | 236 | return UserModel.findAndCountAll(query) |
@@ -336,6 +362,7 @@ export class UserModel extends Model<UserModel> { | |||
336 | email: this.email, | 362 | email: this.email, |
337 | emailVerified: this.emailVerified, | 363 | emailVerified: this.emailVerified, |
338 | nsfwPolicy: this.nsfwPolicy, | 364 | nsfwPolicy: this.nsfwPolicy, |
365 | webTorrentEnabled: this.webTorrentEnabled, | ||
339 | autoPlayVideo: this.autoPlayVideo, | 366 | autoPlayVideo: this.autoPlayVideo, |
340 | role: this.role, | 367 | role: this.role, |
341 | roleLabel: USER_ROLE_LABELS[ this.role ], | 368 | roleLabel: USER_ROLE_LABELS[ this.role ], |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 27bb43dae..3373355ef 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -280,7 +280,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
280 | return ActorFollowModel.findAll(query) | 280 | return ActorFollowModel.findAll(query) |
281 | } | 281 | } |
282 | 282 | ||
283 | static listFollowingForApi (id: number, start: number, count: number, sort: string) { | 283 | static listFollowingForApi (id: number, start: number, count: number, sort: string, search?: string) { |
284 | const query = { | 284 | const query = { |
285 | distinct: true, | 285 | distinct: true, |
286 | offset: start, | 286 | offset: start, |
@@ -299,7 +299,17 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
299 | model: ActorModel, | 299 | model: ActorModel, |
300 | as: 'ActorFollowing', | 300 | as: 'ActorFollowing', |
301 | required: true, | 301 | required: true, |
302 | include: [ ServerModel ] | 302 | include: [ |
303 | { | ||
304 | model: ServerModel, | ||
305 | required: true, | ||
306 | where: search ? { | ||
307 | host: { | ||
308 | [Sequelize.Op.iLike]: '%' + search + '%' | ||
309 | } | ||
310 | } : undefined | ||
311 | } | ||
312 | ] | ||
303 | } | 313 | } |
304 | ] | 314 | ] |
305 | } | 315 | } |
@@ -313,6 +323,49 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
313 | }) | 323 | }) |
314 | } | 324 | } |
315 | 325 | ||
326 | static listFollowersForApi (id: number, start: number, count: number, sort: string, search?: string) { | ||
327 | const query = { | ||
328 | distinct: true, | ||
329 | offset: start, | ||
330 | limit: count, | ||
331 | order: getSort(sort), | ||
332 | include: [ | ||
333 | { | ||
334 | model: ActorModel, | ||
335 | required: true, | ||
336 | as: 'ActorFollower', | ||
337 | include: [ | ||
338 | { | ||
339 | model: ServerModel, | ||
340 | required: true, | ||
341 | where: search ? { | ||
342 | host: { | ||
343 | [ Sequelize.Op.iLike ]: '%' + search + '%' | ||
344 | } | ||
345 | } : undefined | ||
346 | } | ||
347 | ] | ||
348 | }, | ||
349 | { | ||
350 | model: ActorModel, | ||
351 | as: 'ActorFollowing', | ||
352 | required: true, | ||
353 | where: { | ||
354 | id | ||
355 | } | ||
356 | } | ||
357 | ] | ||
358 | } | ||
359 | |||
360 | return ActorFollowModel.findAndCountAll(query) | ||
361 | .then(({ rows, count }) => { | ||
362 | return { | ||
363 | data: rows, | ||
364 | total: count | ||
365 | } | ||
366 | }) | ||
367 | } | ||
368 | |||
316 | static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) { | 369 | static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) { |
317 | const query = { | 370 | const query = { |
318 | attributes: [], | 371 | attributes: [], |
@@ -370,39 +423,6 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
370 | }) | 423 | }) |
371 | } | 424 | } |
372 | 425 | ||
373 | static listFollowersForApi (id: number, start: number, count: number, sort: string) { | ||
374 | const query = { | ||
375 | distinct: true, | ||
376 | offset: start, | ||
377 | limit: count, | ||
378 | order: getSort(sort), | ||
379 | include: [ | ||
380 | { | ||
381 | model: ActorModel, | ||
382 | required: true, | ||
383 | as: 'ActorFollower', | ||
384 | include: [ ServerModel ] | ||
385 | }, | ||
386 | { | ||
387 | model: ActorModel, | ||
388 | as: 'ActorFollowing', | ||
389 | required: true, | ||
390 | where: { | ||
391 | id | ||
392 | } | ||
393 | } | ||
394 | ] | ||
395 | } | ||
396 | |||
397 | return ActorFollowModel.findAndCountAll(query) | ||
398 | .then(({ rows, count }) => { | ||
399 | return { | ||
400 | data: rows, | ||
401 | total: count | ||
402 | } | ||
403 | }) | ||
404 | } | ||
405 | |||
406 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | 426 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { |
407 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | 427 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) |
408 | } | 428 | } |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 2ebe23ef1..cbfc7f7fa 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -408,6 +408,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
408 | url: { | 408 | url: { |
409 | type: 'Link', | 409 | type: 'Link', |
410 | mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, | 410 | mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, |
411 | mediaType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any, | ||
411 | href: this.fileUrl, | 412 | href: this.fileUrl, |
412 | height: this.VideoFile.resolution, | 413 | height: this.VideoFile.resolution, |
413 | size: this.VideoFile.size, | 414 | size: this.VideoFile.size, |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts new file mode 100644 index 000000000..450f27152 --- /dev/null +++ b/server/models/server/server-blocklist.ts | |||
@@ -0,0 +1,121 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { AccountModel } from '../account/account' | ||
3 | import { ServerModel } from './server' | ||
4 | import { ServerBlock } from '../../../shared/models/blocklist' | ||
5 | import { getSort } from '../utils' | ||
6 | |||
7 | enum ScopeNames { | ||
8 | WITH_ACCOUNT = 'WITH_ACCOUNT', | ||
9 | WITH_SERVER = 'WITH_SERVER' | ||
10 | } | ||
11 | |||
12 | @Scopes({ | ||
13 | [ScopeNames.WITH_ACCOUNT]: { | ||
14 | include: [ | ||
15 | { | ||
16 | model: () => AccountModel, | ||
17 | required: true | ||
18 | } | ||
19 | ] | ||
20 | }, | ||
21 | [ScopeNames.WITH_SERVER]: { | ||
22 | include: [ | ||
23 | { | ||
24 | model: () => ServerModel, | ||
25 | required: true | ||
26 | } | ||
27 | ] | ||
28 | } | ||
29 | }) | ||
30 | |||
31 | @Table({ | ||
32 | tableName: 'serverBlocklist', | ||
33 | indexes: [ | ||
34 | { | ||
35 | fields: [ 'accountId', 'targetServerId' ], | ||
36 | unique: true | ||
37 | }, | ||
38 | { | ||
39 | fields: [ 'targetServerId' ] | ||
40 | } | ||
41 | ] | ||
42 | }) | ||
43 | export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | ||
44 | |||
45 | @CreatedAt | ||
46 | createdAt: Date | ||
47 | |||
48 | @UpdatedAt | ||
49 | updatedAt: Date | ||
50 | |||
51 | @ForeignKey(() => AccountModel) | ||
52 | @Column | ||
53 | accountId: number | ||
54 | |||
55 | @BelongsTo(() => AccountModel, { | ||
56 | foreignKey: { | ||
57 | name: 'accountId', | ||
58 | allowNull: false | ||
59 | }, | ||
60 | onDelete: 'CASCADE' | ||
61 | }) | ||
62 | ByAccount: AccountModel | ||
63 | |||
64 | @ForeignKey(() => ServerModel) | ||
65 | @Column | ||
66 | targetServerId: number | ||
67 | |||
68 | @BelongsTo(() => ServerModel, { | ||
69 | foreignKey: { | ||
70 | name: 'targetServerId', | ||
71 | allowNull: false | ||
72 | }, | ||
73 | onDelete: 'CASCADE' | ||
74 | }) | ||
75 | BlockedServer: ServerModel | ||
76 | |||
77 | static loadByAccountAndHost (accountId: number, host: string) { | ||
78 | const query = { | ||
79 | where: { | ||
80 | accountId | ||
81 | }, | ||
82 | include: [ | ||
83 | { | ||
84 | model: ServerModel, | ||
85 | where: { | ||
86 | host | ||
87 | }, | ||
88 | required: true | ||
89 | } | ||
90 | ] | ||
91 | } | ||
92 | |||
93 | return ServerBlocklistModel.findOne(query) | ||
94 | } | ||
95 | |||
96 | static listForApi (accountId: number, start: number, count: number, sort: string) { | ||
97 | const query = { | ||
98 | offset: start, | ||
99 | limit: count, | ||
100 | order: getSort(sort), | ||
101 | where: { | ||
102 | accountId | ||
103 | } | ||
104 | } | ||
105 | |||
106 | return ServerBlocklistModel | ||
107 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) | ||
108 | .findAndCountAll(query) | ||
109 | .then(({ rows, count }) => { | ||
110 | return { total: count, data: rows } | ||
111 | }) | ||
112 | } | ||
113 | |||
114 | toFormattedJSON (): ServerBlock { | ||
115 | return { | ||
116 | byAccount: this.ByAccount.toFormattedJSON(), | ||
117 | blockedServer: this.BlockedServer.toFormattedJSON(), | ||
118 | createdAt: this.createdAt | ||
119 | } | ||
120 | } | ||
121 | } | ||
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index ca3b24d51..300d70938 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -49,4 +49,10 @@ export class ServerModel extends Model<ServerModel> { | |||
49 | 49 | ||
50 | return ServerModel.findOne(query) | 50 | return ServerModel.findOne(query) |
51 | } | 51 | } |
52 | |||
53 | toFormattedJSON () { | ||
54 | return { | ||
55 | host: this.host | ||
56 | } | ||
57 | } | ||
52 | } | 58 | } |
diff --git a/server/models/utils.ts b/server/models/utils.ts index e0bf091ad..60b0906e8 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -64,9 +64,25 @@ function createSimilarityAttribute (col: string, value: string) { | |||
64 | ) | 64 | ) |
65 | } | 65 | } |
66 | 66 | ||
67 | function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number) { | ||
68 | const blockerIds = [ serverAccountId ] | ||
69 | if (userAccountId) blockerIds.push(userAccountId) | ||
70 | |||
71 | const blockerIdsString = blockerIds.join(', ') | ||
72 | |||
73 | const query = 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + | ||
74 | ' UNION ALL ' + | ||
75 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + | ||
76 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + | ||
77 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' | ||
78 | |||
79 | return query | ||
80 | } | ||
81 | |||
67 | // --------------------------------------------------------------------------- | 82 | // --------------------------------------------------------------------------- |
68 | 83 | ||
69 | export { | 84 | export { |
85 | buildBlockedAccountSQL, | ||
70 | SortType, | 86 | SortType, |
71 | getSort, | 87 | getSort, |
72 | getVideoSort, | 88 | getVideoSort, |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index f84c1880c..dd6d08139 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -1,6 +1,17 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { | 2 | import { |
3 | AllowNull, BeforeDestroy, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, | 3 | AllowNull, |
4 | BeforeDestroy, | ||
5 | BelongsTo, | ||
6 | Column, | ||
7 | CreatedAt, | ||
8 | DataType, | ||
9 | ForeignKey, | ||
10 | IFindOptions, | ||
11 | Is, | ||
12 | Model, | ||
13 | Scopes, | ||
14 | Table, | ||
4 | UpdatedAt | 15 | UpdatedAt |
5 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
6 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' | 17 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' |
@@ -13,9 +24,11 @@ import { AccountModel } from '../account/account' | |||
13 | import { ActorModel } from '../activitypub/actor' | 24 | import { ActorModel } from '../activitypub/actor' |
14 | import { AvatarModel } from '../avatar/avatar' | 25 | import { AvatarModel } from '../avatar/avatar' |
15 | import { ServerModel } from '../server/server' | 26 | import { ServerModel } from '../server/server' |
16 | import { getSort, throwIfNotValid } from '../utils' | 27 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' |
17 | import { VideoModel } from './video' | 28 | import { VideoModel } from './video' |
18 | import { VideoChannelModel } from './video-channel' | 29 | import { VideoChannelModel } from './video-channel' |
30 | import { getServerActor } from '../../helpers/utils' | ||
31 | import { UserModel } from '../account/user' | ||
19 | 32 | ||
20 | enum ScopeNames { | 33 | enum ScopeNames { |
21 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 34 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -25,18 +38,29 @@ enum ScopeNames { | |||
25 | } | 38 | } |
26 | 39 | ||
27 | @Scopes({ | 40 | @Scopes({ |
28 | [ScopeNames.ATTRIBUTES_FOR_API]: { | 41 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { |
29 | attributes: { | 42 | return { |
30 | include: [ | 43 | attributes: { |
31 | [ | 44 | include: [ |
32 | Sequelize.literal( | 45 | [ |
33 | '(SELECT COUNT("replies"."id") ' + | 46 | Sequelize.literal( |
34 | 'FROM "videoComment" AS "replies" ' + | 47 | '(' + |
35 | 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id")' | 48 | 'WITH "blocklist" AS (' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' + |
36 | ), | 49 | 'SELECT COUNT("replies"."id") - (' + |
37 | 'totalReplies' | 50 | 'SELECT COUNT("replies"."id") ' + |
51 | 'FROM "videoComment" AS "replies" ' + | ||
52 | 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' + | ||
53 | 'AND "accountId" IN (SELECT "id" FROM "blocklist")' + | ||
54 | ')' + | ||
55 | 'FROM "videoComment" AS "replies" ' + | ||
56 | 'WHERE "replies"."originCommentId" = "VideoCommentModel"."id" ' + | ||
57 | 'AND "accountId" NOT IN (SELECT "id" FROM "blocklist")' + | ||
58 | ')' | ||
59 | ), | ||
60 | 'totalReplies' | ||
61 | ] | ||
38 | ] | 62 | ] |
39 | ] | 63 | } |
40 | } | 64 | } |
41 | }, | 65 | }, |
42 | [ScopeNames.WITH_ACCOUNT]: { | 66 | [ScopeNames.WITH_ACCOUNT]: { |
@@ -267,26 +291,47 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
267 | return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) | 291 | return VideoCommentModel.scope([ ScopeNames.WITH_IN_REPLY_TO, ScopeNames.WITH_VIDEO ]).findOne(query) |
268 | } | 292 | } |
269 | 293 | ||
270 | static listThreadsForApi (videoId: number, start: number, count: number, sort: string) { | 294 | static async listThreadsForApi (videoId: number, start: number, count: number, sort: string, user?: UserModel) { |
295 | const serverActor = await getServerActor() | ||
296 | const serverAccountId = serverActor.Account.id | ||
297 | const userAccountId = user ? user.Account.id : undefined | ||
298 | |||
271 | const query = { | 299 | const query = { |
272 | offset: start, | 300 | offset: start, |
273 | limit: count, | 301 | limit: count, |
274 | order: getSort(sort), | 302 | order: getSort(sort), |
275 | where: { | 303 | where: { |
276 | videoId, | 304 | videoId, |
277 | inReplyToCommentId: null | 305 | inReplyToCommentId: null, |
306 | accountId: { | ||
307 | [Sequelize.Op.notIn]: Sequelize.literal( | ||
308 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' | ||
309 | ) | ||
310 | } | ||
278 | } | 311 | } |
279 | } | 312 | } |
280 | 313 | ||
314 | // FIXME: typings | ||
315 | const scopes: any[] = [ | ||
316 | ScopeNames.WITH_ACCOUNT, | ||
317 | { | ||
318 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] | ||
319 | } | ||
320 | ] | ||
321 | |||
281 | return VideoCommentModel | 322 | return VideoCommentModel |
282 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) | 323 | .scope(scopes) |
283 | .findAndCountAll(query) | 324 | .findAndCountAll(query) |
284 | .then(({ rows, count }) => { | 325 | .then(({ rows, count }) => { |
285 | return { total: count, data: rows } | 326 | return { total: count, data: rows } |
286 | }) | 327 | }) |
287 | } | 328 | } |
288 | 329 | ||
289 | static listThreadCommentsForApi (videoId: number, threadId: number) { | 330 | static async listThreadCommentsForApi (videoId: number, threadId: number, user?: UserModel) { |
331 | const serverActor = await getServerActor() | ||
332 | const serverAccountId = serverActor.Account.id | ||
333 | const userAccountId = user ? user.Account.id : undefined | ||
334 | |||
290 | const query = { | 335 | const query = { |
291 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], | 336 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], |
292 | where: { | 337 | where: { |
@@ -294,12 +339,24 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
294 | [ Sequelize.Op.or ]: [ | 339 | [ Sequelize.Op.or ]: [ |
295 | { id: threadId }, | 340 | { id: threadId }, |
296 | { originCommentId: threadId } | 341 | { originCommentId: threadId } |
297 | ] | 342 | ], |
343 | accountId: { | ||
344 | [Sequelize.Op.notIn]: Sequelize.literal( | ||
345 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' | ||
346 | ) | ||
347 | } | ||
298 | } | 348 | } |
299 | } | 349 | } |
300 | 350 | ||
351 | const scopes: any[] = [ | ||
352 | ScopeNames.WITH_ACCOUNT, | ||
353 | { | ||
354 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] | ||
355 | } | ||
356 | ] | ||
357 | |||
301 | return VideoCommentModel | 358 | return VideoCommentModel |
302 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.ATTRIBUTES_FOR_API ]) | 359 | .scope(scopes) |
303 | .findAndCountAll(query) | 360 | .findAndCountAll(query) |
304 | .then(({ rows, count }) => { | 361 | .then(({ rows, count }) => { |
305 | return { total: count, data: rows } | 362 | return { total: count, data: rows } |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index f23dde9b8..e3f8d525b 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -10,6 +10,7 @@ import { | |||
10 | getVideoLikesActivityPubUrl, | 10 | getVideoLikesActivityPubUrl, |
11 | getVideoSharesActivityPubUrl | 11 | getVideoSharesActivityPubUrl |
12 | } from '../../lib/activitypub' | 12 | } from '../../lib/activitypub' |
13 | import { isArray } from '../../helpers/custom-validators/misc' | ||
13 | 14 | ||
14 | export type VideoFormattingJSONOptions = { | 15 | export type VideoFormattingJSONOptions = { |
15 | completeDescription?: boolean | 16 | completeDescription?: boolean |
@@ -24,6 +25,8 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
24 | const formattedAccount = video.VideoChannel.Account.toFormattedJSON() | 25 | const formattedAccount = video.VideoChannel.Account.toFormattedJSON() |
25 | const formattedVideoChannel = video.VideoChannel.toFormattedJSON() | 26 | const formattedVideoChannel = video.VideoChannel.toFormattedJSON() |
26 | 27 | ||
28 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | ||
29 | |||
27 | const videoObject: Video = { | 30 | const videoObject: Video = { |
28 | id: video.id, | 31 | id: video.id, |
29 | uuid: video.uuid, | 32 | uuid: video.uuid, |
@@ -74,7 +77,11 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
74 | url: formattedVideoChannel.url, | 77 | url: formattedVideoChannel.url, |
75 | host: formattedVideoChannel.host, | 78 | host: formattedVideoChannel.host, |
76 | avatar: formattedVideoChannel.avatar | 79 | avatar: formattedVideoChannel.avatar |
77 | } | 80 | }, |
81 | |||
82 | userHistory: userHistory ? { | ||
83 | currentTime: userHistory.currentTime | ||
84 | } : undefined | ||
78 | } | 85 | } |
79 | 86 | ||
80 | if (options) { | 87 | if (options) { |
@@ -201,6 +208,7 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
201 | url.push({ | 208 | url.push({ |
202 | type: 'Link', | 209 | type: 'Link', |
203 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, | 210 | mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, |
211 | mediaType: VIDEO_EXT_MIMETYPE[ file.extname ] as any, | ||
204 | href: video.getVideoFileUrl(file, baseUrlHttp), | 212 | href: video.getVideoFileUrl(file, baseUrlHttp), |
205 | height: file.resolution, | 213 | height: file.resolution, |
206 | size: file.size, | 214 | size: file.size, |
@@ -210,6 +218,7 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
210 | url.push({ | 218 | url.push({ |
211 | type: 'Link', | 219 | type: 'Link', |
212 | mimeType: 'application/x-bittorrent' as 'application/x-bittorrent', | 220 | mimeType: 'application/x-bittorrent' as 'application/x-bittorrent', |
221 | mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', | ||
213 | href: video.getTorrentUrl(file, baseUrlHttp), | 222 | href: video.getTorrentUrl(file, baseUrlHttp), |
214 | height: file.resolution | 223 | height: file.resolution |
215 | }) | 224 | }) |
@@ -217,6 +226,7 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
217 | url.push({ | 226 | url.push({ |
218 | type: 'Link', | 227 | type: 'Link', |
219 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', | 228 | mimeType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', |
229 | mediaType: 'application/x-bittorrent;x-scheme-handler/magnet' as 'application/x-bittorrent;x-scheme-handler/magnet', | ||
220 | href: video.generateMagnetUri(file, baseUrlHttp, baseUrlWs), | 230 | href: video.generateMagnetUri(file, baseUrlHttp, baseUrlWs), |
221 | height: file.resolution | 231 | height: file.resolution |
222 | }) | 232 | }) |
@@ -226,6 +236,7 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
226 | url.push({ | 236 | url.push({ |
227 | type: 'Link', | 237 | type: 'Link', |
228 | mimeType: 'text/html', | 238 | mimeType: 'text/html', |
239 | mediaType: 'text/html', | ||
229 | href: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | 240 | href: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid |
230 | }) | 241 | }) |
231 | 242 | ||
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 6c89c16bf..6c183933b 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -27,7 +27,7 @@ import { | |||
27 | Table, | 27 | Table, |
28 | UpdatedAt | 28 | UpdatedAt |
29 | } from 'sequelize-typescript' | 29 | } from 'sequelize-typescript' |
30 | import { VideoPrivacy, VideoState } from '../../../shared' | 30 | import { UserRight, VideoPrivacy, VideoState } from '../../../shared' |
31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 31 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 32 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 33 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
@@ -70,7 +70,7 @@ import { AccountVideoRateModel } from '../account/account-video-rate' | |||
70 | import { ActorModel } from '../activitypub/actor' | 70 | import { ActorModel } from '../activitypub/actor' |
71 | import { AvatarModel } from '../avatar/avatar' | 71 | import { AvatarModel } from '../avatar/avatar' |
72 | import { ServerModel } from '../server/server' | 72 | import { ServerModel } from '../server/server' |
73 | import { buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' | 73 | import { buildBlockedAccountSQL, buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' |
74 | import { TagModel } from './tag' | 74 | import { TagModel } from './tag' |
75 | import { VideoAbuseModel } from './video-abuse' | 75 | import { VideoAbuseModel } from './video-abuse' |
76 | import { VideoChannelModel } from './video-channel' | 76 | import { VideoChannelModel } from './video-channel' |
@@ -92,6 +92,8 @@ import { | |||
92 | videoModelToFormattedJSON | 92 | videoModelToFormattedJSON |
93 | } from './video-format-utils' | 93 | } from './video-format-utils' |
94 | import * as validator from 'validator' | 94 | import * as validator from 'validator' |
95 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
96 | import { UserModel } from '../account/user' | ||
95 | 97 | ||
96 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 98 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
97 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 99 | const indexes: Sequelize.DefineIndexesOptions[] = [ |
@@ -127,7 +129,8 @@ export enum ScopeNames { | |||
127 | WITH_TAGS = 'WITH_TAGS', | 129 | WITH_TAGS = 'WITH_TAGS', |
128 | WITH_FILES = 'WITH_FILES', | 130 | WITH_FILES = 'WITH_FILES', |
129 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', | 131 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', |
130 | WITH_BLACKLISTED = 'WITH_BLACKLISTED' | 132 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', |
133 | WITH_USER_HISTORY = 'WITH_USER_HISTORY' | ||
131 | } | 134 | } |
132 | 135 | ||
133 | type ForAPIOptions = { | 136 | type ForAPIOptions = { |
@@ -136,6 +139,7 @@ type ForAPIOptions = { | |||
136 | } | 139 | } |
137 | 140 | ||
138 | type AvailableForListIDsOptions = { | 141 | type AvailableForListIDsOptions = { |
142 | serverAccountId: number | ||
139 | actorId: number | 143 | actorId: number |
140 | includeLocalVideos: boolean | 144 | includeLocalVideos: boolean |
141 | filter?: VideoFilter | 145 | filter?: VideoFilter |
@@ -149,6 +153,7 @@ type AvailableForListIDsOptions = { | |||
149 | accountId?: number | 153 | accountId?: number |
150 | videoChannelId?: number | 154 | videoChannelId?: number |
151 | trendingDays?: number | 155 | trendingDays?: number |
156 | user?: UserModel | ||
152 | } | 157 | } |
153 | 158 | ||
154 | @Scopes({ | 159 | @Scopes({ |
@@ -234,6 +239,22 @@ type AvailableForListIDsOptions = { | |||
234 | } | 239 | } |
235 | ] | 240 | ] |
236 | }, | 241 | }, |
242 | channelId: { | ||
243 | [ Sequelize.Op.notIn ]: Sequelize.literal( | ||
244 | '(' + | ||
245 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | ||
246 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | ||
247 | ')' + | ||
248 | ')' | ||
249 | ) | ||
250 | } | ||
251 | }, | ||
252 | include: [] | ||
253 | } | ||
254 | |||
255 | // Only list public/published videos | ||
256 | if (!options.filter || options.filter !== 'all-local') { | ||
257 | const privacyWhere = { | ||
237 | // Always list public videos | 258 | // Always list public videos |
238 | privacy: VideoPrivacy.PUBLIC, | 259 | privacy: VideoPrivacy.PUBLIC, |
239 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | 260 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding |
@@ -248,8 +269,9 @@ type AvailableForListIDsOptions = { | |||
248 | } | 269 | } |
249 | } | 270 | } |
250 | ] | 271 | ] |
251 | }, | 272 | } |
252 | include: [] | 273 | |
274 | Object.assign(query.where, privacyWhere) | ||
253 | } | 275 | } |
254 | 276 | ||
255 | if (options.filter || options.accountId || options.videoChannelId) { | 277 | if (options.filter || options.accountId || options.videoChannelId) { |
@@ -464,6 +486,8 @@ type AvailableForListIDsOptions = { | |||
464 | include: [ | 486 | include: [ |
465 | { | 487 | { |
466 | model: () => VideoFileModel.unscoped(), | 488 | model: () => VideoFileModel.unscoped(), |
489 | // FIXME: typings | ||
490 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | ||
467 | required: false, | 491 | required: false, |
468 | include: [ | 492 | include: [ |
469 | { | 493 | { |
@@ -482,6 +506,20 @@ type AvailableForListIDsOptions = { | |||
482 | required: false | 506 | required: false |
483 | } | 507 | } |
484 | ] | 508 | ] |
509 | }, | ||
510 | [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { | ||
511 | return { | ||
512 | include: [ | ||
513 | { | ||
514 | attributes: [ 'currentTime' ], | ||
515 | model: UserVideoHistoryModel.unscoped(), | ||
516 | required: false, | ||
517 | where: { | ||
518 | userId | ||
519 | } | ||
520 | } | ||
521 | ] | ||
522 | } | ||
485 | } | 523 | } |
486 | }) | 524 | }) |
487 | @Table({ | 525 | @Table({ |
@@ -672,11 +710,19 @@ export class VideoModel extends Model<VideoModel> { | |||
672 | name: 'videoId', | 710 | name: 'videoId', |
673 | allowNull: false | 711 | allowNull: false |
674 | }, | 712 | }, |
675 | onDelete: 'cascade', | 713 | onDelete: 'cascade' |
676 | hooks: true | ||
677 | }) | 714 | }) |
678 | VideoViews: VideoViewModel[] | 715 | VideoViews: VideoViewModel[] |
679 | 716 | ||
717 | @HasMany(() => UserVideoHistoryModel, { | ||
718 | foreignKey: { | ||
719 | name: 'videoId', | ||
720 | allowNull: false | ||
721 | }, | ||
722 | onDelete: 'cascade' | ||
723 | }) | ||
724 | UserVideoHistories: UserVideoHistoryModel[] | ||
725 | |||
680 | @HasOne(() => ScheduleVideoUpdateModel, { | 726 | @HasOne(() => ScheduleVideoUpdateModel, { |
681 | foreignKey: { | 727 | foreignKey: { |
682 | name: 'videoId', | 728 | name: 'videoId', |
@@ -762,6 +808,16 @@ export class VideoModel extends Model<VideoModel> { | |||
762 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() | 808 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() |
763 | } | 809 | } |
764 | 810 | ||
811 | static listLocal () { | ||
812 | const query = { | ||
813 | where: { | ||
814 | remote: false | ||
815 | } | ||
816 | } | ||
817 | |||
818 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll(query) | ||
819 | } | ||
820 | |||
765 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { | 821 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { |
766 | function getRawQuery (select: string) { | 822 | function getRawQuery (select: string) { |
767 | const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + | 823 | const queryVideo = 'SELECT ' + select + ' FROM "video" AS "Video" ' + |
@@ -930,8 +986,13 @@ export class VideoModel extends Model<VideoModel> { | |||
930 | accountId?: number, | 986 | accountId?: number, |
931 | videoChannelId?: number, | 987 | videoChannelId?: number, |
932 | actorId?: number | 988 | actorId?: number |
933 | trendingDays?: number | 989 | trendingDays?: number, |
990 | user?: UserModel | ||
934 | }, countVideos = true) { | 991 | }, countVideos = true) { |
992 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | ||
993 | throw new Error('Try to filter all-local but no user has not the see all videos right') | ||
994 | } | ||
995 | |||
935 | const query: IFindOptions<VideoModel> = { | 996 | const query: IFindOptions<VideoModel> = { |
936 | offset: options.start, | 997 | offset: options.start, |
937 | limit: options.count, | 998 | limit: options.count, |
@@ -945,11 +1006,14 @@ export class VideoModel extends Model<VideoModel> { | |||
945 | query.group = 'VideoModel.id' | 1006 | query.group = 'VideoModel.id' |
946 | } | 1007 | } |
947 | 1008 | ||
1009 | const serverActor = await getServerActor() | ||
1010 | |||
948 | // actorId === null has a meaning, so just check undefined | 1011 | // actorId === null has a meaning, so just check undefined |
949 | const actorId = options.actorId !== undefined ? options.actorId : (await getServerActor()).id | 1012 | const actorId = options.actorId !== undefined ? options.actorId : serverActor.id |
950 | 1013 | ||
951 | const queryOptions = { | 1014 | const queryOptions = { |
952 | actorId, | 1015 | actorId, |
1016 | serverAccountId: serverActor.Account.id, | ||
953 | nsfw: options.nsfw, | 1017 | nsfw: options.nsfw, |
954 | categoryOneOf: options.categoryOneOf, | 1018 | categoryOneOf: options.categoryOneOf, |
955 | licenceOneOf: options.licenceOneOf, | 1019 | licenceOneOf: options.licenceOneOf, |
@@ -961,6 +1025,7 @@ export class VideoModel extends Model<VideoModel> { | |||
961 | accountId: options.accountId, | 1025 | accountId: options.accountId, |
962 | videoChannelId: options.videoChannelId, | 1026 | videoChannelId: options.videoChannelId, |
963 | includeLocalVideos: options.includeLocalVideos, | 1027 | includeLocalVideos: options.includeLocalVideos, |
1028 | user: options.user, | ||
964 | trendingDays | 1029 | trendingDays |
965 | } | 1030 | } |
966 | 1031 | ||
@@ -983,6 +1048,8 @@ export class VideoModel extends Model<VideoModel> { | |||
983 | tagsAllOf?: string[] | 1048 | tagsAllOf?: string[] |
984 | durationMin?: number // seconds | 1049 | durationMin?: number // seconds |
985 | durationMax?: number // seconds | 1050 | durationMax?: number // seconds |
1051 | user?: UserModel, | ||
1052 | filter?: VideoFilter | ||
986 | }) { | 1053 | }) { |
987 | const whereAnd = [] | 1054 | const whereAnd = [] |
988 | 1055 | ||
@@ -1052,13 +1119,16 @@ export class VideoModel extends Model<VideoModel> { | |||
1052 | const serverActor = await getServerActor() | 1119 | const serverActor = await getServerActor() |
1053 | const queryOptions = { | 1120 | const queryOptions = { |
1054 | actorId: serverActor.id, | 1121 | actorId: serverActor.id, |
1122 | serverAccountId: serverActor.Account.id, | ||
1055 | includeLocalVideos: options.includeLocalVideos, | 1123 | includeLocalVideos: options.includeLocalVideos, |
1056 | nsfw: options.nsfw, | 1124 | nsfw: options.nsfw, |
1057 | categoryOneOf: options.categoryOneOf, | 1125 | categoryOneOf: options.categoryOneOf, |
1058 | licenceOneOf: options.licenceOneOf, | 1126 | licenceOneOf: options.licenceOneOf, |
1059 | languageOneOf: options.languageOneOf, | 1127 | languageOneOf: options.languageOneOf, |
1060 | tagsOneOf: options.tagsOneOf, | 1128 | tagsOneOf: options.tagsOneOf, |
1061 | tagsAllOf: options.tagsAllOf | 1129 | tagsAllOf: options.tagsAllOf, |
1130 | user: options.user, | ||
1131 | filter: options.filter | ||
1062 | } | 1132 | } |
1063 | 1133 | ||
1064 | return VideoModel.getAvailableForApi(query, queryOptions) | 1134 | return VideoModel.getAvailableForApi(query, queryOptions) |
@@ -1125,7 +1195,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1125 | return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) | 1195 | return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) |
1126 | } | 1196 | } |
1127 | 1197 | ||
1128 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction) { | 1198 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { |
1129 | const where = VideoModel.buildWhereIdOrUUID(id) | 1199 | const where = VideoModel.buildWhereIdOrUUID(id) |
1130 | 1200 | ||
1131 | const options = { | 1201 | const options = { |
@@ -1134,14 +1204,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1134 | transaction: t | 1204 | transaction: t |
1135 | } | 1205 | } |
1136 | 1206 | ||
1207 | const scopes = [ | ||
1208 | ScopeNames.WITH_TAGS, | ||
1209 | ScopeNames.WITH_BLACKLISTED, | ||
1210 | ScopeNames.WITH_FILES, | ||
1211 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1212 | ScopeNames.WITH_SCHEDULED_UPDATE | ||
1213 | ] | ||
1214 | |||
1215 | if (userId) { | ||
1216 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | ||
1217 | } | ||
1218 | |||
1137 | return VideoModel | 1219 | return VideoModel |
1138 | .scope([ | 1220 | .scope(scopes) |
1139 | ScopeNames.WITH_TAGS, | ||
1140 | ScopeNames.WITH_BLACKLISTED, | ||
1141 | ScopeNames.WITH_FILES, | ||
1142 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1143 | ScopeNames.WITH_SCHEDULED_UPDATE | ||
1144 | ]) | ||
1145 | .findOne(options) | 1221 | .findOne(options) |
1146 | } | 1222 | } |
1147 | 1223 | ||
@@ -1179,9 +1255,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1179 | 1255 | ||
1180 | // threshold corresponds to how many video the field should have to be returned | 1256 | // threshold corresponds to how many video the field should have to be returned |
1181 | static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { | 1257 | static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { |
1182 | const actorId = (await getServerActor()).id | 1258 | const serverActor = await getServerActor() |
1259 | const actorId = serverActor.id | ||
1183 | 1260 | ||
1184 | const scopeOptions = { | 1261 | const scopeOptions: AvailableForListIDsOptions = { |
1262 | serverAccountId: serverActor.Account.id, | ||
1185 | actorId, | 1263 | actorId, |
1186 | includeLocalVideos: true | 1264 | includeLocalVideos: true |
1187 | } | 1265 | } |
@@ -1216,7 +1294,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1216 | } | 1294 | } |
1217 | 1295 | ||
1218 | private static buildActorWhereWithFilter (filter?: VideoFilter) { | 1296 | private static buildActorWhereWithFilter (filter?: VideoFilter) { |
1219 | if (filter && filter === 'local') { | 1297 | if (filter && (filter === 'local' || filter === 'all-local')) { |
1220 | return { | 1298 | return { |
1221 | serverId: null | 1299 | serverId: null |
1222 | } | 1300 | } |
@@ -1225,7 +1303,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1225 | return {} | 1303 | return {} |
1226 | } | 1304 | } |
1227 | 1305 | ||
1228 | private static async getAvailableForApi (query: IFindOptions<VideoModel>, options: AvailableForListIDsOptions, countVideos = true) { | 1306 | private static async getAvailableForApi ( |
1307 | query: IFindOptions<VideoModel>, | ||
1308 | options: AvailableForListIDsOptions, | ||
1309 | countVideos = true | ||
1310 | ) { | ||
1229 | const idsScope = { | 1311 | const idsScope = { |
1230 | method: [ | 1312 | method: [ |
1231 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options | 1313 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options |
@@ -1249,8 +1331,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1249 | 1331 | ||
1250 | if (ids.length === 0) return { data: [], total: count } | 1332 | if (ids.length === 0) return { data: [], total: count } |
1251 | 1333 | ||
1252 | const apiScope = { | 1334 | // FIXME: typings |
1253 | method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ] | 1335 | const apiScope: any[] = [ |
1336 | { | ||
1337 | method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ] | ||
1338 | } | ||
1339 | ] | ||
1340 | |||
1341 | if (options.user) { | ||
1342 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | ||
1254 | } | 1343 | } |
1255 | 1344 | ||
1256 | const secondQuery = { | 1345 | const secondQuery = { |