aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/abuse/abuse.ts (renamed from server/models/video/video-abuse.ts)327
-rw-r--r--server/models/abuse/video-abuse.ts63
-rw-r--r--server/models/abuse/video-comment-abuse.ts53
-rw-r--r--server/models/account/account-blocklist.ts10
-rw-r--r--server/models/account/account.ts4
-rw-r--r--server/models/account/user-notification.ts100
-rw-r--r--server/models/account/user.ts4
-rw-r--r--server/models/server/server-blocklist.ts10
-rw-r--r--server/models/video/video.ts84
9 files changed, 443 insertions, 212 deletions
diff --git a/server/models/video/video-abuse.ts b/server/models/abuse/abuse.ts
index 1319332f0..4f99f9c9b 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -1,5 +1,6 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { literal, Op } from 'sequelize' 2import { invert } from 'lodash'
3import { literal, Op, WhereOptions } from 'sequelize'
3import { 4import {
4 AllowNull, 5 AllowNull,
5 BelongsTo, 6 BelongsTo,
@@ -8,36 +9,35 @@ import {
8 DataType, 9 DataType,
9 Default, 10 Default,
10 ForeignKey, 11 ForeignKey,
12 HasOne,
11 Is, 13 Is,
12 Model, 14 Model,
13 Scopes, 15 Scopes,
14 Table, 16 Table,
15 UpdatedAt 17 UpdatedAt
16} from 'sequelize-typescript' 18} from 'sequelize-typescript'
17import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' 19import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
18import {
19 VideoAbuseState,
20 VideoDetails,
21 VideoAbusePredefinedReasons,
22 VideoAbusePredefinedReasonsString,
23 videoAbusePredefinedReasonsMap
24} from '../../../shared'
25import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
26import { VideoAbuse } from '../../../shared/models/videos'
27import { 20import {
28 isVideoAbuseModerationCommentValid, 21 Abuse,
29 isVideoAbuseReasonValid, 22 AbuseObject,
30 isVideoAbuseStateValid 23 AbusePredefinedReasons,
31} from '../../helpers/custom-validators/video-abuses' 24 abusePredefinedReasonsMap,
32import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 25 AbusePredefinedReasonsString,
33import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../types/models' 26 AbuseState,
34import { AccountModel } from '../account/account' 27 AbuseVideoIs,
28 VideoAbuse
29} from '@shared/models'
30import { AbuseFilter } from '@shared/models/moderation/abuse/abuse-filter'
31import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants'
32import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models'
33import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
35import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' 34import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils'
36import { ThumbnailModel } from './thumbnail' 35import { ThumbnailModel } from '../video/thumbnail'
37import { VideoModel } from './video' 36import { VideoModel } from '../video/video'
38import { VideoBlacklistModel } from './video-blacklist' 37import { VideoBlacklistModel } from '../video/video-blacklist'
39import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' 38import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel'
40import { invert } from 'lodash' 39import { VideoAbuseModel } from './video-abuse'
40import { VideoCommentAbuseModel } from './video-comment-abuse'
41 41
42export enum ScopeNames { 42export enum ScopeNames {
43 FOR_API = 'FOR_API' 43 FOR_API = 'FOR_API'
@@ -49,20 +49,26 @@ export enum ScopeNames {
49 search?: string 49 search?: string
50 searchReporter?: string 50 searchReporter?: string
51 searchReportee?: string 51 searchReportee?: string
52
53 // video releated
52 searchVideo?: string 54 searchVideo?: string
53 searchVideoChannel?: string 55 searchVideoChannel?: string
56 videoIs?: AbuseVideoIs
54 57
55 // filters 58 // filters
56 id?: number 59 id?: number
57 predefinedReasonId?: number 60 predefinedReasonId?: number
61 filter?: AbuseFilter
58 62
59 state?: VideoAbuseState 63 state?: AbuseState
60 videoIs?: VideoAbuseVideoIs
61 64
62 // accountIds 65 // accountIds
63 serverAccountId: number 66 serverAccountId: number
64 userAccountId: number 67 userAccountId: number
65 }) => { 68 }) => {
69 const onlyBlacklisted = options.videoIs === 'blacklisted'
70 const videoRequired = !!(onlyBlacklisted || options.searchVideo || options.searchVideoChannel)
71
66 const where = { 72 const where = {
67 reporterAccountId: { 73 reporterAccountId: {
68 [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') 74 [Op.notIn]: literal('(' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
@@ -70,33 +76,36 @@ export enum ScopeNames {
70 } 76 }
71 77
72 if (options.search) { 78 if (options.search) {
79 const escapedSearch = AbuseModel.sequelize.escape('%' + options.search + '%')
80
73 Object.assign(where, { 81 Object.assign(where, {
74 [Op.or]: [ 82 [Op.or]: [
75 { 83 {
76 [Op.and]: [ 84 [Op.and]: [
77 { videoId: { [Op.not]: null } }, 85 { '$VideoAbuse.videoId$': { [Op.not]: null } },
78 searchAttribute(options.search, '$Video.name$') 86 searchAttribute(options.search, '$VideoAbuse.Video.name$')
79 ] 87 ]
80 }, 88 },
81 { 89 {
82 [Op.and]: [ 90 [Op.and]: [
83 { videoId: { [Op.not]: null } }, 91 { '$VideoAbuse.videoId$': { [Op.not]: null } },
84 searchAttribute(options.search, '$Video.VideoChannel.name$') 92 searchAttribute(options.search, '$VideoAbuse.Video.VideoChannel.name$')
85 ] 93 ]
86 }, 94 },
87 { 95 {
88 [Op.and]: [ 96 [Op.and]: [
89 { deletedVideo: { [Op.not]: null } }, 97 { '$VideoAbuse.deletedVideo$': { [Op.not]: null } },
90 { deletedVideo: searchAttribute(options.search, 'name') } 98 literal(`"VideoAbuse"."deletedVideo"->>'name' ILIKE ${escapedSearch}`)
91 ] 99 ]
92 }, 100 },
93 { 101 {
94 [Op.and]: [ 102 [Op.and]: [
95 { deletedVideo: { [Op.not]: null } }, 103 { '$VideoAbuse.deletedVideo$': { [Op.not]: null } },
96 { deletedVideo: { channel: searchAttribute(options.search, 'displayName') } } 104 literal(`"VideoAbuse"."deletedVideo"->'channel'->>'displayName' ILIKE ${escapedSearch}`)
97 ] 105 ]
98 }, 106 },
99 searchAttribute(options.search, '$Account.name$') 107 searchAttribute(options.search, '$ReporterAccount.name$'),
108 searchAttribute(options.search, '$FlaggedAccount.name$')
100 ] 109 ]
101 }) 110 })
102 } 111 }
@@ -106,7 +115,7 @@ export enum ScopeNames {
106 115
107 if (options.videoIs === 'deleted') { 116 if (options.videoIs === 'deleted') {
108 Object.assign(where, { 117 Object.assign(where, {
109 deletedVideo: { 118 '$VideoAbuse.deletedVideo$': {
110 [Op.not]: null 119 [Op.not]: null
111 } 120 }
112 }) 121 })
@@ -120,8 +129,6 @@ export enum ScopeNames {
120 }) 129 })
121 } 130 }
122 131
123 const onlyBlacklisted = options.videoIs === 'blacklisted'
124
125 return { 132 return {
126 attributes: { 133 attributes: {
127 include: [ 134 include: [
@@ -131,7 +138,7 @@ export enum ScopeNames {
131 '(' + 138 '(' +
132 'SELECT count(*) ' + 139 'SELECT count(*) ' +
133 'FROM "videoAbuse" ' + 140 'FROM "videoAbuse" ' +
134 'WHERE "videoId" = "VideoAbuseModel"."videoId" ' + 141 'WHERE "videoId" = "VideoAbuse"."videoId" ' +
135 ')' 142 ')'
136 ), 143 ),
137 'countReportsForVideo' 144 'countReportsForVideo'
@@ -146,7 +153,7 @@ export enum ScopeNames {
146 'row_number() OVER (PARTITION BY "videoId" ORDER BY "createdAt") AS nth ' + 153 'row_number() OVER (PARTITION BY "videoId" ORDER BY "createdAt") AS nth ' +
147 'FROM "videoAbuse" ' + 154 'FROM "videoAbuse" ' +
148 ') t ' + 155 ') t ' +
149 'WHERE t.id = "VideoAbuseModel".id ' + 156 'WHERE t.id = "VideoAbuse".id' +
150 ')' 157 ')'
151 ), 158 ),
152 'nthReportForVideo' 159 'nthReportForVideo'
@@ -159,7 +166,7 @@ export enum ScopeNames {
159 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + 166 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' +
160 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 167 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
161 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 168 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
162 'WHERE "account"."id" = "VideoAbuseModel"."reporterAccountId" ' + 169 'WHERE "account"."id" = "AbuseModel"."reporterAccountId" ' +
163 ')' 170 ')'
164 ), 171 ),
165 'countReportsForReporter__video' 172 'countReportsForReporter__video'
@@ -169,7 +176,7 @@ export enum ScopeNames {
169 '(' + 176 '(' +
170 'SELECT count(DISTINCT "videoAbuse"."id") ' + 177 'SELECT count(DISTINCT "videoAbuse"."id") ' +
171 'FROM "videoAbuse" ' + 178 'FROM "videoAbuse" ' +
172 `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "VideoAbuseModel"."reporterAccountId" ` + 179 `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "AbuseModel"."reporterAccountId" ` +
173 ')' 180 ')'
174 ), 181 ),
175 'countReportsForReporter__deletedVideo' 182 'countReportsForReporter__deletedVideo'
@@ -182,8 +189,8 @@ export enum ScopeNames {
182 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + 189 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' +
183 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 190 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
184 'INNER JOIN "account" ON ' + 191 'INNER JOIN "account" ON ' +
185 '"videoChannel"."accountId" = "Video->VideoChannel"."accountId" ' + 192 '"videoChannel"."accountId" = "VideoAbuse->Video->VideoChannel"."accountId" ' +
186 `OR "videoChannel"."accountId" = CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + 193 `OR "videoChannel"."accountId" = CAST("VideoAbuse"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` +
187 ')' 194 ')'
188 ), 195 ),
189 'countReportsForReportee__video' 196 'countReportsForReportee__video'
@@ -193,9 +200,9 @@ export enum ScopeNames {
193 '(' + 200 '(' +
194 'SELECT count(DISTINCT "videoAbuse"."id") ' + 201 'SELECT count(DISTINCT "videoAbuse"."id") ' +
195 'FROM "videoAbuse" ' + 202 'FROM "videoAbuse" ' +
196 `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "Video->VideoChannel"."accountId" ` + 203 `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "VideoAbuse->Video->VideoChannel"."accountId" ` +
197 `OR CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = ` + 204 `OR CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = ` +
198 `CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + 205 `CAST("VideoAbuse"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` +
199 ')' 206 ')'
200 ), 207 ),
201 'countReportsForReportee__deletedVideo' 208 'countReportsForReportee__deletedVideo'
@@ -204,32 +211,47 @@ export enum ScopeNames {
204 }, 211 },
205 include: [ 212 include: [
206 { 213 {
207 model: AccountModel, 214 model: AccountModel.scope(AccountScopeNames.SUMMARY),
215 as: 'ReporterAccount',
208 required: true, 216 required: true,
209 where: searchAttribute(options.searchReporter, 'name') 217 where: searchAttribute(options.searchReporter, 'name')
210 }, 218 },
211 { 219 {
212 model: VideoModel, 220 model: AccountModel.scope(AccountScopeNames.SUMMARY),
213 required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel), 221 as: 'FlaggedAccount',
214 where: searchAttribute(options.searchVideo, 'name'), 222 required: true,
223 where: searchAttribute(options.searchReportee, 'name')
224 },
225 {
226 model: VideoAbuseModel,
227 required: options.filter === 'video' || !!options.videoIs || videoRequired,
215 include: [ 228 include: [
216 { 229 {
217 model: ThumbnailModel 230 model: VideoModel,
218 }, 231 required: videoRequired,
219 { 232 where: searchAttribute(options.searchVideo, 'name'),
220 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
221 where: searchAttribute(options.searchVideoChannel, 'name'),
222 include: [ 233 include: [
223 { 234 {
224 model: AccountModel, 235 model: ThumbnailModel
225 where: searchAttribute(options.searchReportee, 'name') 236 },
237 {
238 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: false } as SummaryOptions ] }),
239 where: searchAttribute(options.searchVideoChannel, 'name'),
240 required: true,
241 include: [
242 {
243 model: AccountModel.scope(AccountScopeNames.SUMMARY),
244 required: true,
245 where: searchAttribute(options.searchReportee, 'name')
246 }
247 ]
248 },
249 {
250 attributes: [ 'id', 'reason', 'unfederated' ],
251 model: VideoBlacklistModel,
252 required: onlyBlacklisted
226 } 253 }
227 ] 254 ]
228 },
229 {
230 attributes: [ 'id', 'reason', 'unfederated' ],
231 model: VideoBlacklistModel,
232 required: onlyBlacklisted
233 } 255 }
234 ] 256 ]
235 } 257 }
@@ -239,55 +261,40 @@ export enum ScopeNames {
239 } 261 }
240})) 262}))
241@Table({ 263@Table({
242 tableName: 'videoAbuse', 264 tableName: 'abuse',
243 indexes: [ 265 indexes: [
244 { 266 {
245 fields: [ 'videoId' ] 267 fields: [ 'reporterAccountId' ]
246 }, 268 },
247 { 269 {
248 fields: [ 'reporterAccountId' ] 270 fields: [ 'flaggedAccountId' ]
249 } 271 }
250 ] 272 ]
251}) 273})
252export class VideoAbuseModel extends Model<VideoAbuseModel> { 274export class AbuseModel extends Model<AbuseModel> {
253 275
254 @AllowNull(false) 276 @AllowNull(false)
255 @Default(null) 277 @Default(null)
256 @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason')) 278 @Is('VideoAbuseReason', value => throwIfNotValid(value, isAbuseReasonValid, 'reason'))
257 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.REASON.max)) 279 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ABUSES.REASON.max))
258 reason: string 280 reason: string
259 281
260 @AllowNull(false) 282 @AllowNull(false)
261 @Default(null) 283 @Default(null)
262 @Is('VideoAbuseState', value => throwIfNotValid(value, isVideoAbuseStateValid, 'state')) 284 @Is('VideoAbuseState', value => throwIfNotValid(value, isAbuseStateValid, 'state'))
263 @Column 285 @Column
264 state: VideoAbuseState 286 state: AbuseState
265 287
266 @AllowNull(true) 288 @AllowNull(true)
267 @Default(null) 289 @Default(null)
268 @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment', true)) 290 @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isAbuseModerationCommentValid, 'moderationComment', true))
269 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) 291 @Column(DataType.STRING(CONSTRAINTS_FIELDS.ABUSES.MODERATION_COMMENT.max))
270 moderationComment: string 292 moderationComment: string
271 293
272 @AllowNull(true) 294 @AllowNull(true)
273 @Default(null) 295 @Default(null)
274 @Column(DataType.JSONB)
275 deletedVideo: VideoDetails
276
277 @AllowNull(true)
278 @Default(null)
279 @Column(DataType.ARRAY(DataType.INTEGER)) 296 @Column(DataType.ARRAY(DataType.INTEGER))
280 predefinedReasons: VideoAbusePredefinedReasons[] 297 predefinedReasons: AbusePredefinedReasons[]
281
282 @AllowNull(true)
283 @Default(null)
284 @Column
285 startAt: number
286
287 @AllowNull(true)
288 @Default(null)
289 @Column
290 endAt: number
291 298
292 @CreatedAt 299 @CreatedAt
293 createdAt: Date 300 createdAt: Date
@@ -301,36 +308,65 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
301 308
302 @BelongsTo(() => AccountModel, { 309 @BelongsTo(() => AccountModel, {
303 foreignKey: { 310 foreignKey: {
311 name: 'reporterAccountId',
304 allowNull: true 312 allowNull: true
305 }, 313 },
314 as: 'ReporterAccount',
306 onDelete: 'set null' 315 onDelete: 'set null'
307 }) 316 })
308 Account: AccountModel 317 ReporterAccount: AccountModel
309 318
310 @ForeignKey(() => VideoModel) 319 @ForeignKey(() => AccountModel)
311 @Column 320 @Column
312 videoId: number 321 flaggedAccountId: number
313 322
314 @BelongsTo(() => VideoModel, { 323 @BelongsTo(() => AccountModel, {
315 foreignKey: { 324 foreignKey: {
325 name: 'flaggedAccountId',
316 allowNull: true 326 allowNull: true
317 }, 327 },
328 as: 'FlaggedAccount',
318 onDelete: 'set null' 329 onDelete: 'set null'
319 }) 330 })
320 Video: VideoModel 331 FlaggedAccount: AccountModel
332
333 @HasOne(() => VideoCommentAbuseModel, {
334 foreignKey: {
335 name: 'abuseId',
336 allowNull: false
337 },
338 onDelete: 'cascade'
339 })
340 VideoCommentAbuse: VideoCommentAbuseModel
321 341
322 static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> { 342 @HasOne(() => VideoAbuseModel, {
323 const videoAttributes = {} 343 foreignKey: {
324 if (videoId) videoAttributes['videoId'] = videoId 344 name: 'abuseId',
325 if (uuid) videoAttributes['deletedVideo'] = { uuid } 345 allowNull: false
346 },
347 onDelete: 'cascade'
348 })
349 VideoAbuse: VideoAbuseModel
350
351 static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MAbuse> {
352 const videoWhere: WhereOptions = {}
353
354 if (videoId) videoWhere.videoId = videoId
355 if (uuid) videoWhere.deletedVideo = { uuid }
326 356
327 const query = { 357 const query = {
358 include: [
359 {
360 model: VideoAbuseModel,
361 required: true,
362 where: videoWhere
363 }
364 ],
328 where: { 365 where: {
329 id, 366 id
330 ...videoAttributes
331 } 367 }
332 } 368 }
333 return VideoAbuseModel.findOne(query) 369 return AbuseModel.findOne(query)
334 } 370 }
335 371
336 static listForApi (parameters: { 372 static listForApi (parameters: {
@@ -338,13 +374,15 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
338 count: number 374 count: number
339 sort: string 375 sort: string
340 376
377 filter?: AbuseFilter
378
341 serverAccountId: number 379 serverAccountId: number
342 user?: MUserAccountId 380 user?: MUserAccountId
343 381
344 id?: number 382 id?: number
345 predefinedReason?: VideoAbusePredefinedReasonsString 383 predefinedReason?: AbusePredefinedReasonsString
346 state?: VideoAbuseState 384 state?: AbuseState
347 videoIs?: VideoAbuseVideoIs 385 videoIs?: AbuseVideoIs
348 386
349 search?: string 387 search?: string
350 searchReporter?: string 388 searchReporter?: string
@@ -364,24 +402,26 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
364 predefinedReason, 402 predefinedReason,
365 searchReportee, 403 searchReportee,
366 searchVideo, 404 searchVideo,
405 filter,
367 searchVideoChannel, 406 searchVideoChannel,
368 searchReporter, 407 searchReporter,
369 id 408 id
370 } = parameters 409 } = parameters
371 410
372 const userAccountId = user ? user.Account.id : undefined 411 const userAccountId = user ? user.Account.id : undefined
373 const predefinedReasonId = predefinedReason ? videoAbusePredefinedReasonsMap[predefinedReason] : undefined 412 const predefinedReasonId = predefinedReason ? abusePredefinedReasonsMap[predefinedReason] : undefined
374 413
375 const query = { 414 const query = {
376 offset: start, 415 offset: start,
377 limit: count, 416 limit: count,
378 order: getSort(sort), 417 order: getSort(sort),
379 col: 'VideoAbuseModel.id', 418 col: 'AbuseModel.id',
380 distinct: true 419 distinct: true
381 } 420 }
382 421
383 const filters = { 422 const filters = {
384 id, 423 id,
424 filter,
385 predefinedReasonId, 425 predefinedReasonId,
386 search, 426 search,
387 state, 427 state,
@@ -394,7 +434,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
394 userAccountId 434 userAccountId
395 } 435 }
396 436
397 return VideoAbuseModel 437 return AbuseModel
398 .scope([ 438 .scope([
399 { method: [ ScopeNames.FOR_API, filters ] } 439 { method: [ ScopeNames.FOR_API, filters ] }
400 ]) 440 ])
@@ -404,8 +444,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
404 }) 444 })
405 } 445 }
406 446
407 toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { 447 toFormattedJSON (this: MAbuseFormattable): Abuse {
408 const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) 448 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons)
409 const countReportsForVideo = this.get('countReportsForVideo') as number 449 const countReportsForVideo = this.get('countReportsForVideo') as number
410 const nthReportForVideo = this.get('nthReportForVideo') as number 450 const nthReportForVideo = this.get('nthReportForVideo') as number
411 const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number 451 const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number
@@ -413,51 +453,70 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
413 const countReportsForReporteeVideo = this.get('countReportsForReportee__video') as number 453 const countReportsForReporteeVideo = this.get('countReportsForReportee__video') as number
414 const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number 454 const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number
415 455
416 const video = this.Video 456 let video: VideoAbuse
417 ? this.Video 457
418 : this.deletedVideo 458 if (this.VideoAbuse) {
459 const abuseModel = this.VideoAbuse
460 const entity = abuseModel.Video || abuseModel.deletedVideo
461
462 video = {
463 id: entity.id,
464 uuid: entity.uuid,
465 name: entity.name,
466 nsfw: entity.nsfw,
467
468 startAt: abuseModel.startAt,
469 endAt: abuseModel.endAt,
470
471 deleted: !abuseModel.Video,
472 blacklisted: abuseModel.Video?.isBlacklisted() || false,
473 thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
474 channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel
475 }
476 }
419 477
420 return { 478 return {
421 id: this.id, 479 id: this.id,
422 reason: this.reason, 480 reason: this.reason,
423 predefinedReasons, 481 predefinedReasons,
424 reporterAccount: this.Account.toFormattedJSON(), 482
483 reporterAccount: this.ReporterAccount.toFormattedJSON(),
484
425 state: { 485 state: {
426 id: this.state, 486 id: this.state,
427 label: VideoAbuseModel.getStateLabel(this.state) 487 label: AbuseModel.getStateLabel(this.state)
428 }, 488 },
489
429 moderationComment: this.moderationComment, 490 moderationComment: this.moderationComment,
430 video: { 491
431 id: video.id, 492 video,
432 uuid: video.uuid, 493 comment: null,
433 name: video.name, 494
434 nsfw: video.nsfw,
435 deleted: !this.Video,
436 blacklisted: this.Video?.isBlacklisted() || false,
437 thumbnailPath: this.Video?.getMiniatureStaticPath(),
438 channel: this.Video?.VideoChannel.toFormattedJSON() || this.deletedVideo?.channel
439 },
440 createdAt: this.createdAt, 495 createdAt: this.createdAt,
441 updatedAt: this.updatedAt, 496 updatedAt: this.updatedAt,
442 startAt: this.startAt,
443 endAt: this.endAt,
444 count: countReportsForVideo || 0, 497 count: countReportsForVideo || 0,
445 nth: nthReportForVideo || 0, 498 nth: nthReportForVideo || 0,
446 countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), 499 countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0),
447 countReportsForReportee: (countReportsForReporteeVideo || 0) + (countReportsForReporteeDeletedVideo || 0) 500 countReportsForReportee: (countReportsForReporteeVideo || 0) + (countReportsForReporteeDeletedVideo || 0),
501
502 // FIXME: deprecated in 2.3, remove this
503 startAt: null,
504 endAt: null
448 } 505 }
449 } 506 }
450 507
451 toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { 508 toActivityPubObject (this: MAbuseAP): AbuseObject {
452 const predefinedReasons = VideoAbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) 509 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons)
510
511 const object = this.VideoAbuse?.Video?.url || this.VideoCommentAbuse?.VideoComment?.url || this.FlaggedAccount.Actor.url
453 512
454 const startAt = this.startAt 513 const startAt = this.VideoAbuse?.startAt
455 const endAt = this.endAt 514 const endAt = this.VideoAbuse?.endAt
456 515
457 return { 516 return {
458 type: 'Flag' as 'Flag', 517 type: 'Flag' as 'Flag',
459 content: this.reason, 518 content: this.reason,
460 object: this.Video.url, 519 object,
461 tag: predefinedReasons.map(r => ({ 520 tag: predefinedReasons.map(r => ({
462 type: 'Hashtag' as 'Hashtag', 521 type: 'Hashtag' as 'Hashtag',
463 name: r 522 name: r
@@ -468,12 +527,12 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
468 } 527 }
469 528
470 private static getStateLabel (id: number) { 529 private static getStateLabel (id: number) {
471 return VIDEO_ABUSE_STATES[id] || 'Unknown' 530 return ABUSE_STATES[id] || 'Unknown'
472 } 531 }
473 532
474 private static getPredefinedReasonsStrings (predefinedReasons: VideoAbusePredefinedReasons[]): VideoAbusePredefinedReasonsString[] { 533 private static getPredefinedReasonsStrings (predefinedReasons: AbusePredefinedReasons[]): AbusePredefinedReasonsString[] {
475 return (predefinedReasons || []) 534 return (predefinedReasons || [])
476 .filter(r => r in VideoAbusePredefinedReasons) 535 .filter(r => r in AbusePredefinedReasons)
477 .map(r => invert(videoAbusePredefinedReasonsMap)[r] as VideoAbusePredefinedReasonsString) 536 .map(r => invert(abusePredefinedReasonsMap)[r] as AbusePredefinedReasonsString)
478 } 537 }
479} 538}
diff --git a/server/models/abuse/video-abuse.ts b/server/models/abuse/video-abuse.ts
new file mode 100644
index 000000000..d92bcf19f
--- /dev/null
+++ b/server/models/abuse/video-abuse.ts
@@ -0,0 +1,63 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoDetails } from '@shared/models'
3import { VideoModel } from '../video/video'
4import { AbuseModel } from './abuse'
5
6@Table({
7 tableName: 'videoAbuse',
8 indexes: [
9 {
10 fields: [ 'abuseId' ]
11 },
12 {
13 fields: [ 'videoId' ]
14 }
15 ]
16})
17export class VideoAbuseModel extends Model<VideoAbuseModel> {
18
19 @CreatedAt
20 createdAt: Date
21
22 @UpdatedAt
23 updatedAt: Date
24
25 @AllowNull(true)
26 @Default(null)
27 @Column
28 startAt: number
29
30 @AllowNull(true)
31 @Default(null)
32 @Column
33 endAt: number
34
35 @AllowNull(true)
36 @Default(null)
37 @Column(DataType.JSONB)
38 deletedVideo: VideoDetails
39
40 @ForeignKey(() => AbuseModel)
41 @Column
42 abuseId: number
43
44 @BelongsTo(() => AbuseModel, {
45 foreignKey: {
46 allowNull: false
47 },
48 onDelete: 'cascade'
49 })
50 Abuse: AbuseModel
51
52 @ForeignKey(() => VideoModel)
53 @Column
54 videoId: number
55
56 @BelongsTo(() => VideoModel, {
57 foreignKey: {
58 allowNull: true
59 },
60 onDelete: 'set null'
61 })
62 Video: VideoModel
63}
diff --git a/server/models/abuse/video-comment-abuse.ts b/server/models/abuse/video-comment-abuse.ts
new file mode 100644
index 000000000..b4cc2762e
--- /dev/null
+++ b/server/models/abuse/video-comment-abuse.ts
@@ -0,0 +1,53 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { VideoComment } from '@shared/models'
3import { VideoCommentModel } from '../video/video-comment'
4import { AbuseModel } from './abuse'
5
6@Table({
7 tableName: 'commentAbuse',
8 indexes: [
9 {
10 fields: [ 'abuseId' ]
11 },
12 {
13 fields: [ 'videoCommentId' ]
14 }
15 ]
16})
17export class VideoCommentAbuseModel extends Model<VideoCommentAbuseModel> {
18
19 @CreatedAt
20 createdAt: Date
21
22 @UpdatedAt
23 updatedAt: Date
24
25 @AllowNull(true)
26 @Default(null)
27 @Column(DataType.JSONB)
28 deletedComment: VideoComment
29
30 @ForeignKey(() => AbuseModel)
31 @Column
32 abuseId: number
33
34 @BelongsTo(() => AbuseModel, {
35 foreignKey: {
36 allowNull: false
37 },
38 onDelete: 'cascade'
39 })
40 Abuse: AbuseModel
41
42 @ForeignKey(() => VideoCommentModel)
43 @Column
44 videoCommentId: number
45
46 @BelongsTo(() => VideoCommentModel, {
47 foreignKey: {
48 allowNull: true
49 },
50 onDelete: 'set null'
51 })
52 VideoComment: VideoCommentModel
53}
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index cf8872fd5..577b7dc19 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -1,12 +1,12 @@
1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { AccountModel } from './account'
3import { getSort, searchAttribute } from '../utils'
4import { AccountBlock } from '../../../shared/models/blocklist'
5import { Op } from 'sequelize'
6import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { Op } from 'sequelize'
3import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
7import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' 4import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models'
5import { AccountBlock } from '../../../shared/models'
8import { ActorModel } from '../activitypub/actor' 6import { ActorModel } from '../activitypub/actor'
9import { ServerModel } from '../server/server' 7import { ServerModel } from '../server/server'
8import { getSort, searchAttribute } from '../utils'
9import { AccountModel } from './account'
10 10
11enum ScopeNames { 11enum ScopeNames {
12 WITH_ACCOUNTS = 'WITH_ACCOUNTS' 12 WITH_ACCOUNTS = 'WITH_ACCOUNTS'
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 4395d179a..466d6258e 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -388,6 +388,10 @@ export class AccountModel extends Model<AccountModel> {
388 .findAll(query) 388 .findAll(query)
389 } 389 }
390 390
391 getClientUrl () {
392 return WEBSERVER.URL + '/accounts/' + this.Actor.getIdentifier()
393 }
394
391 toFormattedJSON (this: MAccountFormattable): Account { 395 toFormattedJSON (this: MAccountFormattable): Account {
392 const actor = this.Actor.toFormattedJSON() 396 const actor = this.Actor.toFormattedJSON()
393 const account = { 397 const account = {
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index 30985bb0f..07db5a2db 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -1,22 +1,24 @@
1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
1import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
2import { UserNotification, UserNotificationType } from '../../../shared' 4import { UserNotification, UserNotificationType } from '../../../shared'
3import { getSort, throwIfNotValid } from '../utils'
4import { isBooleanValid } from '../../helpers/custom-validators/misc' 5import { isBooleanValid } from '../../helpers/custom-validators/misc'
5import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' 6import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
6import { UserModel } from './user' 7import { AbuseModel } from '../abuse/abuse'
7import { VideoModel } from '../video/video' 8import { VideoAbuseModel } from '../abuse/video-abuse'
8import { VideoCommentModel } from '../video/video-comment' 9import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
9import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
10import { VideoChannelModel } from '../video/video-channel'
11import { AccountModel } from './account'
12import { VideoAbuseModel } from '../video/video-abuse'
13import { VideoBlacklistModel } from '../video/video-blacklist'
14import { VideoImportModel } from '../video/video-import'
15import { ActorModel } from '../activitypub/actor' 10import { ActorModel } from '../activitypub/actor'
16import { ActorFollowModel } from '../activitypub/actor-follow' 11import { ActorFollowModel } from '../activitypub/actor-follow'
17import { AvatarModel } from '../avatar/avatar' 12import { AvatarModel } from '../avatar/avatar'
18import { ServerModel } from '../server/server' 13import { ServerModel } from '../server/server'
19import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' 14import { getSort, throwIfNotValid } from '../utils'
15import { VideoModel } from '../video/video'
16import { VideoBlacklistModel } from '../video/video-blacklist'
17import { VideoChannelModel } from '../video/video-channel'
18import { VideoCommentModel } from '../video/video-comment'
19import { VideoImportModel } from '../video/video-import'
20import { AccountModel } from './account'
21import { UserModel } from './user'
20 22
21enum ScopeNames { 23enum ScopeNames {
22 WITH_ALL = 'WITH_ALL' 24 WITH_ALL = 'WITH_ALL'
@@ -87,9 +89,41 @@ function buildAccountInclude (required: boolean, withActor = false) {
87 89
88 { 90 {
89 attributes: [ 'id' ], 91 attributes: [ 'id' ],
90 model: VideoAbuseModel.unscoped(), 92 model: AbuseModel.unscoped(),
91 required: false, 93 required: false,
92 include: [ buildVideoInclude(true) ] 94 include: [
95 {
96 attributes: [ 'id' ],
97 model: VideoAbuseModel.unscoped(),
98 required: false,
99 include: [ buildVideoInclude(true) ]
100 },
101 {
102 attributes: [ 'id' ],
103 model: VideoCommentAbuseModel.unscoped(),
104 required: false,
105 include: [
106 {
107 attributes: [ 'id', 'originCommentId' ],
108 model: VideoCommentModel,
109 required: true,
110 include: [
111 {
112 attributes: [ 'uuid' ],
113 model: VideoModel.unscoped(),
114 required: true
115 }
116 ]
117 }
118 ]
119 },
120 {
121 model: AccountModel,
122 as: 'FlaggedAccount',
123 required: true,
124 include: [ buildActorWithAvatarInclude() ]
125 }
126 ]
93 }, 127 },
94 128
95 { 129 {
@@ -179,9 +213,9 @@ function buildAccountInclude (required: boolean, withActor = false) {
179 } 213 }
180 }, 214 },
181 { 215 {
182 fields: [ 'videoAbuseId' ], 216 fields: [ 'abuseId' ],
183 where: { 217 where: {
184 videoAbuseId: { 218 abuseId: {
185 [Op.ne]: null 219 [Op.ne]: null
186 } 220 }
187 } 221 }
@@ -276,17 +310,17 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
276 }) 310 })
277 Comment: VideoCommentModel 311 Comment: VideoCommentModel
278 312
279 @ForeignKey(() => VideoAbuseModel) 313 @ForeignKey(() => AbuseModel)
280 @Column 314 @Column
281 videoAbuseId: number 315 abuseId: number
282 316
283 @BelongsTo(() => VideoAbuseModel, { 317 @BelongsTo(() => AbuseModel, {
284 foreignKey: { 318 foreignKey: {
285 allowNull: true 319 allowNull: true
286 }, 320 },
287 onDelete: 'cascade' 321 onDelete: 'cascade'
288 }) 322 })
289 VideoAbuse: VideoAbuseModel 323 Abuse: AbuseModel
290 324
291 @ForeignKey(() => VideoBlacklistModel) 325 @ForeignKey(() => VideoBlacklistModel)
292 @Column 326 @Column
@@ -397,10 +431,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
397 video: this.formatVideo(this.Comment.Video) 431 video: this.formatVideo(this.Comment.Video)
398 } : undefined 432 } : undefined
399 433
400 const videoAbuse = this.VideoAbuse ? { 434 const abuse = this.Abuse ? this.formatAbuse(this.Abuse) : undefined
401 id: this.VideoAbuse.id,
402 video: this.formatVideo(this.VideoAbuse.Video)
403 } : undefined
404 435
405 const videoBlacklist = this.VideoBlacklist ? { 436 const videoBlacklist = this.VideoBlacklist ? {
406 id: this.VideoBlacklist.id, 437 id: this.VideoBlacklist.id,
@@ -439,7 +470,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
439 video, 470 video,
440 videoImport, 471 videoImport,
441 comment, 472 comment,
442 videoAbuse, 473 abuse,
443 videoBlacklist, 474 videoBlacklist,
444 account, 475 account,
445 actorFollow, 476 actorFollow,
@@ -456,6 +487,27 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
456 } 487 }
457 } 488 }
458 489
490 formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) {
491 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment ? {
492 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
493
494 video: {
495 uuid: abuse.VideoCommentAbuse.VideoComment.Video.uuid
496 }
497 } : undefined
498
499 const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined
500
501 const accountAbuse = (!commentAbuse && !videoAbuse) ? this.formatActor(abuse.FlaggedAccount) : undefined
502
503 return {
504 id: abuse.id,
505 video: videoAbuse,
506 comment: commentAbuse,
507 account: accountAbuse
508 }
509 }
510
459 formatActor ( 511 formatActor (
460 this: UserNotificationModelForApi, 512 this: UserNotificationModelForApi,
461 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor 513 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index de193131a..f21eff04b 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -19,7 +19,7 @@ import {
19 Table, 19 Table,
20 UpdatedAt 20 UpdatedAt
21} from 'sequelize-typescript' 21} from 'sequelize-typescript'
22import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoAbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' 22import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, AbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared'
23import { User, UserRole } from '../../../shared/models/users' 23import { User, UserRole } from '../../../shared/models/users'
24import { 24import {
25 isNoInstanceConfigWarningModal, 25 isNoInstanceConfigWarningModal,
@@ -169,7 +169,7 @@ enum ScopeNames {
169 `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` + 169 `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` +
170 'FROM (' + 170 'FROM (' +
171 'SELECT COUNT("videoAbuse"."id") AS "abuses", ' + 171 'SELECT COUNT("videoAbuse"."id") AS "abuses", ' +
172 `COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${VideoAbuseState.ACCEPTED}) AS "acceptedAbuses" ` + 172 `COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${AbuseState.ACCEPTED}) AS "acceptedAbuses" ` +
173 'FROM "videoAbuse" ' + 173 'FROM "videoAbuse" ' +
174 'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' + 174 'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' +
175 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 175 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index 30f0525e5..68cd72ee7 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -1,11 +1,11 @@
1import * as Bluebird from 'bluebird'
2import { Op } from 'sequelize'
1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 3import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
4import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models'
5import { ServerBlock } from '@shared/models'
2import { AccountModel } from '../account/account' 6import { AccountModel } from '../account/account'
3import { ServerModel } from './server'
4import { ServerBlock } from '../../../shared/models/blocklist'
5import { getSort, searchAttribute } from '../utils' 7import { getSort, searchAttribute } from '../utils'
6import * as Bluebird from 'bluebird' 8import { ServerModel } from './server'
7import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models'
8import { Op } from 'sequelize'
9 9
10enum ScopeNames { 10enum ScopeNames {
11 WITH_ACCOUNT = 'WITH_ACCOUNT', 11 WITH_ACCOUNT = 'WITH_ACCOUNT',
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index e2718300e..272bba0e1 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1,4 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { remove } from 'fs-extra'
2import { maxBy, minBy, pick } from 'lodash' 3import { maxBy, minBy, pick } from 'lodash'
3import { join } from 'path' 4import { join } from 'path'
4import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' 5import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
@@ -23,10 +24,18 @@ import {
23 Table, 24 Table,
24 UpdatedAt 25 UpdatedAt
25} from 'sequelize-typescript' 26} from 'sequelize-typescript'
26import { UserRight, VideoPrivacy, VideoState, ResultList } from '../../../shared' 27import { buildNSFWFilter } from '@server/helpers/express-utils'
28import { getPrivaciesForFederation, isPrivacyForFederation } from '@server/helpers/video'
29import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
30import { getServerActor } from '@server/models/application/application'
31import { ModelCache } from '@server/models/model-cache'
32import { VideoFile } from '@shared/models/videos/video-file.model'
33import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
27import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' 34import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
28import { Video, VideoDetails } from '../../../shared/models/videos' 35import { Video, VideoDetails } from '../../../shared/models/videos'
36import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
29import { VideoFilter } from '../../../shared/models/videos/video-query.type' 37import { VideoFilter } from '../../../shared/models/videos/video-query.type'
38import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
30import { peertubeTruncate } from '../../helpers/core-utils' 39import { peertubeTruncate } from '../../helpers/core-utils'
31import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 40import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
32import { isBooleanValid } from '../../helpers/custom-validators/misc' 41import { isBooleanValid } from '../../helpers/custom-validators/misc'
@@ -43,6 +52,7 @@ import {
43} from '../../helpers/custom-validators/videos' 52} from '../../helpers/custom-validators/videos'
44import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' 53import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
45import { logger } from '../../helpers/logger' 54import { logger } from '../../helpers/logger'
55import { CONFIG } from '../../initializers/config'
46import { 56import {
47 ACTIVITY_PUB, 57 ACTIVITY_PUB,
48 API_VERSION, 58 API_VERSION,
@@ -59,40 +69,6 @@ import {
59 WEBSERVER 69 WEBSERVER
60} from '../../initializers/constants' 70} from '../../initializers/constants'
61import { sendDeleteVideo } from '../../lib/activitypub/send' 71import { sendDeleteVideo } from '../../lib/activitypub/send'
62import { AccountModel } from '../account/account'
63import { AccountVideoRateModel } from '../account/account-video-rate'
64import { ActorModel } from '../activitypub/actor'
65import { AvatarModel } from '../avatar/avatar'
66import { ServerModel } from '../server/server'
67import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
68import { TagModel } from './tag'
69import { VideoAbuseModel } from './video-abuse'
70import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
71import { VideoCommentModel } from './video-comment'
72import { VideoFileModel } from './video-file'
73import { VideoShareModel } from './video-share'
74import { VideoTagModel } from './video-tag'
75import { ScheduleVideoUpdateModel } from './schedule-video-update'
76import { VideoCaptionModel } from './video-caption'
77import { VideoBlacklistModel } from './video-blacklist'
78import { remove } from 'fs-extra'
79import { VideoViewModel } from './video-view'
80import { VideoRedundancyModel } from '../redundancy/video-redundancy'
81import {
82 videoFilesModelToFormattedJSON,
83 VideoFormattingJSONOptions,
84 videoModelToActivityPubObject,
85 videoModelToFormattedDetailsJSON,
86 videoModelToFormattedJSON
87} from './video-format-utils'
88import { UserVideoHistoryModel } from '../account/user-video-history'
89import { VideoImportModel } from './video-import'
90import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
91import { VideoPlaylistElementModel } from './video-playlist-element'
92import { CONFIG } from '../../initializers/config'
93import { ThumbnailModel } from './thumbnail'
94import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
95import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
96import { 72import {
97 MChannel, 73 MChannel,
98 MChannelAccountDefault, 74 MChannelAccountDefault,
@@ -118,15 +94,39 @@ import {
118 MVideoWithFile, 94 MVideoWithFile,
119 MVideoWithRights 95 MVideoWithRights
120} from '../../types/models' 96} from '../../types/models'
121import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
122import { MThumbnail } from '../../types/models/video/thumbnail' 97import { MThumbnail } from '../../types/models/video/thumbnail'
123import { VideoFile } from '@shared/models/videos/video-file.model' 98import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file'
124import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 99import { VideoAbuseModel } from '../abuse/video-abuse'
125import { ModelCache } from '@server/models/model-cache' 100import { AccountModel } from '../account/account'
101import { AccountVideoRateModel } from '../account/account-video-rate'
102import { UserVideoHistoryModel } from '../account/user-video-history'
103import { ActorModel } from '../activitypub/actor'
104import { AvatarModel } from '../avatar/avatar'
105import { VideoRedundancyModel } from '../redundancy/video-redundancy'
106import { ServerModel } from '../server/server'
107import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils'
108import { ScheduleVideoUpdateModel } from './schedule-video-update'
109import { TagModel } from './tag'
110import { ThumbnailModel } from './thumbnail'
111import { VideoBlacklistModel } from './video-blacklist'
112import { VideoCaptionModel } from './video-caption'
113import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
114import { VideoCommentModel } from './video-comment'
115import { VideoFileModel } from './video-file'
116import {
117 videoFilesModelToFormattedJSON,
118 VideoFormattingJSONOptions,
119 videoModelToActivityPubObject,
120 videoModelToFormattedDetailsJSON,
121 videoModelToFormattedJSON
122} from './video-format-utils'
123import { VideoImportModel } from './video-import'
124import { VideoPlaylistElementModel } from './video-playlist-element'
126import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' 125import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder'
127import { buildNSFWFilter } from '@server/helpers/express-utils' 126import { VideoShareModel } from './video-share'
128import { getServerActor } from '@server/models/application/application' 127import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
129import { getPrivaciesForFederation, isPrivacyForFederation } from "@server/helpers/video" 128import { VideoTagModel } from './video-tag'
129import { VideoViewModel } from './video-view'
130 130
131export enum ScopeNames { 131export enum ScopeNames {
132 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', 132 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',