diff options
Diffstat (limited to 'server')
46 files changed, 1555 insertions, 764 deletions
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts new file mode 100644 index 000000000..ee046cb3a --- /dev/null +++ b/server/controllers/api/abuse.ts | |||
@@ -0,0 +1,168 @@ | |||
1 | import * as express from 'express' | ||
2 | import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' | ||
3 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared' | ||
6 | import { getFormattedObjects } from '../../helpers/utils' | ||
7 | import { sequelizeTypescript } from '../../initializers/database' | ||
8 | import { | ||
9 | abuseGetValidator, | ||
10 | abuseListValidator, | ||
11 | abuseReportValidator, | ||
12 | abusesSortValidator, | ||
13 | abuseUpdateValidator, | ||
14 | asyncMiddleware, | ||
15 | asyncRetryTransactionMiddleware, | ||
16 | authenticate, | ||
17 | ensureUserHasRight, | ||
18 | paginationValidator, | ||
19 | setDefaultPagination, | ||
20 | setDefaultSort | ||
21 | } from '../../middlewares' | ||
22 | import { AccountModel } from '../../models/account/account' | ||
23 | |||
24 | const abuseRouter = express.Router() | ||
25 | |||
26 | abuseRouter.get('/abuse', | ||
27 | authenticate, | ||
28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
29 | paginationValidator, | ||
30 | abusesSortValidator, | ||
31 | setDefaultSort, | ||
32 | setDefaultPagination, | ||
33 | abuseListValidator, | ||
34 | asyncMiddleware(listAbuses) | ||
35 | ) | ||
36 | abuseRouter.put('/:videoId/abuse/:id', | ||
37 | authenticate, | ||
38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
39 | asyncMiddleware(abuseUpdateValidator), | ||
40 | asyncRetryTransactionMiddleware(updateAbuse) | ||
41 | ) | ||
42 | abuseRouter.post('/:videoId/abuse', | ||
43 | authenticate, | ||
44 | asyncMiddleware(abuseReportValidator), | ||
45 | asyncRetryTransactionMiddleware(reportAbuse) | ||
46 | ) | ||
47 | abuseRouter.delete('/:videoId/abuse/:id', | ||
48 | authenticate, | ||
49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), | ||
50 | asyncMiddleware(abuseGetValidator), | ||
51 | asyncRetryTransactionMiddleware(deleteAbuse) | ||
52 | ) | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | export { | ||
57 | abuseRouter, | ||
58 | |||
59 | // FIXME: deprecated in 2.3. Remove these exports | ||
60 | listAbuses, | ||
61 | updateAbuse, | ||
62 | deleteAbuse, | ||
63 | reportAbuse | ||
64 | } | ||
65 | |||
66 | // --------------------------------------------------------------------------- | ||
67 | |||
68 | async function listAbuses (req: express.Request, res: express.Response) { | ||
69 | const user = res.locals.oauth.token.user | ||
70 | const serverActor = await getServerActor() | ||
71 | |||
72 | const resultList = await AbuseModel.listForApi({ | ||
73 | start: req.query.start, | ||
74 | count: req.query.count, | ||
75 | sort: req.query.sort, | ||
76 | id: req.query.id, | ||
77 | filter: 'video', | ||
78 | predefinedReason: req.query.predefinedReason, | ||
79 | search: req.query.search, | ||
80 | state: req.query.state, | ||
81 | videoIs: req.query.videoIs, | ||
82 | searchReporter: req.query.searchReporter, | ||
83 | searchReportee: req.query.searchReportee, | ||
84 | searchVideo: req.query.searchVideo, | ||
85 | searchVideoChannel: req.query.searchVideoChannel, | ||
86 | serverAccountId: serverActor.Account.id, | ||
87 | user | ||
88 | }) | ||
89 | |||
90 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
91 | } | ||
92 | |||
93 | async function updateAbuse (req: express.Request, res: express.Response) { | ||
94 | const abuse = res.locals.abuse | ||
95 | |||
96 | if (req.body.moderationComment !== undefined) abuse.moderationComment = req.body.moderationComment | ||
97 | if (req.body.state !== undefined) abuse.state = req.body.state | ||
98 | |||
99 | await sequelizeTypescript.transaction(t => { | ||
100 | return abuse.save({ transaction: t }) | ||
101 | }) | ||
102 | |||
103 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
104 | |||
105 | return res.type('json').status(204).end() | ||
106 | } | ||
107 | |||
108 | async function deleteAbuse (req: express.Request, res: express.Response) { | ||
109 | const abuse = res.locals.abuse | ||
110 | |||
111 | await sequelizeTypescript.transaction(t => { | ||
112 | return abuse.destroy({ transaction: t }) | ||
113 | }) | ||
114 | |||
115 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
116 | |||
117 | return res.type('json').status(204).end() | ||
118 | } | ||
119 | |||
120 | async function reportAbuse (req: express.Request, res: express.Response) { | ||
121 | const videoInstance = res.locals.videoAll | ||
122 | const commentInstance = res.locals.videoCommentFull | ||
123 | const accountInstance = res.locals.account | ||
124 | |||
125 | const body: AbuseCreate = req.body | ||
126 | |||
127 | const { id } = await sequelizeTypescript.transaction(async t => { | ||
128 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
129 | const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r]) | ||
130 | |||
131 | const baseAbuse = { | ||
132 | reporterAccountId: reporterAccount.id, | ||
133 | reason: body.reason, | ||
134 | state: AbuseState.PENDING, | ||
135 | predefinedReasons | ||
136 | } | ||
137 | |||
138 | if (body.video) { | ||
139 | return createVideoAbuse({ | ||
140 | baseAbuse, | ||
141 | videoInstance, | ||
142 | reporterAccount, | ||
143 | transaction: t, | ||
144 | startAt: body.video.startAt, | ||
145 | endAt: body.video.endAt | ||
146 | }) | ||
147 | } | ||
148 | |||
149 | if (body.comment) { | ||
150 | return createVideoCommentAbuse({ | ||
151 | baseAbuse, | ||
152 | commentInstance, | ||
153 | reporterAccount, | ||
154 | transaction: t | ||
155 | }) | ||
156 | } | ||
157 | |||
158 | // Account report | ||
159 | return createAccountAbuse({ | ||
160 | baseAbuse, | ||
161 | accountInstance, | ||
162 | reporterAccount, | ||
163 | transaction: t | ||
164 | }) | ||
165 | }) | ||
166 | |||
167 | return res.json({ abuse: { id } }) | ||
168 | } | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index c334a26b4..eda9e04d1 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -3,6 +3,7 @@ import * as express from 'express' | |||
3 | import * as RateLimit from 'express-rate-limit' | 3 | import * as RateLimit from 'express-rate-limit' |
4 | import { badRequest } from '../../helpers/express-utils' | 4 | import { badRequest } from '../../helpers/express-utils' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { CONFIG } from '../../initializers/config' |
6 | import { abuseRouter } from './abuse' | ||
6 | import { accountsRouter } from './accounts' | 7 | import { accountsRouter } from './accounts' |
7 | import { bulkRouter } from './bulk' | 8 | import { bulkRouter } from './bulk' |
8 | import { configRouter } from './config' | 9 | import { configRouter } from './config' |
@@ -32,6 +33,7 @@ const apiRateLimiter = RateLimit({ | |||
32 | apiRouter.use(apiRateLimiter) | 33 | apiRouter.use(apiRateLimiter) |
33 | 34 | ||
34 | apiRouter.use('/server', serverRouter) | 35 | apiRouter.use('/server', serverRouter) |
36 | apiRouter.use('/abuses', abuseRouter) | ||
35 | apiRouter.use('/bulk', bulkRouter) | 37 | apiRouter.use('/bulk', bulkRouter) |
36 | apiRouter.use('/oauth-clients', oauthClientsRouter) | 38 | apiRouter.use('/oauth-clients', oauthClientsRouter) |
37 | apiRouter.use('/config', configRouter) | 39 | apiRouter.use('/config', configRouter) |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index ab2074459..b92a66360 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse, videoAbusePredefinedReasonsMap } from '../../../../shared' | 2 | import { AbuseModel } from '@server/models/abuse/abuse' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { getServerActor } from '@server/models/application/application' |
4 | import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared' | ||
4 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers/database' | ||
6 | import { | 6 | import { |
7 | abusesSortValidator, | ||
7 | asyncMiddleware, | 8 | asyncMiddleware, |
8 | asyncRetryTransactionMiddleware, | 9 | asyncRetryTransactionMiddleware, |
9 | authenticate, | 10 | authenticate, |
@@ -12,28 +13,21 @@ import { | |||
12 | setDefaultPagination, | 13 | setDefaultPagination, |
13 | setDefaultSort, | 14 | setDefaultSort, |
14 | videoAbuseGetValidator, | 15 | videoAbuseGetValidator, |
16 | videoAbuseListValidator, | ||
15 | videoAbuseReportValidator, | 17 | videoAbuseReportValidator, |
16 | videoAbusesSortValidator, | 18 | videoAbuseUpdateValidator |
17 | videoAbuseUpdateValidator, | ||
18 | videoAbuseListValidator | ||
19 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
20 | import { AccountModel } from '../../../models/account/account' | 20 | import { deleteAbuse, reportAbuse, updateAbuse } from '../abuse' |
21 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 21 | |
22 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 22 | // FIXME: deprecated in 2.3. Remove this controller |
23 | import { Notifier } from '../../../lib/notifier' | ||
24 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | ||
25 | import { MVideoAbuseAccountVideo } from '../../../types/models/video' | ||
26 | import { getServerActor } from '@server/models/application/application' | ||
27 | import { MAccountDefault } from '@server/types/models' | ||
28 | 23 | ||
29 | const auditLogger = auditLoggerFactory('abuse') | ||
30 | const abuseVideoRouter = express.Router() | 24 | const abuseVideoRouter = express.Router() |
31 | 25 | ||
32 | abuseVideoRouter.get('/abuse', | 26 | abuseVideoRouter.get('/abuse', |
33 | authenticate, | 27 | authenticate, |
34 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 28 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
35 | paginationValidator, | 29 | paginationValidator, |
36 | videoAbusesSortValidator, | 30 | abusesSortValidator, |
37 | setDefaultSort, | 31 | setDefaultSort, |
38 | setDefaultPagination, | 32 | setDefaultPagination, |
39 | videoAbuseListValidator, | 33 | videoAbuseListValidator, |
@@ -41,7 +35,7 @@ abuseVideoRouter.get('/abuse', | |||
41 | ) | 35 | ) |
42 | abuseVideoRouter.put('/:videoId/abuse/:id', | 36 | abuseVideoRouter.put('/:videoId/abuse/:id', |
43 | authenticate, | 37 | authenticate, |
44 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 38 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
45 | asyncMiddleware(videoAbuseUpdateValidator), | 39 | asyncMiddleware(videoAbuseUpdateValidator), |
46 | asyncRetryTransactionMiddleware(updateVideoAbuse) | 40 | asyncRetryTransactionMiddleware(updateVideoAbuse) |
47 | ) | 41 | ) |
@@ -52,7 +46,7 @@ abuseVideoRouter.post('/:videoId/abuse', | |||
52 | ) | 46 | ) |
53 | abuseVideoRouter.delete('/:videoId/abuse/:id', | 47 | abuseVideoRouter.delete('/:videoId/abuse/:id', |
54 | authenticate, | 48 | authenticate, |
55 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | 49 | ensureUserHasRight(UserRight.MANAGE_ABUSES), |
56 | asyncMiddleware(videoAbuseGetValidator), | 50 | asyncMiddleware(videoAbuseGetValidator), |
57 | asyncRetryTransactionMiddleware(deleteVideoAbuse) | 51 | asyncRetryTransactionMiddleware(deleteVideoAbuse) |
58 | ) | 52 | ) |
@@ -69,11 +63,12 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
69 | const user = res.locals.oauth.token.user | 63 | const user = res.locals.oauth.token.user |
70 | const serverActor = await getServerActor() | 64 | const serverActor = await getServerActor() |
71 | 65 | ||
72 | const resultList = await VideoAbuseModel.listForApi({ | 66 | const resultList = await AbuseModel.listForApi({ |
73 | start: req.query.start, | 67 | start: req.query.start, |
74 | count: req.query.count, | 68 | count: req.query.count, |
75 | sort: req.query.sort, | 69 | sort: req.query.sort, |
76 | id: req.query.id, | 70 | id: req.query.id, |
71 | filter: 'video', | ||
77 | predefinedReason: req.query.predefinedReason, | 72 | predefinedReason: req.query.predefinedReason, |
78 | search: req.query.search, | 73 | search: req.query.search, |
79 | state: req.query.state, | 74 | state: req.query.state, |
@@ -90,74 +85,28 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
90 | } | 85 | } |
91 | 86 | ||
92 | async function updateVideoAbuse (req: express.Request, res: express.Response) { | 87 | async function updateVideoAbuse (req: express.Request, res: express.Response) { |
93 | const videoAbuse = res.locals.videoAbuse | 88 | return updateAbuse(req, res) |
94 | |||
95 | if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment | ||
96 | if (req.body.state !== undefined) videoAbuse.state = req.body.state | ||
97 | |||
98 | await sequelizeTypescript.transaction(t => { | ||
99 | return videoAbuse.save({ transaction: t }) | ||
100 | }) | ||
101 | |||
102 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
103 | |||
104 | return res.type('json').status(204).end() | ||
105 | } | 89 | } |
106 | 90 | ||
107 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { | 91 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { |
108 | const videoAbuse = res.locals.videoAbuse | 92 | return deleteAbuse(req, res) |
109 | |||
110 | await sequelizeTypescript.transaction(t => { | ||
111 | return videoAbuse.destroy({ transaction: t }) | ||
112 | }) | ||
113 | |||
114 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
115 | |||
116 | return res.type('json').status(204).end() | ||
117 | } | 93 | } |
118 | 94 | ||
119 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 95 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
120 | const videoInstance = res.locals.videoAll | 96 | const oldBody = req.body as VideoAbuseCreate |
121 | const body: VideoAbuseCreate = req.body | ||
122 | let reporterAccount: MAccountDefault | ||
123 | let videoAbuseJSON: VideoAbuse | ||
124 | |||
125 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { | ||
126 | reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | ||
127 | const predefinedReasons = body.predefinedReasons?.map(r => videoAbusePredefinedReasonsMap[r]) | ||
128 | |||
129 | const abuseToCreate = { | ||
130 | reporterAccountId: reporterAccount.id, | ||
131 | reason: body.reason, | ||
132 | videoId: videoInstance.id, | ||
133 | state: VideoAbuseState.PENDING, | ||
134 | predefinedReasons, | ||
135 | startAt: body.startAt, | ||
136 | endAt: body.endAt | ||
137 | } | ||
138 | |||
139 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | ||
140 | videoAbuseInstance.Video = videoInstance | ||
141 | videoAbuseInstance.Account = reporterAccount | ||
142 | |||
143 | // We send the video abuse to the origin server | ||
144 | if (videoInstance.isOwned() === false) { | ||
145 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) | ||
146 | } | ||
147 | 97 | ||
148 | videoAbuseJSON = videoAbuseInstance.toFormattedJSON() | 98 | req.body = { |
149 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON)) | 99 | accountId: res.locals.videoAll.VideoChannel.accountId, |
150 | 100 | ||
151 | return videoAbuseInstance | 101 | reason: oldBody.reason, |
152 | }) | 102 | predefinedReasons: oldBody.predefinedReasons, |
153 | 103 | ||
154 | Notifier.Instance.notifyOnNewVideoAbuse({ | 104 | video: { |
155 | videoAbuse: videoAbuseJSON, | 105 | id: res.locals.videoAll.id, |
156 | videoAbuseInstance, | 106 | startAt: oldBody.startAt, |
157 | reporter: reporterAccount.Actor.getIdentifier() | 107 | endAt: oldBody.endAt |
158 | }) | 108 | } |
159 | 109 | } as AbuseCreate | |
160 | logger.info('Abuse report for video "%s" created.', videoInstance.name) | ||
161 | 110 | ||
162 | return res.json({ videoAbuse: videoAbuseJSON }).end() | 111 | return reportAbuse(req, res) |
163 | } | 112 | } |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 0bbfbc753..954b0b69d 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as path from 'path' | ||
2 | import * as express from 'express' | ||
3 | import { diff } from 'deep-object-diff' | 1 | import { diff } from 'deep-object-diff' |
4 | import { chain } from 'lodash' | 2 | import * as express from 'express' |
5 | import * as flatten from 'flat' | 3 | import * as flatten from 'flat' |
4 | import { chain } from 'lodash' | ||
5 | import * as path from 'path' | ||
6 | import * as winston from 'winston' | 6 | import * as winston from 'winston' |
7 | import { jsonLoggerFormat, labelFormatter } from './logger' | 7 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' |
8 | import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared' | 8 | import { Abuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' |
9 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | ||
10 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | 9 | import { CustomConfig } from '../../shared/models/server/custom-config.model' |
10 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | ||
11 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../initializers/config' |
12 | import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' | 12 | import { jsonLoggerFormat, labelFormatter } from './logger' |
13 | 13 | ||
14 | function getAuditIdFromRes (res: express.Response) { | 14 | function getAuditIdFromRes (res: express.Response) { |
15 | return res.locals.oauth.token.User.username | 15 | return res.locals.oauth.token.User.username |
@@ -212,18 +212,15 @@ class VideoChannelAuditView extends EntityAuditView { | |||
212 | } | 212 | } |
213 | } | 213 | } |
214 | 214 | ||
215 | const videoAbuseKeysToKeep = [ | 215 | const abuseKeysToKeep = [ |
216 | 'id', | 216 | 'id', |
217 | 'reason', | 217 | 'reason', |
218 | 'reporterAccount', | 218 | 'reporterAccount', |
219 | 'video-id', | ||
220 | 'video-name', | ||
221 | 'video-uuid', | ||
222 | 'createdAt' | 219 | 'createdAt' |
223 | ] | 220 | ] |
224 | class VideoAbuseAuditView extends EntityAuditView { | 221 | class AbuseAuditView extends EntityAuditView { |
225 | constructor (private readonly videoAbuse: VideoAbuse) { | 222 | constructor (private readonly abuse: Abuse) { |
226 | super(videoAbuseKeysToKeep, 'abuse', videoAbuse) | 223 | super(abuseKeysToKeep, 'abuse', abuse) |
227 | } | 224 | } |
228 | } | 225 | } |
229 | 226 | ||
@@ -274,6 +271,6 @@ export { | |||
274 | CommentAuditView, | 271 | CommentAuditView, |
275 | UserAuditView, | 272 | UserAuditView, |
276 | VideoAuditView, | 273 | VideoAuditView, |
277 | VideoAbuseAuditView, | 274 | AbuseAuditView, |
278 | CustomConfigAuditView | 275 | CustomConfigAuditView |
279 | } | 276 | } |
diff --git a/server/helpers/custom-validators/abuses.ts b/server/helpers/custom-validators/abuses.ts new file mode 100644 index 000000000..a6a895c65 --- /dev/null +++ b/server/helpers/custom-validators/abuses.ts | |||
@@ -0,0 +1,54 @@ | |||
1 | import validator from 'validator' | ||
2 | import { abusePredefinedReasonsMap, AbusePredefinedReasonsString, AbuseVideoIs } from '@shared/models' | ||
3 | import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants' | ||
4 | import { exists, isArray } from './misc' | ||
5 | |||
6 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES | ||
7 | |||
8 | function isAbuseReasonValid (value: string) { | ||
9 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
10 | } | ||
11 | |||
12 | function isAbusePredefinedReasonValid (value: AbusePredefinedReasonsString) { | ||
13 | return exists(value) && value in abusePredefinedReasonsMap | ||
14 | } | ||
15 | |||
16 | function isAbusePredefinedReasonsValid (value: AbusePredefinedReasonsString[]) { | ||
17 | return exists(value) && isArray(value) && value.every(v => v in abusePredefinedReasonsMap) | ||
18 | } | ||
19 | |||
20 | function isAbuseTimestampValid (value: number) { | ||
21 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
22 | } | ||
23 | |||
24 | function isAbuseTimestampCoherent (endAt: number, { req }) { | ||
25 | return exists(req.body.startAt) && endAt > req.body.startAt | ||
26 | } | ||
27 | |||
28 | function isAbuseModerationCommentValid (value: string) { | ||
29 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
30 | } | ||
31 | |||
32 | function isAbuseStateValid (value: string) { | ||
33 | return exists(value) && ABUSE_STATES[value] !== undefined | ||
34 | } | ||
35 | |||
36 | function isAbuseVideoIsValid (value: AbuseVideoIs) { | ||
37 | return exists(value) && ( | ||
38 | value === 'deleted' || | ||
39 | value === 'blacklisted' | ||
40 | ) | ||
41 | } | ||
42 | |||
43 | // --------------------------------------------------------------------------- | ||
44 | |||
45 | export { | ||
46 | isAbuseReasonValid, | ||
47 | isAbusePredefinedReasonValid, | ||
48 | isAbusePredefinedReasonsValid, | ||
49 | isAbuseTimestampValid, | ||
50 | isAbuseTimestampCoherent, | ||
51 | isAbuseModerationCommentValid, | ||
52 | isAbuseStateValid, | ||
53 | isAbuseVideoIsValid | ||
54 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/flag.ts b/server/helpers/custom-validators/activitypub/flag.ts index 6452e297c..dc90b3667 100644 --- a/server/helpers/custom-validators/activitypub/flag.ts +++ b/server/helpers/custom-validators/activitypub/flag.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { isActivityPubUrlValid } from './misc' | 1 | import { isActivityPubUrlValid } from './misc' |
2 | import { isVideoAbuseReasonValid } from '../video-abuses' | 2 | import { isAbuseReasonValid } from '../abuses' |
3 | 3 | ||
4 | function isFlagActivityValid (activity: any) { | 4 | function isFlagActivityValid (activity: any) { |
5 | return activity.type === 'Flag' && | 5 | return activity.type === 'Flag' && |
6 | isVideoAbuseReasonValid(activity.content) && | 6 | isAbuseReasonValid(activity.content) && |
7 | isActivityPubUrlValid(activity.object) | 7 | isActivityPubUrlValid(activity.object) |
8 | } | 8 | } |
9 | 9 | ||
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts deleted file mode 100644 index 0c2c34268..000000000 --- a/server/helpers/custom-validators/video-abuses.ts +++ /dev/null | |||
@@ -1,56 +0,0 @@ | |||
1 | import validator from 'validator' | ||
2 | |||
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | ||
4 | import { exists, isArray } from './misc' | ||
5 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | ||
6 | import { VideoAbusePredefinedReasonsString, videoAbusePredefinedReasonsMap } from '@shared/models/videos/abuse/video-abuse-reason.model' | ||
7 | |||
8 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | ||
9 | |||
10 | function isVideoAbuseReasonValid (value: string) { | ||
11 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
12 | } | ||
13 | |||
14 | function isVideoAbusePredefinedReasonValid (value: VideoAbusePredefinedReasonsString) { | ||
15 | return exists(value) && value in videoAbusePredefinedReasonsMap | ||
16 | } | ||
17 | |||
18 | function isVideoAbusePredefinedReasonsValid (value: VideoAbusePredefinedReasonsString[]) { | ||
19 | return exists(value) && isArray(value) && value.every(v => v in videoAbusePredefinedReasonsMap) | ||
20 | } | ||
21 | |||
22 | function isVideoAbuseTimestampValid (value: number) { | ||
23 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
24 | } | ||
25 | |||
26 | function isVideoAbuseTimestampCoherent (endAt: number, { req }) { | ||
27 | return exists(req.body.startAt) && endAt > req.body.startAt | ||
28 | } | ||
29 | |||
30 | function isVideoAbuseModerationCommentValid (value: string) { | ||
31 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
32 | } | ||
33 | |||
34 | function isVideoAbuseStateValid (value: string) { | ||
35 | return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined | ||
36 | } | ||
37 | |||
38 | function isAbuseVideoIsValid (value: VideoAbuseVideoIs) { | ||
39 | return exists(value) && ( | ||
40 | value === 'deleted' || | ||
41 | value === 'blacklisted' | ||
42 | ) | ||
43 | } | ||
44 | |||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | export { | ||
48 | isVideoAbuseReasonValid, | ||
49 | isVideoAbusePredefinedReasonValid, | ||
50 | isVideoAbusePredefinedReasonsValid, | ||
51 | isVideoAbuseTimestampValid, | ||
52 | isVideoAbuseTimestampCoherent, | ||
53 | isVideoAbuseModerationCommentValid, | ||
54 | isVideoAbuseStateValid, | ||
55 | isAbuseVideoIsValid | ||
56 | } | ||
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/abuses.ts index 97a5724b6..3906f6760 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/abuses.ts | |||
@@ -1,19 +1,20 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { AbuseModel } from '../../models/abuse/abuse' |
3 | import { fetchVideo } from '../video' | 3 | import { fetchVideo } from '../video' |
4 | 4 | ||
5 | // FIXME: deprecated in 2.3. Remove this function | ||
5 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { | 6 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { |
6 | const abuseId = parseInt(abuseIdArg + '', 10) | 7 | const abuseId = parseInt(abuseIdArg + '', 10) |
7 | let videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) | 8 | let abuse = await AbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) |
8 | 9 | ||
9 | if (!videoAbuse) { | 10 | if (!abuse) { |
10 | const userId = res.locals.oauth?.token.User.id | 11 | const userId = res.locals.oauth?.token.User.id |
11 | const video = await fetchVideo(videoUUID, 'all', userId) | 12 | const video = await fetchVideo(videoUUID, 'all', userId) |
12 | 13 | ||
13 | if (video) videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, video.id) | 14 | if (video) abuse = await AbuseModel.loadByIdAndVideoId(abuseId, video.id) |
14 | } | 15 | } |
15 | 16 | ||
16 | if (videoAbuse === null) { | 17 | if (abuse === null) { |
17 | res.status(404) | 18 | res.status(404) |
18 | .json({ error: 'Video abuse not found' }) | 19 | .json({ error: 'Video abuse not found' }) |
19 | .end() | 20 | .end() |
@@ -21,12 +22,17 @@ async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: stri | |||
21 | return false | 22 | return false |
22 | } | 23 | } |
23 | 24 | ||
24 | res.locals.videoAbuse = videoAbuse | 25 | res.locals.abuse = abuse |
25 | return true | 26 | return true |
26 | } | 27 | } |
27 | 28 | ||
29 | async function doesAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { | ||
30 | |||
31 | } | ||
32 | |||
28 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
29 | 34 | ||
30 | export { | 35 | export { |
36 | doesAbuseExist, | ||
31 | doesVideoAbuseExist | 37 | doesVideoAbuseExist |
32 | } | 38 | } |
diff --git a/server/helpers/middlewares/index.ts b/server/helpers/middlewares/index.ts index f91aeaa12..f57f3ad31 100644 --- a/server/helpers/middlewares/index.ts +++ b/server/helpers/middlewares/index.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export * from './abuses' | ||
1 | export * from './accounts' | 2 | export * from './accounts' |
2 | export * from './video-abuses' | ||
3 | export * from './video-blacklists' | 3 | export * from './video-blacklists' |
4 | export * from './video-captions' | 4 | export * from './video-captions' |
5 | export * from './video-channels' | 5 | export * from './video-channels' |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index e730e3c84..8f86bbbef 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -1,9 +1,17 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { randomBytes } from 'crypto' | 2 | import { randomBytes } from 'crypto' |
3 | import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models' | ||
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 3 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 4 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' | 5 | import { |
6 | AbuseState, | ||
7 | VideoImportState, | ||
8 | VideoPrivacy, | ||
9 | VideoTranscodingFPS, | ||
10 | JobType, | ||
11 | VideoRateType, | ||
12 | VideoResolution, | ||
13 | VideoState | ||
14 | } from '../../shared/models' | ||
7 | // Do not use barrels, remain constants as independent as possible | 15 | // Do not use barrels, remain constants as independent as possible |
8 | import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils' | 16 | import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils' |
9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 17 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
@@ -51,7 +59,6 @@ const SORTABLE_COLUMNS = { | |||
51 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], | 59 | USER_SUBSCRIPTIONS: [ 'id', 'createdAt' ], |
52 | ACCOUNTS: [ 'createdAt' ], | 60 | ACCOUNTS: [ 'createdAt' ], |
53 | JOBS: [ 'createdAt' ], | 61 | JOBS: [ 'createdAt' ], |
54 | VIDEO_ABUSES: [ 'id', 'createdAt', 'state' ], | ||
55 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], | 62 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], |
56 | VIDEO_IMPORTS: [ 'createdAt' ], | 63 | VIDEO_IMPORTS: [ 'createdAt' ], |
57 | VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ], | 64 | VIDEO_COMMENT_THREADS: [ 'createdAt', 'totalReplies' ], |
@@ -66,6 +73,8 @@ const SORTABLE_COLUMNS = { | |||
66 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], | 73 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], |
67 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], | 74 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], |
68 | 75 | ||
76 | ABUSES: [ 'id', 'createdAt', 'state' ], | ||
77 | |||
69 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], | 78 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], |
70 | SERVERS_BLOCKLIST: [ 'createdAt' ], | 79 | SERVERS_BLOCKLIST: [ 'createdAt' ], |
71 | 80 | ||
@@ -193,7 +202,7 @@ const CONSTRAINTS_FIELDS = { | |||
193 | VIDEO_LANGUAGES: { max: 500 }, // Array length | 202 | VIDEO_LANGUAGES: { max: 500 }, // Array length |
194 | BLOCKED_REASON: { min: 3, max: 250 } // Length | 203 | BLOCKED_REASON: { min: 3, max: 250 } // Length |
195 | }, | 204 | }, |
196 | VIDEO_ABUSES: { | 205 | ABUSES: { |
197 | REASON: { min: 2, max: 3000 }, // Length | 206 | REASON: { min: 2, max: 3000 }, // Length |
198 | MODERATION_COMMENT: { min: 2, max: 3000 } // Length | 207 | MODERATION_COMMENT: { min: 2, max: 3000 } // Length |
199 | }, | 208 | }, |
@@ -378,10 +387,10 @@ const VIDEO_IMPORT_STATES = { | |||
378 | [VideoImportState.REJECTED]: 'Rejected' | 387 | [VideoImportState.REJECTED]: 'Rejected' |
379 | } | 388 | } |
380 | 389 | ||
381 | const VIDEO_ABUSE_STATES = { | 390 | const ABUSE_STATES = { |
382 | [VideoAbuseState.PENDING]: 'Pending', | 391 | [AbuseState.PENDING]: 'Pending', |
383 | [VideoAbuseState.REJECTED]: 'Rejected', | 392 | [AbuseState.REJECTED]: 'Rejected', |
384 | [VideoAbuseState.ACCEPTED]: 'Accepted' | 393 | [AbuseState.ACCEPTED]: 'Accepted' |
385 | } | 394 | } |
386 | 395 | ||
387 | const VIDEO_PLAYLIST_PRIVACIES = { | 396 | const VIDEO_PLAYLIST_PRIVACIES = { |
@@ -778,7 +787,7 @@ export { | |||
778 | VIDEO_RATE_TYPES, | 787 | VIDEO_RATE_TYPES, |
779 | VIDEO_TRANSCODING_FPS, | 788 | VIDEO_TRANSCODING_FPS, |
780 | FFMPEG_NICE, | 789 | FFMPEG_NICE, |
781 | VIDEO_ABUSE_STATES, | 790 | ABUSE_STATES, |
782 | VIDEO_CHANNELS, | 791 | VIDEO_CHANNELS, |
783 | LRU_CACHE, | 792 | LRU_CACHE, |
784 | JOB_REQUEST_TIMEOUT, | 793 | JOB_REQUEST_TIMEOUT, |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 633d4f956..0775f1fad 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -1,44 +1,45 @@ | |||
1 | import { QueryTypes, Transaction } from 'sequelize' | ||
1 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' | 2 | import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' |
3 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
4 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
5 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
2 | import { isTestInstance } from '../helpers/core-utils' | 6 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { logger } from '../helpers/logger' | 7 | import { logger } from '../helpers/logger' |
4 | |||
5 | import { AccountModel } from '../models/account/account' | 8 | import { AccountModel } from '../models/account/account' |
9 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
6 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | 10 | import { AccountVideoRateModel } from '../models/account/account-video-rate' |
7 | import { UserModel } from '../models/account/user' | 11 | import { UserModel } from '../models/account/user' |
12 | import { UserNotificationModel } from '../models/account/user-notification' | ||
13 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
14 | import { UserVideoHistoryModel } from '../models/account/user-video-history' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | 15 | import { ActorModel } from '../models/activitypub/actor' |
9 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 16 | import { ActorFollowModel } from '../models/activitypub/actor-follow' |
10 | import { ApplicationModel } from '../models/application/application' | 17 | import { ApplicationModel } from '../models/application/application' |
11 | import { AvatarModel } from '../models/avatar/avatar' | 18 | import { AvatarModel } from '../models/avatar/avatar' |
12 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 19 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
13 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 20 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
21 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
22 | import { PluginModel } from '../models/server/plugin' | ||
14 | import { ServerModel } from '../models/server/server' | 23 | import { ServerModel } from '../models/server/server' |
24 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | ||
15 | import { TagModel } from '../models/video/tag' | 26 | import { TagModel } from '../models/video/tag' |
27 | import { ThumbnailModel } from '../models/video/thumbnail' | ||
16 | import { VideoModel } from '../models/video/video' | 28 | import { VideoModel } from '../models/video/video' |
17 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
18 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 29 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
30 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
31 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
19 | import { VideoChannelModel } from '../models/video/video-channel' | 32 | import { VideoChannelModel } from '../models/video/video-channel' |
20 | import { VideoCommentModel } from '../models/video/video-comment' | 33 | import { VideoCommentModel } from '../models/video/video-comment' |
21 | import { VideoFileModel } from '../models/video/video-file' | 34 | import { VideoFileModel } from '../models/video/video-file' |
22 | import { VideoShareModel } from '../models/video/video-share' | ||
23 | import { VideoTagModel } from '../models/video/video-tag' | ||
24 | import { CONFIG } from './config' | ||
25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | ||
26 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
27 | import { VideoImportModel } from '../models/video/video-import' | 35 | import { VideoImportModel } from '../models/video/video-import' |
28 | import { VideoViewModel } from '../models/video/video-view' | ||
29 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
30 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
31 | import { UserVideoHistoryModel } from '../models/account/user-video-history' | ||
32 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
33 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
34 | import { UserNotificationModel } from '../models/account/user-notification' | ||
35 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | ||
36 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
37 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 36 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
38 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | 37 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' |
39 | import { ThumbnailModel } from '../models/video/thumbnail' | 38 | import { VideoShareModel } from '../models/video/video-share' |
40 | import { PluginModel } from '../models/server/plugin' | 39 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
41 | import { QueryTypes, Transaction } from 'sequelize' | 40 | import { VideoTagModel } from '../models/video/video-tag' |
41 | import { VideoViewModel } from '../models/video/video-view' | ||
42 | import { CONFIG } from './config' | ||
42 | 43 | ||
43 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 44 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
44 | 45 | ||
@@ -86,6 +87,8 @@ async function initDatabaseModels (silent: boolean) { | |||
86 | TagModel, | 87 | TagModel, |
87 | AccountVideoRateModel, | 88 | AccountVideoRateModel, |
88 | UserModel, | 89 | UserModel, |
90 | AbuseModel, | ||
91 | VideoCommentAbuseModel, | ||
89 | VideoAbuseModel, | 92 | VideoAbuseModel, |
90 | VideoModel, | 93 | VideoModel, |
91 | VideoChangeOwnershipModel, | 94 | VideoChangeOwnershipModel, |
diff --git a/server/initializers/migrations/0250-video-abuse-state.ts b/server/initializers/migrations/0250-video-abuse-state.ts index 50de25182..e4993c393 100644 --- a/server/initializers/migrations/0250-video-abuse-state.ts +++ b/server/initializers/migrations/0250-video-abuse-state.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoAbuseState } from '../../../shared/models/videos' | 2 | import { AbuseState } from '../../../shared/models' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction | 5 | transaction: Sequelize.Transaction |
@@ -16,7 +16,7 @@ async function up (utils: { | |||
16 | } | 16 | } |
17 | 17 | ||
18 | { | 18 | { |
19 | const query = 'UPDATE "videoAbuse" SET "state" = ' + VideoAbuseState.PENDING | 19 | const query = 'UPDATE "videoAbuse" SET "state" = ' + AbuseState.PENDING |
20 | await utils.sequelize.query(query) | 20 | await utils.sequelize.query(query) |
21 | } | 21 | } |
22 | 22 | ||
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1d7132a3a..6350cee12 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts | |||
@@ -1,24 +1,19 @@ | |||
1 | import { | 1 | import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' |
2 | ActivityCreate, | 2 | import { AccountModel } from '@server/models/account/account' |
3 | ActivityFlag, | 3 | import { VideoModel } from '@server/models/video/video' |
4 | VideoAbuseState, | 4 | import { VideoCommentModel } from '@server/models/video/video-comment' |
5 | videoAbusePredefinedReasonsMap | 5 | import { AbuseObject, abusePredefinedReasonsMap, AbuseState, ActivityCreate, ActivityFlag } from '../../../../shared' |
6 | } from '../../../../shared' | 6 | import { getAPId } from '../../../helpers/activitypub' |
7 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' | ||
8 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
9 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
10 | import { sequelizeTypescript } from '../../../initializers/database' | 9 | import { sequelizeTypescript } from '../../../initializers/database' |
11 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
12 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | ||
13 | import { Notifier } from '../../notifier' | ||
14 | import { getAPId } from '../../../helpers/activitypub' | ||
15 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../types/activitypub-processor.model' |
16 | import { MActorSignature, MVideoAbuseAccountVideo } from '../../../types/models' | 11 | import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models' |
17 | import { AccountModel } from '@server/models/account/account' | ||
18 | 12 | ||
19 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { | 13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { |
20 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
21 | return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor) | 15 | |
16 | return retryTransactionWrapper(processCreateAbuse, activity, byActor) | ||
22 | } | 17 | } |
23 | 18 | ||
24 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
@@ -29,55 +24,79 @@ export { | |||
29 | 24 | ||
30 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
31 | 26 | ||
32 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { | 27 | async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { |
33 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) | 28 | const flag = activity.type === 'Flag' ? activity : (activity.object as AbuseObject) |
34 | 29 | ||
35 | const account = byActor.Account | 30 | const account = byActor.Account |
36 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) | 31 | if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url) |
32 | |||
33 | const reporterAccount = await AccountModel.load(account.id) | ||
37 | 34 | ||
38 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] | 35 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] |
39 | 36 | ||
37 | const tags = Array.isArray(flag.tag) ? flag.tag : [] | ||
38 | const predefinedReasons = tags.map(tag => abusePredefinedReasonsMap[tag.name]) | ||
39 | .filter(v => !isNaN(v)) | ||
40 | |||
41 | const startAt = flag.startAt | ||
42 | const endAt = flag.endAt | ||
43 | |||
40 | for (const object of objects) { | 44 | for (const object of objects) { |
41 | try { | 45 | try { |
42 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) | 46 | const uri = getAPId(object) |
43 | |||
44 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) | ||
45 | const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t)) | ||
46 | const tags = Array.isArray(flag.tag) ? flag.tag : [] | ||
47 | const predefinedReasons = tags.map(tag => videoAbusePredefinedReasonsMap[tag.name]) | ||
48 | .filter(v => !isNaN(v)) | ||
49 | const startAt = flag.startAt | ||
50 | const endAt = flag.endAt | ||
51 | |||
52 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { | ||
53 | const videoAbuseData = { | ||
54 | reporterAccountId: account.id, | ||
55 | reason: flag.content, | ||
56 | videoId: video.id, | ||
57 | state: VideoAbuseState.PENDING, | ||
58 | predefinedReasons, | ||
59 | startAt, | ||
60 | endAt | ||
61 | } | ||
62 | 47 | ||
63 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) | 48 | logger.debug('Reporting remote abuse for object %s.', uri) |
64 | videoAbuseInstance.Video = video | ||
65 | videoAbuseInstance.Account = reporterAccount | ||
66 | 49 | ||
67 | logger.info('Remote abuse for video uuid %s created', flag.object) | 50 | await sequelizeTypescript.transaction(async t => { |
68 | 51 | ||
69 | return videoAbuseInstance | 52 | const video = await VideoModel.loadByUrlAndPopulateAccount(uri) |
70 | }) | 53 | let videoComment: MCommentOwnerVideo |
54 | let flaggedAccount: MAccountDefault | ||
55 | |||
56 | if (!video) videoComment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(uri) | ||
57 | if (!videoComment) flaggedAccount = await AccountModel.loadByUrl(uri) | ||
58 | |||
59 | if (!video && !videoComment && !flaggedAccount) { | ||
60 | logger.warn('Cannot flag unknown entity %s.', object) | ||
61 | return | ||
62 | } | ||
63 | |||
64 | const baseAbuse = { | ||
65 | reporterAccountId: reporterAccount.id, | ||
66 | reason: flag.content, | ||
67 | state: AbuseState.PENDING, | ||
68 | predefinedReasons | ||
69 | } | ||
71 | 70 | ||
72 | const videoAbuseJSON = videoAbuseInstance.toFormattedJSON() | 71 | if (video) { |
72 | return createVideoAbuse({ | ||
73 | baseAbuse, | ||
74 | startAt, | ||
75 | endAt, | ||
76 | reporterAccount, | ||
77 | transaction: t, | ||
78 | videoInstance: video | ||
79 | }) | ||
80 | } | ||
81 | |||
82 | if (videoComment) { | ||
83 | return createVideoCommentAbuse({ | ||
84 | baseAbuse, | ||
85 | reporterAccount, | ||
86 | transaction: t, | ||
87 | commentInstance: videoComment | ||
88 | }) | ||
89 | } | ||
73 | 90 | ||
74 | Notifier.Instance.notifyOnNewVideoAbuse({ | 91 | return await createAccountAbuse({ |
75 | videoAbuse: videoAbuseJSON, | 92 | baseAbuse, |
76 | videoAbuseInstance, | 93 | reporterAccount, |
77 | reporter: reporterAccount.Actor.getIdentifier() | 94 | transaction: t, |
95 | accountInstance: flaggedAccount | ||
96 | }) | ||
78 | }) | 97 | }) |
79 | } catch (err) { | 98 | } catch (err) { |
80 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | 99 | logger.debug('Cannot process report of %s', getAPId(object), { err }) |
81 | } | 100 | } |
82 | } | 101 | } |
83 | } | 102 | } |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 3a1fe0812..821637ec8 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -1,32 +1,31 @@ | |||
1 | import { getVideoAbuseActivityPubUrl } from '../url' | 1 | import { Transaction } from 'sequelize' |
2 | import { unicastTo } from './utils' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' |
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MAbuseAP, MAccountLight, MActor } from '../../../types/models' | ||
5 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
6 | import { Transaction } from 'sequelize' | 6 | import { getAbuseActivityPubUrl } from '../url' |
7 | import { MActor, MVideoFullLight } from '../../../types/models' | 7 | import { unicastTo } from './utils' |
8 | import { MVideoAbuseVideo } from '../../../types/models/video' | ||
9 | 8 | ||
10 | function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { | 9 | function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLight, t: Transaction) { |
11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 10 | if (!flaggedAccount.Actor.serverId) return // Local user |
12 | 11 | ||
13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 12 | const url = getAbuseActivityPubUrl(abuse) |
14 | 13 | ||
15 | logger.info('Creating job to send video abuse %s.', url) | 14 | logger.info('Creating job to send abuse %s.', url) |
16 | 15 | ||
17 | // Custom audience, we only send the abuse to the origin instance | 16 | // Custom audience, we only send the abuse to the origin instance |
18 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } | 17 | const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } |
19 | const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience) | 18 | const flagActivity = buildFlagActivity(url, byActor, abuse, audience) |
20 | 19 | ||
21 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.getSharedInbox())) | 20 | t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) |
22 | } | 21 | } |
23 | 22 | ||
24 | function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { | 23 | function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { |
25 | if (!audience) audience = getAudience(byActor) | 24 | if (!audience) audience = getAudience(byActor) |
26 | 25 | ||
27 | const activity = Object.assign( | 26 | const activity = Object.assign( |
28 | { id: url, actor: byActor.url }, | 27 | { id: url, actor: byActor.url }, |
29 | videoAbuse.toActivityPubObject() | 28 | abuse.toActivityPubObject() |
30 | ) | 29 | ) |
31 | 30 | ||
32 | return audiencify(activity, audience) | 31 | return audiencify(activity, audience) |
@@ -35,5 +34,5 @@ function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbus | |||
35 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
36 | 35 | ||
37 | export { | 36 | export { |
38 | sendVideoAbuse | 37 | sendAbuse |
39 | } | 38 | } |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index 7f98751a1..b54e038a4 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -5,10 +5,10 @@ import { | |||
5 | MActorId, | 5 | MActorId, |
6 | MActorUrl, | 6 | MActorUrl, |
7 | MCommentId, | 7 | MCommentId, |
8 | MVideoAbuseId, | ||
9 | MVideoId, | 8 | MVideoId, |
10 | MVideoUrl, | 9 | MVideoUrl, |
11 | MVideoUUID | 10 | MVideoUUID, |
11 | MAbuseId | ||
12 | } from '../../types/models' | 12 | } from '../../types/models' |
13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' | 13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../types/models/video/video-playlist' |
14 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' | 14 | import { MVideoFileVideoUUID } from '../../types/models/video/video-file' |
@@ -48,8 +48,8 @@ function getAccountActivityPubUrl (accountName: string) { | |||
48 | return WEBSERVER.URL + '/accounts/' + accountName | 48 | return WEBSERVER.URL + '/accounts/' + accountName |
49 | } | 49 | } |
50 | 50 | ||
51 | function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { | 51 | function getAbuseActivityPubUrl (abuse: MAbuseId) { |
52 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | 52 | return WEBSERVER.URL + '/admin/abuses/' + abuse.id |
53 | } | 53 | } |
54 | 54 | ||
55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { | 55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
@@ -118,7 +118,7 @@ export { | |||
118 | getVideoCacheStreamingPlaylistActivityPubUrl, | 118 | getVideoCacheStreamingPlaylistActivityPubUrl, |
119 | getVideoChannelActivityPubUrl, | 119 | getVideoChannelActivityPubUrl, |
120 | getAccountActivityPubUrl, | 120 | getAccountActivityPubUrl, |
121 | getVideoAbuseActivityPubUrl, | 121 | getAbuseActivityPubUrl, |
122 | getActorFollowActivityPubUrl, | 122 | getActorFollowActivityPubUrl, |
123 | getActorFollowAcceptActivityPubUrl, | 123 | getActorFollowAcceptActivityPubUrl, |
124 | getVideoAnnounceActivityPubUrl, | 124 | getVideoAnnounceActivityPubUrl, |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index c08732b48..e821aea5f 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,26 +1,20 @@ | |||
1 | import { readFileSync } from 'fs-extra' | ||
2 | import { merge } from 'lodash' | ||
1 | import { createTransport, Transporter } from 'nodemailer' | 3 | import { createTransport, Transporter } from 'nodemailer' |
4 | import { join } from 'path' | ||
5 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
6 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | ||
7 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | ||
8 | import { Abuse, EmailPayload } from '@shared/models' | ||
9 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | ||
2 | import { isTestInstance, root } from '../helpers/core-utils' | 10 | import { isTestInstance, root } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 11 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG, isEmailEnabled } from '../initializers/config' | 12 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
5 | import { JobQueue } from './job-queue' | ||
6 | import { readFileSync } from 'fs-extra' | ||
7 | import { WEBSERVER } from '../initializers/constants' | 13 | import { WEBSERVER } from '../initializers/constants' |
8 | import { | 14 | import { MAbuseFull, MActorFollowActors, MActorFollowFull, MUser } from '../types/models' |
9 | MCommentOwnerVideo, | 15 | import { MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../types/models/video' |
10 | MVideo, | 16 | import { JobQueue } from './job-queue' |
11 | MVideoAbuseVideo, | 17 | |
12 | MVideoAccountLight, | ||
13 | MVideoBlacklistLightVideo, | ||
14 | MVideoBlacklistVideo | ||
15 | } from '../types/models/video' | ||
16 | import { MActorFollowActors, MActorFollowFull, MUser } from '../types/models' | ||
17 | import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' | ||
18 | import { EmailPayload } from '@shared/models' | ||
19 | import { join } from 'path' | ||
20 | import { VideoAbuse } from '../../shared/models/videos' | ||
21 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' | ||
22 | import { merge } from 'lodash' | ||
23 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
24 | const Email = require('email-templates') | 18 | const Email = require('email-templates') |
25 | 19 | ||
26 | class Emailer { | 20 | class Emailer { |
@@ -288,28 +282,70 @@ class Emailer { | |||
288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 282 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
289 | } | 283 | } |
290 | 284 | ||
291 | addVideoAbuseModeratorsNotification (to: string[], parameters: { | 285 | addAbuseModeratorsNotification (to: string[], parameters: { |
292 | videoAbuse: VideoAbuse | 286 | abuse: Abuse |
293 | videoAbuseInstance: MVideoAbuseVideo | 287 | abuseInstance: MAbuseFull |
294 | reporter: string | 288 | reporter: string |
295 | }) { | 289 | }) { |
296 | const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id | 290 | const { abuse, abuseInstance, reporter } = parameters |
297 | const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() | ||
298 | 291 | ||
299 | const emailPayload: EmailPayload = { | 292 | const action = { |
300 | template: 'video-abuse-new', | 293 | text: 'View report #' + abuse.id, |
301 | to, | 294 | url: WEBSERVER.URL + '/admin/moderation/abuses/list?search=%23' + abuse.id |
302 | subject: `New video abuse report from ${parameters.reporter}`, | 295 | } |
303 | locals: { | 296 | |
304 | videoUrl, | 297 | let emailPayload: EmailPayload |
305 | videoAbuseUrl, | 298 | |
306 | videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), | 299 | if (abuseInstance.VideoAbuse) { |
307 | videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), | 300 | const video = abuseInstance.VideoAbuse.Video |
308 | videoAbuse: parameters.videoAbuse, | 301 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
309 | reporter: parameters.reporter, | 302 | |
310 | action: { | 303 | emailPayload = { |
311 | text: 'View report #' + parameters.videoAbuse.id, | 304 | template: 'video-abuse-new', |
312 | url: videoAbuseUrl | 305 | to, |
306 | subject: `New video abuse report from ${reporter}`, | ||
307 | locals: { | ||
308 | videoUrl, | ||
309 | isLocal: video.remote === false, | ||
310 | videoCreatedAt: new Date(video.createdAt).toLocaleString(), | ||
311 | videoPublishedAt: new Date(video.publishedAt).toLocaleString(), | ||
312 | videoName: video.name, | ||
313 | reason: abuse.reason, | ||
314 | videoChannel: video.VideoChannel, | ||
315 | action | ||
316 | } | ||
317 | } | ||
318 | } else if (abuseInstance.VideoCommentAbuse) { | ||
319 | const comment = abuseInstance.VideoCommentAbuse.VideoComment | ||
320 | const commentUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() + ';threadId=' + comment.getThreadId() | ||
321 | |||
322 | emailPayload = { | ||
323 | template: 'comment-abuse-new', | ||
324 | to, | ||
325 | subject: `New comment abuse report from ${reporter}`, | ||
326 | locals: { | ||
327 | commentUrl, | ||
328 | isLocal: comment.isOwned(), | ||
329 | commentCreatedAt: new Date(comment.createdAt).toLocaleString(), | ||
330 | reason: abuse.reason, | ||
331 | flaggedAccount: abuseInstance.FlaggedAccount.getDisplayName(), | ||
332 | action | ||
333 | } | ||
334 | } | ||
335 | } else { | ||
336 | const account = abuseInstance.FlaggedAccount | ||
337 | const accountUrl = account.getClientUrl() | ||
338 | |||
339 | emailPayload = { | ||
340 | template: 'account-abuse-new', | ||
341 | to, | ||
342 | subject: `New account abuse report from ${reporter}`, | ||
343 | locals: { | ||
344 | accountUrl, | ||
345 | accountDisplayName: account.getDisplayName(), | ||
346 | isLocal: account.isOwned(), | ||
347 | reason: abuse.reason, | ||
348 | action | ||
313 | } | 349 | } |
314 | } | 350 | } |
315 | } | 351 | } |
diff --git a/server/lib/emails/account-abuse-new/html.pug b/server/lib/emails/account-abuse-new/html.pug new file mode 100644 index 000000000..06be8025b --- /dev/null +++ b/server/lib/emails/account-abuse-new/html.pug | |||
@@ -0,0 +1,14 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | An account is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}account " | ||
10 | a(href=accountUrl) #{accountDisplayName} | ||
11 | |||
12 | p The reporter, #{reporter}, cited the following reason(s): | ||
13 | blockquote #{reason} | ||
14 | br(style="display: none;") | ||
diff --git a/server/lib/emails/common/mixins.pug b/server/lib/emails/common/mixins.pug index 76b805a24..831211864 100644 --- a/server/lib/emails/common/mixins.pug +++ b/server/lib/emails/common/mixins.pug | |||
@@ -1,3 +1,7 @@ | |||
1 | mixin channel(channel) | 1 | mixin channel(channel) |
2 | - var handle = `${channel.name}@${channel.host}` | 2 | - var handle = `${channel.name}@${channel.host}` |
3 | | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] \ No newline at end of file | 3 | | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] |
4 | |||
5 | mixin account(account) | ||
6 | - var handle = `${account.name}@${account.host}` | ||
7 | | #[a(href=`${WEBSERVER.URL}/accounts/${handle}` title=handle) #{account.displayName}] | ||
diff --git a/server/lib/emails/video-abuse-new/html.pug b/server/lib/emails/video-abuse-new/html.pug index 999c89d26..a1acdabdc 100644 --- a/server/lib/emails/video-abuse-new/html.pug +++ b/server/lib/emails/video-abuse-new/html.pug | |||
@@ -6,13 +6,13 @@ block title | |||
6 | 6 | ||
7 | block content | 7 | block content |
8 | p | 8 | p |
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video " | 9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}video " |
10 | a(href=videoUrl) #{videoAbuse.video.name} | 10 | a(href=videoUrl) #{videoName} |
11 | | " by #[+channel(videoAbuse.video.channel)] | 11 | | " by #[+channel(videoChannel)] |
12 | if videoPublishedAt | 12 | if videoPublishedAt |
13 | | , published the #{videoPublishedAt}. | 13 | | , published the #{videoPublishedAt}. |
14 | else | 14 | else |
15 | | , uploaded the #{videoCreatedAt} but not yet published. | 15 | | , uploaded the #{videoCreatedAt} but not yet published. |
16 | p The reporter, #{reporter}, cited the following reason(s): | 16 | p The reporter, #{reporter}, cited the following reason(s): |
17 | blockquote #{videoAbuse.reason} | 17 | blockquote #{reason} |
18 | br(style="display: none;") | 18 | br(style="display: none;") |
diff --git a/server/lib/emails/video-comment-abuse-new/html.pug b/server/lib/emails/video-comment-abuse-new/html.pug new file mode 100644 index 000000000..170b79576 --- /dev/null +++ b/server/lib/emails/video-comment-abuse-new/html.pug | |||
@@ -0,0 +1,15 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | A comment is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{isLocal ? '' : 'remote '}comment " | ||
10 | a(href=commentUrl) of #{flaggedAccount} | ||
11 | | created on #{commentCreatedAt} | ||
12 | |||
13 | p The reporter, #{reporter}, cited the following reason(s): | ||
14 | blockquote #{reason} | ||
15 | br(style="display: none;") | ||
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index 60d1b4053..4fc9cd747 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts | |||
@@ -1,15 +1,33 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { PathLike } from 'fs-extra' |
2 | import { VideoCommentModel } from '../models/video/video-comment' | 2 | import { Transaction } from 'sequelize/types' |
3 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | 3 | import { AbuseAuditView, auditLoggerFactory } from '@server/helpers/audit-logger' |
4 | import { logger } from '@server/helpers/logger' | ||
5 | import { AbuseModel } from '@server/models/abuse/abuse' | ||
6 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
7 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
8 | import { VideoFileModel } from '@server/models/video/video-file' | ||
9 | import { FilteredModelAttributes } from '@server/types' | ||
10 | import { | ||
11 | MAbuseFull, | ||
12 | MAccountDefault, | ||
13 | MAccountLight, | ||
14 | MCommentAbuseAccountVideo, | ||
15 | MCommentOwnerVideo, | ||
16 | MUser, | ||
17 | MVideoAbuseVideoFull, | ||
18 | MVideoAccountLightBlacklistAllFiles | ||
19 | } from '@server/types/models' | ||
20 | import { ActivityCreate } from '../../shared/models/activitypub' | ||
21 | import { VideoTorrentObject } from '../../shared/models/activitypub/objects' | ||
22 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | ||
4 | import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' | 23 | import { VideoCreate, VideoImportCreate } from '../../shared/models/videos' |
24 | import { VideoCommentCreate } from '../../shared/models/videos/video-comment.model' | ||
5 | import { UserModel } from '../models/account/user' | 25 | import { UserModel } from '../models/account/user' |
6 | import { VideoTorrentObject } from '../../shared/models/activitypub/objects' | ||
7 | import { ActivityCreate } from '../../shared/models/activitypub' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | 26 | import { ActorModel } from '../models/activitypub/actor' |
9 | import { VideoCommentObject } from '../../shared/models/activitypub/objects/video-comment-object' | 27 | import { VideoModel } from '../models/video/video' |
10 | import { VideoFileModel } from '@server/models/video/video-file' | 28 | import { VideoCommentModel } from '../models/video/video-comment' |
11 | import { PathLike } from 'fs-extra' | 29 | import { sendAbuse } from './activitypub/send/send-flag' |
12 | import { MUser } from '@server/types/models' | 30 | import { Notifier } from './notifier' |
13 | 31 | ||
14 | export type AcceptResult = { | 32 | export type AcceptResult = { |
15 | accepted: boolean | 33 | accepted: boolean |
@@ -73,6 +91,89 @@ function isPostImportVideoAccepted (object: { | |||
73 | return { accepted: true } | 91 | return { accepted: true } |
74 | } | 92 | } |
75 | 93 | ||
94 | async function createVideoAbuse (options: { | ||
95 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
96 | videoInstance: MVideoAccountLightBlacklistAllFiles | ||
97 | startAt: number | ||
98 | endAt: number | ||
99 | transaction: Transaction | ||
100 | reporterAccount: MAccountDefault | ||
101 | }) { | ||
102 | const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount } = options | ||
103 | |||
104 | const associateFun = async (abuseInstance: MAbuseFull) => { | ||
105 | const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({ | ||
106 | abuseId: abuseInstance.id, | ||
107 | videoId: videoInstance.id, | ||
108 | startAt: startAt, | ||
109 | endAt: endAt | ||
110 | }, { transaction }) | ||
111 | |||
112 | videoAbuseInstance.Video = videoInstance | ||
113 | abuseInstance.VideoAbuse = videoAbuseInstance | ||
114 | |||
115 | return { isOwned: videoInstance.isOwned() } | ||
116 | } | ||
117 | |||
118 | return createAbuse({ | ||
119 | base: baseAbuse, | ||
120 | reporterAccount, | ||
121 | flaggedAccount: videoInstance.VideoChannel.Account, | ||
122 | transaction, | ||
123 | associateFun | ||
124 | }) | ||
125 | } | ||
126 | |||
127 | function createVideoCommentAbuse (options: { | ||
128 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
129 | commentInstance: MCommentOwnerVideo | ||
130 | transaction: Transaction | ||
131 | reporterAccount: MAccountDefault | ||
132 | }) { | ||
133 | const { baseAbuse, commentInstance, transaction, reporterAccount } = options | ||
134 | |||
135 | const associateFun = async (abuseInstance: MAbuseFull) => { | ||
136 | const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({ | ||
137 | abuseId: abuseInstance.id, | ||
138 | videoCommentId: commentInstance.id | ||
139 | }, { transaction }) | ||
140 | |||
141 | commentAbuseInstance.VideoComment = commentInstance | ||
142 | abuseInstance.VideoCommentAbuse = commentAbuseInstance | ||
143 | |||
144 | return { isOwned: commentInstance.isOwned() } | ||
145 | } | ||
146 | |||
147 | return createAbuse({ | ||
148 | base: baseAbuse, | ||
149 | reporterAccount, | ||
150 | flaggedAccount: commentInstance.Account, | ||
151 | transaction, | ||
152 | associateFun | ||
153 | }) | ||
154 | } | ||
155 | |||
156 | function createAccountAbuse (options: { | ||
157 | baseAbuse: FilteredModelAttributes<AbuseModel> | ||
158 | accountInstance: MAccountDefault | ||
159 | transaction: Transaction | ||
160 | reporterAccount: MAccountDefault | ||
161 | }) { | ||
162 | const { baseAbuse, accountInstance, transaction, reporterAccount } = options | ||
163 | |||
164 | const associateFun = async () => { | ||
165 | return { isOwned: accountInstance.isOwned() } | ||
166 | } | ||
167 | |||
168 | return createAbuse({ | ||
169 | base: baseAbuse, | ||
170 | reporterAccount, | ||
171 | flaggedAccount: accountInstance, | ||
172 | transaction, | ||
173 | associateFun | ||
174 | }) | ||
175 | } | ||
176 | |||
76 | export { | 177 | export { |
77 | isLocalVideoAccepted, | 178 | isLocalVideoAccepted, |
78 | isLocalVideoThreadAccepted, | 179 | isLocalVideoThreadAccepted, |
@@ -80,5 +181,48 @@ export { | |||
80 | isRemoteVideoCommentAccepted, | 181 | isRemoteVideoCommentAccepted, |
81 | isLocalVideoCommentReplyAccepted, | 182 | isLocalVideoCommentReplyAccepted, |
82 | isPreImportVideoAccepted, | 183 | isPreImportVideoAccepted, |
83 | isPostImportVideoAccepted | 184 | isPostImportVideoAccepted, |
185 | |||
186 | createAbuse, | ||
187 | createVideoAbuse, | ||
188 | createVideoCommentAbuse, | ||
189 | createAccountAbuse | ||
190 | } | ||
191 | |||
192 | // --------------------------------------------------------------------------- | ||
193 | |||
194 | async function createAbuse (options: { | ||
195 | base: FilteredModelAttributes<AbuseModel> | ||
196 | reporterAccount: MAccountDefault | ||
197 | flaggedAccount: MAccountLight | ||
198 | associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean} > | ||
199 | transaction: Transaction | ||
200 | }) { | ||
201 | const { base, reporterAccount, flaggedAccount, associateFun, transaction } = options | ||
202 | const auditLogger = auditLoggerFactory('abuse') | ||
203 | |||
204 | const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id }) | ||
205 | const abuseInstance: MAbuseFull = await AbuseModel.create(abuseAttributes, { transaction }) | ||
206 | |||
207 | abuseInstance.ReporterAccount = reporterAccount | ||
208 | abuseInstance.FlaggedAccount = flaggedAccount | ||
209 | |||
210 | const { isOwned } = await associateFun(abuseInstance) | ||
211 | |||
212 | if (isOwned === false) { | ||
213 | await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) | ||
214 | } | ||
215 | |||
216 | const abuseJSON = abuseInstance.toFormattedJSON() | ||
217 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) | ||
218 | |||
219 | Notifier.Instance.notifyOnNewAbuse({ | ||
220 | abuse: abuseJSON, | ||
221 | abuseInstance, | ||
222 | reporter: reporterAccount.Actor.getIdentifier() | ||
223 | }) | ||
224 | |||
225 | logger.info('Abuse report %d created.', abuseInstance.id) | ||
226 | |||
227 | return abuseJSON | ||
84 | } | 228 | } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 943a087d2..40cff66d2 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -8,23 +8,18 @@ import { | |||
8 | MUserWithNotificationSetting, | 8 | MUserWithNotificationSetting, |
9 | UserNotificationModelForApi | 9 | UserNotificationModelForApi |
10 | } from '@server/types/models/user' | 10 | } from '@server/types/models/user' |
11 | import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' | ||
11 | import { MVideoImportVideo } from '@server/types/models/video/video-import' | 12 | import { MVideoImportVideo } from '@server/types/models/video/video-import' |
13 | import { Abuse } from '@shared/models' | ||
12 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' | 14 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' |
13 | import { VideoAbuse, VideoPrivacy, VideoState } from '../../shared/models/videos' | 15 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
14 | import { logger } from '../helpers/logger' | 16 | import { logger } from '../helpers/logger' |
15 | import { CONFIG } from '../initializers/config' | 17 | import { CONFIG } from '../initializers/config' |
16 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 18 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
17 | import { UserModel } from '../models/account/user' | 19 | import { UserModel } from '../models/account/user' |
18 | import { UserNotificationModel } from '../models/account/user-notification' | 20 | import { UserNotificationModel } from '../models/account/user-notification' |
19 | import { MAccountServer, MActorFollowFull } from '../types/models' | 21 | import { MAbuseFull, MAbuseVideo, MAccountServer, MActorFollowFull } from '../types/models' |
20 | import { | 22 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoFullLight } from '../types/models/video' |
21 | MCommentOwnerVideo, | ||
22 | MVideoAbuseVideo, | ||
23 | MVideoAccountLight, | ||
24 | MVideoBlacklistLightVideo, | ||
25 | MVideoBlacklistVideo, | ||
26 | MVideoFullLight | ||
27 | } from '../types/models/video' | ||
28 | import { isBlockedByServerOrAccount } from './blocklist' | 23 | import { isBlockedByServerOrAccount } from './blocklist' |
29 | import { Emailer } from './emailer' | 24 | import { Emailer } from './emailer' |
30 | import { PeerTubeSocket } from './peertube-socket' | 25 | import { PeerTubeSocket } from './peertube-socket' |
@@ -78,9 +73,9 @@ class Notifier { | |||
78 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) | 73 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) |
79 | } | 74 | } |
80 | 75 | ||
81 | notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void { | 76 | notifyOnNewAbuse (parameters: { abuse: Abuse, abuseInstance: MAbuseFull, reporter: string }): void { |
82 | this.notifyModeratorsOfNewVideoAbuse(parameters) | 77 | this.notifyModeratorsOfNewAbuse(parameters) |
83 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err })) | 78 | .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err })) |
84 | } | 79 | } |
85 | 80 | ||
86 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { | 81 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
@@ -354,33 +349,37 @@ class Notifier { | |||
354 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | 349 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) |
355 | } | 350 | } |
356 | 351 | ||
357 | private async notifyModeratorsOfNewVideoAbuse (parameters: { | 352 | private async notifyModeratorsOfNewAbuse (parameters: { |
358 | videoAbuse: VideoAbuse | 353 | abuse: Abuse |
359 | videoAbuseInstance: MVideoAbuseVideo | 354 | abuseInstance: MAbuseFull |
360 | reporter: string | 355 | reporter: string |
361 | }) { | 356 | }) { |
362 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 357 | const { abuse, abuseInstance } = parameters |
358 | |||
359 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_ABUSES) | ||
363 | if (moderators.length === 0) return | 360 | if (moderators.length === 0) return |
364 | 361 | ||
365 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url) | 362 | const url = abuseInstance.VideoAbuse?.Video?.url || abuseInstance.VideoCommentAbuse?.VideoComment?.url |
363 | |||
364 | logger.info('Notifying %s user/moderators of new abuse %s.', moderators.length, url) | ||
366 | 365 | ||
367 | function settingGetter (user: MUserWithNotificationSetting) { | 366 | function settingGetter (user: MUserWithNotificationSetting) { |
368 | return user.NotificationSetting.videoAbuseAsModerator | 367 | return user.NotificationSetting.videoAbuseAsModerator |
369 | } | 368 | } |
370 | 369 | ||
371 | async function notificationCreator (user: MUserWithNotificationSetting) { | 370 | async function notificationCreator (user: MUserWithNotificationSetting) { |
372 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ | 371 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
373 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | 372 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, |
374 | userId: user.id, | 373 | userId: user.id, |
375 | videoAbuseId: parameters.videoAbuse.id | 374 | abuseId: abuse.id |
376 | }) | 375 | }) |
377 | notification.VideoAbuse = parameters.videoAbuseInstance | 376 | notification.Abuse = abuseInstance |
378 | 377 | ||
379 | return notification | 378 | return notification |
380 | } | 379 | } |
381 | 380 | ||
382 | function emailSender (emails: string[]) { | 381 | function emailSender (emails: string[]) { |
383 | return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters) | 382 | return Emailer.Instance.addAbuseModeratorsNotification(emails, parameters) |
384 | } | 383 | } |
385 | 384 | ||
386 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 385 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts new file mode 100644 index 000000000..f098e2ff9 --- /dev/null +++ b/server/middlewares/validators/abuse.ts | |||
@@ -0,0 +1,253 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param, query } from 'express-validator' | ||
3 | import { | ||
4 | isAbuseModerationCommentValid, | ||
5 | isAbusePredefinedReasonsValid, | ||
6 | isAbusePredefinedReasonValid, | ||
7 | isAbuseReasonValid, | ||
8 | isAbuseStateValid, | ||
9 | isAbuseTimestampCoherent, | ||
10 | isAbuseTimestampValid, | ||
11 | isAbuseVideoIsValid | ||
12 | } from '@server/helpers/custom-validators/abuses' | ||
13 | import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers/custom-validators/misc' | ||
14 | import { logger } from '@server/helpers/logger' | ||
15 | import { doesAbuseExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares' | ||
16 | import { areValidationErrors } from './utils' | ||
17 | |||
18 | const abuseReportValidator = [ | ||
19 | param('videoId') | ||
20 | .custom(isIdOrUUIDValid) | ||
21 | .not() | ||
22 | .isEmpty() | ||
23 | .withMessage('Should have a valid videoId'), | ||
24 | body('reason') | ||
25 | .custom(isAbuseReasonValid) | ||
26 | .withMessage('Should have a valid reason'), | ||
27 | body('predefinedReasons') | ||
28 | .optional() | ||
29 | .custom(isAbusePredefinedReasonsValid) | ||
30 | .withMessage('Should have a valid list of predefined reasons'), | ||
31 | body('startAt') | ||
32 | .optional() | ||
33 | .customSanitizer(toIntOrNull) | ||
34 | .custom(isAbuseTimestampValid) | ||
35 | .withMessage('Should have valid starting time value'), | ||
36 | body('endAt') | ||
37 | .optional() | ||
38 | .customSanitizer(toIntOrNull) | ||
39 | .custom(isAbuseTimestampValid) | ||
40 | .withMessage('Should have valid ending time value') | ||
41 | .bail() | ||
42 | .custom(isAbuseTimestampCoherent) | ||
43 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
44 | |||
45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
46 | logger.debug('Checking abuseReport parameters', { parameters: req.body }) | ||
47 | |||
48 | if (areValidationErrors(req, res)) return | ||
49 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
50 | |||
51 | // TODO: check comment or video (exlusive) | ||
52 | |||
53 | return next() | ||
54 | } | ||
55 | ] | ||
56 | |||
57 | const abuseGetValidator = [ | ||
58 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
59 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
60 | |||
61 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
62 | logger.debug('Checking abuseGetValidator parameters', { parameters: req.body }) | ||
63 | |||
64 | if (areValidationErrors(req, res)) return | ||
65 | // if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return | ||
66 | |||
67 | return next() | ||
68 | } | ||
69 | ] | ||
70 | |||
71 | const abuseUpdateValidator = [ | ||
72 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
73 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
74 | body('state') | ||
75 | .optional() | ||
76 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
77 | body('moderationComment') | ||
78 | .optional() | ||
79 | .custom(isAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
80 | |||
81 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
82 | logger.debug('Checking abuseUpdateValidator parameters', { parameters: req.body }) | ||
83 | |||
84 | if (areValidationErrors(req, res)) return | ||
85 | // if (!await doesAbuseExist(req.params.id, req.params.videoId, res)) return | ||
86 | |||
87 | return next() | ||
88 | } | ||
89 | ] | ||
90 | |||
91 | const abuseListValidator = [ | ||
92 | query('id') | ||
93 | .optional() | ||
94 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
95 | query('predefinedReason') | ||
96 | .optional() | ||
97 | .custom(isAbusePredefinedReasonValid) | ||
98 | .withMessage('Should have a valid predefinedReason'), | ||
99 | query('search') | ||
100 | .optional() | ||
101 | .custom(exists).withMessage('Should have a valid search'), | ||
102 | query('state') | ||
103 | .optional() | ||
104 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
105 | query('videoIs') | ||
106 | .optional() | ||
107 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
108 | query('searchReporter') | ||
109 | .optional() | ||
110 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
111 | query('searchReportee') | ||
112 | .optional() | ||
113 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
114 | query('searchVideo') | ||
115 | .optional() | ||
116 | .custom(exists).withMessage('Should have a valid video search'), | ||
117 | query('searchVideoChannel') | ||
118 | .optional() | ||
119 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
120 | |||
121 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
122 | logger.debug('Checking abuseListValidator parameters', { parameters: req.body }) | ||
123 | |||
124 | if (areValidationErrors(req, res)) return | ||
125 | |||
126 | return next() | ||
127 | } | ||
128 | ] | ||
129 | |||
130 | // FIXME: deprecated in 2.3. Remove these validators | ||
131 | |||
132 | const videoAbuseReportValidator = [ | ||
133 | param('videoId') | ||
134 | .custom(isIdOrUUIDValid) | ||
135 | .not() | ||
136 | .isEmpty() | ||
137 | .withMessage('Should have a valid videoId'), | ||
138 | body('reason') | ||
139 | .custom(isAbuseReasonValid) | ||
140 | .withMessage('Should have a valid reason'), | ||
141 | body('predefinedReasons') | ||
142 | .optional() | ||
143 | .custom(isAbusePredefinedReasonsValid) | ||
144 | .withMessage('Should have a valid list of predefined reasons'), | ||
145 | body('startAt') | ||
146 | .optional() | ||
147 | .customSanitizer(toIntOrNull) | ||
148 | .custom(isAbuseTimestampValid) | ||
149 | .withMessage('Should have valid starting time value'), | ||
150 | body('endAt') | ||
151 | .optional() | ||
152 | .customSanitizer(toIntOrNull) | ||
153 | .custom(isAbuseTimestampValid) | ||
154 | .withMessage('Should have valid ending time value') | ||
155 | .bail() | ||
156 | .custom(isAbuseTimestampCoherent) | ||
157 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
158 | |||
159 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
160 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
161 | |||
162 | if (areValidationErrors(req, res)) return | ||
163 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
164 | |||
165 | return next() | ||
166 | } | ||
167 | ] | ||
168 | |||
169 | const videoAbuseGetValidator = [ | ||
170 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
171 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
172 | |||
173 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
174 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | ||
175 | |||
176 | if (areValidationErrors(req, res)) return | ||
177 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
178 | |||
179 | return next() | ||
180 | } | ||
181 | ] | ||
182 | |||
183 | const videoAbuseUpdateValidator = [ | ||
184 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
185 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
186 | body('state') | ||
187 | .optional() | ||
188 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
189 | body('moderationComment') | ||
190 | .optional() | ||
191 | .custom(isAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
192 | |||
193 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
194 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | ||
195 | |||
196 | if (areValidationErrors(req, res)) return | ||
197 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
198 | |||
199 | return next() | ||
200 | } | ||
201 | ] | ||
202 | |||
203 | const videoAbuseListValidator = [ | ||
204 | query('id') | ||
205 | .optional() | ||
206 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
207 | query('predefinedReason') | ||
208 | .optional() | ||
209 | .custom(isAbusePredefinedReasonValid) | ||
210 | .withMessage('Should have a valid predefinedReason'), | ||
211 | query('search') | ||
212 | .optional() | ||
213 | .custom(exists).withMessage('Should have a valid search'), | ||
214 | query('state') | ||
215 | .optional() | ||
216 | .custom(isAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
217 | query('videoIs') | ||
218 | .optional() | ||
219 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
220 | query('searchReporter') | ||
221 | .optional() | ||
222 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
223 | query('searchReportee') | ||
224 | .optional() | ||
225 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
226 | query('searchVideo') | ||
227 | .optional() | ||
228 | .custom(exists).withMessage('Should have a valid video search'), | ||
229 | query('searchVideoChannel') | ||
230 | .optional() | ||
231 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
232 | |||
233 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
234 | logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body }) | ||
235 | |||
236 | if (areValidationErrors(req, res)) return | ||
237 | |||
238 | return next() | ||
239 | } | ||
240 | ] | ||
241 | |||
242 | // --------------------------------------------------------------------------- | ||
243 | |||
244 | export { | ||
245 | abuseListValidator, | ||
246 | abuseReportValidator, | ||
247 | abuseGetValidator, | ||
248 | abuseUpdateValidator, | ||
249 | videoAbuseReportValidator, | ||
250 | videoAbuseGetValidator, | ||
251 | videoAbuseUpdateValidator, | ||
252 | videoAbuseListValidator | ||
253 | } | ||
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 65dd00335..4086d77aa 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './abuse' | ||
1 | export * from './account' | 2 | export * from './account' |
2 | export * from './blocklist' | 3 | export * from './blocklist' |
3 | export * from './oembed' | 4 | export * from './oembed' |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index b76dab722..29aba0436 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -5,7 +5,7 @@ import { checkSort, createSortableColumns } from './utils' | |||
5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) | 5 | const SORTABLE_USERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USERS) |
6 | const SORTABLE_ACCOUNTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS) | 6 | const SORTABLE_ACCOUNTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS) |
7 | const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) | 7 | const SORTABLE_JOBS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.JOBS) |
8 | const SORTABLE_VIDEO_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_ABUSES) | 8 | const SORTABLE_ABUSES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ABUSES) |
9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) | 9 | const SORTABLE_VIDEOS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS) |
10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) | 10 | const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEOS_SEARCH) |
11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) | 11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) |
@@ -28,7 +28,7 @@ const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUM | |||
28 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) | 28 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) |
29 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) | 29 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) |
30 | const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) | 30 | const jobsSortValidator = checkSort(SORTABLE_JOBS_COLUMNS) |
31 | const videoAbusesSortValidator = checkSort(SORTABLE_VIDEO_ABUSES_COLUMNS) | 31 | const abusesSortValidator = checkSort(SORTABLE_ABUSES_COLUMNS) |
32 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) | 32 | const videosSortValidator = checkSort(SORTABLE_VIDEOS_COLUMNS) |
33 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) | 33 | const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) |
34 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) | 34 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) |
@@ -52,7 +52,7 @@ const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COL | |||
52 | 52 | ||
53 | export { | 53 | export { |
54 | usersSortValidator, | 54 | usersSortValidator, |
55 | videoAbusesSortValidator, | 55 | abusesSortValidator, |
56 | videoChannelsSortValidator, | 56 | videoChannelsSortValidator, |
57 | videoImportsSortValidator, | 57 | videoImportsSortValidator, |
58 | videosSearchSortValidator, | 58 | videosSearchSortValidator, |
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts index a0d585b93..1eabada0a 100644 --- a/server/middlewares/validators/videos/index.ts +++ b/server/middlewares/validators/videos/index.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | export * from './video-abuses' | ||
2 | export * from './video-blacklist' | 1 | export * from './video-blacklist' |
3 | export * from './video-captions' | 2 | export * from './video-captions' |
4 | export * from './video-channels' | 3 | export * from './video-channels' |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts deleted file mode 100644 index 5bbd1e3c6..000000000 --- a/server/middlewares/validators/videos/video-abuses.ts +++ /dev/null | |||
@@ -1,135 +0,0 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param, query } from 'express-validator' | ||
3 | import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '../../../helpers/custom-validators/misc' | ||
4 | import { | ||
5 | isAbuseVideoIsValid, | ||
6 | isVideoAbuseModerationCommentValid, | ||
7 | isVideoAbuseReasonValid, | ||
8 | isVideoAbuseStateValid, | ||
9 | isVideoAbusePredefinedReasonsValid, | ||
10 | isVideoAbusePredefinedReasonValid, | ||
11 | isVideoAbuseTimestampValid, | ||
12 | isVideoAbuseTimestampCoherent | ||
13 | } from '../../../helpers/custom-validators/video-abuses' | ||
14 | import { logger } from '../../../helpers/logger' | ||
15 | import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares' | ||
16 | import { areValidationErrors } from '../utils' | ||
17 | |||
18 | const videoAbuseReportValidator = [ | ||
19 | param('videoId') | ||
20 | .custom(isIdOrUUIDValid) | ||
21 | .not() | ||
22 | .isEmpty() | ||
23 | .withMessage('Should have a valid videoId'), | ||
24 | body('reason') | ||
25 | .custom(isVideoAbuseReasonValid) | ||
26 | .withMessage('Should have a valid reason'), | ||
27 | body('predefinedReasons') | ||
28 | .optional() | ||
29 | .custom(isVideoAbusePredefinedReasonsValid) | ||
30 | .withMessage('Should have a valid list of predefined reasons'), | ||
31 | body('startAt') | ||
32 | .optional() | ||
33 | .customSanitizer(toIntOrNull) | ||
34 | .custom(isVideoAbuseTimestampValid) | ||
35 | .withMessage('Should have valid starting time value'), | ||
36 | body('endAt') | ||
37 | .optional() | ||
38 | .customSanitizer(toIntOrNull) | ||
39 | .custom(isVideoAbuseTimestampValid) | ||
40 | .withMessage('Should have valid ending time value') | ||
41 | .bail() | ||
42 | .custom(isVideoAbuseTimestampCoherent) | ||
43 | .withMessage('Should have a startAt timestamp beginning before endAt'), | ||
44 | |||
45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
46 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
47 | |||
48 | if (areValidationErrors(req, res)) return | ||
49 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
50 | |||
51 | return next() | ||
52 | } | ||
53 | ] | ||
54 | |||
55 | const videoAbuseGetValidator = [ | ||
56 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
57 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
58 | |||
59 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
60 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | ||
61 | |||
62 | if (areValidationErrors(req, res)) return | ||
63 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
64 | |||
65 | return next() | ||
66 | } | ||
67 | ] | ||
68 | |||
69 | const videoAbuseUpdateValidator = [ | ||
70 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
71 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
72 | body('state') | ||
73 | .optional() | ||
74 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
75 | body('moderationComment') | ||
76 | .optional() | ||
77 | .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
78 | |||
79 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
80 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | ||
81 | |||
82 | if (areValidationErrors(req, res)) return | ||
83 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return | ||
84 | |||
85 | return next() | ||
86 | } | ||
87 | ] | ||
88 | |||
89 | const videoAbuseListValidator = [ | ||
90 | query('id') | ||
91 | .optional() | ||
92 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
93 | query('predefinedReason') | ||
94 | .optional() | ||
95 | .custom(isVideoAbusePredefinedReasonValid) | ||
96 | .withMessage('Should have a valid predefinedReason'), | ||
97 | query('search') | ||
98 | .optional() | ||
99 | .custom(exists).withMessage('Should have a valid search'), | ||
100 | query('state') | ||
101 | .optional() | ||
102 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
103 | query('videoIs') | ||
104 | .optional() | ||
105 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
106 | query('searchReporter') | ||
107 | .optional() | ||
108 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
109 | query('searchReportee') | ||
110 | .optional() | ||
111 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
112 | query('searchVideo') | ||
113 | .optional() | ||
114 | .custom(exists).withMessage('Should have a valid video search'), | ||
115 | query('searchVideoChannel') | ||
116 | .optional() | ||
117 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
118 | |||
119 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
120 | logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body }) | ||
121 | |||
122 | if (areValidationErrors(req, res)) return | ||
123 | |||
124 | return next() | ||
125 | } | ||
126 | ] | ||
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | export { | ||
131 | videoAbuseListValidator, | ||
132 | videoAbuseReportValidator, | ||
133 | videoAbuseGetValidator, | ||
134 | videoAbuseUpdateValidator | ||
135 | } | ||
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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { literal, Op } from 'sequelize' | 2 | import { invert } from 'lodash' |
3 | import { literal, Op, WhereOptions } from 'sequelize' | ||
3 | import { | 4 | import { |
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' |
17 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | 19 | import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' |
18 | import { | ||
19 | VideoAbuseState, | ||
20 | VideoDetails, | ||
21 | VideoAbusePredefinedReasons, | ||
22 | VideoAbusePredefinedReasonsString, | ||
23 | videoAbusePredefinedReasonsMap | ||
24 | } from '../../../shared' | ||
25 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | ||
26 | import { VideoAbuse } from '../../../shared/models/videos' | ||
27 | import { | 20 | import { |
28 | isVideoAbuseModerationCommentValid, | 21 | Abuse, |
29 | isVideoAbuseReasonValid, | 22 | AbuseObject, |
30 | isVideoAbuseStateValid | 23 | AbusePredefinedReasons, |
31 | } from '../../helpers/custom-validators/video-abuses' | 24 | abusePredefinedReasonsMap, |
32 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 25 | AbusePredefinedReasonsString, |
33 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../types/models' | 26 | AbuseState, |
34 | import { AccountModel } from '../account/account' | 27 | AbuseVideoIs, |
28 | VideoAbuse | ||
29 | } from '@shared/models' | ||
30 | import { AbuseFilter } from '@shared/models/moderation/abuse/abuse-filter' | ||
31 | import { CONSTRAINTS_FIELDS, ABUSE_STATES } from '../../initializers/constants' | ||
32 | import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' | ||
33 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | ||
35 | import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' | 34 | import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' |
36 | import { ThumbnailModel } from './thumbnail' | 35 | import { ThumbnailModel } from '../video/thumbnail' |
37 | import { VideoModel } from './video' | 36 | import { VideoModel } from '../video/video' |
38 | import { VideoBlacklistModel } from './video-blacklist' | 37 | import { VideoBlacklistModel } from '../video/video-blacklist' |
39 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 38 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
40 | import { invert } from 'lodash' | 39 | import { VideoAbuseModel } from './video-abuse' |
40 | import { VideoCommentAbuseModel } from './video-comment-abuse' | ||
41 | 41 | ||
42 | export enum ScopeNames { | 42 | export 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 | }) |
252 | export class VideoAbuseModel extends Model<VideoAbuseModel> { | 274 | export 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 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { VideoDetails } from '@shared/models' | ||
3 | import { VideoModel } from '../video/video' | ||
4 | import { AbuseModel } from './abuse' | ||
5 | |||
6 | @Table({ | ||
7 | tableName: 'videoAbuse', | ||
8 | indexes: [ | ||
9 | { | ||
10 | fields: [ 'abuseId' ] | ||
11 | }, | ||
12 | { | ||
13 | fields: [ 'videoId' ] | ||
14 | } | ||
15 | ] | ||
16 | }) | ||
17 | export 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 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { VideoComment } from '@shared/models' | ||
3 | import { VideoCommentModel } from '../video/video-comment' | ||
4 | import { AbuseModel } from './abuse' | ||
5 | |||
6 | @Table({ | ||
7 | tableName: 'commentAbuse', | ||
8 | indexes: [ | ||
9 | { | ||
10 | fields: [ 'abuseId' ] | ||
11 | }, | ||
12 | { | ||
13 | fields: [ 'videoCommentId' ] | ||
14 | } | ||
15 | ] | ||
16 | }) | ||
17 | export 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 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { AccountModel } from './account' | ||
3 | import { getSort, searchAttribute } from '../utils' | ||
4 | import { AccountBlock } from '../../../shared/models/blocklist' | ||
5 | import { Op } from 'sequelize' | ||
6 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { Op } from 'sequelize' | ||
3 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
7 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' | 4 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' |
5 | import { AccountBlock } from '../../../shared/models' | ||
8 | import { ActorModel } from '../activitypub/actor' | 6 | import { ActorModel } from '../activitypub/actor' |
9 | import { ServerModel } from '../server/server' | 7 | import { ServerModel } from '../server/server' |
8 | import { getSort, searchAttribute } from '../utils' | ||
9 | import { AccountModel } from './account' | ||
10 | 10 | ||
11 | enum ScopeNames { | 11 | enum 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 @@ | |||
1 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | ||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | ||
2 | import { UserNotification, UserNotificationType } from '../../../shared' | 4 | import { UserNotification, UserNotificationType } from '../../../shared' |
3 | import { getSort, throwIfNotValid } from '../utils' | ||
4 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 5 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
5 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | 6 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' |
6 | import { UserModel } from './user' | 7 | import { AbuseModel } from '../abuse/abuse' |
7 | import { VideoModel } from '../video/video' | 8 | import { VideoAbuseModel } from '../abuse/video-abuse' |
8 | import { VideoCommentModel } from '../video/video-comment' | 9 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' |
9 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | ||
10 | import { VideoChannelModel } from '../video/video-channel' | ||
11 | import { AccountModel } from './account' | ||
12 | import { VideoAbuseModel } from '../video/video-abuse' | ||
13 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
14 | import { VideoImportModel } from '../video/video-import' | ||
15 | import { ActorModel } from '../activitypub/actor' | 10 | import { ActorModel } from '../activitypub/actor' |
16 | import { ActorFollowModel } from '../activitypub/actor-follow' | 11 | import { ActorFollowModel } from '../activitypub/actor-follow' |
17 | import { AvatarModel } from '../avatar/avatar' | 12 | import { AvatarModel } from '../avatar/avatar' |
18 | import { ServerModel } from '../server/server' | 13 | import { ServerModel } from '../server/server' |
19 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | 14 | import { getSort, throwIfNotValid } from '../utils' |
15 | import { VideoModel } from '../video/video' | ||
16 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
17 | import { VideoChannelModel } from '../video/video-channel' | ||
18 | import { VideoCommentModel } from '../video/video-comment' | ||
19 | import { VideoImportModel } from '../video/video-import' | ||
20 | import { AccountModel } from './account' | ||
21 | import { UserModel } from './user' | ||
20 | 22 | ||
21 | enum ScopeNames { | 23 | enum 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' |
22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoAbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' | 22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, AbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
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 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { Op } from 'sequelize' | ||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | ||
5 | import { ServerBlock } from '@shared/models' | ||
2 | import { AccountModel } from '../account/account' | 6 | import { AccountModel } from '../account/account' |
3 | import { ServerModel } from './server' | ||
4 | import { ServerBlock } from '../../../shared/models/blocklist' | ||
5 | import { getSort, searchAttribute } from '../utils' | 7 | import { getSort, searchAttribute } from '../utils' |
6 | import * as Bluebird from 'bluebird' | 8 | import { ServerModel } from './server' |
7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' | ||
8 | import { Op } from 'sequelize' | ||
9 | 9 | ||
10 | enum ScopeNames { | 10 | enum 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 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { remove } from 'fs-extra' | ||
2 | import { maxBy, minBy, pick } from 'lodash' | 3 | import { maxBy, minBy, pick } from 'lodash' |
3 | import { join } from 'path' | 4 | import { join } from 'path' |
4 | import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' | 5 | import { 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' |
26 | import { UserRight, VideoPrivacy, VideoState, ResultList } from '../../../shared' | 27 | import { buildNSFWFilter } from '@server/helpers/express-utils' |
28 | import { getPrivaciesForFederation, isPrivacyForFederation } from '@server/helpers/video' | ||
29 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | ||
30 | import { getServerActor } from '@server/models/application/application' | ||
31 | import { ModelCache } from '@server/models/model-cache' | ||
32 | import { VideoFile } from '@shared/models/videos/video-file.model' | ||
33 | import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' | ||
27 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 34 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
28 | import { Video, VideoDetails } from '../../../shared/models/videos' | 35 | import { Video, VideoDetails } from '../../../shared/models/videos' |
36 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
29 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 37 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
38 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
30 | import { peertubeTruncate } from '../../helpers/core-utils' | 39 | import { peertubeTruncate } from '../../helpers/core-utils' |
31 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 40 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
32 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 41 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
@@ -43,6 +52,7 @@ import { | |||
43 | } from '../../helpers/custom-validators/videos' | 52 | } from '../../helpers/custom-validators/videos' |
44 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 53 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
45 | import { logger } from '../../helpers/logger' | 54 | import { logger } from '../../helpers/logger' |
55 | import { CONFIG } from '../../initializers/config' | ||
46 | import { | 56 | import { |
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' |
61 | import { sendDeleteVideo } from '../../lib/activitypub/send' | 71 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
62 | import { AccountModel } from '../account/account' | ||
63 | import { AccountVideoRateModel } from '../account/account-video-rate' | ||
64 | import { ActorModel } from '../activitypub/actor' | ||
65 | import { AvatarModel } from '../avatar/avatar' | ||
66 | import { ServerModel } from '../server/server' | ||
67 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' | ||
68 | import { TagModel } from './tag' | ||
69 | import { VideoAbuseModel } from './video-abuse' | ||
70 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | ||
71 | import { VideoCommentModel } from './video-comment' | ||
72 | import { VideoFileModel } from './video-file' | ||
73 | import { VideoShareModel } from './video-share' | ||
74 | import { VideoTagModel } from './video-tag' | ||
75 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | ||
76 | import { VideoCaptionModel } from './video-caption' | ||
77 | import { VideoBlacklistModel } from './video-blacklist' | ||
78 | import { remove } from 'fs-extra' | ||
79 | import { VideoViewModel } from './video-view' | ||
80 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
81 | import { | ||
82 | videoFilesModelToFormattedJSON, | ||
83 | VideoFormattingJSONOptions, | ||
84 | videoModelToActivityPubObject, | ||
85 | videoModelToFormattedDetailsJSON, | ||
86 | videoModelToFormattedJSON | ||
87 | } from './video-format-utils' | ||
88 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
89 | import { VideoImportModel } from './video-import' | ||
90 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
91 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
92 | import { CONFIG } from '../../initializers/config' | ||
93 | import { ThumbnailModel } from './thumbnail' | ||
94 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
95 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
96 | import { | 72 | import { |
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' |
121 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' | ||
122 | import { MThumbnail } from '../../types/models/video/thumbnail' | 97 | import { MThumbnail } from '../../types/models/video/thumbnail' |
123 | import { VideoFile } from '@shared/models/videos/video-file.model' | 98 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../types/models/video/video-file' |
124 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 99 | import { VideoAbuseModel } from '../abuse/video-abuse' |
125 | import { ModelCache } from '@server/models/model-cache' | 100 | import { AccountModel } from '../account/account' |
101 | import { AccountVideoRateModel } from '../account/account-video-rate' | ||
102 | import { UserVideoHistoryModel } from '../account/user-video-history' | ||
103 | import { ActorModel } from '../activitypub/actor' | ||
104 | import { AvatarModel } from '../avatar/avatar' | ||
105 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
106 | import { ServerModel } from '../server/server' | ||
107 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' | ||
108 | import { ScheduleVideoUpdateModel } from './schedule-video-update' | ||
109 | import { TagModel } from './tag' | ||
110 | import { ThumbnailModel } from './thumbnail' | ||
111 | import { VideoBlacklistModel } from './video-blacklist' | ||
112 | import { VideoCaptionModel } from './video-caption' | ||
113 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | ||
114 | import { VideoCommentModel } from './video-comment' | ||
115 | import { VideoFileModel } from './video-file' | ||
116 | import { | ||
117 | videoFilesModelToFormattedJSON, | ||
118 | VideoFormattingJSONOptions, | ||
119 | videoModelToActivityPubObject, | ||
120 | videoModelToFormattedDetailsJSON, | ||
121 | videoModelToFormattedJSON | ||
122 | } from './video-format-utils' | ||
123 | import { VideoImportModel } from './video-import' | ||
124 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
126 | import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' | 125 | import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' |
127 | import { buildNSFWFilter } from '@server/helpers/express-utils' | 126 | import { VideoShareModel } from './video-share' |
128 | import { getServerActor } from '@server/models/application/application' | 127 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
129 | import { getPrivaciesForFederation, isPrivacyForFederation } from "@server/helpers/video" | 128 | import { VideoTagModel } from './video-tag' |
129 | import { VideoViewModel } from './video-view' | ||
130 | 130 | ||
131 | export enum ScopeNames { | 131 | export enum ScopeNames { |
132 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 132 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts index 557bf20eb..f122baef4 100644 --- a/server/tests/api/check-params/video-abuses.ts +++ b/server/tests/api/check-params/video-abuses.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | import { AbuseState, VideoAbuseCreate } from '@shared/models' | |
5 | import { | 5 | import { |
6 | cleanupTests, | 6 | cleanupTests, |
7 | createUser, | 7 | createUser, |
@@ -20,7 +20,8 @@ import { | |||
20 | checkBadSortPagination, | 20 | checkBadSortPagination, |
21 | checkBadStartPagination | 21 | checkBadStartPagination |
22 | } from '../../../../shared/extra-utils/requests/check-api-params' | 22 | } from '../../../../shared/extra-utils/requests/check-api-params' |
23 | import { VideoAbuseState, VideoAbuseCreate } from '../../../../shared/models/videos' | 23 | |
24 | // FIXME: deprecated in 2.3. Remove this controller | ||
24 | 25 | ||
25 | describe('Test video abuses API validators', function () { | 26 | describe('Test video abuses API validators', function () { |
26 | let server: ServerInfo | 27 | let server: ServerInfo |
@@ -136,7 +137,7 @@ describe('Test video abuses API validators', function () { | |||
136 | const fields = { reason: 'my super reason' } | 137 | const fields = { reason: 'my super reason' } |
137 | 138 | ||
138 | const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) | 139 | const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) |
139 | videoAbuseId = res.body.videoAbuse.id | 140 | videoAbuseId = res.body.abuse.id |
140 | }) | 141 | }) |
141 | 142 | ||
142 | it('Should fail with a wrong predefined reason', async function () { | 143 | it('Should fail with a wrong predefined reason', async function () { |
@@ -190,7 +191,7 @@ describe('Test video abuses API validators', function () { | |||
190 | }) | 191 | }) |
191 | 192 | ||
192 | it('Should succeed with the correct params', async function () { | 193 | it('Should succeed with the correct params', async function () { |
193 | const body = { state: VideoAbuseState.ACCEPTED } | 194 | const body = { state: AbuseState.ACCEPTED } |
194 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body) | 195 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body) |
195 | }) | 196 | }) |
196 | }) | 197 | }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 0a66bd1ce..88b68d977 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { MyUser, User, UserRole, Video, VideoAbuseState, VideoAbuseUpdate, VideoPlaylistType } from '../../../../shared/index' | 5 | import { MyUser, User, UserRole, Video, AbuseState, AbuseUpdate, VideoPlaylistType } from '@shared/models' |
6 | import { | 6 | import { |
7 | addVideoCommentThread, | 7 | addVideoCommentThread, |
8 | blockUser, | 8 | blockUser, |
@@ -937,7 +937,7 @@ describe('Test users', function () { | |||
937 | expect(user2.videoAbusesCount).to.equal(1) // number of incriminations | 937 | expect(user2.videoAbusesCount).to.equal(1) // number of incriminations |
938 | expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created | 938 | expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created |
939 | 939 | ||
940 | const body: VideoAbuseUpdate = { state: VideoAbuseState.ACCEPTED } | 940 | const body: AbuseUpdate = { state: AbuseState.ACCEPTED } |
941 | await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body) | 941 | await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body) |
942 | 942 | ||
943 | const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) | 943 | const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index 7383bd991..20975aa4a 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -1,21 +1,21 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { VideoAbuse, VideoAbuseState, VideoAbusePredefinedReasonsString } from '../../../../shared/models/videos' | 4 | import * as chai from 'chai' |
5 | import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models' | ||
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | createUser, | ||
8 | deleteVideoAbuse, | 9 | deleteVideoAbuse, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
10 | getVideoAbusesList, | 11 | getVideoAbusesList, |
11 | getVideosList, | 12 | getVideosList, |
13 | removeVideo, | ||
12 | reportVideoAbuse, | 14 | reportVideoAbuse, |
13 | ServerInfo, | 15 | ServerInfo, |
14 | setAccessTokensToServers, | 16 | setAccessTokensToServers, |
15 | updateVideoAbuse, | 17 | updateVideoAbuse, |
16 | uploadVideo, | 18 | uploadVideo, |
17 | removeVideo, | ||
18 | createUser, | ||
19 | userLogin | 19 | userLogin |
20 | } from '../../../../shared/extra-utils/index' | 20 | } from '../../../../shared/extra-utils/index' |
21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
@@ -29,9 +29,11 @@ import { | |||
29 | 29 | ||
30 | const expect = chai.expect | 30 | const expect = chai.expect |
31 | 31 | ||
32 | // FIXME: deprecated in 2.3. Remove this controller | ||
33 | |||
32 | describe('Test video abuses', function () { | 34 | describe('Test video abuses', function () { |
33 | let servers: ServerInfo[] = [] | 35 | let servers: ServerInfo[] = [] |
34 | let abuseServer2: VideoAbuse | 36 | let abuseServer2: Abuse |
35 | 37 | ||
36 | before(async function () { | 38 | before(async function () { |
37 | this.timeout(50000) | 39 | this.timeout(50000) |
@@ -95,7 +97,7 @@ describe('Test video abuses', function () { | |||
95 | expect(res1.body.data).to.be.an('array') | 97 | expect(res1.body.data).to.be.an('array') |
96 | expect(res1.body.data.length).to.equal(1) | 98 | expect(res1.body.data.length).to.equal(1) |
97 | 99 | ||
98 | const abuse: VideoAbuse = res1.body.data[0] | 100 | const abuse: Abuse = res1.body.data[0] |
99 | expect(abuse.reason).to.equal('my super bad reason') | 101 | expect(abuse.reason).to.equal('my super bad reason') |
100 | expect(abuse.reporterAccount.name).to.equal('root') | 102 | expect(abuse.reporterAccount.name).to.equal('root') |
101 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 103 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
@@ -128,23 +130,23 @@ describe('Test video abuses', function () { | |||
128 | expect(res1.body.data).to.be.an('array') | 130 | expect(res1.body.data).to.be.an('array') |
129 | expect(res1.body.data.length).to.equal(2) | 131 | expect(res1.body.data.length).to.equal(2) |
130 | 132 | ||
131 | const abuse1: VideoAbuse = res1.body.data[0] | 133 | const abuse1: Abuse = res1.body.data[0] |
132 | expect(abuse1.reason).to.equal('my super bad reason') | 134 | expect(abuse1.reason).to.equal('my super bad reason') |
133 | expect(abuse1.reporterAccount.name).to.equal('root') | 135 | expect(abuse1.reporterAccount.name).to.equal('root') |
134 | expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 136 | expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
135 | expect(abuse1.video.id).to.equal(servers[0].video.id) | 137 | expect(abuse1.video.id).to.equal(servers[0].video.id) |
136 | expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) | 138 | expect(abuse1.state.id).to.equal(AbuseState.PENDING) |
137 | expect(abuse1.state.label).to.equal('Pending') | 139 | expect(abuse1.state.label).to.equal('Pending') |
138 | expect(abuse1.moderationComment).to.be.null | 140 | expect(abuse1.moderationComment).to.be.null |
139 | expect(abuse1.count).to.equal(1) | 141 | expect(abuse1.count).to.equal(1) |
140 | expect(abuse1.nth).to.equal(1) | 142 | expect(abuse1.nth).to.equal(1) |
141 | 143 | ||
142 | const abuse2: VideoAbuse = res1.body.data[1] | 144 | const abuse2: Abuse = res1.body.data[1] |
143 | expect(abuse2.reason).to.equal('my super bad reason 2') | 145 | expect(abuse2.reason).to.equal('my super bad reason 2') |
144 | expect(abuse2.reporterAccount.name).to.equal('root') | 146 | expect(abuse2.reporterAccount.name).to.equal('root') |
145 | expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 147 | expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
146 | expect(abuse2.video.id).to.equal(servers[1].video.id) | 148 | expect(abuse2.video.id).to.equal(servers[1].video.id) |
147 | expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING) | 149 | expect(abuse2.state.id).to.equal(AbuseState.PENDING) |
148 | expect(abuse2.state.label).to.equal('Pending') | 150 | expect(abuse2.state.label).to.equal('Pending') |
149 | expect(abuse2.moderationComment).to.be.null | 151 | expect(abuse2.moderationComment).to.be.null |
150 | 152 | ||
@@ -157,25 +159,25 @@ describe('Test video abuses', function () { | |||
157 | expect(abuseServer2.reason).to.equal('my super bad reason 2') | 159 | expect(abuseServer2.reason).to.equal('my super bad reason 2') |
158 | expect(abuseServer2.reporterAccount.name).to.equal('root') | 160 | expect(abuseServer2.reporterAccount.name).to.equal('root') |
159 | expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 161 | expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
160 | expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING) | 162 | expect(abuseServer2.state.id).to.equal(AbuseState.PENDING) |
161 | expect(abuseServer2.state.label).to.equal('Pending') | 163 | expect(abuseServer2.state.label).to.equal('Pending') |
162 | expect(abuseServer2.moderationComment).to.be.null | 164 | expect(abuseServer2.moderationComment).to.be.null |
163 | }) | 165 | }) |
164 | 166 | ||
165 | it('Should update the state of a video abuse', async function () { | 167 | it('Should update the state of a video abuse', async function () { |
166 | const body = { state: VideoAbuseState.REJECTED } | 168 | const body = { state: AbuseState.REJECTED } |
167 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 169 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
168 | 170 | ||
169 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) | 171 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
170 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) | 172 | expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED) |
171 | }) | 173 | }) |
172 | 174 | ||
173 | it('Should add a moderation comment', async function () { | 175 | it('Should add a moderation comment', async function () { |
174 | const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } | 176 | const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' } |
175 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 177 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
176 | 178 | ||
177 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) | 179 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
178 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) | 180 | expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED) |
179 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | 181 | expect(res.body.data[0].moderationComment).to.equal('It is valid') |
180 | }) | 182 | }) |
181 | 183 | ||
@@ -243,7 +245,7 @@ describe('Test video abuses', function () { | |||
243 | expect(res.body.data.length).to.equal(2, "wrong number of videos returned") | 245 | expect(res.body.data.length).to.equal(2, "wrong number of videos returned") |
244 | expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") | 246 | expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") |
245 | 247 | ||
246 | const abuse: VideoAbuse = res.body.data[0] | 248 | const abuse: Abuse = res.body.data[0] |
247 | expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") | 249 | expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") |
248 | expect(abuse.video.channel).to.exist | 250 | expect(abuse.video.channel).to.exist |
249 | expect(abuse.video.deleted).to.be.true | 251 | expect(abuse.video.deleted).to.be.true |
@@ -277,7 +279,7 @@ describe('Test video abuses', function () { | |||
277 | const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) | 279 | const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
278 | 280 | ||
279 | { | 281 | { |
280 | for (const abuse of res2.body.data as VideoAbuse[]) { | 282 | for (const abuse of res2.body.data as Abuse[]) { |
281 | if (abuse.video.id === video3.id) { | 283 | if (abuse.video.id === video3.id) { |
282 | expect(abuse.count).to.equal(1, "wrong reports count for video 3") | 284 | expect(abuse.count).to.equal(1, "wrong reports count for video 3") |
283 | expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") | 285 | expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") |
@@ -295,7 +297,7 @@ describe('Test video abuses', function () { | |||
295 | this.timeout(10000) | 297 | this.timeout(10000) |
296 | 298 | ||
297 | const reason5 = 'my super bad reason 5' | 299 | const reason5 = 'my super bad reason 5' |
298 | const predefinedReasons5: VideoAbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ] | 300 | const predefinedReasons5: AbusePredefinedReasonsString[] = [ 'violentOrRepulsive', 'captions' ] |
299 | const createdAbuse = (await reportVideoAbuse( | 301 | const createdAbuse = (await reportVideoAbuse( |
300 | servers[0].url, | 302 | servers[0].url, |
301 | servers[0].accessToken, | 303 | servers[0].accessToken, |
@@ -304,16 +306,16 @@ describe('Test video abuses', function () { | |||
304 | predefinedReasons5, | 306 | predefinedReasons5, |
305 | 1, | 307 | 1, |
306 | 5 | 308 | 5 |
307 | )).body.videoAbuse as VideoAbuse | 309 | )).body.abuse |
308 | 310 | ||
309 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) | 311 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
310 | 312 | ||
311 | { | 313 | { |
312 | const abuse = (res.body.data as VideoAbuse[]).find(a => a.id === createdAbuse.id) | 314 | const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) |
313 | expect(abuse.reason).to.equals(reason5) | 315 | expect(abuse.reason).to.equals(reason5) |
314 | expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") | 316 | expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") |
315 | expect(abuse.startAt).to.equal(1, "starting timestamp doesn't match the one reported") | 317 | expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported") |
316 | expect(abuse.endAt).to.equal(5, "ending timestamp doesn't match the one reported") | 318 | expect(abuse.video.endAt).to.equal(5, "ending timestamp doesn't match the one reported") |
317 | } | 319 | } |
318 | }) | 320 | }) |
319 | 321 | ||
@@ -348,7 +350,7 @@ describe('Test video abuses', function () { | |||
348 | 350 | ||
349 | const res = await getVideoAbusesList(options) | 351 | const res = await getVideoAbusesList(options) |
350 | 352 | ||
351 | return res.body.data as VideoAbuse[] | 353 | return res.body.data as Abuse[] |
352 | } | 354 | } |
353 | 355 | ||
354 | expect(await list({ id: 56 })).to.have.lengthOf(0) | 356 | expect(await list({ id: 56 })).to.have.lengthOf(0) |
@@ -365,14 +367,14 @@ describe('Test video abuses', function () { | |||
365 | expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1) | 367 | expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1) |
366 | expect(await list({ searchReporter: 'root' })).to.have.lengthOf(5) | 368 | expect(await list({ searchReporter: 'root' })).to.have.lengthOf(5) |
367 | 369 | ||
368 | expect(await list({ searchReportee: 'root' })).to.have.lengthOf(4) | 370 | expect(await list({ searchReportee: 'root' })).to.have.lengthOf(5) |
369 | expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0) | 371 | expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0) |
370 | 372 | ||
371 | expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1) | 373 | expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1) |
372 | expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0) | 374 | expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0) |
373 | 375 | ||
374 | expect(await list({ state: VideoAbuseState.ACCEPTED })).to.have.lengthOf(0) | 376 | expect(await list({ state: AbuseState.ACCEPTED })).to.have.lengthOf(0) |
375 | expect(await list({ state: VideoAbuseState.PENDING })).to.have.lengthOf(6) | 377 | expect(await list({ state: AbuseState.PENDING })).to.have.lengthOf(6) |
376 | 378 | ||
377 | expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1) | 379 | expect(await list({ predefinedReason: 'violentOrRepulsive' })).to.have.lengthOf(1) |
378 | expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0) | 380 | expect(await list({ predefinedReason: 'serverRules' })).to.have.lengthOf(0) |
diff --git a/server/types/models/index.ts b/server/types/models/index.ts index 78b4948ce..affa17425 100644 --- a/server/types/models/index.ts +++ b/server/types/models/index.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | export * from './account' | 1 | export * from './account' |
2 | export * from './moderation' | ||
2 | export * from './oauth' | 3 | export * from './oauth' |
3 | export * from './server' | 4 | export * from './server' |
4 | export * from './user' | 5 | export * from './user' |
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/moderation/abuse.ts new file mode 100644 index 000000000..abbc93d6f --- /dev/null +++ b/server/types/models/moderation/abuse.ts | |||
@@ -0,0 +1,97 @@ | |||
1 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' | ||
2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
3 | import { PickWith } from '@shared/core-utils' | ||
4 | import { AbuseModel } from '../../../models/abuse/abuse' | ||
5 | import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account' | ||
6 | import { MCommentOwner, MCommentUrl, MVideoUrl, MCommentOwnerVideo } from '../video' | ||
7 | import { MVideo, MVideoAccountLightBlacklistAllFiles } from '../video/video' | ||
8 | |||
9 | type Use<K extends keyof AbuseModel, M> = PickWith<AbuseModel, K, M> | ||
10 | type UseVideoAbuse<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
11 | type UseCommentAbuse<K extends keyof VideoCommentAbuseModel, M> = PickWith<VideoCommentAbuseModel, K, M> | ||
12 | |||
13 | // ############################################################################ | ||
14 | |||
15 | export type MAbuse = Omit<AbuseModel, 'VideoCommentAbuse' | 'VideoAbuse' | 'ReporterAccount' | 'FlaggedAccount' | 'toActivityPubObject'> | ||
16 | |||
17 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Abuse' | 'Video'> | ||
18 | |||
19 | export type MCommentAbuse = Omit<VideoCommentAbuseModel, 'Abuse' | 'VideoComment'> | ||
20 | |||
21 | // ############################################################################ | ||
22 | |||
23 | export type MVideoAbuseVideo = | ||
24 | MVideoAbuse & | ||
25 | UseVideoAbuse<'Video', MVideo> | ||
26 | |||
27 | export type MVideoAbuseVideoUrl = | ||
28 | MVideoAbuse & | ||
29 | UseVideoAbuse<'Video', MVideoUrl> | ||
30 | |||
31 | export type MVideoAbuseVideoFull = | ||
32 | MVideoAbuse & | ||
33 | UseVideoAbuse<'Video', MVideoAccountLightBlacklistAllFiles> | ||
34 | |||
35 | export type MVideoAbuseFormattable = | ||
36 | MVideoAbuse & | ||
37 | UseVideoAbuse<'Video', Pick<MVideoAccountLightBlacklistAllFiles, | ||
38 | 'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>> | ||
39 | |||
40 | // ############################################################################ | ||
41 | |||
42 | export type MCommentAbuseAccount = | ||
43 | MCommentAbuse & | ||
44 | UseCommentAbuse<'VideoComment', MCommentOwner> | ||
45 | |||
46 | export type MCommentAbuseAccountVideo = | ||
47 | MCommentAbuse & | ||
48 | UseCommentAbuse<'VideoComment', MCommentOwnerVideo> | ||
49 | |||
50 | export type MCommentAbuseUrl = | ||
51 | MCommentAbuse & | ||
52 | UseCommentAbuse<'VideoComment', MCommentUrl> | ||
53 | |||
54 | // ############################################################################ | ||
55 | |||
56 | export type MAbuseId = Pick<AbuseModel, 'id'> | ||
57 | |||
58 | export type MAbuseVideo = | ||
59 | MAbuse & | ||
60 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
61 | Use<'VideoAbuse', MVideoAbuseVideo> | ||
62 | |||
63 | export type MAbuseUrl = | ||
64 | MAbuse & | ||
65 | Use<'VideoAbuse', MVideoAbuseVideoUrl> & | ||
66 | Use<'VideoCommentAbuse', MCommentAbuseUrl> | ||
67 | |||
68 | export type MAbuseAccountVideo = | ||
69 | MAbuse & | ||
70 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
71 | Use<'VideoAbuse', MVideoAbuseVideoFull> & | ||
72 | Use<'ReporterAccount', MAccountDefault> | ||
73 | |||
74 | export type MAbuseAP = | ||
75 | MAbuse & | ||
76 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
77 | Use<'ReporterAccount', MAccountUrl> & | ||
78 | Use<'FlaggedAccount', MAccountUrl> & | ||
79 | Use<'VideoAbuse', MVideoAbuseVideo> & | ||
80 | Use<'VideoCommentAbuse', MCommentAbuseAccount> | ||
81 | |||
82 | export type MAbuseFull = | ||
83 | MAbuse & | ||
84 | Pick<AbuseModel, 'toActivityPubObject'> & | ||
85 | Use<'ReporterAccount', MAccountLight> & | ||
86 | Use<'FlaggedAccount', MAccountLight> & | ||
87 | Use<'VideoAbuse', MVideoAbuseVideoFull> & | ||
88 | Use<'VideoCommentAbuse', MCommentAbuseAccountVideo> | ||
89 | |||
90 | // ############################################################################ | ||
91 | |||
92 | // Format for API or AP object | ||
93 | |||
94 | export type MAbuseFormattable = | ||
95 | MAbuse & | ||
96 | Use<'ReporterAccount', MAccountFormattable> & | ||
97 | Use<'VideoAbuse', MVideoAbuseFormattable> | ||
diff --git a/server/types/models/moderation/index.ts b/server/types/models/moderation/index.ts new file mode 100644 index 000000000..8bea1708f --- /dev/null +++ b/server/types/models/moderation/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './abuse' | |||
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts index dd3de423b..92ea16768 100644 --- a/server/types/models/user/user-notification.ts +++ b/server/types/models/user/user-notification.ts | |||
@@ -1,16 +1,18 @@ | |||
1 | import { UserNotificationModel } from '../../../models/account/user-notification' | 1 | import { VideoAbuseModel } from '@server/models/abuse/video-abuse' |
2 | import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' | ||
2 | import { PickWith, PickWithOpt } from '@shared/core-utils' | 3 | import { PickWith, PickWithOpt } from '@shared/core-utils' |
3 | import { VideoModel } from '../../../models/video/video' | 4 | import { AbuseModel } from '../../../models/abuse/abuse' |
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { UserNotificationModel } from '../../../models/account/user-notification' | ||
4 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { ServerModel } from '../../../models/server/server' | 8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { AvatarModel } from '../../../models/avatar/avatar' | 9 | import { AvatarModel } from '../../../models/avatar/avatar' |
10 | import { ServerModel } from '../../../models/server/server' | ||
11 | import { VideoModel } from '../../../models/video/video' | ||
12 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | 13 | import { VideoChannelModel } from '../../../models/video/video-channel' |
8 | import { AccountModel } from '../../../models/account/account' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 14 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
11 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
12 | import { VideoImportModel } from '../../../models/video/video-import' | 15 | import { VideoImportModel } from '../../../models/video/video-import' |
13 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
14 | 16 | ||
15 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> | 17 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> |
16 | 18 | ||
@@ -47,6 +49,18 @@ export module UserNotificationIncludes { | |||
47 | Pick<VideoAbuseModel, 'id'> & | 49 | Pick<VideoAbuseModel, 'id'> & |
48 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 50 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
49 | 51 | ||
52 | export type VideoCommentAbuseInclude = | ||
53 | Pick<VideoCommentAbuseModel, 'id'> & | ||
54 | PickWith<VideoCommentAbuseModel, 'VideoComment', | ||
55 | Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | ||
56 | PickWith<VideoCommentModel, 'Video', Pick<VideoModel, 'uuid'>>> | ||
57 | |||
58 | export type AbuseInclude = | ||
59 | Pick<AbuseModel, 'id'> & | ||
60 | PickWith<AbuseModel, 'VideoAbuse', VideoAbuseInclude> & | ||
61 | PickWith<AbuseModel, 'VideoCommentAbuse', VideoCommentAbuseInclude> & | ||
62 | PickWith<AbuseModel, 'FlaggedAccount', AccountIncludeActor> | ||
63 | |||
50 | export type VideoBlacklistInclude = | 64 | export type VideoBlacklistInclude = |
51 | Pick<VideoBlacklistModel, 'id'> & | 65 | Pick<VideoBlacklistModel, 'id'> & |
52 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 66 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
@@ -76,7 +90,7 @@ export module UserNotificationIncludes { | |||
76 | // ############################################################################ | 90 | // ############################################################################ |
77 | 91 | ||
78 | export type MUserNotification = | 92 | export type MUserNotification = |
79 | Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | 93 | Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'Abuse' | 'VideoBlacklist' | |
80 | 'VideoImport' | 'Account' | 'ActorFollow'> | 94 | 'VideoImport' | 'Account' | 'ActorFollow'> |
81 | 95 | ||
82 | // ############################################################################ | 96 | // ############################################################################ |
@@ -85,7 +99,7 @@ export type UserNotificationModelForApi = | |||
85 | MUserNotification & | 99 | MUserNotification & |
86 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & | 100 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & |
87 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & | 101 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & |
88 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & | 102 | Use<'Abuse', UserNotificationIncludes.AbuseInclude> & |
89 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & | 103 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & |
90 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & | 104 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & |
91 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & | 105 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & |
diff --git a/server/types/models/video/index.ts b/server/types/models/video/index.ts index bd69c8a4b..25db23898 100644 --- a/server/types/models/video/index.ts +++ b/server/types/models/video/index.ts | |||
@@ -2,7 +2,6 @@ export * from './schedule-video-update' | |||
2 | export * from './tag' | 2 | export * from './tag' |
3 | export * from './thumbnail' | 3 | export * from './thumbnail' |
4 | export * from './video' | 4 | export * from './video' |
5 | export * from './video-abuse' | ||
6 | export * from './video-blacklist' | 5 | export * from './video-blacklist' |
7 | export * from './video-caption' | 6 | export * from './video-caption' |
8 | export * from './video-change-ownership' | 7 | export * from './video-change-ownership' |
diff --git a/server/types/models/video/video-abuse.ts b/server/types/models/video/video-abuse.ts deleted file mode 100644 index 279a87cf3..000000000 --- a/server/types/models/video/video-abuse.ts +++ /dev/null | |||
@@ -1,35 +0,0 @@ | |||
1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
2 | import { PickWith } from '@shared/core-utils' | ||
3 | import { MVideoAccountLightBlacklistAllFiles, MVideo } from './video' | ||
4 | import { MAccountDefault, MAccountFormattable } from '../account' | ||
5 | |||
6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'> | ||
11 | |||
12 | // ############################################################################ | ||
13 | |||
14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> | ||
15 | |||
16 | export type MVideoAbuseVideo = | ||
17 | MVideoAbuse & | ||
18 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
19 | Use<'Video', MVideo> | ||
20 | |||
21 | export type MVideoAbuseAccountVideo = | ||
22 | MVideoAbuse & | ||
23 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
24 | Use<'Video', MVideoAccountLightBlacklistAllFiles> & | ||
25 | Use<'Account', MAccountDefault> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Format for API or AP object | ||
30 | |||
31 | export type MVideoAbuseFormattable = | ||
32 | MVideoAbuse & | ||
33 | Use<'Account', MAccountFormattable> & | ||
34 | Use<'Video', Pick<MVideoAccountLightBlacklistAllFiles, | ||
35 | 'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>> | ||
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts index cac801e55..7595e6d86 100644 --- a/server/typings/express/index.d.ts +++ b/server/typings/express/index.d.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { RegisterServerAuthExternalOptions } from '@server/types' | 1 | import { RegisterServerAuthExternalOptions } from '@server/types' |
2 | import { | 2 | import { |
3 | MAbuse, | ||
3 | MAccountBlocklist, | 4 | MAccountBlocklist, |
4 | MActorUrl, | 5 | MActorUrl, |
5 | MStreamingPlaylist, | 6 | MStreamingPlaylist, |
@@ -26,7 +27,6 @@ import { | |||
26 | MComment, | 27 | MComment, |
27 | MCommentOwnerVideoReply, | 28 | MCommentOwnerVideoReply, |
28 | MUserDefault, | 29 | MUserDefault, |
29 | MVideoAbuse, | ||
30 | MVideoBlacklist, | 30 | MVideoBlacklist, |
31 | MVideoCaptionVideo, | 31 | MVideoCaptionVideo, |
32 | MVideoFullLight, | 32 | MVideoFullLight, |
@@ -77,7 +77,7 @@ declare module 'express' { | |||
77 | 77 | ||
78 | videoCaption?: MVideoCaptionVideo | 78 | videoCaption?: MVideoCaptionVideo |
79 | 79 | ||
80 | videoAbuse?: MVideoAbuse | 80 | abuse?: MAbuse |
81 | 81 | ||
82 | videoStreamingPlaylist?: MStreamingPlaylist | 82 | videoStreamingPlaylist?: MStreamingPlaylist |
83 | 83 | ||