]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/models/video/video-abuse.ts
Add video channel and video thumbnail, rework video appearance in row
[github/Chocobozzz/PeerTube.git] / server / models / video / video-abuse.ts
1 import {
2 AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt, DefaultScope
3 } from 'sequelize-typescript'
4 import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
5 import { VideoAbuse } from '../../../shared/models/videos'
6 import {
7 isVideoAbuseModerationCommentValid,
8 isVideoAbuseReasonValid,
9 isVideoAbuseStateValid
10 } from '../../helpers/custom-validators/video-abuses'
11 import { AccountModel } from '../account/account'
12 import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils'
13 import { VideoModel } from './video'
14 import { VideoAbuseState, Video } from '../../../shared'
15 import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
16 import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
17 import * as Bluebird from 'bluebird'
18 import { literal, Op } from 'sequelize'
19 import { ThumbnailModel } from './thumbnail'
20 import { VideoChannelModel } from './video-channel'
21 import { ActorModel } from '../activitypub/actor'
22 import { VideoBlacklistModel } from './video-blacklist'
23
24 @DefaultScope(() => ({
25 include: [
26 {
27 model: AccountModel,
28 required: true
29 },
30 {
31 model: VideoModel,
32 required: false,
33 include: [
34 {
35 model: ThumbnailModel
36 },
37 {
38 model: VideoChannelModel.unscoped(),
39 include: [
40 {
41 model: ActorModel
42 }
43 ]
44 },
45 {
46 attributes: [ 'id', 'reason', 'unfederated' ],
47 model: VideoBlacklistModel
48 }
49 ]
50 }
51 ]
52 }))
53 @Table({
54 tableName: 'videoAbuse',
55 indexes: [
56 {
57 fields: [ 'videoId' ]
58 },
59 {
60 fields: [ 'reporterAccountId' ]
61 }
62 ]
63 })
64 export class VideoAbuseModel extends Model<VideoAbuseModel> {
65
66 @AllowNull(false)
67 @Default(null)
68 @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason'))
69 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.REASON.max))
70 reason: string
71
72 @AllowNull(false)
73 @Default(null)
74 @Is('VideoAbuseState', value => throwIfNotValid(value, isVideoAbuseStateValid, 'state'))
75 @Column
76 state: VideoAbuseState
77
78 @AllowNull(true)
79 @Default(null)
80 @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment', true))
81 @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max))
82 moderationComment: string
83
84 @AllowNull(true)
85 @Default(null)
86 @Column(DataType.JSONB)
87 deletedVideo: Video
88
89 @CreatedAt
90 createdAt: Date
91
92 @UpdatedAt
93 updatedAt: Date
94
95 @ForeignKey(() => AccountModel)
96 @Column
97 reporterAccountId: number
98
99 @BelongsTo(() => AccountModel, {
100 foreignKey: {
101 allowNull: true
102 },
103 onDelete: 'set null'
104 })
105 Account: AccountModel
106
107 @ForeignKey(() => VideoModel)
108 @Column
109 videoId: number
110
111 @BelongsTo(() => VideoModel, {
112 foreignKey: {
113 allowNull: true
114 },
115 onDelete: 'set null'
116 })
117 Video: VideoModel
118
119 static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> {
120 const videoAttributes = {}
121 if (videoId) videoAttributes['videoId'] = videoId
122 if (uuid) videoAttributes['deletedVideo'] = { uuid }
123
124 const query = {
125 where: {
126 id,
127 ...videoAttributes
128 }
129 }
130 return VideoAbuseModel.findOne(query)
131 }
132
133 static listForApi (parameters: {
134 start: number
135 count: number
136 sort: string
137 serverAccountId: number
138 user?: MUserAccountId
139 }) {
140 const { start, count, sort, user, serverAccountId } = parameters
141 const userAccountId = user ? user.Account.id : undefined
142
143 const query = {
144 offset: start,
145 limit: count,
146 order: getSort(sort),
147 where: {
148 reporterAccountId: {
149 [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')')
150 }
151 },
152 col: 'VideoAbuseModel.id',
153 distinct: true
154 }
155
156 return VideoAbuseModel.findAndCountAll(query)
157 .then(({ rows, count }) => {
158 return { total: count, data: rows }
159 })
160 }
161
162 toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse {
163 const video = this.Video
164 ? this.Video
165 : this.deletedVideo
166
167 return {
168 id: this.id,
169 reason: this.reason,
170 reporterAccount: this.Account.toFormattedJSON(),
171 state: {
172 id: this.state,
173 label: VideoAbuseModel.getStateLabel(this.state)
174 },
175 moderationComment: this.moderationComment,
176 video: {
177 id: video.id,
178 uuid: video.uuid,
179 name: video.name,
180 nsfw: video.nsfw,
181 deleted: !this.Video,
182 blacklisted: this.Video && this.Video.isBlacklisted(),
183 thumbnailPath: this.Video?.getMiniatureStaticPath(),
184 channel: this.Video?.VideoChannel.toFormattedSummaryJSON() || this.deletedVideo?.channel
185 },
186 createdAt: this.createdAt
187 }
188 }
189
190 toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject {
191 return {
192 type: 'Flag' as 'Flag',
193 content: this.reason,
194 object: this.Video.url
195 }
196 }
197
198 private static getStateLabel (id: number) {
199 return VIDEO_ABUSE_STATES[id] || 'Unknown'
200 }
201 }