diff options
19 files changed, 482 insertions, 64 deletions
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 7782fc639..59bdf6257 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate } from '../../../../shared' | 2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers' |
@@ -12,8 +12,10 @@ import { | |||
12 | paginationValidator, | 12 | paginationValidator, |
13 | setDefaultPagination, | 13 | setDefaultPagination, |
14 | setDefaultSort, | 14 | setDefaultSort, |
15 | videoAbuseGetValidator, | ||
15 | videoAbuseReportValidator, | 16 | videoAbuseReportValidator, |
16 | videoAbusesSortValidator | 17 | videoAbusesSortValidator, |
18 | videoAbuseUpdateValidator | ||
17 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
18 | import { AccountModel } from '../../../models/account/account' | 20 | import { AccountModel } from '../../../models/account/account' |
19 | import { VideoModel } from '../../../models/video/video' | 21 | import { VideoModel } from '../../../models/video/video' |
@@ -32,11 +34,23 @@ abuseVideoRouter.get('/abuse', | |||
32 | setDefaultPagination, | 34 | setDefaultPagination, |
33 | asyncMiddleware(listVideoAbuses) | 35 | asyncMiddleware(listVideoAbuses) |
34 | ) | 36 | ) |
35 | abuseVideoRouter.post('/:id/abuse', | 37 | abuseVideoRouter.put('/:videoId/abuse/:id', |
38 | authenticate, | ||
39 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | ||
40 | asyncMiddleware(videoAbuseUpdateValidator), | ||
41 | asyncRetryTransactionMiddleware(updateVideoAbuse) | ||
42 | ) | ||
43 | abuseVideoRouter.post('/:videoId/abuse', | ||
36 | authenticate, | 44 | authenticate, |
37 | asyncMiddleware(videoAbuseReportValidator), | 45 | asyncMiddleware(videoAbuseReportValidator), |
38 | asyncRetryTransactionMiddleware(reportVideoAbuse) | 46 | asyncRetryTransactionMiddleware(reportVideoAbuse) |
39 | ) | 47 | ) |
48 | abuseVideoRouter.delete('/:videoId/abuse/:id', | ||
49 | authenticate, | ||
50 | ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES), | ||
51 | asyncMiddleware(videoAbuseGetValidator), | ||
52 | asyncRetryTransactionMiddleware(deleteVideoAbuse) | ||
53 | ) | ||
40 | 54 | ||
41 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
42 | 56 | ||
@@ -46,12 +60,39 @@ export { | |||
46 | 60 | ||
47 | // --------------------------------------------------------------------------- | 61 | // --------------------------------------------------------------------------- |
48 | 62 | ||
49 | async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) { | 63 | async function listVideoAbuses (req: express.Request, res: express.Response) { |
50 | const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) | 64 | const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) |
51 | 65 | ||
52 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 66 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
53 | } | 67 | } |
54 | 68 | ||
69 | async function updateVideoAbuse (req: express.Request, res: express.Response) { | ||
70 | const videoAbuse: VideoAbuseModel = res.locals.videoAbuse | ||
71 | |||
72 | if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment | ||
73 | if (req.body.state !== undefined) videoAbuse.state = req.body.state | ||
74 | |||
75 | await sequelizeTypescript.transaction(t => { | ||
76 | return videoAbuse.save({ transaction: t }) | ||
77 | }) | ||
78 | |||
79 | // Do not send the delete to other instances, we updated OUR copy of this video abuse | ||
80 | |||
81 | return res.type('json').status(204).end() | ||
82 | } | ||
83 | |||
84 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { | ||
85 | const videoAbuse: VideoAbuseModel = res.locals.videoAbuse | ||
86 | |||
87 | await sequelizeTypescript.transaction(t => { | ||
88 | return videoAbuse.destroy({ transaction: t }) | ||
89 | }) | ||
90 | |||
91 | // Do not send the delete to other instances, we delete OUR copy of this video abuse | ||
92 | |||
93 | return res.type('json').status(204).end() | ||
94 | } | ||
95 | |||
55 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 96 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
56 | const videoInstance = res.locals.video as VideoModel | 97 | const videoInstance = res.locals.video as VideoModel |
57 | const reporterAccount = res.locals.oauth.token.User.Account as AccountModel | 98 | const reporterAccount = res.locals.oauth.token.User.Account as AccountModel |
@@ -60,10 +101,11 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
60 | const abuseToCreate = { | 101 | const abuseToCreate = { |
61 | reporterAccountId: reporterAccount.id, | 102 | reporterAccountId: reporterAccount.id, |
62 | reason: body.reason, | 103 | reason: body.reason, |
63 | videoId: videoInstance.id | 104 | videoId: videoInstance.id, |
105 | state: VideoAbuseState.PENDING | ||
64 | } | 106 | } |
65 | 107 | ||
66 | await sequelizeTypescript.transaction(async t => { | 108 | const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { |
67 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | 109 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) |
68 | videoAbuseInstance.Video = videoInstance | 110 | videoAbuseInstance.Video = videoInstance |
69 | videoAbuseInstance.Account = reporterAccount | 111 | videoAbuseInstance.Account = reporterAccount |
@@ -74,8 +116,12 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
74 | } | 116 | } |
75 | 117 | ||
76 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) | 118 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) |
77 | logger.info('Abuse report for video %s created.', videoInstance.name) | 119 | |
120 | return videoAbuseInstance | ||
78 | }) | 121 | }) |
79 | 122 | ||
80 | return res.type('json').status(204).end() | 123 | logger.info('Abuse report for video %s created.', videoInstance.name) |
124 | return res.json({ | ||
125 | videoAbuse: videoAbuse.toFormattedJSON() | ||
126 | }).end() | ||
81 | } | 127 | } |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index b8075f3c7..702c09842 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -3,7 +3,6 @@ import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' | |||
3 | import { peertubeTruncate } from '../../core-utils' | 3 | import { peertubeTruncate } from '../../core-utils' |
4 | import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc' | 4 | import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc' |
5 | import { | 5 | import { |
6 | isVideoAbuseReasonValid, | ||
7 | isVideoDurationValid, | 6 | isVideoDurationValid, |
8 | isVideoNameValid, | 7 | isVideoNameValid, |
9 | isVideoStateValid, | 8 | isVideoStateValid, |
@@ -13,6 +12,7 @@ import { | |||
13 | } from '../videos' | 12 | } from '../videos' |
14 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
15 | import { VideoState } from '../../../../shared/models/videos' | 14 | import { VideoState } from '../../../../shared/models/videos' |
15 | import { isVideoAbuseReasonValid } from '../video-abuses' | ||
16 | 16 | ||
17 | function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) { | 17 | function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) { |
18 | return isBaseActivityValid(activity, 'Create') && | 18 | return isBaseActivityValid(activity, 'Create') && |
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts new file mode 100644 index 000000000..290efb149 --- /dev/null +++ b/server/helpers/custom-validators/video-abuses.ts | |||
@@ -0,0 +1,43 @@ | |||
1 | import { Response } from 'express' | ||
2 | import * as validator from 'validator' | ||
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers' | ||
4 | import { exists } from './misc' | ||
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | ||
6 | |||
7 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | ||
8 | |||
9 | function isVideoAbuseReasonValid (value: string) { | ||
10 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
11 | } | ||
12 | |||
13 | function isVideoAbuseModerationCommentValid (value: string) { | ||
14 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT) | ||
15 | } | ||
16 | |||
17 | function isVideoAbuseStateValid (value: string) { | ||
18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined | ||
19 | } | ||
20 | |||
21 | async function isVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | ||
22 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | ||
23 | |||
24 | if (videoAbuse === null) { | ||
25 | res.status(404) | ||
26 | .json({ error: 'Video abuse not found' }) | ||
27 | .end() | ||
28 | |||
29 | return false | ||
30 | } | ||
31 | |||
32 | res.locals.videoAbuse = videoAbuse | ||
33 | return true | ||
34 | } | ||
35 | |||
36 | // --------------------------------------------------------------------------- | ||
37 | |||
38 | export { | ||
39 | isVideoAbuseExist, | ||
40 | isVideoAbuseStateValid, | ||
41 | isVideoAbuseReasonValid, | ||
42 | isVideoAbuseModerationCommentValid | ||
43 | } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index f4c1c8b07..5e6cfe217 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -6,6 +6,7 @@ import * as validator from 'validator' | |||
6 | import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared' | 6 | import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared' |
7 | import { | 7 | import { |
8 | CONSTRAINTS_FIELDS, | 8 | CONSTRAINTS_FIELDS, |
9 | VIDEO_ABUSE_STATES, | ||
9 | VIDEO_CATEGORIES, | 10 | VIDEO_CATEGORIES, |
10 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
11 | VIDEO_MIMETYPE_EXT, | 12 | VIDEO_MIMETYPE_EXT, |
@@ -18,6 +19,7 @@ import { exists, isArray, isFileValid } from './misc' | |||
18 | import { VideoChannelModel } from '../../models/video/video-channel' | 19 | import { VideoChannelModel } from '../../models/video/video-channel' |
19 | import { UserModel } from '../../models/account/user' | 20 | import { UserModel } from '../../models/account/user' |
20 | import * as magnetUtil from 'magnet-uri' | 21 | import * as magnetUtil from 'magnet-uri' |
22 | import { VideoAbuseModel } from '../../models/video/video-abuse' | ||
21 | 23 | ||
22 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS | 24 | const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS |
23 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | 25 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES |
@@ -71,10 +73,6 @@ function isVideoTagsValid (tags: string[]) { | |||
71 | ) | 73 | ) |
72 | } | 74 | } |
73 | 75 | ||
74 | function isVideoAbuseReasonValid (value: string) { | ||
75 | return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
76 | } | ||
77 | |||
78 | function isVideoViewsValid (value: string) { | 76 | function isVideoViewsValid (value: string) { |
79 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS) | 77 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS) |
80 | } | 78 | } |
@@ -220,7 +218,6 @@ export { | |||
220 | isVideoTagsValid, | 218 | isVideoTagsValid, |
221 | isVideoFPSResolutionValid, | 219 | isVideoFPSResolutionValid, |
222 | isScheduleVideoUpdatePrivacyValid, | 220 | isScheduleVideoUpdatePrivacyValid, |
223 | isVideoAbuseReasonValid, | ||
224 | isVideoFile, | 221 | isVideoFile, |
225 | isVideoMagnetUriValid, | 222 | isVideoMagnetUriValid, |
226 | isVideoStateValid, | 223 | isVideoStateValid, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index ea561b686..a008bf4c5 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -3,7 +3,7 @@ import { dirname, join } from 'path' | |||
3 | import { JobType, VideoRateType, VideoState } from '../../shared/models' | 3 | import { JobType, VideoRateType, VideoState } from '../../shared/models' |
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 4 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 5 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy, VideoAbuseState } from '../../shared/models/videos' |
7 | // Do not use barrels, remain constants as independent as possible | 7 | // Do not use barrels, remain constants as independent as possible |
8 | import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' | 8 | import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' |
9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
@@ -15,7 +15,7 @@ let config: IConfig = require('config') | |||
15 | 15 | ||
16 | // --------------------------------------------------------------------------- | 16 | // --------------------------------------------------------------------------- |
17 | 17 | ||
18 | const LAST_MIGRATION_VERSION = 245 | 18 | const LAST_MIGRATION_VERSION = 250 |
19 | 19 | ||
20 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
21 | 21 | ||
@@ -258,7 +258,8 @@ const CONSTRAINTS_FIELDS = { | |||
258 | BLOCKED_REASON: { min: 3, max: 250 } // Length | 258 | BLOCKED_REASON: { min: 3, max: 250 } // Length |
259 | }, | 259 | }, |
260 | VIDEO_ABUSES: { | 260 | VIDEO_ABUSES: { |
261 | REASON: { min: 2, max: 300 } // Length | 261 | REASON: { min: 2, max: 300 }, // Length |
262 | MODERATION_COMMENT: { min: 2, max: 300 } // Length | ||
262 | }, | 263 | }, |
263 | VIDEO_CHANNELS: { | 264 | VIDEO_CHANNELS: { |
264 | NAME: { min: 3, max: 120 }, // Length | 265 | NAME: { min: 3, max: 120 }, // Length |
@@ -409,6 +410,12 @@ const VIDEO_IMPORT_STATES = { | |||
409 | [VideoImportState.SUCCESS]: 'Success' | 410 | [VideoImportState.SUCCESS]: 'Success' |
410 | } | 411 | } |
411 | 412 | ||
413 | const VIDEO_ABUSE_STATES = { | ||
414 | [VideoAbuseState.PENDING]: 'Pending', | ||
415 | [VideoAbuseState.REJECTED]: 'Rejected', | ||
416 | [VideoAbuseState.ACCEPTED]: 'Accepted' | ||
417 | } | ||
418 | |||
412 | const VIDEO_MIMETYPE_EXT = { | 419 | const VIDEO_MIMETYPE_EXT = { |
413 | 'video/webm': '.webm', | 420 | 'video/webm': '.webm', |
414 | 'video/ogg': '.ogv', | 421 | 'video/ogg': '.ogv', |
@@ -625,6 +632,7 @@ export { | |||
625 | VIDEO_MIMETYPE_EXT, | 632 | VIDEO_MIMETYPE_EXT, |
626 | VIDEO_TRANSCODING_FPS, | 633 | VIDEO_TRANSCODING_FPS, |
627 | FFMPEG_NICE, | 634 | FFMPEG_NICE, |
635 | VIDEO_ABUSE_STATES, | ||
628 | JOB_REQUEST_TIMEOUT, | 636 | JOB_REQUEST_TIMEOUT, |
629 | USER_PASSWORD_RESET_LIFETIME, | 637 | USER_PASSWORD_RESET_LIFETIME, |
630 | IMAGE_MIMETYPE_EXT, | 638 | IMAGE_MIMETYPE_EXT, |
diff --git a/server/initializers/migrations/0250-video-abuse-state.ts b/server/initializers/migrations/0250-video-abuse-state.ts new file mode 100644 index 000000000..acb668ae1 --- /dev/null +++ b/server/initializers/migrations/0250-video-abuse-state.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { CONSTRAINTS_FIELDS } from '../constants' | ||
3 | import { VideoAbuseState } from '../../../shared/models/videos' | ||
4 | |||
5 | async function up (utils: { | ||
6 | transaction: Sequelize.Transaction | ||
7 | queryInterface: Sequelize.QueryInterface | ||
8 | sequelize: Sequelize.Sequelize | ||
9 | }): Promise<any> { | ||
10 | { | ||
11 | const data = { | ||
12 | type: Sequelize.INTEGER, | ||
13 | allowNull: true, | ||
14 | defaultValue: null | ||
15 | } | ||
16 | await utils.queryInterface.addColumn('videoAbuse', 'state', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const query = 'UPDATE "videoAbuse" SET "state" = ' + VideoAbuseState.PENDING | ||
21 | await utils.sequelize.query(query) | ||
22 | } | ||
23 | |||
24 | { | ||
25 | const data = { | ||
26 | type: Sequelize.INTEGER, | ||
27 | allowNull: false, | ||
28 | defaultValue: null | ||
29 | } | ||
30 | await utils.queryInterface.changeColumn('videoAbuse', 'state', data) | ||
31 | } | ||
32 | |||
33 | { | ||
34 | const data = { | ||
35 | type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max), | ||
36 | allowNull: true, | ||
37 | defaultValue: null | ||
38 | } | ||
39 | await utils.queryInterface.addColumn('videoAbuse', 'moderationComment', data) | ||
40 | } | ||
41 | } | ||
42 | |||
43 | function down (options) { | ||
44 | throw new Error('Not implemented.') | ||
45 | } | ||
46 | |||
47 | export { up, down } | ||
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 6364bf135..791148919 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { ActivityCreate, VideoTorrentObject } from '../../../../shared' | 1 | import { ActivityCreate, VideoAbuseState, VideoTorrentObject } from '../../../../shared' |
2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' | 2 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' |
3 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' | 3 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' |
4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
@@ -112,7 +112,8 @@ async function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateDat | |||
112 | const videoAbuseData = { | 112 | const videoAbuseData = { |
113 | reporterAccountId: account.id, | 113 | reporterAccountId: account.id, |
114 | reason: videoAbuseToCreateData.content, | 114 | reason: videoAbuseToCreateData.content, |
115 | videoId: video.id | 115 | videoId: video.id, |
116 | state: VideoAbuseState.PENDING | ||
116 | } | 117 | } |
117 | 118 | ||
118 | await VideoAbuseModel.create(videoAbuseData) | 119 | await VideoAbuseModel.create(videoAbuseData) |
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index c5400c8f5..ccbedd57d 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -7,6 +7,7 @@ export * from './feeds' | |||
7 | export * from './sort' | 7 | export * from './sort' |
8 | export * from './users' | 8 | export * from './users' |
9 | export * from './videos' | 9 | export * from './videos' |
10 | export * from './video-abuses' | ||
10 | export * from './video-blacklist' | 11 | export * from './video-blacklist' |
11 | export * from './video-channels' | 12 | export * from './video-channels' |
12 | export * from './webfinger' | 13 | export * from './webfinger' |
diff --git a/server/middlewares/validators/video-abuses.ts b/server/middlewares/validators/video-abuses.ts new file mode 100644 index 000000000..f15d55a75 --- /dev/null +++ b/server/middlewares/validators/video-abuses.ts | |||
@@ -0,0 +1,71 @@ | |||
1 | import * as express from 'express' | ||
2 | import 'express-validator' | ||
3 | import { body, param } from 'express-validator/check' | ||
4 | import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc' | ||
5 | import { isVideoExist } from '../../helpers/custom-validators/videos' | ||
6 | import { logger } from '../../helpers/logger' | ||
7 | import { areValidationErrors } from './utils' | ||
8 | import { | ||
9 | isVideoAbuseExist, | ||
10 | isVideoAbuseModerationCommentValid, | ||
11 | isVideoAbuseReasonValid, | ||
12 | isVideoAbuseStateValid | ||
13 | } from '../../helpers/custom-validators/video-abuses' | ||
14 | |||
15 | const videoAbuseReportValidator = [ | ||
16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
17 | body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), | ||
18 | |||
19 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
20 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
21 | |||
22 | if (areValidationErrors(req, res)) return | ||
23 | if (!await isVideoExist(req.params.videoId, res)) return | ||
24 | |||
25 | return next() | ||
26 | } | ||
27 | ] | ||
28 | |||
29 | const videoAbuseGetValidator = [ | ||
30 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
31 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
32 | |||
33 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
34 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | ||
35 | |||
36 | if (areValidationErrors(req, res)) return | ||
37 | if (!await isVideoExist(req.params.videoId, res)) return | ||
38 | if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | ||
39 | |||
40 | return next() | ||
41 | } | ||
42 | ] | ||
43 | |||
44 | const videoAbuseUpdateValidator = [ | ||
45 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | ||
46 | param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'), | ||
47 | body('state') | ||
48 | .optional() | ||
49 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
50 | body('moderationComment') | ||
51 | .optional() | ||
52 | .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'), | ||
53 | |||
54 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
55 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | ||
56 | |||
57 | if (areValidationErrors(req, res)) return | ||
58 | if (!await isVideoExist(req.params.videoId, res)) return | ||
59 | if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | ||
60 | |||
61 | return next() | ||
62 | } | ||
63 | ] | ||
64 | |||
65 | // --------------------------------------------------------------------------- | ||
66 | |||
67 | export { | ||
68 | videoAbuseReportValidator, | ||
69 | videoAbuseGetValidator, | ||
70 | videoAbuseUpdateValidator | ||
71 | } | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index c812d4677..203a00876 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -14,7 +14,6 @@ import { | |||
14 | import { | 14 | import { |
15 | checkUserCanManageVideo, | 15 | checkUserCanManageVideo, |
16 | isScheduleVideoUpdatePrivacyValid, | 16 | isScheduleVideoUpdatePrivacyValid, |
17 | isVideoAbuseReasonValid, | ||
18 | isVideoCategoryValid, | 17 | isVideoCategoryValid, |
19 | isVideoChannelOfAccountExist, | 18 | isVideoChannelOfAccountExist, |
20 | isVideoDescriptionValid, | 19 | isVideoDescriptionValid, |
@@ -174,20 +173,6 @@ const videosRemoveValidator = [ | |||
174 | } | 173 | } |
175 | ] | 174 | ] |
176 | 175 | ||
177 | const videoAbuseReportValidator = [ | ||
178 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
179 | body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'), | ||
180 | |||
181 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
182 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
183 | |||
184 | if (areValidationErrors(req, res)) return | ||
185 | if (!await isVideoExist(req.params.id, res)) return | ||
186 | |||
187 | return next() | ||
188 | } | ||
189 | ] | ||
190 | |||
191 | const videoRateValidator = [ | 176 | const videoRateValidator = [ |
192 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 177 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
193 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), | 178 | body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'), |
@@ -299,8 +284,6 @@ export { | |||
299 | videosRemoveValidator, | 284 | videosRemoveValidator, |
300 | videosShareValidator, | 285 | videosShareValidator, |
301 | 286 | ||
302 | videoAbuseReportValidator, | ||
303 | |||
304 | videoRateValidator, | 287 | videoRateValidator, |
305 | 288 | ||
306 | getCommonVideoAttributes | 289 | getCommonVideoAttributes |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 39f0c2cb2..10a191372 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -1,11 +1,30 @@ | |||
1 | import { AfterCreate, AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { |
2 | AfterCreate, | ||
3 | AllowNull, | ||
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | Default, | ||
9 | ForeignKey, | ||
10 | Is, | ||
11 | Model, | ||
12 | Table, | ||
13 | UpdatedAt | ||
14 | } from 'sequelize-typescript' | ||
2 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | 15 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' |
3 | import { VideoAbuse } from '../../../shared/models/videos' | 16 | import { VideoAbuse } from '../../../shared/models/videos' |
4 | import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos' | 17 | import { |
18 | isVideoAbuseModerationCommentValid, | ||
19 | isVideoAbuseReasonValid, | ||
20 | isVideoAbuseStateValid | ||
21 | } from '../../helpers/custom-validators/video-abuses' | ||
5 | import { Emailer } from '../../lib/emailer' | 22 | import { Emailer } from '../../lib/emailer' |
6 | import { AccountModel } from '../account/account' | 23 | import { AccountModel } from '../account/account' |
7 | import { getSort, throwIfNotValid } from '../utils' | 24 | import { getSort, throwIfNotValid } from '../utils' |
8 | import { VideoModel } from './video' | 25 | import { VideoModel } from './video' |
26 | import { VideoAbuseState } from '../../../shared' | ||
27 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers' | ||
9 | 28 | ||
10 | @Table({ | 29 | @Table({ |
11 | tableName: 'videoAbuse', | 30 | tableName: 'videoAbuse', |
@@ -25,6 +44,18 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
25 | @Column | 44 | @Column |
26 | reason: string | 45 | reason: string |
27 | 46 | ||
47 | @AllowNull(false) | ||
48 | @Default(null) | ||
49 | @Is('VideoAbuseState', value => throwIfNotValid(value, isVideoAbuseStateValid, 'state')) | ||
50 | @Column | ||
51 | state: VideoAbuseState | ||
52 | |||
53 | @AllowNull(true) | ||
54 | @Default(null) | ||
55 | @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment')) | ||
56 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) | ||
57 | moderationComment: string | ||
58 | |||
28 | @CreatedAt | 59 | @CreatedAt |
29 | createdAt: Date | 60 | createdAt: Date |
30 | 61 | ||
@@ -60,6 +91,16 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
60 | return Emailer.Instance.addVideoAbuseReportJob(instance.videoId) | 91 | return Emailer.Instance.addVideoAbuseReportJob(instance.videoId) |
61 | } | 92 | } |
62 | 93 | ||
94 | static loadByIdAndVideoId (id: number, videoId: number) { | ||
95 | const query = { | ||
96 | where: { | ||
97 | id, | ||
98 | videoId | ||
99 | } | ||
100 | } | ||
101 | return VideoAbuseModel.findOne(query) | ||
102 | } | ||
103 | |||
63 | static listForApi (start: number, count: number, sort: string) { | 104 | static listForApi (start: number, count: number, sort: string) { |
64 | const query = { | 105 | const query = { |
65 | offset: start, | 106 | offset: start, |
@@ -88,6 +129,11 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
88 | id: this.id, | 129 | id: this.id, |
89 | reason: this.reason, | 130 | reason: this.reason, |
90 | reporterAccount: this.Account.toFormattedJSON(), | 131 | reporterAccount: this.Account.toFormattedJSON(), |
132 | state: { | ||
133 | id: this.state, | ||
134 | label: VideoAbuseModel.getStateLabel(this.state) | ||
135 | }, | ||
136 | moderationComment: this.moderationComment, | ||
91 | video: { | 137 | video: { |
92 | id: this.Video.id, | 138 | id: this.Video.id, |
93 | uuid: this.Video.uuid, | 139 | uuid: this.Video.uuid, |
@@ -105,4 +151,8 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
105 | object: this.Video.url | 151 | object: this.Video.url |
106 | } | 152 | } |
107 | } | 153 | } |
154 | |||
155 | private static getStateLabel (id: number) { | ||
156 | return VIDEO_ABUSE_STATES[id] || 'Unknown' | ||
157 | } | ||
108 | } | 158 | } |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index b794d8324..9d1f783c7 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -171,6 +171,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
171 | video | 171 | video |
172 | } | 172 | } |
173 | } | 173 | } |
174 | |||
174 | private static getStateLabel (id: number) { | 175 | private static getStateLabel (id: number) { |
175 | return VIDEO_IMPORT_STATES[id] || 'Unknown' | 176 | return VIDEO_IMPORT_STATES[id] || 'Unknown' |
176 | } | 177 | } |
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts index 68b965bbe..d2bed6a2a 100644 --- a/server/tests/api/check-params/video-abuses.ts +++ b/server/tests/api/check-params/video-abuses.ts | |||
@@ -3,14 +3,26 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | createUser, flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers, | 6 | createUser, |
7 | uploadVideo, userLogin | 7 | deleteVideoAbuse, |
8 | flushTests, | ||
9 | killallServers, | ||
10 | makeGetRequest, | ||
11 | makePostBodyRequest, | ||
12 | runServer, | ||
13 | ServerInfo, | ||
14 | setAccessTokensToServers, | ||
15 | updateVideoAbuse, | ||
16 | uploadVideo, | ||
17 | userLogin | ||
8 | } from '../../utils' | 18 | } from '../../utils' |
9 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | 19 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' |
20 | import { VideoAbuseState } from '../../../../shared/models/videos' | ||
10 | 21 | ||
11 | describe('Test video abuses API validators', function () { | 22 | describe('Test video abuses API validators', function () { |
12 | let server: ServerInfo | 23 | let server: ServerInfo |
13 | let userAccessToken = '' | 24 | let userAccessToken = '' |
25 | let videoAbuseId: number | ||
14 | 26 | ||
15 | // --------------------------------------------------------------- | 27 | // --------------------------------------------------------------- |
16 | 28 | ||
@@ -67,44 +79,111 @@ describe('Test video abuses API validators', function () { | |||
67 | 79 | ||
68 | describe('When reporting a video abuse', function () { | 80 | describe('When reporting a video abuse', function () { |
69 | const basePath = '/api/v1/videos/' | 81 | const basePath = '/api/v1/videos/' |
82 | let path: string | ||
83 | |||
84 | before(() => { | ||
85 | path = basePath + server.video.id + '/abuse' | ||
86 | }) | ||
70 | 87 | ||
71 | it('Should fail with nothing', async function () { | 88 | it('Should fail with nothing', async function () { |
72 | const path = basePath + server.video.id + '/abuse' | ||
73 | const fields = {} | 89 | const fields = {} |
74 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | 90 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) |
75 | }) | 91 | }) |
76 | 92 | ||
77 | it('Should fail with a wrong video', async function () { | 93 | it('Should fail with a wrong video', async function () { |
78 | const wrongPath = '/api/v1/videos/blabla/abuse' | 94 | const wrongPath = '/api/v1/videos/blabla/abuse' |
79 | const fields = { | 95 | const fields = { reason: 'my super reason' } |
80 | reason: 'my super reason' | 96 | |
81 | } | ||
82 | await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields }) | 97 | await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields }) |
83 | }) | 98 | }) |
84 | 99 | ||
85 | it('Should fail with a non authenticated user', async function () { | 100 | it('Should fail with a non authenticated user', async function () { |
86 | const path = basePath + server.video.id + '/abuse' | 101 | const fields = { reason: 'my super reason' } |
87 | const fields = { | 102 | |
88 | reason: 'my super reason' | ||
89 | } | ||
90 | await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 }) | 103 | await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 }) |
91 | }) | 104 | }) |
92 | 105 | ||
93 | it('Should fail with a reason too short', async function () { | 106 | it('Should fail with a reason too short', async function () { |
94 | const path = basePath + server.video.id + '/abuse' | 107 | const fields = { reason: 'h' } |
95 | const fields = { | 108 | |
96 | reason: 'h' | ||
97 | } | ||
98 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | 109 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) |
99 | }) | 110 | }) |
100 | 111 | ||
101 | it('Should fail with a reason too big', async function () { | 112 | it('Should fail with a reason too big', async function () { |
102 | const path = basePath + server.video.id + '/abuse' | 113 | const fields = { reason: 'super'.repeat(61) } |
103 | const fields = { | 114 | |
104 | reason: 'super'.repeat(61) | ||
105 | } | ||
106 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | 115 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) |
107 | }) | 116 | }) |
117 | |||
118 | it('Should succeed with the correct parameters', async function () { | ||
119 | const fields = { reason: 'super reason' } | ||
120 | |||
121 | const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) | ||
122 | videoAbuseId = res.body.videoAbuse.id | ||
123 | }) | ||
124 | }) | ||
125 | |||
126 | describe('When updating a video abuse', function () { | ||
127 | const basePath = '/api/v1/videos/' | ||
128 | let path: string | ||
129 | |||
130 | before(() => { | ||
131 | path = basePath + server.video.id + '/abuse/' + videoAbuseId | ||
132 | }) | ||
133 | |||
134 | it('Should fail with a non authenticated user', async function () { | ||
135 | await updateVideoAbuse(server.url, 'blabla', server.video.uuid, videoAbuseId, {}, 401) | ||
136 | }) | ||
137 | |||
138 | it('Should fail with a non admin user', async function () { | ||
139 | await updateVideoAbuse(server.url, userAccessToken, server.video.uuid, videoAbuseId, {}, 403) | ||
140 | }) | ||
141 | |||
142 | it('Should fail with a bad video id or bad video abuse id', async function () { | ||
143 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, 45, {}, 404) | ||
144 | await updateVideoAbuse(server.url, server.accessToken, 52, videoAbuseId, {}, 404) | ||
145 | }) | ||
146 | |||
147 | it('Should fail with a bad state', async function () { | ||
148 | const body = { state: 5 } | ||
149 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400) | ||
150 | }) | ||
151 | |||
152 | it('Should fail with a bad moderation comment', async function () { | ||
153 | const body = { moderationComment: 'b'.repeat(305) } | ||
154 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400) | ||
155 | }) | ||
156 | |||
157 | it('Should succeed with the correct params', async function () { | ||
158 | const body = { state: VideoAbuseState.ACCEPTED } | ||
159 | await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body) | ||
160 | }) | ||
161 | }) | ||
162 | |||
163 | describe('When deleting a video abuse', function () { | ||
164 | const basePath = '/api/v1/videos/' | ||
165 | let path: string | ||
166 | |||
167 | before(() => { | ||
168 | path = basePath + server.video.id + '/abuse/' + videoAbuseId | ||
169 | }) | ||
170 | |||
171 | it('Should fail with a non authenticated user', async function () { | ||
172 | await deleteVideoAbuse(server.url, 'blabla', server.video.uuid, videoAbuseId, 401) | ||
173 | }) | ||
174 | |||
175 | it('Should fail with a non admin user', async function () { | ||
176 | await deleteVideoAbuse(server.url, userAccessToken, server.video.uuid, videoAbuseId, 403) | ||
177 | }) | ||
178 | |||
179 | it('Should fail with a bad video id or bad video abuse id', async function () { | ||
180 | await deleteVideoAbuse(server.url, server.accessToken, server.video.uuid, 45, 404) | ||
181 | await deleteVideoAbuse(server.url, server.accessToken, 52, videoAbuseId, 404) | ||
182 | }) | ||
183 | |||
184 | it('Should succeed with the correct params', async function () { | ||
185 | await deleteVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId) | ||
186 | }) | ||
108 | }) | 187 | }) |
109 | 188 | ||
110 | after(async function () { | 189 | after(async function () { |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index dde309b96..a17f3c8de 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -2,8 +2,9 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoAbuse } from '../../../../shared/models/videos' | 5 | import { VideoAbuse, VideoAbuseState } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | deleteVideoAbuse, | ||
7 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
8 | getVideoAbusesList, | 9 | getVideoAbusesList, |
9 | getVideosList, | 10 | getVideosList, |
@@ -11,6 +12,7 @@ import { | |||
11 | reportVideoAbuse, | 12 | reportVideoAbuse, |
12 | ServerInfo, | 13 | ServerInfo, |
13 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
15 | updateVideoAbuse, | ||
14 | uploadVideo | 16 | uploadVideo |
15 | } from '../../utils/index' | 17 | } from '../../utils/index' |
16 | import { doubleFollow } from '../../utils/server/follows' | 18 | import { doubleFollow } from '../../utils/server/follows' |
@@ -20,6 +22,7 @@ const expect = chai.expect | |||
20 | 22 | ||
21 | describe('Test video abuses', function () { | 23 | describe('Test video abuses', function () { |
22 | let servers: ServerInfo[] = [] | 24 | let servers: ServerInfo[] = [] |
25 | let abuseServer2: VideoAbuse | ||
23 | 26 | ||
24 | before(async function () { | 27 | before(async function () { |
25 | this.timeout(50000) | 28 | this.timeout(50000) |
@@ -105,7 +108,7 @@ describe('Test video abuses', function () { | |||
105 | await waitJobs(servers) | 108 | await waitJobs(servers) |
106 | }) | 109 | }) |
107 | 110 | ||
108 | it('Should have 2 video abuse on server 1 and 1 on server 2', async function () { | 111 | it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { |
109 | const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 112 | const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) |
110 | expect(res1.body.total).to.equal(2) | 113 | expect(res1.body.total).to.equal(2) |
111 | expect(res1.body.data).to.be.an('array') | 114 | expect(res1.body.data).to.be.an('array') |
@@ -116,22 +119,57 @@ describe('Test video abuses', function () { | |||
116 | expect(abuse1.reporterAccount.name).to.equal('root') | 119 | expect(abuse1.reporterAccount.name).to.equal('root') |
117 | expect(abuse1.reporterAccount.host).to.equal('localhost:9001') | 120 | expect(abuse1.reporterAccount.host).to.equal('localhost:9001') |
118 | expect(abuse1.video.id).to.equal(servers[0].video.id) | 121 | expect(abuse1.video.id).to.equal(servers[0].video.id) |
122 | expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) | ||
123 | expect(abuse1.state.label).to.equal('Pending') | ||
124 | expect(abuse1.moderationComment).to.be.null | ||
119 | 125 | ||
120 | const abuse2: VideoAbuse = res1.body.data[1] | 126 | const abuse2: VideoAbuse = res1.body.data[1] |
121 | expect(abuse2.reason).to.equal('my super bad reason 2') | 127 | expect(abuse2.reason).to.equal('my super bad reason 2') |
122 | expect(abuse2.reporterAccount.name).to.equal('root') | 128 | expect(abuse2.reporterAccount.name).to.equal('root') |
123 | expect(abuse2.reporterAccount.host).to.equal('localhost:9001') | 129 | expect(abuse2.reporterAccount.host).to.equal('localhost:9001') |
124 | expect(abuse2.video.id).to.equal(servers[1].video.id) | 130 | expect(abuse2.video.id).to.equal(servers[1].video.id) |
131 | expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING) | ||
132 | expect(abuse2.state.label).to.equal('Pending') | ||
133 | expect(abuse2.moderationComment).to.be.null | ||
125 | 134 | ||
126 | const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 135 | const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) |
127 | expect(res2.body.total).to.equal(1) | 136 | expect(res2.body.total).to.equal(1) |
128 | expect(res2.body.data).to.be.an('array') | 137 | expect(res2.body.data).to.be.an('array') |
129 | expect(res2.body.data.length).to.equal(1) | 138 | expect(res2.body.data.length).to.equal(1) |
130 | 139 | ||
131 | const abuse3: VideoAbuse = res2.body.data[0] | 140 | abuseServer2 = res2.body.data[0] |
132 | expect(abuse3.reason).to.equal('my super bad reason 2') | 141 | expect(abuseServer2.reason).to.equal('my super bad reason 2') |
133 | expect(abuse3.reporterAccount.name).to.equal('root') | 142 | expect(abuseServer2.reporterAccount.name).to.equal('root') |
134 | expect(abuse3.reporterAccount.host).to.equal('localhost:9001') | 143 | expect(abuseServer2.reporterAccount.host).to.equal('localhost:9001') |
144 | expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING) | ||
145 | expect(abuseServer2.state.label).to.equal('Pending') | ||
146 | expect(abuseServer2.moderationComment).to.be.null | ||
147 | }) | ||
148 | |||
149 | it('Should update the state of a video abuse', async function () { | ||
150 | const body = { state: VideoAbuseState.REJECTED } | ||
151 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | ||
152 | |||
153 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | ||
154 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) | ||
155 | }) | ||
156 | |||
157 | it('Should add a moderation comment', async function () { | ||
158 | const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } | ||
159 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | ||
160 | |||
161 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | ||
162 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) | ||
163 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | ||
164 | }) | ||
165 | |||
166 | it('Should delete the video abuse', async function () { | ||
167 | await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) | ||
168 | |||
169 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | ||
170 | expect(res.body.total).to.equal(0) | ||
171 | expect(res.body.data).to.be.an('array') | ||
172 | expect(res.body.data.length).to.equal(0) | ||
135 | }) | 173 | }) |
136 | 174 | ||
137 | after(async function () { | 175 | after(async function () { |
diff --git a/server/tests/utils/videos/video-abuses.ts b/server/tests/utils/videos/video-abuses.ts index 0d72bf457..5f138d6b3 100644 --- a/server/tests/utils/videos/video-abuses.ts +++ b/server/tests/utils/videos/video-abuses.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { VideoAbuseUpdate } from '../../../../shared/models/videos/video-abuse-update.model' | ||
3 | import { makeDeleteRequest, makePutBodyRequest } from '..' | ||
2 | 4 | ||
3 | function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 204) { | 5 | function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) { |
4 | const path = '/api/v1/videos/' + videoId + '/abuse' | 6 | const path = '/api/v1/videos/' + videoId + '/abuse' |
5 | 7 | ||
6 | return request(url) | 8 | return request(url) |
@@ -23,9 +25,41 @@ function getVideoAbusesList (url: string, token: string) { | |||
23 | .expect('Content-Type', /json/) | 25 | .expect('Content-Type', /json/) |
24 | } | 26 | } |
25 | 27 | ||
28 | function updateVideoAbuse ( | ||
29 | url: string, | ||
30 | token: string, | ||
31 | videoId: string | number, | ||
32 | videoAbuseId: number, | ||
33 | body: VideoAbuseUpdate, | ||
34 | statusCodeExpected = 204 | ||
35 | ) { | ||
36 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId | ||
37 | |||
38 | return makePutBodyRequest({ | ||
39 | url, | ||
40 | token, | ||
41 | path, | ||
42 | fields: body, | ||
43 | statusCodeExpected | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) { | ||
48 | const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId | ||
49 | |||
50 | return makeDeleteRequest({ | ||
51 | url, | ||
52 | token, | ||
53 | path, | ||
54 | statusCodeExpected | ||
55 | }) | ||
56 | } | ||
57 | |||
26 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
27 | 59 | ||
28 | export { | 60 | export { |
29 | reportVideoAbuse, | 61 | reportVideoAbuse, |
30 | getVideoAbusesList | 62 | getVideoAbusesList, |
63 | updateVideoAbuse, | ||
64 | deleteVideoAbuse | ||
31 | } | 65 | } |
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts index 17cf8be24..2d7de2a0d 100644 --- a/shared/models/videos/index.ts +++ b/shared/models/videos/index.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | export * from './user-video-rate-update.model' | 1 | export * from './user-video-rate-update.model' |
2 | export * from './user-video-rate.model' | 2 | export * from './user-video-rate.model' |
3 | export * from './user-video-rate.type' | 3 | export * from './user-video-rate.type' |
4 | export * from './video-abuse-state.model' | ||
4 | export * from './video-abuse-create.model' | 5 | export * from './video-abuse-create.model' |
5 | export * from './video-abuse.model' | 6 | export * from './video-abuse.model' |
6 | export * from './video-blacklist.model' | 7 | export * from './video-blacklist.model' |
diff --git a/shared/models/videos/video-abuse-state.model.ts b/shared/models/videos/video-abuse-state.model.ts new file mode 100644 index 000000000..529f034bd --- /dev/null +++ b/shared/models/videos/video-abuse-state.model.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export enum VideoAbuseState { | ||
2 | PENDING = 1, | ||
3 | REJECTED = 2, | ||
4 | ACCEPTED = 3 | ||
5 | } | ||
diff --git a/shared/models/videos/video-abuse-update.model.ts b/shared/models/videos/video-abuse-update.model.ts new file mode 100644 index 000000000..9b32aae48 --- /dev/null +++ b/shared/models/videos/video-abuse-update.model.ts | |||
@@ -0,0 +1,6 @@ | |||
1 | import { VideoAbuseState } from './video-abuse-state.model' | ||
2 | |||
3 | export interface VideoAbuseUpdate { | ||
4 | moderationComment?: string | ||
5 | state?: VideoAbuseState | ||
6 | } | ||
diff --git a/shared/models/videos/video-abuse.model.ts b/shared/models/videos/video-abuse.model.ts index 51070a7a3..1fecce037 100644 --- a/shared/models/videos/video-abuse.model.ts +++ b/shared/models/videos/video-abuse.model.ts | |||
@@ -1,14 +1,21 @@ | |||
1 | import { Account } from '../actors' | 1 | import { Account } from '../actors' |
2 | import { VideoConstant } from './video-constant.model' | ||
3 | import { VideoAbuseState } from './video-abuse-state.model' | ||
2 | 4 | ||
3 | export interface VideoAbuse { | 5 | export interface VideoAbuse { |
4 | id: number | 6 | id: number |
5 | reason: string | 7 | reason: string |
6 | reporterAccount: Account | 8 | reporterAccount: Account |
9 | |||
10 | state: VideoConstant<VideoAbuseState> | ||
11 | moderationComment?: string | ||
12 | |||
7 | video: { | 13 | video: { |
8 | id: number | 14 | id: number |
9 | name: string | 15 | name: string |
10 | uuid: string | 16 | uuid: string |
11 | url: string | 17 | url: string |
12 | } | 18 | } |
19 | |||
13 | createdAt: Date | 20 | createdAt: Date |
14 | } | 21 | } |