aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
Diffstat (limited to 'server/models')
-rw-r--r--server/models/utils.ts60
-rw-r--r--server/models/video/video-abuse.ts127
2 files changed, 73 insertions, 114 deletions
diff --git a/server/models/utils.ts b/server/models/utils.ts
index fe4596d31..b2573cd35 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -207,60 +207,13 @@ function buildDirectionAndField (value: string) {
207 return { direction, field } 207 return { direction, field }
208} 208}
209 209
210function searchAttribute (sourceField, targetField) { 210function searchAttribute (sourceField?: string, targetField?: string) {
211 if (sourceField) { 211 if (!sourceField) return {}
212 return {
213 [targetField]: {
214 [Op.iLike]: `%${sourceField}%`
215 }
216 }
217 } else {
218 return {}
219 }
220}
221
222interface QueryStringFilterPrefixes {
223 [key: string]: string | { prefix: string, handler: Function, multiple?: boolean }
224}
225
226function parseQueryStringFilter (q: string, prefixes: QueryStringFilterPrefixes): {
227 search: string
228 [key: string]: string | number | string[] | number[]
229} {
230 const tokens = q // tokenize only if we have a querystring
231 ? [].concat.apply([], q.split('"').map((v, i) => i % 2 ? v : v.split(' '))).filter(Boolean) // split by space unless using double quotes
232 : []
233
234 const objectMap = (obj, fn) => Object.fromEntries(
235 Object.entries(obj).map(
236 ([ k, v ], i) => [ k, fn(v, k, i) ]
237 )
238 )
239 212
240 return { 213 return {
241 // search is the querystring minus defined filters 214 [targetField]: {
242 search: tokens.filter(e => !Object.values(prefixes).some(p => { 215 [Op.iLike]: `%${sourceField}%`
243 if (typeof p === 'string') { 216 }
244 return e.startsWith(p)
245 } else {
246 return e.startsWith(p.prefix)
247 }
248 })).join(' '),
249 // filters defined in prefixes are added under their own name
250 ...objectMap(prefixes, p => {
251 if (typeof p === 'string') {
252 return tokens.filter(e => e.startsWith(p)).map(e => e.slice(p.length)) // we keep the matched item, and remove its prefix
253 } else {
254 const _tokens = tokens.filter(e => e.startsWith(p.prefix)).map(e => e.slice(p.prefix.length)).map(p.handler)
255 // multiple is false by default, meaning we usually just keep the first occurence of a given prefix
256 if (!p.multiple && _tokens.length > 0) {
257 return _tokens[0]
258 } else if (!p.multiple) {
259 return ''
260 }
261 return _tokens
262 }
263 })
264 } 217 }
265} 218}
266 219
@@ -286,8 +239,7 @@ export {
286 getFollowsSort, 239 getFollowsSort,
287 buildDirectionAndField, 240 buildDirectionAndField,
288 createSafeIn, 241 createSafeIn,
289 searchAttribute, 242 searchAttribute
290 parseQueryStringFilter
291} 243}
292 244
293// --------------------------------------------------------------------------- 245// ---------------------------------------------------------------------------
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 6cd2c0418..0844f702d 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -1,6 +1,21 @@
1import * as Bluebird from 'bluebird'
2import { literal, Op } from 'sequelize'
1import { 3import {
2 AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt, Scopes 4 AllowNull,
5 BelongsTo,
6 Column,
7 CreatedAt,
8 DataType,
9 Default,
10 ForeignKey,
11 Is,
12 Model,
13 Scopes,
14 Table,
15 UpdatedAt
3} from 'sequelize-typescript' 16} from 'sequelize-typescript'
17import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type'
18import { VideoAbuseState, VideoDetails } from '../../../shared'
4import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' 19import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
5import { VideoAbuse } from '../../../shared/models/videos' 20import { VideoAbuse } from '../../../shared/models/videos'
6import { 21import {
@@ -8,15 +23,12 @@ import {
8 isVideoAbuseReasonValid, 23 isVideoAbuseReasonValid,
9 isVideoAbuseStateValid 24 isVideoAbuseStateValid
10} from '../../helpers/custom-validators/video-abuses' 25} from '../../helpers/custom-validators/video-abuses'
11import { AccountModel } from '../account/account'
12import { buildBlockedAccountSQL, getSort, throwIfNotValid, searchAttribute, parseQueryStringFilter } from '../utils'
13import { VideoModel } from './video'
14import { VideoAbuseState, VideoDetails } from '../../../shared'
15import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 26import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
16import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' 27import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models'
17import * as Bluebird from 'bluebird' 28import { AccountModel } from '../account/account'
18import { literal, Op } from 'sequelize' 29import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils'
19import { ThumbnailModel } from './thumbnail' 30import { ThumbnailModel } from './thumbnail'
31import { VideoModel } from './video'
20import { VideoBlacklistModel } from './video-blacklist' 32import { VideoBlacklistModel } from './video-blacklist'
21import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' 33import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel'
22 34
@@ -35,21 +47,22 @@ export enum ScopeNames {
35 47
36 // filters 48 // filters
37 id?: number 49 id?: number
50
38 state?: VideoAbuseState 51 state?: VideoAbuseState
39 is?: 'deleted' | 'blacklisted' 52 videoIs?: VideoAbuseVideoIs
40 53
41 // accountIds 54 // accountIds
42 serverAccountId: number 55 serverAccountId: number
43 userAccountId: number 56 userAccountId: number
44 }) => { 57 }) => {
45 let where = { 58 const where = {
46 reporterAccountId: { 59 reporterAccountId: {
47 [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') 60 [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')')
48 } 61 }
49 } 62 }
50 63
51 if (options.search) { 64 if (options.search) {
52 where = Object.assign(where, { 65 Object.assign(where, {
53 [Op.or]: [ 66 [Op.or]: [
54 { 67 {
55 [Op.and]: [ 68 [Op.and]: [
@@ -80,26 +93,18 @@ export enum ScopeNames {
80 }) 93 })
81 } 94 }
82 95
83 if (options.id) { 96 if (options.id) Object.assign(where, { id: options.id })
84 where = Object.assign(where, { 97 if (options.state) Object.assign(where, { state: options.state })
85 id: options.id
86 })
87 }
88 98
89 if (options.state) { 99 if (options.videoIs === 'deleted') {
90 where = Object.assign(where, { 100 Object.assign(where, {
91 state: options.state 101 deletedVideo: {
102 [Op.not]: null
103 }
92 }) 104 })
93 } 105 }
94 106
95 let onlyBlacklisted = false 107 const onlyBlacklisted = options.videoIs === 'blacklisted'
96 if (options.is === 'deleted') {
97 where = Object.assign(where, {
98 deletedVideo: { [Op.not]: null }
99 })
100 } else if (options.is === 'blacklisted') {
101 onlyBlacklisted = true
102 }
103 108
104 return { 109 return {
105 attributes: { 110 attributes: {
@@ -189,7 +194,7 @@ export enum ScopeNames {
189 }, 194 },
190 { 195 {
191 model: VideoModel, 196 model: VideoModel,
192 required: onlyBlacklisted, 197 required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel),
193 where: searchAttribute(options.searchVideo, 'name'), 198 where: searchAttribute(options.searchVideo, 'name'),
194 include: [ 199 include: [
195 { 200 {
@@ -301,11 +306,36 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
301 start: number 306 start: number
302 count: number 307 count: number
303 sort: string 308 sort: string
304 search?: string 309
305 serverAccountId: number 310 serverAccountId: number
306 user?: MUserAccountId 311 user?: MUserAccountId
312
313 id?: number
314 state?: VideoAbuseState
315 videoIs?: VideoAbuseVideoIs
316
317 search?: string
318 searchReporter?: string
319 searchReportee?: string
320 searchVideo?: string
321 searchVideoChannel?: string
307 }) { 322 }) {
308 const { start, count, sort, search, user, serverAccountId } = parameters 323 const {
324 start,
325 count,
326 sort,
327 search,
328 user,
329 serverAccountId,
330 state,
331 videoIs,
332 searchReportee,
333 searchVideo,
334 searchVideoChannel,
335 searchReporter,
336 id
337 } = parameters
338
309 const userAccountId = user ? user.Account.id : undefined 339 const userAccountId = user ? user.Account.id : undefined
310 340
311 const query = { 341 const query = {
@@ -317,37 +347,14 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
317 } 347 }
318 348
319 const filters = { 349 const filters = {
320 ...parseQueryStringFilter(search, { 350 id,
321 id: { 351 search,
322 prefix: '#', 352 state,
323 handler: v => v 353 videoIs,
324 }, 354 searchReportee,
325 state: { 355 searchVideo,
326 prefix: 'state:', 356 searchVideoChannel,
327 handler: v => { 357 searchReporter,
328 if (v === 'accepted') return VideoAbuseState.ACCEPTED
329 if (v === 'pending') return VideoAbuseState.PENDING
330 if (v === 'rejected') return VideoAbuseState.REJECTED
331 return undefined
332 }
333 },
334 is: {
335 prefix: 'is:',
336 handler: v => {
337 if (v === 'deleted') return v
338 if (v === 'blacklisted') return v
339 return undefined
340 }
341 },
342 searchReporter: {
343 prefix: 'reporter:',
344 handler: v => v
345 },
346 searchReportee: {
347 prefix: 'reportee:',
348 handler: v => v
349 }
350 }),
351 serverAccountId, 358 serverAccountId,
352 userAccountId 359 userAccountId
353 } 360 }