aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/account/account-blocklist.ts8
-rw-r--r--server/models/account/account-video-rate.ts28
-rw-r--r--server/models/account/account.ts27
-rw-r--r--server/models/account/user-notification-setting.ts15
-rw-r--r--server/models/account/user-notification.ts29
-rw-r--r--server/models/account/user-video-history.ts7
-rw-r--r--server/models/account/user.ts94
-rw-r--r--server/models/activitypub/actor-follow.ts71
-rw-r--r--server/models/activitypub/actor.ts71
-rw-r--r--server/models/avatar/avatar.ts3
-rw-r--r--server/models/oauth/oauth-token.ts19
-rw-r--r--server/models/redundancy/video-redundancy.ts11
-rw-r--r--server/models/server/plugin.ts12
-rw-r--r--server/models/server/server-blocklist.ts8
-rw-r--r--server/models/server/server.ts17
-rw-r--r--server/models/video/schedule-video-update.ts3
-rw-r--r--server/models/video/tag.ts15
-rw-r--r--server/models/video/video-abuse.ts27
-rw-r--r--server/models/video/video-blacklist.ts12
-rw-r--r--server/models/video/video-caption.ts16
-rw-r--r--server/models/video/video-change-ownership.ts8
-rw-r--r--server/models/video/video-channel.ts69
-rw-r--r--server/models/video/video-comment.ts87
-rw-r--r--server/models/video/video-file.ts3
-rw-r--r--server/models/video/video-format-utils.ts17
-rw-r--r--server/models/video/video-import.ts14
-rw-r--r--server/models/video/video-playlist-element.ts34
-rw-r--r--server/models/video/video-playlist.ts23
-rw-r--r--server/models/video/video-share.ts14
-rw-r--r--server/models/video/video-streaming-playlist.ts12
-rw-r--r--server/models/video/video.ts149
31 files changed, 582 insertions, 341 deletions
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index d5746ad76..8bcaca828 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -3,6 +3,8 @@ import { AccountModel } from './account'
3import { getSort } from '../utils' 3import { getSort } from '../utils'
4import { AccountBlock } from '../../../shared/models/blocklist' 4import { AccountBlock } from '../../../shared/models/blocklist'
5import { Op } from 'sequelize' 5import { Op } from 'sequelize'
6import * as Bluebird from 'bluebird'
7import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/typings/models'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNTS = 'WITH_ACCOUNTS' 10 WITH_ACCOUNTS = 'WITH_ACCOUNTS'
@@ -103,7 +105,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
103 }) 105 })
104 } 106 }
105 107
106 static loadByAccountAndTarget (accountId: number, targetAccountId: number) { 108 static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird<MAccountBlocklist> {
107 const query = { 109 const query = {
108 where: { 110 where: {
109 accountId, 111 accountId,
@@ -126,13 +128,13 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
126 128
127 return AccountBlocklistModel 129 return AccountBlocklistModel
128 .scope([ ScopeNames.WITH_ACCOUNTS ]) 130 .scope([ ScopeNames.WITH_ACCOUNTS ])
129 .findAndCountAll(query) 131 .findAndCountAll<MAccountBlocklistAccounts>(query)
130 .then(({ rows, count }) => { 132 .then(({ rows, count }) => {
131 return { total: count, data: rows } 133 return { total: count, data: rows }
132 }) 134 })
133 } 135 }
134 136
135 toFormattedJSON (): AccountBlock { 137 toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock {
136 return { 138 return {
137 byAccount: this.ByAccount.toFormattedJSON(), 139 byAccount: this.ByAccount.toFormattedJSON(),
138 blockedAccount: this.BlockedAccount.toFormattedJSON(), 140 blockedAccount: this.BlockedAccount.toFormattedJSON(),
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index 4bd8114cf..a6edbeee8 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -10,6 +10,13 @@ import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
11import { AccountVideoRate } from '../../../shared' 11import { AccountVideoRate } from '../../../shared'
12import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' 12import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
13import * as Bluebird from 'bluebird'
14import {
15 MAccountVideoRate,
16 MAccountVideoRateAccountUrl,
17 MAccountVideoRateAccountVideo,
18 MAccountVideoRateFormattable
19} from '@server/typings/models/video/video-rate'
13 20
14/* 21/*
15 Account rates per video. 22 Account rates per video.
@@ -77,7 +84,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
77 }) 84 })
78 Account: AccountModel 85 Account: AccountModel
79 86
80 static load (accountId: number, videoId: number, transaction?: Transaction) { 87 static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird<MAccountVideoRate> {
81 const options: FindOptions = { 88 const options: FindOptions = {
82 where: { 89 where: {
83 accountId, 90 accountId,
@@ -89,7 +96,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
89 return AccountVideoRateModel.findOne(options) 96 return AccountVideoRateModel.findOne(options)
90 } 97 }
91 98
92 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, transaction?: Transaction) { 99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> {
93 const options: FindOptions = { 100 const options: FindOptions = {
94 where: { 101 where: {
95 [ Op.or]: [ 102 [ Op.or]: [
@@ -103,7 +110,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
103 ] 110 ]
104 } 111 }
105 } 112 }
106 if (transaction) options.transaction = transaction 113 if (t) options.transaction = t
107 114
108 return AccountVideoRateModel.findOne(options) 115 return AccountVideoRateModel.findOne(options)
109 } 116 }
@@ -140,7 +147,12 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
140 return AccountVideoRateModel.findAndCountAll(query) 147 return AccountVideoRateModel.findAndCountAll(query)
141 } 148 }
142 149
143 static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { 150 static loadLocalAndPopulateVideo (
151 rateType: VideoRateType,
152 accountName: string,
153 videoId: number,
154 t?: Transaction
155 ): Bluebird<MAccountVideoRateAccountVideo> {
144 const options: FindOptions = { 156 const options: FindOptions = {
145 where: { 157 where: {
146 videoId, 158 videoId,
@@ -152,7 +164,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
152 required: true, 164 required: true,
153 include: [ 165 include: [
154 { 166 {
155 attributes: [ 'id', 'url', 'preferredUsername' ], 167 attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ],
156 model: ActorModel.unscoped(), 168 model: ActorModel.unscoped(),
157 required: true, 169 required: true,
158 where: { 170 where: {
@@ -167,7 +179,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
167 } 179 }
168 ] 180 ]
169 } 181 }
170 if (transaction) options.transaction = transaction 182 if (t) options.transaction = t
171 183
172 return AccountVideoRateModel.findOne(options) 184 return AccountVideoRateModel.findOne(options)
173 } 185 }
@@ -208,7 +220,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
208 ] 220 ]
209 } 221 }
210 222
211 return AccountVideoRateModel.findAndCountAll(query) 223 return AccountVideoRateModel.findAndCountAll<MAccountVideoRateAccountUrl>(query)
212 } 224 }
213 225
214 static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { 226 static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) {
@@ -241,7 +253,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
241 }) 253 })
242 } 254 }
243 255
244 toFormattedJSON (): AccountVideoRate { 256 toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate {
245 return { 257 return {
246 video: this.Video.toFormattedJSON(), 258 video: this.Video.toFormattedJSON(),
247 rating: this.type 259 rating: this.type
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 4dc412301..ba1094536 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -3,7 +3,8 @@ import {
3 BeforeDestroy, 3 BeforeDestroy,
4 BelongsTo, 4 BelongsTo,
5 Column, 5 Column,
6 CreatedAt, DataType, 6 CreatedAt,
7 DataType,
7 Default, 8 Default,
8 DefaultScope, 9 DefaultScope,
9 ForeignKey, 10 ForeignKey,
@@ -31,6 +32,8 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ
31import { AccountBlocklistModel } from './account-blocklist' 32import { AccountBlocklistModel } from './account-blocklist'
32import { ServerBlocklistModel } from '../server/server-blocklist' 33import { ServerBlocklistModel } from '../server/server-blocklist'
33import { ActorFollowModel } from '../activitypub/actor-follow' 34import { ActorFollowModel } from '../activitypub/actor-follow'
35import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models'
36import * as Bluebird from 'bluebird'
34 37
35export enum ScopeNames { 38export enum ScopeNames {
36 SUMMARY = 'SUMMARY' 39 SUMMARY = 'SUMMARY'
@@ -229,11 +232,11 @@ export class AccountModel extends Model<AccountModel> {
229 return undefined 232 return undefined
230 } 233 }
231 234
232 static load (id: number, transaction?: Transaction) { 235 static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> {
233 return AccountModel.findByPk(id, { transaction }) 236 return AccountModel.findByPk(id, { transaction })
234 } 237 }
235 238
236 static loadByNameWithHost (nameWithHost: string) { 239 static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> {
237 const [ accountName, host ] = nameWithHost.split('@') 240 const [ accountName, host ] = nameWithHost.split('@')
238 241
239 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) 242 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName)
@@ -241,7 +244,7 @@ export class AccountModel extends Model<AccountModel> {
241 return AccountModel.loadByNameAndHost(accountName, host) 244 return AccountModel.loadByNameAndHost(accountName, host)
242 } 245 }
243 246
244 static loadLocalByName (name: string) { 247 static loadLocalByName (name: string): Bluebird<MAccountDefault> {
245 const query = { 248 const query = {
246 where: { 249 where: {
247 [ Op.or ]: [ 250 [ Op.or ]: [
@@ -271,7 +274,7 @@ export class AccountModel extends Model<AccountModel> {
271 return AccountModel.findOne(query) 274 return AccountModel.findOne(query)
272 } 275 }
273 276
274 static loadByNameAndHost (name: string, host: string) { 277 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
275 const query = { 278 const query = {
276 include: [ 279 include: [
277 { 280 {
@@ -296,7 +299,7 @@ export class AccountModel extends Model<AccountModel> {
296 return AccountModel.findOne(query) 299 return AccountModel.findOne(query)
297 } 300 }
298 301
299 static loadByUrl (url: string, transaction?: Transaction) { 302 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> {
300 const query = { 303 const query = {
301 include: [ 304 include: [
302 { 305 {
@@ -329,7 +332,7 @@ export class AccountModel extends Model<AccountModel> {
329 }) 332 })
330 } 333 }
331 334
332 static listLocalsForSitemap (sort: string) { 335 static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> {
333 const query = { 336 const query = {
334 attributes: [ ], 337 attributes: [ ],
335 offset: 0, 338 offset: 0,
@@ -350,7 +353,7 @@ export class AccountModel extends Model<AccountModel> {
350 .findAll(query) 353 .findAll(query)
351 } 354 }
352 355
353 toFormattedJSON (): Account { 356 toFormattedJSON (this: MAccountFormattable): Account {
354 const actor = this.Actor.toFormattedJSON() 357 const actor = this.Actor.toFormattedJSON()
355 const account = { 358 const account = {
356 id: this.id, 359 id: this.id,
@@ -364,8 +367,8 @@ export class AccountModel extends Model<AccountModel> {
364 return Object.assign(actor, account) 367 return Object.assign(actor, account)
365 } 368 }
366 369
367 toFormattedSummaryJSON (): AccountSummary { 370 toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary {
368 const actor = this.Actor.toFormattedJSON() 371 const actor = this.Actor.toFormattedSummaryJSON()
369 372
370 return { 373 return {
371 id: this.id, 374 id: this.id,
@@ -377,8 +380,8 @@ export class AccountModel extends Model<AccountModel> {
377 } 380 }
378 } 381 }
379 382
380 toActivityPubObject () { 383 toActivityPubObject (this: MAccountAP) {
381 const obj = this.Actor.toActivityPubObject(this.name, 'Account') 384 const obj = this.Actor.toActivityPubObject(this.name)
382 385
383 return Object.assign(obj, { 386 return Object.assign(obj, {
384 summary: this.description 387 summary: this.description
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts
index c2fbc6d23..dc69a17fd 100644
--- a/server/models/account/user-notification-setting.ts
+++ b/server/models/account/user-notification-setting.ts
@@ -17,6 +17,7 @@ import { UserModel } from './user'
17import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 17import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' 18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
19import { clearCacheByUserId } from '../../lib/oauth-model' 19import { clearCacheByUserId } from '../../lib/oauth-model'
20import { MNotificationSettingFormattable } from '@server/typings/models'
20 21
21@Table({ 22@Table({
22 tableName: 'userNotificationSetting', 23 tableName: 'userNotificationSetting',
@@ -113,6 +114,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
113 @AllowNull(false) 114 @AllowNull(false)
114 @Default(null) 115 @Default(null)
115 @Is( 116 @Is(
117 'UserNotificationSettingNewInstanceFollower',
118 value => throwIfNotValid(value, isUserNotificationSettingValid, 'autoInstanceFollowing')
119 )
120 @Column
121 autoInstanceFollowing: UserNotificationSettingValue
122
123 @AllowNull(false)
124 @Default(null)
125 @Is(
116 'UserNotificationSettingNewFollow', 126 'UserNotificationSettingNewFollow',
117 value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') 127 value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow')
118 ) 128 )
@@ -152,7 +162,7 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
152 return clearCacheByUserId(instance.userId) 162 return clearCacheByUserId(instance.userId)
153 } 163 }
154 164
155 toFormattedJSON (): UserNotificationSetting { 165 toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting {
156 return { 166 return {
157 newCommentOnMyVideo: this.newCommentOnMyVideo, 167 newCommentOnMyVideo: this.newCommentOnMyVideo,
158 newVideoFromSubscription: this.newVideoFromSubscription, 168 newVideoFromSubscription: this.newVideoFromSubscription,
@@ -164,7 +174,8 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM
164 newUserRegistration: this.newUserRegistration, 174 newUserRegistration: this.newUserRegistration,
165 commentMention: this.commentMention, 175 commentMention: this.commentMention,
166 newFollow: this.newFollow, 176 newFollow: this.newFollow,
167 newInstanceFollower: this.newInstanceFollower 177 newInstanceFollower: this.newInstanceFollower,
178 autoInstanceFollowing: this.autoInstanceFollowing
168 } 179 }
169 } 180 }
170} 181}
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index f38cd7e78..ccb81b891 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -16,6 +16,7 @@ import { ActorModel } from '../activitypub/actor'
16import { ActorFollowModel } from '../activitypub/actor-follow' 16import { ActorFollowModel } from '../activitypub/actor-follow'
17import { AvatarModel } from '../avatar/avatar' 17import { AvatarModel } from '../avatar/avatar'
18import { ServerModel } from '../server/server' 18import { ServerModel } from '../server/server'
19import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user'
19 20
20enum ScopeNames { 21enum ScopeNames {
21 WITH_ALL = 'WITH_ALL' 22 WITH_ALL = 'WITH_ALL'
@@ -134,13 +135,18 @@ function buildAccountInclude (required: boolean, withActor = false) {
134 ] 135 ]
135 }, 136 },
136 { 137 {
137 attributes: [ 'preferredUsername' ], 138 attributes: [ 'preferredUsername', 'type' ],
138 model: ActorModel.unscoped(), 139 model: ActorModel.unscoped(),
139 required: true, 140 required: true,
140 as: 'ActorFollowing', 141 as: 'ActorFollowing',
141 include: [ 142 include: [
142 buildChannelInclude(false), 143 buildChannelInclude(false),
143 buildAccountInclude(false) 144 buildAccountInclude(false),
145 {
146 attributes: [ 'host' ],
147 model: ServerModel.unscoped(),
148 required: false
149 }
144 ] 150 ]
145 } 151 }
146 ] 152 ]
@@ -371,7 +377,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
371 return UserNotificationModel.update({ read: true }, query) 377 return UserNotificationModel.update({ read: true }, query)
372 } 378 }
373 379
374 toFormattedJSON (): UserNotification { 380 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
375 const video = this.Video 381 const video = this.Video
376 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) 382 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
377 : undefined 383 : undefined
@@ -403,6 +409,11 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
403 409
404 const account = this.Account ? this.formatActor(this.Account) : undefined 410 const account = this.Account ? this.formatActor(this.Account) : undefined
405 411
412 const actorFollowingType = {
413 Application: 'instance' as 'instance',
414 Group: 'channel' as 'channel',
415 Person: 'account' as 'account'
416 }
406 const actorFollow = this.ActorFollow ? { 417 const actorFollow = this.ActorFollow ? {
407 id: this.ActorFollow.id, 418 id: this.ActorFollow.id,
408 state: this.ActorFollow.state, 419 state: this.ActorFollow.state,
@@ -414,9 +425,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
414 host: this.ActorFollow.ActorFollower.getHost() 425 host: this.ActorFollow.ActorFollower.getHost()
415 }, 426 },
416 following: { 427 following: {
417 type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account', 428 type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
418 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), 429 displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
419 name: this.ActorFollow.ActorFollowing.preferredUsername 430 name: this.ActorFollow.ActorFollowing.preferredUsername,
431 host: this.ActorFollow.ActorFollowing.getHost()
420 } 432 }
421 } : undefined 433 } : undefined
422 434
@@ -436,7 +448,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
436 } 448 }
437 } 449 }
438 450
439 private formatVideo (video: VideoModel) { 451 formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) {
440 return { 452 return {
441 id: video.id, 453 id: video.id,
442 uuid: video.uuid, 454 uuid: video.uuid,
@@ -444,7 +456,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
444 } 456 }
445 } 457 }
446 458
447 private formatActor (accountOrChannel: AccountModel | VideoChannelModel) { 459 formatActor (
460 this: UserNotificationModelForApi,
461 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
462 ) {
448 const avatar = accountOrChannel.Actor.Avatar 463 const avatar = accountOrChannel.Actor.Avatar
449 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } 464 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
450 : undefined 465 : undefined
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts
index a862fc45f..3fe4c8db1 100644
--- a/server/models/account/user-video-history.ts
+++ b/server/models/account/user-video-history.ts
@@ -1,7 +1,8 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoModel } from '../video/video' 2import { VideoModel } from '../video/video'
3import { UserModel } from './user' 3import { UserModel } from './user'
4import { Transaction, Op, DestroyOptions } from 'sequelize' 4import { DestroyOptions, Op, Transaction } from 'sequelize'
5import { MUserAccountId, MUserId } from '@server/typings/models'
5 6
6@Table({ 7@Table({
7 tableName: 'userVideoHistory', 8 tableName: 'userVideoHistory',
@@ -54,7 +55,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
54 }) 55 })
55 User: UserModel 56 User: UserModel
56 57
57 static listForApi (user: UserModel, start: number, count: number) { 58 static listForApi (user: MUserAccountId, start: number, count: number) {
58 return VideoModel.listForApi({ 59 return VideoModel.listForApi({
59 start, 60 start,
60 count, 61 count,
@@ -67,7 +68,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
67 }) 68 })
68 } 69 }
69 70
70 static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { 71 static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) {
71 const query: DestroyOptions = { 72 const query: DestroyOptions = {
72 where: { 73 where: {
73 userId: user.id 74 userId: user.id
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 0041bf577..451e1fd6b 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -22,6 +22,7 @@ import {
22import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' 22import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
23import { User, UserRole } from '../../../shared/models/users' 23import { User, UserRole } from '../../../shared/models/users'
24import { 24import {
25 isNoInstanceConfigWarningModal,
25 isUserAdminFlagsValid, 26 isUserAdminFlagsValid,
26 isUserAutoPlayVideoValid, 27 isUserAutoPlayVideoValid,
27 isUserBlockedReasonValid, 28 isUserBlockedReasonValid,
@@ -35,7 +36,8 @@ import {
35 isUserVideoQuotaDailyValid, 36 isUserVideoQuotaDailyValid,
36 isUserVideoQuotaValid, 37 isUserVideoQuotaValid,
37 isUserVideosHistoryEnabledValid, 38 isUserVideosHistoryEnabledValid,
38 isUserWebTorrentEnabledValid 39 isUserWebTorrentEnabledValid,
40 isNoWelcomeModal
39} from '../../helpers/custom-validators/users' 41} from '../../helpers/custom-validators/users'
40import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 42import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
41import { OAuthTokenModel } from '../oauth/oauth-token' 43import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -54,6 +56,14 @@ import { VideoImportModel } from '../video/video-import'
54import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' 56import { UserAdminFlag } from '../../../shared/models/users/user-flag.model'
55import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 57import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
56import { getThemeOrDefault } from '../../lib/plugins/theme-utils' 58import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
59import * as Bluebird from 'bluebird'
60import {
61 MUserDefault,
62 MUserFormattable,
63 MUserId,
64 MUserNotifSettingChannelDefault,
65 MUserWithNotificationSetting
66} from '@server/typings/models'
57 67
58enum ScopeNames { 68enum ScopeNames {
59 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' 69 WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -195,6 +205,24 @@ export class UserModel extends Model<UserModel> {
195 @Column 205 @Column
196 theme: string 206 theme: string
197 207
208 @AllowNull(false)
209 @Default(false)
210 @Is(
211 'UserNoInstanceConfigWarningModal',
212 value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal')
213 )
214 @Column
215 noInstanceConfigWarningModal: boolean
216
217 @AllowNull(false)
218 @Default(false)
219 @Is(
220 'UserNoInstanceConfigWarningModal',
221 value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal')
222 )
223 @Column
224 noWelcomeModal: boolean
225
198 @CreatedAt 226 @CreatedAt
199 createdAt: Date 227 createdAt: Date
200 228
@@ -303,7 +331,7 @@ export class UserModel extends Model<UserModel> {
303 }) 331 })
304 } 332 }
305 333
306 static listWithRight (right: UserRight) { 334 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
307 const roles = Object.keys(USER_ROLE_LABELS) 335 const roles = Object.keys(USER_ROLE_LABELS)
308 .map(k => parseInt(k, 10) as UserRole) 336 .map(k => parseInt(k, 10) as UserRole)
309 .filter(role => hasUserRight(role, right)) 337 .filter(role => hasUserRight(role, right))
@@ -319,7 +347,7 @@ export class UserModel extends Model<UserModel> {
319 return UserModel.findAll(query) 347 return UserModel.findAll(query)
320 } 348 }
321 349
322 static listUserSubscribersOf (actorId: number) { 350 static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> {
323 const query = { 351 const query = {
324 include: [ 352 include: [
325 { 353 {
@@ -358,7 +386,7 @@ export class UserModel extends Model<UserModel> {
358 return UserModel.unscoped().findAll(query) 386 return UserModel.unscoped().findAll(query)
359 } 387 }
360 388
361 static listByUsernames (usernames: string[]) { 389 static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> {
362 const query = { 390 const query = {
363 where: { 391 where: {
364 username: usernames 392 username: usernames
@@ -368,11 +396,11 @@ export class UserModel extends Model<UserModel> {
368 return UserModel.findAll(query) 396 return UserModel.findAll(query)
369 } 397 }
370 398
371 static loadById (id: number) { 399 static loadById (id: number): Bluebird<MUserDefault> {
372 return UserModel.findByPk(id) 400 return UserModel.findByPk(id)
373 } 401 }
374 402
375 static loadByUsername (username: string) { 403 static loadByUsername (username: string): Bluebird<MUserDefault> {
376 const query = { 404 const query = {
377 where: { 405 where: {
378 username: { [ Op.iLike ]: username } 406 username: { [ Op.iLike ]: username }
@@ -382,7 +410,7 @@ export class UserModel extends Model<UserModel> {
382 return UserModel.findOne(query) 410 return UserModel.findOne(query)
383 } 411 }
384 412
385 static loadByUsernameAndPopulateChannels (username: string) { 413 static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> {
386 const query = { 414 const query = {
387 where: { 415 where: {
388 username: { [ Op.iLike ]: username } 416 username: { [ Op.iLike ]: username }
@@ -392,7 +420,7 @@ export class UserModel extends Model<UserModel> {
392 return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) 420 return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query)
393 } 421 }
394 422
395 static loadByEmail (email: string) { 423 static loadByEmail (email: string): Bluebird<MUserDefault> {
396 const query = { 424 const query = {
397 where: { 425 where: {
398 email 426 email
@@ -402,7 +430,7 @@ export class UserModel extends Model<UserModel> {
402 return UserModel.findOne(query) 430 return UserModel.findOne(query)
403 } 431 }
404 432
405 static loadByUsernameOrEmail (username: string, email?: string) { 433 static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> {
406 if (!email) email = username 434 if (!email) email = username
407 435
408 const query = { 436 const query = {
@@ -414,7 +442,7 @@ export class UserModel extends Model<UserModel> {
414 return UserModel.findOne(query) 442 return UserModel.findOne(query)
415 } 443 }
416 444
417 static loadByVideoId (videoId: number) { 445 static loadByVideoId (videoId: number): Bluebird<MUserDefault> {
418 const query = { 446 const query = {
419 include: [ 447 include: [
420 { 448 {
@@ -445,7 +473,7 @@ export class UserModel extends Model<UserModel> {
445 return UserModel.findOne(query) 473 return UserModel.findOne(query)
446 } 474 }
447 475
448 static loadByVideoImportId (videoImportId: number) { 476 static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> {
449 const query = { 477 const query = {
450 include: [ 478 include: [
451 { 479 {
@@ -462,7 +490,7 @@ export class UserModel extends Model<UserModel> {
462 return UserModel.findOne(query) 490 return UserModel.findOne(query)
463 } 491 }
464 492
465 static loadByChannelActorId (videoChannelActorId: number) { 493 static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> {
466 const query = { 494 const query = {
467 include: [ 495 include: [
468 { 496 {
@@ -486,7 +514,7 @@ export class UserModel extends Model<UserModel> {
486 return UserModel.findOne(query) 514 return UserModel.findOne(query)
487 } 515 }
488 516
489 static loadByAccountActorId (accountActorId: number) { 517 static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> {
490 const query = { 518 const query = {
491 include: [ 519 include: [
492 { 520 {
@@ -503,7 +531,7 @@ export class UserModel extends Model<UserModel> {
503 return UserModel.findOne(query) 531 return UserModel.findOne(query)
504 } 532 }
505 533
506 static getOriginalVideoFileTotalFromUser (user: UserModel) { 534 static getOriginalVideoFileTotalFromUser (user: MUserId) {
507 // Don't use sequelize because we need to use a sub query 535 // Don't use sequelize because we need to use a sub query
508 const query = UserModel.generateUserQuotaBaseSQL() 536 const query = UserModel.generateUserQuotaBaseSQL()
509 537
@@ -511,7 +539,7 @@ export class UserModel extends Model<UserModel> {
511 } 539 }
512 540
513 // Returns cumulative size of all video files uploaded in the last 24 hours. 541 // Returns cumulative size of all video files uploaded in the last 24 hours.
514 static getOriginalVideoFileTotalDailyFromUser (user: UserModel) { 542 static getOriginalVideoFileTotalDailyFromUser (user: MUserId) {
515 // Don't use sequelize because we need to use a sub query 543 // Don't use sequelize because we need to use a sub query
516 const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') 544 const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'')
517 545
@@ -552,38 +580,52 @@ export class UserModel extends Model<UserModel> {
552 return comparePassword(password, this.password) 580 return comparePassword(password, this.password)
553 } 581 }
554 582
555 toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User { 583 toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User {
556 const videoQuotaUsed = this.get('videoQuotaUsed') 584 const videoQuotaUsed = this.get('videoQuotaUsed')
557 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') 585 const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily')
558 586
559 const json = { 587 const json: User = {
560 id: this.id, 588 id: this.id,
561 username: this.username, 589 username: this.username,
562 email: this.email, 590 email: this.email,
591 theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME),
592
563 pendingEmail: this.pendingEmail, 593 pendingEmail: this.pendingEmail,
564 emailVerified: this.emailVerified, 594 emailVerified: this.emailVerified,
595
565 nsfwPolicy: this.nsfwPolicy, 596 nsfwPolicy: this.nsfwPolicy,
566 webTorrentEnabled: this.webTorrentEnabled, 597 webTorrentEnabled: this.webTorrentEnabled,
567 videosHistoryEnabled: this.videosHistoryEnabled, 598 videosHistoryEnabled: this.videosHistoryEnabled,
568 autoPlayVideo: this.autoPlayVideo, 599 autoPlayVideo: this.autoPlayVideo,
569 videoLanguages: this.videoLanguages, 600 videoLanguages: this.videoLanguages,
601
570 role: this.role, 602 role: this.role,
571 theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME),
572 roleLabel: USER_ROLE_LABELS[ this.role ], 603 roleLabel: USER_ROLE_LABELS[ this.role ],
604
573 videoQuota: this.videoQuota, 605 videoQuota: this.videoQuota,
574 videoQuotaDaily: this.videoQuotaDaily, 606 videoQuotaDaily: this.videoQuotaDaily,
575 createdAt: this.createdAt, 607 videoQuotaUsed: videoQuotaUsed !== undefined
608 ? parseInt(videoQuotaUsed + '', 10)
609 : undefined,
610 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
611 ? parseInt(videoQuotaUsedDaily + '', 10)
612 : undefined,
613
614 noInstanceConfigWarningModal: this.noInstanceConfigWarningModal,
615 noWelcomeModal: this.noWelcomeModal,
616
576 blocked: this.blocked, 617 blocked: this.blocked,
577 blockedReason: this.blockedReason, 618 blockedReason: this.blockedReason,
619
578 account: this.Account.toFormattedJSON(), 620 account: this.Account.toFormattedJSON(),
579 notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, 621
622 notificationSettings: this.NotificationSetting
623 ? this.NotificationSetting.toFormattedJSON()
624 : undefined,
625
580 videoChannels: [], 626 videoChannels: [],
581 videoQuotaUsed: videoQuotaUsed !== undefined 627
582 ? parseInt(videoQuotaUsed + '', 10) 628 createdAt: this.createdAt
583 : undefined,
584 videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined
585 ? parseInt(videoQuotaUsedDaily + '', 10)
586 : undefined
587 } 629 }
588 630
589 if (parameters.withAdminFlags) { 631 if (parameters.withAdminFlags) {
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts
index 51b09e09b..8498692f0 100644
--- a/server/models/activitypub/actor-follow.ts
+++ b/server/models/activitypub/actor-follow.ts
@@ -1,5 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { values } from 'lodash' 2import { values, difference } from 'lodash'
3import { 3import {
4 AfterCreate, 4 AfterCreate,
5 AfterDestroy, 5 AfterDestroy,
@@ -21,13 +21,20 @@ import { FollowState } from '../../../shared/models/actors'
21import { ActorFollow } from '../../../shared/models/actors/follow.model' 21import { ActorFollow } from '../../../shared/models/actors/follow.model'
22import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
23import { getServerActor } from '../../helpers/utils' 23import { getServerActor } from '../../helpers/utils'
24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES } from '../../initializers/constants' 24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants'
25import { ServerModel } from '../server/server' 25import { ServerModel } from '../server/server'
26import { createSafeIn, getSort } from '../utils' 26import { createSafeIn, getSort } from '../utils'
27import { ActorModel, unusedActorAttributesForAPI } from './actor' 27import { ActorModel, unusedActorAttributesForAPI } from './actor'
28import { VideoChannelModel } from '../video/video-channel' 28import { VideoChannelModel } from '../video/video-channel'
29import { AccountModel } from '../account/account' 29import { AccountModel } from '../account/account'
30import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize' 30import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize'
31import {
32 MActorFollowActorsDefault,
33 MActorFollowActorsDefaultSubscription,
34 MActorFollowFollowingHost,
35 MActorFollowFormattable,
36 MActorFollowSubscriptions
37} from '@server/typings/models'
31 38
32@Table({ 39@Table({
33 tableName: 'actorFollow', 40 tableName: 'actorFollow',
@@ -143,7 +150,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
143 if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) 150 if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
144 } 151 }
145 152
146 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) { 153 static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> {
147 const query = { 154 const query = {
148 where: { 155 where: {
149 actorId, 156 actorId,
@@ -167,7 +174,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
167 return ActorFollowModel.findOne(query) 174 return ActorFollowModel.findOne(query)
168 } 175 }
169 176
170 static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) { 177 static loadByActorAndTargetNameAndHostForAPI (
178 actorId: number,
179 targetName: string,
180 targetHost: string,
181 t?: Transaction
182 ): Bluebird<MActorFollowActorsDefaultSubscription> {
171 const actorFollowingPartInclude: IncludeOptions = { 183 const actorFollowingPartInclude: IncludeOptions = {
172 model: ActorModel, 184 model: ActorModel,
173 required: true, 185 required: true,
@@ -220,7 +232,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
220 }) 232 })
221 } 233 }
222 234
223 static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) { 235 static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird<MActorFollowFollowingHost[]> {
224 const whereTab = targets 236 const whereTab = targets
225 .map(t => { 237 .map(t => {
226 if (t.host) { 238 if (t.host) {
@@ -314,7 +326,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
314 ] 326 ]
315 } 327 }
316 328
317 return ActorFollowModel.findAndCountAll(query) 329 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
318 .then(({ rows, count }) => { 330 .then(({ rows, count }) => {
319 return { 331 return {
320 data: rows, 332 data: rows,
@@ -357,7 +369,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
357 ] 369 ]
358 } 370 }
359 371
360 return ActorFollowModel.findAndCountAll(query) 372 return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query)
361 .then(({ rows, count }) => { 373 .then(({ rows, count }) => {
362 return { 374 return {
363 data: rows, 375 data: rows,
@@ -414,7 +426,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
414 ] 426 ]
415 } 427 }
416 428
417 return ActorFollowModel.findAndCountAll(query) 429 return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query)
418 .then(({ rows, count }) => { 430 .then(({ rows, count }) => {
419 return { 431 return {
420 data: rows.map(r => r.ActorFollowing.VideoChannel), 432 data: rows.map(r => r.ActorFollowing.VideoChannel),
@@ -423,6 +435,45 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
423 }) 435 })
424 } 436 }
425 437
438 static async keepUnfollowedInstance (hosts: string[]) {
439 const followerId = (await getServerActor()).id
440
441 const query = {
442 attributes: [ 'id' ],
443 where: {
444 actorId: followerId
445 },
446 include: [
447 {
448 attributes: [ 'id' ],
449 model: ActorModel.unscoped(),
450 required: true,
451 as: 'ActorFollowing',
452 where: {
453 preferredUsername: SERVER_ACTOR_NAME
454 },
455 include: [
456 {
457 attributes: [ 'host' ],
458 model: ServerModel.unscoped(),
459 required: true,
460 where: {
461 host: {
462 [Op.in]: hosts
463 }
464 }
465 }
466 ]
467 }
468 ]
469 }
470
471 const res = await ActorFollowModel.findAll(query)
472 const followedHosts = res.map(row => row.ActorFollowing.Server.host)
473
474 return difference(hosts, followedHosts)
475 }
476
426 static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { 477 static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) {
427 return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) 478 return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count)
428 } 479 }
@@ -569,7 +620,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
569 return ActorFollowModel.findAll(query) 620 return ActorFollowModel.findAll(query)
570 } 621 }
571 622
572 toFormattedJSON (): ActorFollow { 623 toFormattedJSON (this: MActorFollowFormattable): ActorFollow {
573 const follower = this.ActorFollower.toFormattedJSON() 624 const follower = this.ActorFollower.toFormattedJSON()
574 const following = this.ActorFollowing.toFormattedJSON() 625 const following = this.ActorFollowing.toFormattedJSON()
575 626
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 9cc53f78a..05de1905d 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -36,6 +36,17 @@ import { isOutdated, throwIfNotValid } from '../utils'
36import { VideoChannelModel } from '../video/video-channel' 36import { VideoChannelModel } from '../video/video-channel'
37import { ActorFollowModel } from './actor-follow' 37import { ActorFollowModel } from './actor-follow'
38import { VideoModel } from '../video/video' 38import { VideoModel } from '../video/video'
39import {
40 MActor,
41 MActorAccountChannelId,
42 MActorAP,
43 MActorFormattable,
44 MActorFull,
45 MActorHost,
46 MActorServer,
47 MActorSummaryFormattable
48} from '../../typings/models'
49import * as Bluebird from 'bluebird'
39 50
40enum ScopeNames { 51enum ScopeNames {
41 FULL = 'FULL' 52 FULL = 'FULL'
@@ -163,8 +174,8 @@ export class ActorModel extends Model<ActorModel> {
163 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 174 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
164 inboxUrl: string 175 inboxUrl: string
165 176
166 @AllowNull(false) 177 @AllowNull(true)
167 @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) 178 @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true))
168 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 179 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
169 outboxUrl: string 180 outboxUrl: string
170 181
@@ -173,13 +184,13 @@ export class ActorModel extends Model<ActorModel> {
173 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 184 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
174 sharedInboxUrl: string 185 sharedInboxUrl: string
175 186
176 @AllowNull(false) 187 @AllowNull(true)
177 @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) 188 @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true))
178 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 189 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
179 followersUrl: string 190 followersUrl: string
180 191
181 @AllowNull(false) 192 @AllowNull(true)
182 @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) 193 @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true))
183 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) 194 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max))
184 followingUrl: string 195 followingUrl: string
185 196
@@ -252,11 +263,15 @@ export class ActorModel extends Model<ActorModel> {
252 }) 263 })
253 VideoChannel: VideoChannelModel 264 VideoChannel: VideoChannelModel
254 265
255 static load (id: number) { 266 static load (id: number): Bluebird<MActor> {
256 return ActorModel.unscoped().findByPk(id) 267 return ActorModel.unscoped().findByPk(id)
257 } 268 }
258 269
259 static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { 270 static loadFull (id: number): Bluebird<MActorFull> {
271 return ActorModel.scope(ScopeNames.FULL).findByPk(id)
272 }
273
274 static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> {
260 const query = { 275 const query = {
261 include: [ 276 include: [
262 { 277 {
@@ -300,7 +315,7 @@ export class ActorModel extends Model<ActorModel> {
300 .then(a => !!a) 315 .then(a => !!a)
301 } 316 }
302 317
303 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { 318 static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> {
304 const query = { 319 const query = {
305 where: { 320 where: {
306 followersUrl: { 321 followersUrl: {
@@ -313,7 +328,7 @@ export class ActorModel extends Model<ActorModel> {
313 return ActorModel.scope(ScopeNames.FULL).findAll(query) 328 return ActorModel.scope(ScopeNames.FULL).findAll(query)
314 } 329 }
315 330
316 static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { 331 static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
317 const query = { 332 const query = {
318 where: { 333 where: {
319 preferredUsername, 334 preferredUsername,
@@ -325,7 +340,7 @@ export class ActorModel extends Model<ActorModel> {
325 return ActorModel.scope(ScopeNames.FULL).findOne(query) 340 return ActorModel.scope(ScopeNames.FULL).findOne(query)
326 } 341 }
327 342
328 static loadByNameAndHost (preferredUsername: string, host: string) { 343 static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> {
329 const query = { 344 const query = {
330 where: { 345 where: {
331 preferredUsername 346 preferredUsername
@@ -344,7 +359,7 @@ export class ActorModel extends Model<ActorModel> {
344 return ActorModel.scope(ScopeNames.FULL).findOne(query) 359 return ActorModel.scope(ScopeNames.FULL).findOne(query)
345 } 360 }
346 361
347 static loadByUrl (url: string, transaction?: Sequelize.Transaction) { 362 static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> {
348 const query = { 363 const query = {
349 where: { 364 where: {
350 url 365 url
@@ -367,7 +382,7 @@ export class ActorModel extends Model<ActorModel> {
367 return ActorModel.unscoped().findOne(query) 382 return ActorModel.unscoped().findOne(query)
368 } 383 }
369 384
370 static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) { 385 static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> {
371 const query = { 386 const query = {
372 where: { 387 where: {
373 url 388 url
@@ -387,35 +402,35 @@ export class ActorModel extends Model<ActorModel> {
387 }) 402 })
388 } 403 }
389 404
390 toFormattedJSON () { 405 toFormattedSummaryJSON (this: MActorSummaryFormattable) {
391 let avatar: Avatar = null 406 let avatar: Avatar = null
392 if (this.Avatar) { 407 if (this.Avatar) {
393 avatar = this.Avatar.toFormattedJSON() 408 avatar = this.Avatar.toFormattedJSON()
394 } 409 }
395 410
396 return { 411 return {
397 id: this.id,
398 url: this.url, 412 url: this.url,
399 name: this.preferredUsername, 413 name: this.preferredUsername,
400 host: this.getHost(), 414 host: this.getHost(),
415 avatar
416 }
417 }
418
419 toFormattedJSON (this: MActorFormattable) {
420 const base = this.toFormattedSummaryJSON()
421
422 return Object.assign(base, {
423 id: this.id,
401 hostRedundancyAllowed: this.getRedundancyAllowed(), 424 hostRedundancyAllowed: this.getRedundancyAllowed(),
402 followingCount: this.followingCount, 425 followingCount: this.followingCount,
403 followersCount: this.followersCount, 426 followersCount: this.followersCount,
404 avatar,
405 createdAt: this.createdAt, 427 createdAt: this.createdAt,
406 updatedAt: this.updatedAt 428 updatedAt: this.updatedAt
407 } 429 })
408 } 430 }
409 431
410 toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') { 432 toActivityPubObject (this: MActorAP, name: string) {
411 let activityPubType 433 let activityPubType
412 if (type === 'Account') {
413 activityPubType = 'Person' as 'Person'
414 } else if (type === 'Application') {
415 activityPubType = 'Application' as 'Application'
416 } else { // VideoChannel
417 activityPubType = 'Group' as 'Group'
418 }
419 434
420 let icon = undefined 435 let icon = undefined
421 if (this.avatarId) { 436 if (this.avatarId) {
@@ -428,7 +443,7 @@ export class ActorModel extends Model<ActorModel> {
428 } 443 }
429 444
430 const json = { 445 const json = {
431 type: activityPubType, 446 type: this.type,
432 id: this.url, 447 id: this.url,
433 following: this.getFollowingUrl(), 448 following: this.getFollowingUrl(),
434 followers: this.getFollowersUrl(), 449 followers: this.getFollowersUrl(),
@@ -494,7 +509,7 @@ export class ActorModel extends Model<ActorModel> {
494 return this.serverId === null 509 return this.serverId === null
495 } 510 }
496 511
497 getWebfingerUrl () { 512 getWebfingerUrl (this: MActorServer) {
498 return 'acct:' + this.preferredUsername + '@' + this.getHost() 513 return 'acct:' + this.preferredUsername + '@' + this.getHost()
499 } 514 }
500 515
@@ -502,7 +517,7 @@ export class ActorModel extends Model<ActorModel> {
502 return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername 517 return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername
503 } 518 }
504 519
505 getHost () { 520 getHost (this: MActorHost) {
506 return this.Server ? this.Server.host : WEBSERVER.HOST 521 return this.Server ? this.Server.host : WEBSERVER.HOST
507 } 522 }
508 523
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts
index b40144592..950e4b181 100644
--- a/server/models/avatar/avatar.ts
+++ b/server/models/avatar/avatar.ts
@@ -7,6 +7,7 @@ import { remove } from 'fs-extra'
7import { CONFIG } from '../../initializers/config' 7import { CONFIG } from '../../initializers/config'
8import { throwIfNotValid } from '../utils' 8import { throwIfNotValid } from '../utils'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { MAvatarFormattable } from '@server/typings/models'
10 11
11@Table({ 12@Table({
12 tableName: 'avatar', 13 tableName: 'avatar',
@@ -57,7 +58,7 @@ export class AvatarModel extends Model<AvatarModel> {
57 return AvatarModel.findOne(query) 58 return AvatarModel.findOne(query)
58 } 59 }
59 60
60 toFormattedJSON (): Avatar { 61 toFormattedJSON (this: MAvatarFormattable): Avatar {
61 return { 62 return {
62 path: this.getStaticPath(), 63 path: this.getStaticPath(),
63 createdAt: this.createdAt, 64 createdAt: this.createdAt,
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index 903d551df..b680be237 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -18,6 +18,8 @@ import { Transaction } from 'sequelize'
18import { AccountModel } from '../account/account' 18import { AccountModel } from '../account/account'
19import { ActorModel } from '../activitypub/actor' 19import { ActorModel } from '../activitypub/actor'
20import { clearCacheByToken } from '../../lib/oauth-model' 20import { clearCacheByToken } from '../../lib/oauth-model'
21import * as Bluebird from 'bluebird'
22import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
21 23
22export type OAuthTokenInfo = { 24export type OAuthTokenInfo = {
23 refreshToken: string 25 refreshToken: string
@@ -160,7 +162,7 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
160 }) 162 })
161 } 163 }
162 164
163 static getByTokenAndPopulateUser (bearerToken: string) { 165 static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> {
164 const query = { 166 const query = {
165 where: { 167 where: {
166 accessToken: bearerToken 168 accessToken: bearerToken
@@ -170,13 +172,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
170 return OAuthTokenModel.scope(ScopeNames.WITH_USER) 172 return OAuthTokenModel.scope(ScopeNames.WITH_USER)
171 .findOne(query) 173 .findOne(query)
172 .then(token => { 174 .then(token => {
173 if (token) token[ 'user' ] = token.User 175 if (!token) return null
174 176
175 return token 177 return Object.assign(token, { user: token.User })
176 }) 178 })
177 } 179 }
178 180
179 static getByRefreshTokenAndPopulateUser (refreshToken: string) { 181 static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> {
180 const query = { 182 const query = {
181 where: { 183 where: {
182 refreshToken: refreshToken 184 refreshToken: refreshToken
@@ -186,12 +188,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
186 return OAuthTokenModel.scope(ScopeNames.WITH_USER) 188 return OAuthTokenModel.scope(ScopeNames.WITH_USER)
187 .findOne(query) 189 .findOne(query)
188 .then(token => { 190 .then(token => {
189 if (token) { 191 if (!token) return new OAuthTokenModel()
190 token['user'] = token.User 192
191 return token 193 return Object.assign(token, { user: token.User })
192 } else {
193 return new OAuthTokenModel()
194 }
195 }) 194 })
196 } 195 }
197 196
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 3df1c4f9c..61d9a5612 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -30,6 +30,7 @@ import * as Bluebird from 'bluebird'
30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' 30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize'
31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' 31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
32import { CONFIG } from '../../initializers/config' 32import { CONFIG } from '../../initializers/config'
33import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models'
33 34
34export enum ScopeNames { 35export enum ScopeNames {
35 WITH_VIDEO = 'WITH_VIDEO' 36 WITH_VIDEO = 'WITH_VIDEO'
@@ -166,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
166 return undefined 167 return undefined
167 } 168 }
168 169
169 static async loadLocalByFileId (videoFileId: number) { 170 static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> {
170 const actor = await getServerActor() 171 const actor = await getServerActor()
171 172
172 const query = { 173 const query = {
@@ -179,7 +180,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
179 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 180 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
180 } 181 }
181 182
182 static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { 183 static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> {
183 const actor = await getServerActor() 184 const actor = await getServerActor()
184 185
185 const query = { 186 const query = {
@@ -192,7 +193,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
192 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 193 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
193 } 194 }
194 195
195 static loadByUrl (url: string, transaction?: Transaction) { 196 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> {
196 const query = { 197 const query = {
197 where: { 198 where: {
198 url 199 url
@@ -306,7 +307,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
306 return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) 307 return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query))
307 } 308 }
308 309
309 static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) { 310 static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> {
310 const expiredDate = new Date() 311 const expiredDate = new Date()
311 expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) 312 expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs)
312 313
@@ -487,7 +488,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
487 return !!this.strategy 488 return !!this.strategy
488 } 489 }
489 490
490 toActivityPubObject (): CacheFileObject { 491 toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject {
491 if (this.VideoStreamingPlaylist) { 492 if (this.VideoStreamingPlaylist) {
492 return { 493 return {
493 id: this.url, 494 id: this.url,
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index a15f9a7e2..d094da1f5 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -11,6 +11,8 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' 11import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model'
12import { FindAndCountOptions, json } from 'sequelize' 12import { FindAndCountOptions, json } from 'sequelize'
13import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' 13import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model'
14import * as Bluebird from 'bluebird'
15import { MPlugin, MPluginFormattable } from '@server/typings/models'
14 16
15@DefaultScope(() => ({ 17@DefaultScope(() => ({
16 attributes: { 18 attributes: {
@@ -85,7 +87,7 @@ export class PluginModel extends Model<PluginModel> {
85 @UpdatedAt 87 @UpdatedAt
86 updatedAt: Date 88 updatedAt: Date
87 89
88 static listEnabledPluginsAndThemes () { 90 static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> {
89 const query = { 91 const query = {
90 where: { 92 where: {
91 enabled: true, 93 enabled: true,
@@ -96,7 +98,7 @@ export class PluginModel extends Model<PluginModel> {
96 return PluginModel.findAll(query) 98 return PluginModel.findAll(query)
97 } 99 }
98 100
99 static loadByNpmName (npmName: string) { 101 static loadByNpmName (npmName: string): Bluebird<MPlugin> {
100 const name = this.normalizePluginName(npmName) 102 const name = this.normalizePluginName(npmName)
101 const type = this.getTypeFromNpmName(npmName) 103 const type = this.getTypeFromNpmName(npmName)
102 104
@@ -206,13 +208,13 @@ export class PluginModel extends Model<PluginModel> {
206 if (options.pluginType) query.where['type'] = options.pluginType 208 if (options.pluginType) query.where['type'] = options.pluginType
207 209
208 return PluginModel 210 return PluginModel
209 .findAndCountAll(query) 211 .findAndCountAll<MPlugin>(query)
210 .then(({ rows, count }) => { 212 .then(({ rows, count }) => {
211 return { total: count, data: rows } 213 return { total: count, data: rows }
212 }) 214 })
213 } 215 }
214 216
215 static listInstalled () { 217 static listInstalled (): Bluebird<MPlugin[]> {
216 const query = { 218 const query = {
217 where: { 219 where: {
218 uninstalled: false 220 uninstalled: false
@@ -251,7 +253,7 @@ export class PluginModel extends Model<PluginModel> {
251 return result 253 return result
252 } 254 }
253 255
254 toFormattedJSON (): PeerTubePlugin { 256 toFormattedJSON (this: MPluginFormattable): PeerTubePlugin {
255 return { 257 return {
256 name: this.name, 258 name: this.name,
257 type: this.type, 259 type: this.type,
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 5138b0f76..3e9687191 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account'
3import { ServerModel } from './server' 3import { ServerModel } from './server'
4import { ServerBlock } from '../../../shared/models/blocklist' 4import { ServerBlock } from '../../../shared/models/blocklist'
5import { getSort } from '../utils' 5import { getSort } from '../utils'
6import * as Bluebird from 'bluebird'
7import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNT = 'WITH_ACCOUNT', 10 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -73,7 +75,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
73 }) 75 })
74 BlockedServer: ServerModel 76 BlockedServer: ServerModel
75 77
76 static loadByAccountAndHost (accountId: number, host: string) { 78 static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> {
77 const query = { 79 const query = {
78 where: { 80 where: {
79 accountId 81 accountId
@@ -104,13 +106,13 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
104 106
105 return ServerBlocklistModel 107 return ServerBlocklistModel
106 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) 108 .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ])
107 .findAndCountAll(query) 109 .findAndCountAll<MServerBlocklistAccountServer>(query)
108 .then(({ rows, count }) => { 110 .then(({ rows, count }) => {
109 return { total: count, data: rows } 111 return { total: count, data: rows }
110 }) 112 })
111 } 113 }
112 114
113 toFormattedJSON (): ServerBlock { 115 toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock {
114 return { 116 return {
115 byAccount: this.ByAccount.toFormattedJSON(), 117 byAccount: this.ByAccount.toFormattedJSON(),
116 blockedServer: this.BlockedServer.toFormattedJSON(), 118 blockedServer: this.BlockedServer.toFormattedJSON(),
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index 1d211f1e0..8b07115f1 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -2,8 +2,9 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat
2import { isHostValid } from '../../helpers/custom-validators/servers' 2import { isHostValid } from '../../helpers/custom-validators/servers'
3import { ActorModel } from '../activitypub/actor' 3import { ActorModel } from '../activitypub/actor'
4import { throwIfNotValid } from '../utils' 4import { throwIfNotValid } from '../utils'
5import { AccountBlocklistModel } from '../account/account-blocklist'
6import { ServerBlocklistModel } from './server-blocklist' 5import { ServerBlocklistModel } from './server-blocklist'
6import * as Bluebird from 'bluebird'
7import { MServer, MServerFormattable } from '@server/typings/models/server'
7 8
8@Table({ 9@Table({
9 tableName: 'server', 10 tableName: 'server',
@@ -50,7 +51,17 @@ export class ServerModel extends Model<ServerModel> {
50 }) 51 })
51 BlockedByAccounts: ServerBlocklistModel[] 52 BlockedByAccounts: ServerBlocklistModel[]
52 53
53 static loadByHost (host: string) { 54 static load (id: number): Bluebird<MServer> {
55 const query = {
56 where: {
57 id
58 }
59 }
60
61 return ServerModel.findOne(query)
62 }
63
64 static loadByHost (host: string): Bluebird<MServer> {
54 const query = { 65 const query = {
55 where: { 66 where: {
56 host 67 host
@@ -64,7 +75,7 @@ export class ServerModel extends Model<ServerModel> {
64 return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 75 return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0
65 } 76 }
66 77
67 toFormattedJSON () { 78 toFormattedJSON (this: MServerFormattable) {
68 return { 79 return {
69 host: this.host 80 host: this.host
70 } 81 }
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts
index 603d55692..fc2a424aa 100644
--- a/server/models/video/schedule-video-update.ts
+++ b/server/models/video/schedule-video-update.ts
@@ -2,6 +2,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Ta
2import { ScopeNames as VideoScopeNames, VideoModel } from './video' 2import { ScopeNames as VideoScopeNames, VideoModel } from './video'
3import { VideoPrivacy } from '../../../shared/models/videos' 3import { VideoPrivacy } from '../../../shared/models/videos'
4import { Op, Transaction } from 'sequelize' 4import { Op, Transaction } from 'sequelize'
5import { MScheduleVideoUpdateFormattable } from '@server/typings/models'
5 6
6@Table({ 7@Table({
7 tableName: 'scheduleVideoUpdate', 8 tableName: 'scheduleVideoUpdate',
@@ -96,7 +97,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> {
96 return ScheduleVideoUpdateModel.destroy(query) 97 return ScheduleVideoUpdateModel.destroy(query)
97 } 98 }
98 99
99 toFormattedJSON () { 100 toFormattedJSON (this: MScheduleVideoUpdateFormattable) {
100 return { 101 return {
101 updateAt: this.updateAt, 102 updateAt: this.updateAt,
102 privacy: this.privacy || undefined 103 privacy: this.privacy || undefined
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index 0fc3cfd4c..ed8df8b48 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -1,11 +1,12 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { QueryTypes, Transaction } from 'sequelize' 2import { fn, QueryTypes, Transaction, col } from 'sequelize'
3import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { isVideoTagValid } from '../../helpers/custom-validators/videos' 4import { isVideoTagValid } from '../../helpers/custom-validators/videos'
5import { throwIfNotValid } from '../utils' 5import { throwIfNotValid } from '../utils'
6import { VideoModel } from './video' 6import { VideoModel } from './video'
7import { VideoTagModel } from './video-tag' 7import { VideoTagModel } from './video-tag'
8import { VideoPrivacy, VideoState } from '../../../shared/models/videos' 8import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
9import { MTag } from '@server/typings/models'
9 10
10@Table({ 11@Table({
11 tableName: 'tag', 12 tableName: 'tag',
@@ -14,6 +15,10 @@ import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
14 { 15 {
15 fields: [ 'name' ], 16 fields: [ 'name' ],
16 unique: true 17 unique: true
18 },
19 {
20 name: 'tag_lower_name',
21 fields: [ fn('lower', col('name')) ] as any // FIXME: typings
17 } 22 }
18 ] 23 ]
19}) 24})
@@ -37,10 +42,10 @@ export class TagModel extends Model<TagModel> {
37 }) 42 })
38 Videos: VideoModel[] 43 Videos: VideoModel[]
39 44
40 static findOrCreateTags (tags: string[], transaction: Transaction) { 45 static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> {
41 if (tags === null) return [] 46 if (tags === null) return Promise.resolve([])
42 47
43 const tasks: Bluebird<TagModel>[] = [] 48 const tasks: Bluebird<MTag>[] = []
44 tags.forEach(tag => { 49 tags.forEach(tag => {
45 const query = { 50 const query = {
46 where: { 51 where: {
@@ -52,7 +57,7 @@ export class TagModel extends Model<TagModel> {
52 transaction 57 transaction
53 } 58 }
54 59
55 const promise = TagModel.findOrCreate(query) 60 const promise = TagModel.findOrCreate<MTag>(query)
56 .then(([ tagInstance ]) => tagInstance) 61 .then(([ tagInstance ]) => tagInstance)
57 tasks.push(promise) 62 tasks.push(promise)
58 }) 63 })
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 1ac7919b3..3636db18d 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -7,10 +7,13 @@ import {
7 isVideoAbuseStateValid 7 isVideoAbuseStateValid
8} from '../../helpers/custom-validators/video-abuses' 8} from '../../helpers/custom-validators/video-abuses'
9import { AccountModel } from '../account/account' 9import { AccountModel } from '../account/account'
10import { getSort, throwIfNotValid } from '../utils' 10import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
11import { VideoModel } from './video' 11import { VideoModel } from './video'
12import { VideoAbuseState } from '../../../shared' 12import { VideoAbuseState } from '../../../shared'
13import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 13import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
14import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
15import * as Bluebird from 'bluebird'
16import { literal, Op } from 'sequelize'
14 17
15@Table({ 18@Table({
16 tableName: 'videoAbuse', 19 tableName: 'videoAbuse',
@@ -73,7 +76,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
73 }) 76 })
74 Video: VideoModel 77 Video: VideoModel
75 78
76 static loadByIdAndVideoId (id: number, videoId: number) { 79 static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> {
77 const query = { 80 const query = {
78 where: { 81 where: {
79 id, 82 id,
@@ -83,11 +86,25 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
83 return VideoAbuseModel.findOne(query) 86 return VideoAbuseModel.findOne(query)
84 } 87 }
85 88
86 static listForApi (start: number, count: number, sort: string) { 89 static listForApi (parameters: {
90 start: number,
91 count: number,
92 sort: string,
93 serverAccountId: number
94 user?: MUserAccountId
95 }) {
96 const { start, count, sort, user, serverAccountId } = parameters
97 const userAccountId = user ? user.Account.id : undefined
98
87 const query = { 99 const query = {
88 offset: start, 100 offset: start,
89 limit: count, 101 limit: count,
90 order: getSort(sort), 102 order: getSort(sort),
103 where: {
104 reporterAccountId: {
105 [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')')
106 }
107 },
91 include: [ 108 include: [
92 { 109 {
93 model: AccountModel, 110 model: AccountModel,
@@ -106,7 +123,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
106 }) 123 })
107 } 124 }
108 125
109 toFormattedJSON (): VideoAbuse { 126 toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse {
110 return { 127 return {
111 id: this.id, 128 id: this.id,
112 reason: this.reason, 129 reason: this.reason,
@@ -125,7 +142,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
125 } 142 }
126 } 143 }
127 144
128 toActivityPubObject (): VideoAbuseObject { 145 toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject {
129 return { 146 return {
130 type: 'Flag' as 'Flag', 147 type: 'Flag' as 'Flag',
131 content: this.reason, 148 content: this.reason,
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index cdb725e7a..694983cb3 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,12 +1,14 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { getBlacklistSort, getSort, SortType, throwIfNotValid } from '../utils' 2import { getBlacklistSort, SortType, throwIfNotValid } from '../utils'
3import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 3import { VideoModel } from './video'
4import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' 4import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 5import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 6import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
7import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
8import { FindOptions, literal } from 'sequelize' 8import { FindOptions } from 'sequelize'
9import { ThumbnailModel } from './thumbnail' 9import { ThumbnailModel } from './thumbnail'
10import * as Bluebird from 'bluebird'
11import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/typings/models'
10 12
11@Table({ 13@Table({
12 tableName: 'videoBlacklist', 14 tableName: 'videoBlacklist',
@@ -98,7 +100,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
98 }) 100 })
99 } 101 }
100 102
101 static loadByVideoId (id: number) { 103 static loadByVideoId (id: number): Bluebird<MVideoBlacklist> {
102 const query = { 104 const query = {
103 where: { 105 where: {
104 videoId: id 106 videoId: id
@@ -108,7 +110,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
108 return VideoBlacklistModel.findOne(query) 110 return VideoBlacklistModel.findOne(query)
109 } 111 }
110 112
111 toFormattedJSON (): VideoBlacklist { 113 toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist {
112 return { 114 return {
113 id: this.id, 115 id: this.id,
114 createdAt: this.createdAt, 116 createdAt: this.createdAt,
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index a01565851..ad5801768 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -21,6 +21,8 @@ import { join } from 'path'
21import { logger } from '../../helpers/logger' 21import { logger } from '../../helpers/logger'
22import { remove } from 'fs-extra' 22import { remove } from 'fs-extra'
23import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
24import * as Bluebird from 'bluebird'
25import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models'
24 26
25export enum ScopeNames { 27export enum ScopeNames {
26 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' 28 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
@@ -30,7 +32,7 @@ export enum ScopeNames {
30 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { 32 [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: {
31 include: [ 33 include: [
32 { 34 {
33 attributes: [ 'uuid', 'remote' ], 35 attributes: [ 'id', 'uuid', 'remote' ],
34 model: VideoModel.unscoped(), 36 model: VideoModel.unscoped(),
35 required: true 37 required: true
36 } 38 }
@@ -93,7 +95,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
93 return undefined 95 return undefined
94 } 96 }
95 97
96 static loadByVideoIdAndLanguage (videoId: string | number, language: string) { 98 static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird<MVideoCaptionVideo> {
97 const videoInclude = { 99 const videoInclude = {
98 model: VideoModel.unscoped(), 100 model: VideoModel.unscoped(),
99 attributes: [ 'id', 'remote', 'uuid' ], 101 attributes: [ 'id', 'remote', 'uuid' ],
@@ -122,7 +124,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
122 .then(([ caption ]) => caption) 124 .then(([ caption ]) => caption)
123 } 125 }
124 126
125 static listVideoCaptions (videoId: number) { 127 static listVideoCaptions (videoId: number): Bluebird<MVideoCaptionVideo[]> {
126 const query = { 128 const query = {
127 order: [ [ 'language', 'ASC' ] ] as OrderItem[], 129 order: [ [ 'language', 'ASC' ] ] as OrderItem[],
128 where: { 130 where: {
@@ -152,7 +154,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
152 return this.Video.remote === false 154 return this.Video.remote === false
153 } 155 }
154 156
155 toFormattedJSON (): VideoCaption { 157 toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption {
156 return { 158 return {
157 language: { 159 language: {
158 id: this.language, 160 id: this.language,
@@ -162,15 +164,15 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
162 } 164 }
163 } 165 }
164 166
165 getCaptionStaticPath () { 167 getCaptionStaticPath (this: MVideoCaptionFormattable) {
166 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) 168 return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName())
167 } 169 }
168 170
169 getCaptionName () { 171 getCaptionName (this: MVideoCaptionFormattable) {
170 return `${this.Video.uuid}-${this.language}.vtt` 172 return `${this.Video.uuid}-${this.language}.vtt`
171 } 173 }
172 174
173 removeCaptionFile () { 175 removeCaptionFile (this: MVideoCaptionFormattable) {
174 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) 176 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
175 } 177 }
176} 178}
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
index b545a2f8c..f7a351329 100644
--- a/server/models/video/video-change-ownership.ts
+++ b/server/models/video/video-change-ownership.ts
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account'
3import { ScopeNames as VideoScopeNames, VideoModel } from './video' 3import { ScopeNames as VideoScopeNames, VideoModel } from './video'
4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' 4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
5import { getSort } from '../utils' 5import { getSort } from '../utils'
6import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership'
7import * as Bluebird from 'bluebird'
6 8
7enum ScopeNames { 9enum ScopeNames {
8 WITH_ACCOUNTS = 'WITH_ACCOUNTS', 10 WITH_ACCOUNTS = 'WITH_ACCOUNTS',
@@ -108,16 +110,16 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel>
108 110
109 return Promise.all([ 111 return Promise.all([
110 VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), 112 VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query),
111 VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query) 113 VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll<MVideoChangeOwnershipFull>(query)
112 ]).then(([ count, rows ]) => ({ total: count, data: rows })) 114 ]).then(([ count, rows ]) => ({ total: count, data: rows }))
113 } 115 }
114 116
115 static load (id: number) { 117 static load (id: number): Bluebird<MVideoChangeOwnershipFull> {
116 return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) 118 return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ])
117 .findByPk(id) 119 .findByPk(id)
118 } 120 }
119 121
120 toFormattedJSON (): VideoChangeOwnership { 122 toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership {
121 return { 123 return {
122 id: this.id, 124 id: this.id,
123 status: this.status, 125 status: this.status,
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 6241a75a3..05545bd9d 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -33,6 +33,15 @@ import { ServerModel } from '../server/server'
33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' 33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize'
34import { AvatarModel } from '../avatar/avatar' 34import { AvatarModel } from '../avatar/avatar'
35import { VideoPlaylistModel } from './video-playlist' 35import { VideoPlaylistModel } from './video-playlist'
36import * as Bluebird from 'bluebird'
37import {
38 MChannelAccountDefault,
39 MChannelActor,
40 MChannelActorAccountDefaultVideos,
41 MChannelAP,
42 MChannelFormattable,
43 MChannelSummaryFormattable
44} from '../../typings/models/video'
36 45
37// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 46// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
38const indexes: ModelIndexesOptions[] = [ 47const indexes: ModelIndexesOptions[] = [
@@ -47,7 +56,7 @@ const indexes: ModelIndexesOptions[] = [
47] 56]
48 57
49export enum ScopeNames { 58export enum ScopeNames {
50 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 59 FOR_API = 'FOR_API',
51 WITH_ACCOUNT = 'WITH_ACCOUNT', 60 WITH_ACCOUNT = 'WITH_ACCOUNT',
52 WITH_ACTOR = 'WITH_ACTOR', 61 WITH_ACTOR = 'WITH_ACTOR',
53 WITH_VIDEOS = 'WITH_VIDEOS', 62 WITH_VIDEOS = 'WITH_VIDEOS',
@@ -74,10 +83,10 @@ export type SummaryOptions = {
74@Scopes(() => ({ 83@Scopes(() => ({
75 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { 84 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
76 const base: FindOptions = { 85 const base: FindOptions = {
77 attributes: [ 'name', 'description', 'id', 'actorId' ], 86 attributes: [ 'id', 'name', 'description', 'actorId' ],
78 include: [ 87 include: [
79 { 88 {
80 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], 89 attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
81 model: ActorModel.unscoped(), 90 model: ActorModel.unscoped(),
82 required: true, 91 required: true,
83 include: [ 92 include: [
@@ -106,7 +115,7 @@ export type SummaryOptions = {
106 115
107 return base 116 return base
108 }, 117 },
109 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { 118 [ScopeNames.FOR_API]: (options: AvailableForListOptions) => {
110 // Only list local channels OR channels that are on an instance followed by actorId 119 // Only list local channels OR channels that are on an instance followed by actorId
111 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) 120 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId)
112 121
@@ -268,7 +277,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
268 } 277 }
269 278
270 const scopes = { 279 const scopes = {
271 method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ] 280 method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ]
272 } 281 }
273 return VideoChannelModel 282 return VideoChannelModel
274 .scope(scopes) 283 .scope(scopes)
@@ -278,7 +287,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
278 }) 287 })
279 } 288 }
280 289
281 static listLocalsForSitemap (sort: string) { 290 static listLocalsForSitemap (sort: string): Bluebird<MChannelActor[]> {
282 const query = { 291 const query = {
283 attributes: [ ], 292 attributes: [ ],
284 offset: 0, 293 offset: 0,
@@ -331,7 +340,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
331 } 340 }
332 341
333 const scopes = { 342 const scopes = {
334 method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId: options.actorId } as AvailableForListOptions ] 343 method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ]
335 } 344 }
336 return VideoChannelModel 345 return VideoChannelModel
337 .scope(scopes) 346 .scope(scopes)
@@ -369,13 +378,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
369 }) 378 })
370 } 379 }
371 380
372 static loadByIdAndPopulateAccount (id: number) { 381 static loadByIdAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
373 return VideoChannelModel.unscoped() 382 return VideoChannelModel.unscoped()
374 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) 383 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
375 .findByPk(id) 384 .findByPk(id)
376 } 385 }
377 386
378 static loadByIdAndAccount (id: number, accountId: number) { 387 static loadByIdAndAccount (id: number, accountId: number): Bluebird<MChannelAccountDefault> {
379 const query = { 388 const query = {
380 where: { 389 where: {
381 id, 390 id,
@@ -388,13 +397,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
388 .findOne(query) 397 .findOne(query)
389 } 398 }
390 399
391 static loadAndPopulateAccount (id: number) { 400 static loadAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> {
392 return VideoChannelModel.unscoped() 401 return VideoChannelModel.unscoped()
393 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) 402 .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ])
394 .findByPk(id) 403 .findByPk(id)
395 } 404 }
396 405
397 static loadByUrlAndPopulateAccount (url: string) { 406 static loadByUrlAndPopulateAccount (url: string): Bluebird<MChannelAccountDefault> {
398 const query = { 407 const query = {
399 include: [ 408 include: [
400 { 409 {
@@ -420,7 +429,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
420 return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) 429 return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host)
421 } 430 }
422 431
423 static loadLocalByNameAndPopulateAccount (name: string) { 432 static loadLocalByNameAndPopulateAccount (name: string): Bluebird<MChannelAccountDefault> {
424 const query = { 433 const query = {
425 include: [ 434 include: [
426 { 435 {
@@ -439,7 +448,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
439 .findOne(query) 448 .findOne(query)
440 } 449 }
441 450
442 static loadByNameAndHostAndPopulateAccount (name: string, host: string) { 451 static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird<MChannelAccountDefault> {
443 const query = { 452 const query = {
444 include: [ 453 include: [
445 { 454 {
@@ -464,7 +473,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
464 .findOne(query) 473 .findOne(query)
465 } 474 }
466 475
467 static loadAndPopulateAccountAndVideos (id: number) { 476 static loadAndPopulateAccountAndVideos (id: number): Bluebird<MChannelActorAccountDefaultVideos> {
468 const options = { 477 const options = {
469 include: [ 478 include: [
470 VideoModel 479 VideoModel
@@ -476,7 +485,20 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
476 .findByPk(id, options) 485 .findByPk(id, options)
477 } 486 }
478 487
479 toFormattedJSON (): VideoChannel { 488 toFormattedSummaryJSON (this: MChannelSummaryFormattable): VideoChannelSummary {
489 const actor = this.Actor.toFormattedSummaryJSON()
490
491 return {
492 id: this.id,
493 name: actor.name,
494 displayName: this.getDisplayName(),
495 url: actor.url,
496 host: actor.host,
497 avatar: actor.avatar
498 }
499 }
500
501 toFormattedJSON (this: MChannelFormattable): VideoChannel {
480 const actor = this.Actor.toFormattedJSON() 502 const actor = this.Actor.toFormattedJSON()
481 const videoChannel = { 503 const videoChannel = {
482 id: this.id, 504 id: this.id,
@@ -494,21 +516,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
494 return Object.assign(actor, videoChannel) 516 return Object.assign(actor, videoChannel)
495 } 517 }
496 518
497 toFormattedSummaryJSON (): VideoChannelSummary { 519 toActivityPubObject (this: MChannelAP): ActivityPubActor {
498 const actor = this.Actor.toFormattedJSON() 520 const obj = this.Actor.toActivityPubObject(this.name)
499
500 return {
501 id: this.id,
502 name: actor.name,
503 displayName: this.getDisplayName(),
504 url: actor.url,
505 host: actor.host,
506 avatar: actor.avatar
507 }
508 }
509
510 toActivityPubObject (): ActivityPubActor {
511 const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel')
512 521
513 return Object.assign(obj, { 522 return Object.assign(obj, {
514 summary: this.description, 523 summary: this.description,
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index 58b75510d..2e4220434 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -1,36 +1,32 @@
1import { 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2 AllowNull,
3 BeforeDestroy,
4 BelongsTo,
5 Column,
6 CreatedAt,
7 DataType,
8 ForeignKey,
9 Is,
10 Model,
11 Scopes,
12 Table,
13 UpdatedAt
14} from 'sequelize-typescript'
15import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' 2import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects'
16import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 3import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
17import { VideoComment } from '../../../shared/models/videos/video-comment.model' 4import { VideoComment } from '../../../shared/models/videos/video-comment.model'
18import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 5import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
19import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' 6import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
20import { sendDeleteVideoComment } from '../../lib/activitypub/send'
21import { AccountModel } from '../account/account' 7import { AccountModel } from '../account/account'
22import { ActorModel } from '../activitypub/actor' 8import { ActorModel } from '../activitypub/actor'
23import { AvatarModel } from '../avatar/avatar'
24import { ServerModel } from '../server/server'
25import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' 9import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils'
26import { VideoModel } from './video' 10import { VideoModel } from './video'
27import { VideoChannelModel } from './video-channel' 11import { VideoChannelModel } from './video-channel'
28import { getServerActor } from '../../helpers/utils' 12import { getServerActor } from '../../helpers/utils'
29import { UserModel } from '../account/user'
30import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' 13import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
31import { regexpCapture } from '../../helpers/regexp' 14import { regexpCapture } from '../../helpers/regexp'
32import { uniq } from 'lodash' 15import { uniq } from 'lodash'
33import { FindOptions, literal, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' 16import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize'
17import * as Bluebird from 'bluebird'
18import {
19 MComment,
20 MCommentAP,
21 MCommentFormattable,
22 MCommentId,
23 MCommentOwner,
24 MCommentOwnerReplyVideoLight,
25 MCommentOwnerVideo,
26 MCommentOwnerVideoFeed,
27 MCommentOwnerVideoReply
28} from '../../typings/models/video'
29import { MUserAccountId } from '@server/typings/models'
34 30
35enum ScopeNames { 31enum ScopeNames {
36 WITH_ACCOUNT = 'WITH_ACCOUNT', 32 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -68,22 +64,7 @@ enum ScopeNames {
68 [ScopeNames.WITH_ACCOUNT]: { 64 [ScopeNames.WITH_ACCOUNT]: {
69 include: [ 65 include: [
70 { 66 {
71 model: AccountModel, 67 model: AccountModel
72 include: [
73 {
74 model: ActorModel,
75 include: [
76 {
77 model: ServerModel,
78 required: false
79 },
80 {
81 model: AvatarModel,
82 required: false
83 }
84 ]
85 }
86 ]
87 } 68 }
88 ] 69 ]
89 }, 70 },
@@ -102,22 +83,12 @@ enum ScopeNames {
102 required: true, 83 required: true,
103 include: [ 84 include: [
104 { 85 {
105 model: VideoChannelModel.unscoped(), 86 model: VideoChannelModel,
106 required: true, 87 required: true,
107 include: [ 88 include: [
108 { 89 {
109 model: ActorModel,
110 required: true
111 },
112 {
113 model: AccountModel, 90 model: AccountModel,
114 required: true, 91 required: true
115 include: [
116 {
117 model: ActorModel,
118 required: true
119 }
120 ]
121 } 92 }
122 ] 93 ]
123 } 94 }
@@ -212,7 +183,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
212 }) 183 })
213 Account: AccountModel 184 Account: AccountModel
214 185
215 static loadById (id: number, t?: Transaction) { 186 static loadById (id: number, t?: Transaction): Bluebird<MComment> {
216 const query: FindOptions = { 187 const query: FindOptions = {
217 where: { 188 where: {
218 id 189 id
@@ -224,7 +195,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
224 return VideoCommentModel.findOne(query) 195 return VideoCommentModel.findOne(query)
225 } 196 }
226 197
227 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { 198 static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird<MCommentOwnerVideoReply> {
228 const query: FindOptions = { 199 const query: FindOptions = {
229 where: { 200 where: {
230 id 201 id
@@ -238,7 +209,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
238 .findOne(query) 209 .findOne(query)
239 } 210 }
240 211
241 static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) { 212 static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird<MCommentOwnerVideo> {
242 const query: FindOptions = { 213 const query: FindOptions = {
243 where: { 214 where: {
244 url 215 url
@@ -250,7 +221,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
250 return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) 221 return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query)
251 } 222 }
252 223
253 static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) { 224 static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird<MCommentOwnerReplyVideoLight> {
254 const query: FindOptions = { 225 const query: FindOptions = {
255 where: { 226 where: {
256 url 227 url
@@ -273,7 +244,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
273 start: number, 244 start: number,
274 count: number, 245 count: number,
275 sort: string, 246 sort: string,
276 user?: UserModel 247 user?: MUserAccountId
277 }) { 248 }) {
278 const { videoId, start, count, sort, user } = parameters 249 const { videoId, start, count, sort, user } = parameters
279 250
@@ -314,7 +285,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
314 static async listThreadCommentsForApi (parameters: { 285 static async listThreadCommentsForApi (parameters: {
315 videoId: number, 286 videoId: number,
316 threadId: number, 287 threadId: number,
317 user?: UserModel 288 user?: MUserAccountId
318 }) { 289 }) {
319 const { videoId, threadId, user } = parameters 290 const { videoId, threadId, user } = parameters
320 291
@@ -353,7 +324,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
353 }) 324 })
354 } 325 }
355 326
356 static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { 327 static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird<MCommentOwner[]> {
357 const query = { 328 const query = {
358 order: [ [ 'createdAt', order ] ] as Order, 329 order: [ [ 'createdAt', order ] ] as Order,
359 where: { 330 where: {
@@ -389,10 +360,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
389 transaction: t 360 transaction: t
390 } 361 }
391 362
392 return VideoCommentModel.findAndCountAll(query) 363 return VideoCommentModel.findAndCountAll<MComment>(query)
393 } 364 }
394 365
395 static listForFeed (start: number, count: number, videoId?: number) { 366 static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> {
396 const query = { 367 const query = {
397 order: [ [ 'createdAt', 'DESC' ] ] as Order, 368 order: [ [ 'createdAt', 'DESC' ] ] as Order,
398 offset: start, 369 offset: start,
@@ -506,7 +477,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
506 return uniq(result) 477 return uniq(result)
507 } 478 }
508 479
509 toFormattedJSON () { 480 toFormattedJSON (this: MCommentFormattable) {
510 return { 481 return {
511 id: this.id, 482 id: this.id,
512 url: this.url, 483 url: this.url,
@@ -521,7 +492,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
521 } as VideoComment 492 } as VideoComment
522 } 493 }
523 494
524 toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { 495 toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject {
525 let inReplyTo: string 496 let inReplyTo: string
526 // New thread, so in AS we reply to the video 497 // New thread, so in AS we reply to the video
527 if (this.inReplyToCommentId === null) { 498 if (this.inReplyToCommentId === null) {
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 05c490759..6304f741c 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -25,6 +25,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy'
25import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 25import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26import { FindOptions, QueryTypes, Transaction } from 'sequelize' 26import { FindOptions, QueryTypes, Transaction } from 'sequelize'
27import { MIMETYPES } from '../../initializers/constants' 27import { MIMETYPES } from '../../initializers/constants'
28import { MVideoFile } from '@server/typings/models'
28 29
29@Table({ 30@Table({
30 tableName: 'videoFile', 31 tableName: 'videoFile',
@@ -166,7 +167,7 @@ export class VideoFileModel extends Model<VideoFileModel> {
166 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] 167 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
167 } 168 }
168 169
169 hasSameUniqueKeysThan (other: VideoFileModel) { 170 hasSameUniqueKeysThan (other: MVideoFile) {
170 return this.fps === other.fps && 171 return this.fps === other.fps &&
171 this.resolution === other.resolution && 172 this.resolution === other.resolution &&
172 this.videoId === other.videoId 173 this.videoId === other.videoId
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index 284539def..2987aa780 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -1,6 +1,5 @@
1import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' 1import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
2import { VideoModel } from './video' 2import { VideoModel } from './video'
3import { VideoFileModel } from './video-file'
4import { 3import {
5 ActivityPlaylistInfohashesObject, 4 ActivityPlaylistInfohashesObject,
6 ActivityPlaylistSegmentHashesObject, 5 ActivityPlaylistSegmentHashesObject,
@@ -17,7 +16,9 @@ import {
17} from '../../lib/activitypub' 16} from '../../lib/activitypub'
18import { isArray } from '../../helpers/custom-validators/misc' 17import { isArray } from '../../helpers/custom-validators/misc'
19import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' 18import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model'
20import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 19import { MStreamingPlaylistRedundanciesOpt, MVideo, MVideoAP, MVideoFormattable, MVideoFormattableDetails } from '../../typings/models'
20import { MStreamingPlaylistRedundancies } from '../../typings/models/video/video-streaming-playlist'
21import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
21 22
22export type VideoFormattingJSONOptions = { 23export type VideoFormattingJSONOptions = {
23 completeDescription?: boolean 24 completeDescription?: boolean
@@ -28,7 +29,7 @@ export type VideoFormattingJSONOptions = {
28 blacklistInfo?: boolean 29 blacklistInfo?: boolean
29 } 30 }
30} 31}
31function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { 32function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
32 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined 33 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
33 34
34 const videoObject: Video = { 35 const videoObject: Video = {
@@ -102,7 +103,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting
102 return videoObject 103 return videoObject
103} 104}
104 105
105function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { 106function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails {
106 const formattedJson = video.toFormattedJSON({ 107 const formattedJson = video.toFormattedJSON({
107 additionalAttributes: { 108 additionalAttributes: {
108 scheduledUpdate: true, 109 scheduledUpdate: true,
@@ -114,7 +115,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
114 115
115 const tags = video.Tags ? video.Tags.map(t => t.name) : [] 116 const tags = video.Tags ? video.Tags.map(t => t.name) : []
116 117
117 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) 118 const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video.VideoStreamingPlaylists)
118 119
119 const detailsJson = { 120 const detailsJson = {
120 support: video.support, 121 support: video.support,
@@ -142,7 +143,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails {
142 return Object.assign(formattedJson, detailsJson) 143 return Object.assign(formattedJson, detailsJson)
143} 144}
144 145
145function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { 146function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] {
146 if (isArray(playlists) === false) return [] 147 if (isArray(playlists) === false) return []
147 148
148 return playlists 149 return playlists
@@ -161,7 +162,7 @@ function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: V
161 }) 162 })
162} 163}
163 164
164function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { 165function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] {
165 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 166 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
166 167
167 return videoFiles 168 return videoFiles
@@ -189,7 +190,7 @@ function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFil
189 }) 190 })
190} 191}
191 192
192function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { 193function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
193 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 194 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
194 if (!video.Tags) video.Tags = [] 195 if (!video.Tags) video.Tags = []
195 196
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 480a671c8..af5314ce9 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -20,6 +20,8 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help
20import { VideoImport, VideoImportState } from '../../../shared' 20import { VideoImport, VideoImportState } from '../../../shared'
21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22import { UserModel } from '../account/user' 22import { UserModel } from '../account/user'
23import * as Bluebird from 'bluebird'
24import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import'
23 25
24@DefaultScope(() => ({ 26@DefaultScope(() => ({
25 include: [ 27 include: [
@@ -28,7 +30,11 @@ import { UserModel } from '../account/user'
28 required: true 30 required: true
29 }, 31 },
30 { 32 {
31 model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), 33 model: VideoModel.scope([
34 VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
35 VideoModelScopeNames.WITH_TAGS,
36 VideoModelScopeNames.WITH_THUMBNAILS
37 ]),
32 required: false 38 required: false
33 } 39 }
34 ] 40 ]
@@ -114,7 +120,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
114 return undefined 120 return undefined
115 } 121 }
116 122
117 static loadAndPopulateVideo (id: number) { 123 static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> {
118 return VideoImportModel.findByPk(id) 124 return VideoImportModel.findByPk(id)
119 } 125 }
120 126
@@ -135,7 +141,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
135 } 141 }
136 } 142 }
137 143
138 return VideoImportModel.findAndCountAll(query) 144 return VideoImportModel.findAndCountAll<MVideoImportDefault>(query)
139 .then(({ rows, count }) => { 145 .then(({ rows, count }) => {
140 return { 146 return {
141 data: rows, 147 data: rows,
@@ -148,7 +154,7 @@ export class VideoImportModel extends Model<VideoImportModel> {
148 return this.targetUrl || this.magnetUri || this.torrentName 154 return this.targetUrl || this.magnetUri || this.torrentName
149 } 155 }
150 156
151 toFormattedJSON (): VideoImport { 157 toFormattedJSON (this: MVideoImportFormattable): VideoImport {
152 const videoFormatOptions = { 158 const videoFormatOptions = {
153 completeDescription: true, 159 completeDescription: true,
154 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } 160 additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true }
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index dd7653533..a28021313 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -21,10 +21,18 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' 21import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object'
22import * as validator from 'validator' 22import * as validator from 'validator'
23import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' 23import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize'
24import { UserModel } from '../account/user'
25import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' 24import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model'
26import { AccountModel } from '../account/account' 25import { AccountModel } from '../account/account'
27import { VideoPrivacy } from '../../../shared/models/videos' 26import { VideoPrivacy } from '../../../shared/models/videos'
27import * as Bluebird from 'bluebird'
28import {
29 MVideoPlaylistElement,
30 MVideoPlaylistElementAP,
31 MVideoPlaylistElementFormattable,
32 MVideoPlaylistElementVideoUrlPlaylistPrivacy,
33 MVideoPlaylistVideoThumbnail
34} from '@server/typings/models/video/video-playlist-element'
35import { MUserAccountId } from '@server/typings/models'
28 36
29@Table({ 37@Table({
30 tableName: 'videoPlaylistElement', 38 tableName: 'videoPlaylistElement',
@@ -116,7 +124,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
116 count: number, 124 count: number,
117 videoPlaylistId: number, 125 videoPlaylistId: number,
118 serverAccount: AccountModel, 126 serverAccount: AccountModel,
119 user?: UserModel 127 user?: MUserAccountId
120 }) { 128 }) {
121 const accountIds = [ options.serverAccount.id ] 129 const accountIds = [ options.serverAccount.id ]
122 const videoScope: (ScopeOptions | string)[] = [ 130 const videoScope: (ScopeOptions | string)[] = [
@@ -162,7 +170,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
162 ]).then(([ total, data ]) => ({ total, data })) 170 ]).then(([ total, data ]) => ({ total, data }))
163 } 171 }
164 172
165 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { 173 static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird<MVideoPlaylistElement> {
166 const query = { 174 const query = {
167 where: { 175 where: {
168 videoPlaylistId, 176 videoPlaylistId,
@@ -173,11 +181,14 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
173 return VideoPlaylistElementModel.findOne(query) 181 return VideoPlaylistElementModel.findOne(query)
174 } 182 }
175 183
176 static loadById (playlistElementId: number) { 184 static loadById (playlistElementId: number): Bluebird<MVideoPlaylistElement> {
177 return VideoPlaylistElementModel.findByPk(playlistElementId) 185 return VideoPlaylistElementModel.findByPk(playlistElementId)
178 } 186 }
179 187
180 static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { 188 static loadByPlaylistAndVideoForAP (
189 playlistId: number | string,
190 videoId: number | string
191 ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> {
181 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } 192 const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId }
182 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } 193 const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId }
183 194
@@ -218,7 +229,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
218 }) 229 })
219 } 230 }
220 231
221 static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) { 232 static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird<MVideoPlaylistVideoThumbnail> {
222 const query = { 233 const query = {
223 order: getSort('position'), 234 order: getSort('position'),
224 where: { 235 where: {
@@ -290,7 +301,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
290 return VideoPlaylistElementModel.increment({ position: by }, query) 301 return VideoPlaylistElementModel.increment({ position: by }, query)
291 } 302 }
292 303
293 getType (displayNSFW?: boolean, accountId?: number) { 304 getType (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
294 const video = this.Video 305 const video = this.Video
295 306
296 if (!video) return VideoPlaylistElementType.DELETED 307 if (!video) return VideoPlaylistElementType.DELETED
@@ -306,14 +317,17 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
306 return VideoPlaylistElementType.REGULAR 317 return VideoPlaylistElementType.REGULAR
307 } 318 }
308 319
309 getVideoElement (displayNSFW?: boolean, accountId?: number) { 320 getVideoElement (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) {
310 if (!this.Video) return null 321 if (!this.Video) return null
311 if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null 322 if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null
312 323
313 return this.Video.toFormattedJSON() 324 return this.Video.toFormattedJSON()
314 } 325 }
315 326
316 toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement { 327 toFormattedJSON (
328 this: MVideoPlaylistElementFormattable,
329 options: { displayNSFW?: boolean, accountId?: number } = {}
330 ): VideoPlaylistElement {
317 return { 331 return {
318 id: this.id, 332 id: this.id,
319 position: this.position, 333 position: this.position,
@@ -326,7 +340,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
326 } 340 }
327 } 341 }
328 342
329 toActivityPubObject (): PlaylistElementObject { 343 toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject {
330 const base: PlaylistElementObject = { 344 const base: PlaylistElementObject = {
331 id: this.url, 345 id: this.url,
332 type: 'PlaylistElement', 346 type: 'PlaylistElement',
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index c8e97c491..278d80ac0 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -43,6 +43,15 @@ import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-
43import { ThumbnailModel } from './thumbnail' 43import { ThumbnailModel } from './thumbnail'
44import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 44import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
45import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' 45import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize'
46import * as Bluebird from 'bluebird'
47import {
48 MVideoPlaylistAccountThumbnail, MVideoPlaylistAP,
49 MVideoPlaylistFormattable,
50 MVideoPlaylistFull,
51 MVideoPlaylistFullSummary,
52 MVideoPlaylistIdWithElements
53} from '../../typings/models/video/video-playlist'
54import { MThumbnail } from '../../typings/models/video/thumbnail'
46 55
47enum ScopeNames { 56enum ScopeNames {
48 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', 57 AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST',
@@ -332,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
332 }) 341 })
333 } 342 }
334 343
335 static listPlaylistIdsOf (accountId: number, videoIds: number[]) { 344 static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> {
336 const query = { 345 const query = {
337 attributes: [ 'id' ], 346 attributes: [ 'id' ],
338 where: { 347 where: {
@@ -368,7 +377,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
368 .then(e => !!e) 377 .then(e => !!e)
369 } 378 }
370 379
371 static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { 380 static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> {
372 const where = buildWhereIdOrUUID(id) 381 const where = buildWhereIdOrUUID(id)
373 382
374 const query = { 383 const query = {
@@ -381,7 +390,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
381 .findOne(query) 390 .findOne(query)
382 } 391 }
383 392
384 static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { 393 static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> {
385 const where = buildWhereIdOrUUID(id) 394 const where = buildWhereIdOrUUID(id)
386 395
387 const query = { 396 const query = {
@@ -394,7 +403,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
394 .findOne(query) 403 .findOne(query)
395 } 404 }
396 405
397 static loadByUrlAndPopulateAccount (url: string) { 406 static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> {
398 const query = { 407 const query = {
399 where: { 408 where: {
400 url 409 url
@@ -423,7 +432,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
423 return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) 432 return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query)
424 } 433 }
425 434
426 async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { 435 async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) {
427 thumbnail.videoPlaylistId = this.id 436 thumbnail.videoPlaylistId = this.id
428 437
429 this.Thumbnail = await thumbnail.save({ transaction: t }) 438 this.Thumbnail = await thumbnail.save({ transaction: t })
@@ -471,7 +480,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
471 return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) 480 return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL)
472 } 481 }
473 482
474 toFormattedJSON (): VideoPlaylist { 483 toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist {
475 return { 484 return {
476 id: this.id, 485 id: this.id,
477 uuid: this.uuid, 486 uuid: this.uuid,
@@ -501,7 +510,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
501 } 510 }
502 } 511 }
503 512
504 toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> { 513 toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> {
505 const handler = (start: number, count: number) => { 514 const handler = (start: number, count: number) => {
506 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) 515 return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t)
507 } 516 }
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index d8ed64557..9019b401a 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -8,6 +8,8 @@ import { buildLocalActorIdsIn, throwIfNotValid } from '../utils'
8import { VideoModel } from './video' 8import { VideoModel } from './video'
9import { VideoChannelModel } from './video-channel' 9import { VideoChannelModel } from './video-channel'
10import { Op, Transaction } from 'sequelize' 10import { Op, Transaction } from 'sequelize'
11import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video'
12import { MActorDefault } from '../../typings/models'
11 13
12enum ScopeNames { 14enum ScopeNames {
13 FULL = 'FULL', 15 FULL = 'FULL',
@@ -88,7 +90,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
88 }) 90 })
89 Video: VideoModel 91 Video: VideoModel
90 92
91 static load (actorId: number, videoId: number, t?: Transaction) { 93 static load (actorId: number, videoId: number, t?: Transaction): Bluebird<MVideoShareActor> {
92 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ 94 return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({
93 where: { 95 where: {
94 actorId, 96 actorId,
@@ -98,7 +100,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
98 }) 100 })
99 } 101 }
100 102
101 static loadByUrl (url: string, t: Transaction) { 103 static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> {
102 return VideoShareModel.scope(ScopeNames.FULL).findOne({ 104 return VideoShareModel.scope(ScopeNames.FULL).findOne({
103 where: { 105 where: {
104 url 106 url
@@ -107,7 +109,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
107 }) 109 })
108 } 110 }
109 111
110 static loadActorsByShare (videoId: number, t: Transaction) { 112 static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> {
111 const query = { 113 const query = {
112 where: { 114 where: {
113 videoId 115 videoId
@@ -122,10 +124,10 @@ export class VideoShareModel extends Model<VideoShareModel> {
122 } 124 }
123 125
124 return VideoShareModel.scope(ScopeNames.FULL).findAll(query) 126 return VideoShareModel.scope(ScopeNames.FULL).findAll(query)
125 .then(res => res.map(r => r.Actor)) 127 .then((res: MVideoShareFull[]) => res.map(r => r.Actor))
126 } 128 }
127 129
128 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<ActorModel[]> { 130 static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> {
129 const query = { 131 const query = {
130 attributes: [], 132 attributes: [],
131 include: [ 133 include: [
@@ -163,7 +165,7 @@ export class VideoShareModel extends Model<VideoShareModel> {
163 .then(res => res.map(r => r.Actor)) 165 .then(res => res.map(r => r.Actor))
164 } 166 }
165 167
166 static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<ActorModel[]> { 168 static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> {
167 const query = { 169 const query = {
168 attributes: [], 170 attributes: [],
169 include: [ 171 include: [
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index 31dc82c54..0ea90d28c 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -1,16 +1,16 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 2import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
3import { throwIfNotValid } from '../utils' 3import { throwIfNotValid } from '../utils'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5import { VideoRedundancyModel } from '../redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../redundancy/video-redundancy'
6import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 6import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 7import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
8import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' 8import { CONSTRAINTS_FIELDS, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants'
9import { VideoFileModel } from './video-file'
10import { join } from 'path' 9import { join } from 'path'
11import { sha1 } from '../../helpers/core-utils' 10import { sha1 } from '../../helpers/core-utils'
12import { isArrayOf } from '../../helpers/custom-validators/misc' 11import { isArrayOf } from '../../helpers/custom-validators/misc'
13import { QueryTypes, Op } from 'sequelize' 12import { Op, QueryTypes } from 'sequelize'
13import { MStreamingPlaylist, MVideoFile } from '@server/typings/models'
14 14
15@Table({ 15@Table({
16 tableName: 'videoStreamingPlaylist', 16 tableName: 'videoStreamingPlaylist',
@@ -91,7 +91,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
91 .then(results => results.length === 1) 91 .then(results => results.length === 1)
92 } 92 }
93 93
94 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { 94 static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: MVideoFile[]) {
95 const hashes: string[] = [] 95 const hashes: string[] = []
96 96
97 // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 97 // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115
@@ -165,7 +165,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod
165 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid 165 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid
166 } 166 }
167 167
168 hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { 168 hasSameUniqueKeysThan (other: MStreamingPlaylist) {
169 return this.type === other.type && 169 return this.type === other.type &&
170 this.videoId === other.videoId 170 this.videoId === other.videoId
171 } 171 }
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index b59df397d..6856dcd9f 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -36,7 +36,7 @@ import {
36 Table, 36 Table,
37 UpdatedAt 37 UpdatedAt
38} from 'sequelize-typescript' 38} from 'sequelize-typescript'
39import { UserRight, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' 39import { UserRight, VideoPrivacy, VideoState } from '../../../shared'
40import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 40import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
41import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' 41import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
42import { VideoFilter } from '../../../shared/models/videos/video-query.type' 42import { VideoFilter } from '../../../shared/models/videos/video-query.type'
@@ -111,7 +111,6 @@ import {
111 videoModelToFormattedJSON 111 videoModelToFormattedJSON
112} from './video-format-utils' 112} from './video-format-utils'
113import { UserVideoHistoryModel } from '../account/user-video-history' 113import { UserVideoHistoryModel } from '../account/user-video-history'
114import { UserModel } from '../account/user'
115import { VideoImportModel } from './video-import' 114import { VideoImportModel } from './video-import'
116import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 115import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
117import { VideoPlaylistElementModel } from './video-playlist-element' 116import { VideoPlaylistElementModel } from './video-playlist-element'
@@ -120,6 +119,29 @@ import { ThumbnailModel } from './thumbnail'
120import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 119import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
121import { createTorrentPromise } from '../../helpers/webtorrent' 120import { createTorrentPromise } from '../../helpers/webtorrent'
122import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 121import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
122import {
123 MChannel,
124 MChannelAccountDefault,
125 MChannelId,
126 MUserAccountId,
127 MUserId,
128 MVideoAccountLight,
129 MVideoAccountLightBlacklistAllFiles,
130 MVideoAP,
131 MVideoDetails,
132 MVideoFormattable,
133 MVideoFormattableDetails,
134 MVideoForUser,
135 MVideoFullLight,
136 MVideoIdThumbnail,
137 MVideoThumbnail,
138 MVideoThumbnailBlacklist,
139 MVideoWithAllFiles,
140 MVideoWithFile,
141 MVideoWithRights
142} from '../../typings/models'
143import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file'
144import { MThumbnail } from '../../typings/models/video/thumbnail'
123 145
124// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation 146// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
125const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ 147const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
@@ -232,8 +254,8 @@ export type AvailableForListIDsOptions = {
232 videoPlaylistId?: number 254 videoPlaylistId?: number
233 255
234 trendingDays?: number 256 trendingDays?: number
235 user?: UserModel, 257 user?: MUserAccountId
236 historyOfUser?: UserModel 258 historyOfUser?: MUserId
237 259
238 baseWhere?: WhereOptions[] 260 baseWhere?: WhereOptions[]
239} 261}
@@ -446,13 +468,15 @@ export type AvailableForListIDsOptions = {
446 // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() 468 // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN()
447 if (options.tagsAllOf || options.tagsOneOf) { 469 if (options.tagsAllOf || options.tagsOneOf) {
448 if (options.tagsOneOf) { 470 if (options.tagsOneOf) {
471 const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase())
472
449 whereAnd.push({ 473 whereAnd.push({
450 id: { 474 id: {
451 [ Op.in ]: Sequelize.literal( 475 [ Op.in ]: Sequelize.literal(
452 '(' + 476 '(' +
453 'SELECT "videoId" FROM "videoTag" ' + 477 'SELECT "videoId" FROM "videoTag" ' +
454 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 478 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
455 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' + 479 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' +
456 ')' 480 ')'
457 ) 481 )
458 } 482 }
@@ -460,14 +484,16 @@ export type AvailableForListIDsOptions = {
460 } 484 }
461 485
462 if (options.tagsAllOf) { 486 if (options.tagsAllOf) {
487 const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase())
488
463 whereAnd.push({ 489 whereAnd.push({
464 id: { 490 id: {
465 [ Op.in ]: Sequelize.literal( 491 [ Op.in ]: Sequelize.literal(
466 '(' + 492 '(' +
467 'SELECT "videoId" FROM "videoTag" ' + 493 'SELECT "videoId" FROM "videoTag" ' +
468 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 494 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
469 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' + 495 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' +
470 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length + 496 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length +
471 ')' 497 ')'
472 ) 498 )
473 } 499 }
@@ -634,7 +660,7 @@ export type AvailableForListIDsOptions = {
634 [ ScopeNames.WITH_BLACKLISTED ]: { 660 [ ScopeNames.WITH_BLACKLISTED ]: {
635 include: [ 661 include: [
636 { 662 {
637 attributes: [ 'id', 'reason' ], 663 attributes: [ 'id', 'reason', 'unfederated' ],
638 model: VideoBlacklistModel, 664 model: VideoBlacklistModel,
639 required: false 665 required: false
640 } 666 }
@@ -989,18 +1015,16 @@ export class VideoModel extends Model<VideoModel> {
989 VideoCaptions: VideoCaptionModel[] 1015 VideoCaptions: VideoCaptionModel[]
990 1016
991 @BeforeDestroy 1017 @BeforeDestroy
992 static async sendDelete (instance: VideoModel, options) { 1018 static async sendDelete (instance: MVideoAccountLight, options) {
993 if (instance.isOwned()) { 1019 if (instance.isOwned()) {
994 if (!instance.VideoChannel) { 1020 if (!instance.VideoChannel) {
995 instance.VideoChannel = await instance.$get('VideoChannel', { 1021 instance.VideoChannel = await instance.$get('VideoChannel', {
996 include: [ 1022 include: [
997 { 1023 ActorModel,
998 model: AccountModel, 1024 AccountModel
999 include: [ ActorModel ]
1000 }
1001 ], 1025 ],
1002 transaction: options.transaction 1026 transaction: options.transaction
1003 }) as VideoChannelModel 1027 }) as MChannelAccountDefault
1004 } 1028 }
1005 1029
1006 return sendDeleteVideo(instance, options.transaction) 1030 return sendDeleteVideo(instance, options.transaction)
@@ -1039,7 +1063,7 @@ export class VideoModel extends Model<VideoModel> {
1039 return undefined 1063 return undefined
1040 } 1064 }
1041 1065
1042 static listLocal () { 1066 static listLocal (): Bluebird<MVideoWithAllFiles[]> {
1043 const query = { 1067 const query = {
1044 where: { 1068 where: {
1045 remote: false 1069 remote: false
@@ -1159,7 +1183,7 @@ export class VideoModel extends Model<VideoModel> {
1159 }) 1183 })
1160 } 1184 }
1161 1185
1162 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { 1186 static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) {
1163 function buildBaseQuery (): FindOptions { 1187 function buildBaseQuery (): FindOptions {
1164 return { 1188 return {
1165 offset: start, 1189 offset: start,
@@ -1192,16 +1216,9 @@ export class VideoModel extends Model<VideoModel> {
1192 ScopeNames.WITH_THUMBNAILS 1216 ScopeNames.WITH_THUMBNAILS
1193 ] 1217 ]
1194 1218
1195 if (withFiles === true) {
1196 findQuery.include.push({
1197 model: VideoFileModel.unscoped(),
1198 required: true
1199 })
1200 }
1201
1202 return Promise.all([ 1219 return Promise.all([
1203 VideoModel.count(countQuery), 1220 VideoModel.count(countQuery),
1204 VideoModel.scope(findScopes).findAll(findQuery) 1221 VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery)
1205 ]).then(([ count, rows ]) => { 1222 ]).then(([ count, rows ]) => {
1206 return { 1223 return {
1207 data: rows, 1224 data: rows,
@@ -1228,8 +1245,8 @@ export class VideoModel extends Model<VideoModel> {
1228 followerActorId?: number 1245 followerActorId?: number
1229 videoPlaylistId?: number, 1246 videoPlaylistId?: number,
1230 trendingDays?: number, 1247 trendingDays?: number,
1231 user?: UserModel, 1248 user?: MUserAccountId,
1232 historyOfUser?: UserModel 1249 historyOfUser?: MUserId
1233 }, countVideos = true) { 1250 }, countVideos = true) {
1234 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1251 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
1235 throw new Error('Try to filter all-local but no user has not the see all videos right') 1252 throw new Error('Try to filter all-local but no user has not the see all videos right')
@@ -1294,7 +1311,7 @@ export class VideoModel extends Model<VideoModel> {
1294 tagsAllOf?: string[] 1311 tagsAllOf?: string[]
1295 durationMin?: number // seconds 1312 durationMin?: number // seconds
1296 durationMax?: number // seconds 1313 durationMax?: number // seconds
1297 user?: UserModel, 1314 user?: MUserAccountId,
1298 filter?: VideoFilter 1315 filter?: VideoFilter
1299 }) { 1316 }) {
1300 const whereAnd = [] 1317 const whereAnd = []
@@ -1387,7 +1404,7 @@ export class VideoModel extends Model<VideoModel> {
1387 return VideoModel.getAvailableForApi(query, queryOptions) 1404 return VideoModel.getAvailableForApi(query, queryOptions)
1388 } 1405 }
1389 1406
1390 static load (id: number | string, t?: Transaction) { 1407 static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> {
1391 const where = buildWhereIdOrUUID(id) 1408 const where = buildWhereIdOrUUID(id)
1392 const options = { 1409 const options = {
1393 where, 1410 where,
@@ -1397,7 +1414,20 @@ export class VideoModel extends Model<VideoModel> {
1397 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1414 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1398 } 1415 }
1399 1416
1400 static loadWithRights (id: number | string, t?: Transaction) { 1417 static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> {
1418 const where = buildWhereIdOrUUID(id)
1419 const options = {
1420 where,
1421 transaction: t
1422 }
1423
1424 return VideoModel.scope([
1425 ScopeNames.WITH_THUMBNAILS,
1426 ScopeNames.WITH_BLACKLISTED
1427 ]).findOne(options)
1428 }
1429
1430 static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> {
1401 const where = buildWhereIdOrUUID(id) 1431 const where = buildWhereIdOrUUID(id)
1402 const options = { 1432 const options = {
1403 where, 1433 where,
@@ -1411,7 +1441,7 @@ export class VideoModel extends Model<VideoModel> {
1411 ]).findOne(options) 1441 ]).findOne(options)
1412 } 1442 }
1413 1443
1414 static loadOnlyId (id: number | string, t?: Transaction) { 1444 static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> {
1415 const where = buildWhereIdOrUUID(id) 1445 const where = buildWhereIdOrUUID(id)
1416 1446
1417 const options = { 1447 const options = {
@@ -1423,7 +1453,7 @@ export class VideoModel extends Model<VideoModel> {
1423 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1453 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1424 } 1454 }
1425 1455
1426 static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean) { 1456 static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> {
1427 const where = buildWhereIdOrUUID(id) 1457 const where = buildWhereIdOrUUID(id)
1428 1458
1429 const query = { 1459 const query = {
@@ -1439,7 +1469,7 @@ export class VideoModel extends Model<VideoModel> {
1439 ]).findOne(query) 1469 ]).findOne(query)
1440 } 1470 }
1441 1471
1442 static loadByUUID (uuid: string) { 1472 static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> {
1443 const options = { 1473 const options = {
1444 where: { 1474 where: {
1445 uuid 1475 uuid
@@ -1449,7 +1479,7 @@ export class VideoModel extends Model<VideoModel> {
1449 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) 1479 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
1450 } 1480 }
1451 1481
1452 static loadByUrl (url: string, transaction?: Transaction) { 1482 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> {
1453 const query: FindOptions = { 1483 const query: FindOptions = {
1454 where: { 1484 where: {
1455 url 1485 url
@@ -1460,7 +1490,7 @@ export class VideoModel extends Model<VideoModel> {
1460 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) 1490 return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query)
1461 } 1491 }
1462 1492
1463 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { 1493 static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> {
1464 const query: FindOptions = { 1494 const query: FindOptions = {
1465 where: { 1495 where: {
1466 url 1496 url
@@ -1472,11 +1502,12 @@ export class VideoModel extends Model<VideoModel> {
1472 ScopeNames.WITH_ACCOUNT_DETAILS, 1502 ScopeNames.WITH_ACCOUNT_DETAILS,
1473 ScopeNames.WITH_FILES, 1503 ScopeNames.WITH_FILES,
1474 ScopeNames.WITH_STREAMING_PLAYLISTS, 1504 ScopeNames.WITH_STREAMING_PLAYLISTS,
1475 ScopeNames.WITH_THUMBNAILS 1505 ScopeNames.WITH_THUMBNAILS,
1506 ScopeNames.WITH_BLACKLISTED
1476 ]).findOne(query) 1507 ]).findOne(query)
1477 } 1508 }
1478 1509
1479 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) { 1510 static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> {
1480 const where = buildWhereIdOrUUID(id) 1511 const where = buildWhereIdOrUUID(id)
1481 1512
1482 const options = { 1513 const options = {
@@ -1508,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> {
1508 id: number | string, 1539 id: number | string,
1509 t?: Transaction, 1540 t?: Transaction,
1510 userId?: number 1541 userId?: number
1511 }) { 1542 }): Bluebird<MVideoDetails> {
1512 const { id, t, userId } = parameters 1543 const { id, t, userId } = parameters
1513 const where = buildWhereIdOrUUID(id) 1544 const where = buildWhereIdOrUUID(id)
1514 1545
@@ -1586,7 +1617,7 @@ export class VideoModel extends Model<VideoModel> {
1586 .then(results => results.length === 1) 1617 .then(results => results.length === 1)
1587 } 1618 }
1588 1619
1589 static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) { 1620 static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) {
1590 const options = { 1621 const options = {
1591 where: { 1622 where: {
1592 channelId: videoChannel.id 1623 channelId: videoChannel.id
@@ -1597,7 +1628,7 @@ export class VideoModel extends Model<VideoModel> {
1597 return VideoModel.update({ support: videoChannel.support }, options) 1628 return VideoModel.update({ support: videoChannel.support }, options)
1598 } 1629 }
1599 1630
1600 static getAllIdsFromChannel (videoChannel: VideoChannelModel) { 1631 static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> {
1601 const query = { 1632 const query = {
1602 attributes: [ 'id' ], 1633 attributes: [ 'id' ],
1603 where: { 1634 where: {
@@ -1756,20 +1787,20 @@ export class VideoModel extends Model<VideoModel> {
1756 this.VideoChannel.Account.isBlocked() 1787 this.VideoChannel.Account.isBlocked()
1757 } 1788 }
1758 1789
1759 getOriginalFile () { 1790 getOriginalFile <T extends MVideoWithFile> (this: T) {
1760 if (Array.isArray(this.VideoFiles) === false) return undefined 1791 if (Array.isArray(this.VideoFiles) === false) return undefined
1761 1792
1762 // The original file is the file that have the higher resolution 1793 // The original file is the file that have the higher resolution
1763 return maxBy(this.VideoFiles, file => file.resolution) 1794 return maxBy(this.VideoFiles, file => file.resolution)
1764 } 1795 }
1765 1796
1766 getFile (resolution: number) { 1797 getFile <T extends MVideoWithFile> (this: T, resolution: number) {
1767 if (Array.isArray(this.VideoFiles) === false) return undefined 1798 if (Array.isArray(this.VideoFiles) === false) return undefined
1768 1799
1769 return this.VideoFiles.find(f => f.resolution === resolution) 1800 return this.VideoFiles.find(f => f.resolution === resolution)
1770 } 1801 }
1771 1802
1772 async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { 1803 async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) {
1773 thumbnail.videoId = this.id 1804 thumbnail.videoId = this.id
1774 1805
1775 const savedThumbnail = await thumbnail.save({ transaction }) 1806 const savedThumbnail = await thumbnail.save({ transaction })
@@ -1782,7 +1813,7 @@ export class VideoModel extends Model<VideoModel> {
1782 this.Thumbnails.push(savedThumbnail) 1813 this.Thumbnails.push(savedThumbnail)
1783 } 1814 }
1784 1815
1785 getVideoFilename (videoFile: VideoFileModel) { 1816 getVideoFilename (videoFile: MVideoFile) {
1786 return this.uuid + '-' + videoFile.resolution + videoFile.extname 1817 return this.uuid + '-' + videoFile.resolution + videoFile.extname
1787 } 1818 }
1788 1819
@@ -1806,7 +1837,7 @@ export class VideoModel extends Model<VideoModel> {
1806 return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) 1837 return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
1807 } 1838 }
1808 1839
1809 getTorrentFileName (videoFile: VideoFileModel) { 1840 getTorrentFileName (videoFile: MVideoFile) {
1810 const extension = '.torrent' 1841 const extension = '.torrent'
1811 return this.uuid + '-' + videoFile.resolution + extension 1842 return this.uuid + '-' + videoFile.resolution + extension
1812 } 1843 }
@@ -1815,15 +1846,15 @@ export class VideoModel extends Model<VideoModel> {
1815 return this.remote === false 1846 return this.remote === false
1816 } 1847 }
1817 1848
1818 getTorrentFilePath (videoFile: VideoFileModel) { 1849 getTorrentFilePath (videoFile: MVideoFile) {
1819 return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 1850 return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1820 } 1851 }
1821 1852
1822 getVideoFilePath (videoFile: VideoFileModel) { 1853 getVideoFilePath (videoFile: MVideoFile) {
1823 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) 1854 return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
1824 } 1855 }
1825 1856
1826 async createTorrentAndSetInfoHash (videoFile: VideoFileModel) { 1857 async createTorrentAndSetInfoHash (videoFile: MVideoFile) {
1827 const options = { 1858 const options = {
1828 // Keep the extname, it's used by the client to stream the file inside a web browser 1859 // Keep the extname, it's used by the client to stream the file inside a web browser
1829 name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, 1860 name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`,
@@ -1869,11 +1900,11 @@ export class VideoModel extends Model<VideoModel> {
1869 return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) 1900 return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename)
1870 } 1901 }
1871 1902
1872 toFormattedJSON (options?: VideoFormattingJSONOptions): Video { 1903 toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
1873 return videoModelToFormattedJSON(this, options) 1904 return videoModelToFormattedJSON(this, options)
1874 } 1905 }
1875 1906
1876 toFormattedDetailsJSON (): VideoDetails { 1907 toFormattedDetailsJSON (this: MVideoFormattableDetails): VideoDetails {
1877 return videoModelToFormattedDetailsJSON(this) 1908 return videoModelToFormattedDetailsJSON(this)
1878 } 1909 }
1879 1910
@@ -1881,7 +1912,7 @@ export class VideoModel extends Model<VideoModel> {
1881 return videoFilesModelToFormattedJSON(this, this.VideoFiles) 1912 return videoFilesModelToFormattedJSON(this, this.VideoFiles)
1882 } 1913 }
1883 1914
1884 toActivityPubObject (): VideoTorrentObject { 1915 toActivityPubObject (this: MVideoAP): VideoTorrentObject {
1885 return videoModelToActivityPubObject(this) 1916 return videoModelToActivityPubObject(this)
1886 } 1917 }
1887 1918
@@ -1908,7 +1939,7 @@ export class VideoModel extends Model<VideoModel> {
1908 return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) 1939 return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS)
1909 } 1940 }
1910 1941
1911 removeFile (videoFile: VideoFileModel, isRedundancy = false) { 1942 removeFile (videoFile: MVideoFile, isRedundancy = false) {
1912 const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR 1943 const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
1913 1944
1914 const filePath = join(baseDir, this.getVideoFilename(videoFile)) 1945 const filePath = join(baseDir, this.getVideoFilename(videoFile))
@@ -1916,7 +1947,7 @@ export class VideoModel extends Model<VideoModel> {
1916 .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) 1947 .catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
1917 } 1948 }
1918 1949
1919 removeTorrent (videoFile: VideoFileModel) { 1950 removeTorrent (videoFile: MVideoFile) {
1920 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) 1951 const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile))
1921 return remove(torrentPath) 1952 return remove(torrentPath)
1922 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) 1953 .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err }))
@@ -1957,7 +1988,7 @@ export class VideoModel extends Model<VideoModel> {
1957 return { baseUrlHttp, baseUrlWs } 1988 return { baseUrlHttp, baseUrlWs }
1958 } 1989 }
1959 1990
1960 generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { 1991 generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) {
1961 const xs = this.getTorrentUrl(videoFile, baseUrlHttp) 1992 const xs = this.getTorrentUrl(videoFile, baseUrlHttp)
1962 const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) 1993 const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs)
1963 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] 1994 let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ]
@@ -1980,27 +2011,27 @@ export class VideoModel extends Model<VideoModel> {
1980 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] 2011 return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ]
1981 } 2012 }
1982 2013
1983 getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2014 getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1984 return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) 2015 return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
1985 } 2016 }
1986 2017
1987 getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2018 getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1988 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) 2019 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile)
1989 } 2020 }
1990 2021
1991 getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2022 getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1992 return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) 2023 return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
1993 } 2024 }
1994 2025
1995 getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2026 getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) {
1996 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) 2027 return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
1997 } 2028 }
1998 2029
1999 getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { 2030 getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) {
2000 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) 2031 return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
2001 } 2032 }
2002 2033
2003 getBandwidthBits (videoFile: VideoFileModel) { 2034 getBandwidthBits (videoFile: MVideoFile) {
2004 return Math.ceil((videoFile.size * 8) / this.duration) 2035 return Math.ceil((videoFile.size * 8) / this.duration)
2005 } 2036 }
2006} 2037}