import * as express from 'express'
-import { UserRight, VideoAbuseCreate } from '../../../../shared'
+import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared'
import { logger } from '../../../helpers/logger'
import { getFormattedObjects } from '../../../helpers/utils'
import { sequelizeTypescript } from '../../../initializers'
paginationValidator,
setDefaultPagination,
setDefaultSort,
+ videoAbuseGetValidator,
videoAbuseReportValidator,
- videoAbusesSortValidator
+ videoAbusesSortValidator,
+ videoAbuseUpdateValidator
} from '../../../middlewares'
import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video'
setDefaultPagination,
asyncMiddleware(listVideoAbuses)
)
-abuseVideoRouter.post('/:id/abuse',
+abuseVideoRouter.put('/:videoId/abuse/:id',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES),
+ asyncMiddleware(videoAbuseUpdateValidator),
+ asyncRetryTransactionMiddleware(updateVideoAbuse)
+)
+abuseVideoRouter.post('/:videoId/abuse',
authenticate,
asyncMiddleware(videoAbuseReportValidator),
asyncRetryTransactionMiddleware(reportVideoAbuse)
)
+abuseVideoRouter.delete('/:videoId/abuse/:id',
+ authenticate,
+ ensureUserHasRight(UserRight.MANAGE_VIDEO_ABUSES),
+ asyncMiddleware(videoAbuseGetValidator),
+ asyncRetryTransactionMiddleware(deleteVideoAbuse)
+)
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-async function listVideoAbuses (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function listVideoAbuses (req: express.Request, res: express.Response) {
const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort)
return res.json(getFormattedObjects(resultList.data, resultList.total))
}
+async function updateVideoAbuse (req: express.Request, res: express.Response) {
+ const videoAbuse: VideoAbuseModel = res.locals.videoAbuse
+
+ if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment
+ if (req.body.state !== undefined) videoAbuse.state = req.body.state
+
+ await sequelizeTypescript.transaction(t => {
+ return videoAbuse.save({ transaction: t })
+ })
+
+ // Do not send the delete to other instances, we updated OUR copy of this video abuse
+
+ return res.type('json').status(204).end()
+}
+
+async function deleteVideoAbuse (req: express.Request, res: express.Response) {
+ const videoAbuse: VideoAbuseModel = res.locals.videoAbuse
+
+ await sequelizeTypescript.transaction(t => {
+ return videoAbuse.destroy({ transaction: t })
+ })
+
+ // Do not send the delete to other instances, we delete OUR copy of this video abuse
+
+ return res.type('json').status(204).end()
+}
+
async function reportVideoAbuse (req: express.Request, res: express.Response) {
const videoInstance = res.locals.video as VideoModel
const reporterAccount = res.locals.oauth.token.User.Account as AccountModel
const abuseToCreate = {
reporterAccountId: reporterAccount.id,
reason: body.reason,
- videoId: videoInstance.id
+ videoId: videoInstance.id,
+ state: VideoAbuseState.PENDING
}
- await sequelizeTypescript.transaction(async t => {
+ const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => {
const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t })
videoAbuseInstance.Video = videoInstance
videoAbuseInstance.Account = reporterAccount
}
auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
- logger.info('Abuse report for video %s created.', videoInstance.name)
+
+ return videoAbuseInstance
})
- return res.type('json').status(204).end()
+ logger.info('Abuse report for video %s created.', videoInstance.name)
+ return res.json({
+ videoAbuse: videoAbuse.toFormattedJSON()
+ }).end()
}
import { peertubeTruncate } from '../../core-utils'
import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc'
import {
- isVideoAbuseReasonValid,
isVideoDurationValid,
isVideoNameValid,
isVideoStateValid,
} from '../videos'
import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc'
import { VideoState } from '../../../../shared/models/videos'
+import { isVideoAbuseReasonValid } from '../video-abuses'
function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) {
return isBaseActivityValid(activity, 'Create') &&
--- /dev/null
+import { Response } from 'express'
+import * as validator from 'validator'
+import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers'
+import { exists } from './misc'
+import { VideoAbuseModel } from '../../models/video/video-abuse'
+
+const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
+
+function isVideoAbuseReasonValid (value: string) {
+ return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
+}
+
+function isVideoAbuseModerationCommentValid (value: string) {
+ return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.MODERATION_COMMENT)
+}
+
+function isVideoAbuseStateValid (value: string) {
+ return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined
+}
+
+async function isVideoAbuseExist (abuseId: number, videoId: number, res: Response) {
+ const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId)
+
+ if (videoAbuse === null) {
+ res.status(404)
+ .json({ error: 'Video abuse not found' })
+ .end()
+
+ return false
+ }
+
+ res.locals.videoAbuse = videoAbuse
+ return true
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ isVideoAbuseExist,
+ isVideoAbuseStateValid,
+ isVideoAbuseReasonValid,
+ isVideoAbuseModerationCommentValid
+}
import { UserRight, VideoPrivacy, VideoRateType } from '../../../shared'
import {
CONSTRAINTS_FIELDS,
+ VIDEO_ABUSE_STATES,
VIDEO_CATEGORIES,
VIDEO_LICENCES,
VIDEO_MIMETYPE_EXT,
import { VideoChannelModel } from '../../models/video/video-channel'
import { UserModel } from '../../models/account/user'
import * as magnetUtil from 'magnet-uri'
+import { VideoAbuseModel } from '../../models/video/video-abuse'
const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
)
}
-function isVideoAbuseReasonValid (value: string) {
- return exists(value) && validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON)
-}
-
function isVideoViewsValid (value: string) {
return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
}
isVideoTagsValid,
isVideoFPSResolutionValid,
isScheduleVideoUpdatePrivacyValid,
- isVideoAbuseReasonValid,
isVideoFile,
isVideoMagnetUriValid,
isVideoStateValid,
import { JobType, VideoRateType, VideoState } from '../../shared/models'
import { ActivityPubActorType } from '../../shared/models/activitypub'
import { FollowState } from '../../shared/models/actors'
-import { VideoPrivacy } from '../../shared/models/videos'
+import { VideoPrivacy, VideoAbuseState } from '../../shared/models/videos'
// Do not use barrels, remain constants as independent as possible
import { buildPath, isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 245
+const LAST_MIGRATION_VERSION = 250
// ---------------------------------------------------------------------------
BLOCKED_REASON: { min: 3, max: 250 } // Length
},
VIDEO_ABUSES: {
- REASON: { min: 2, max: 300 } // Length
+ REASON: { min: 2, max: 300 }, // Length
+ MODERATION_COMMENT: { min: 2, max: 300 } // Length
},
VIDEO_CHANNELS: {
NAME: { min: 3, max: 120 }, // Length
[VideoImportState.SUCCESS]: 'Success'
}
+const VIDEO_ABUSE_STATES = {
+ [VideoAbuseState.PENDING]: 'Pending',
+ [VideoAbuseState.REJECTED]: 'Rejected',
+ [VideoAbuseState.ACCEPTED]: 'Accepted'
+}
+
const VIDEO_MIMETYPE_EXT = {
'video/webm': '.webm',
'video/ogg': '.ogv',
VIDEO_MIMETYPE_EXT,
VIDEO_TRANSCODING_FPS,
FFMPEG_NICE,
+ VIDEO_ABUSE_STATES,
JOB_REQUEST_TIMEOUT,
USER_PASSWORD_RESET_LIFETIME,
IMAGE_MIMETYPE_EXT,
--- /dev/null
+import * as Sequelize from 'sequelize'
+import { CONSTRAINTS_FIELDS } from '../constants'
+import { VideoAbuseState } from '../../../shared/models/videos'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction
+ queryInterface: Sequelize.QueryInterface
+ sequelize: Sequelize.Sequelize
+}): Promise<any> {
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: true,
+ defaultValue: null
+ }
+ await utils.queryInterface.addColumn('videoAbuse', 'state', data)
+ }
+
+ {
+ const query = 'UPDATE "videoAbuse" SET "state" = ' + VideoAbuseState.PENDING
+ await utils.sequelize.query(query)
+ }
+
+ {
+ const data = {
+ type: Sequelize.INTEGER,
+ allowNull: false,
+ defaultValue: null
+ }
+ await utils.queryInterface.changeColumn('videoAbuse', 'state', data)
+ }
+
+ {
+ const data = {
+ type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max),
+ allowNull: true,
+ defaultValue: null
+ }
+ await utils.queryInterface.addColumn('videoAbuse', 'moderationComment', data)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export { up, down }
-import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
+import { ActivityCreate, VideoAbuseState, VideoTorrentObject } from '../../../../shared'
import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
import { retryTransactionWrapper } from '../../../helpers/database-utils'
const videoAbuseData = {
reporterAccountId: account.id,
reason: videoAbuseToCreateData.content,
- videoId: video.id
+ videoId: video.id,
+ state: VideoAbuseState.PENDING
}
await VideoAbuseModel.create(videoAbuseData)
export * from './sort'
export * from './users'
export * from './videos'
+export * from './video-abuses'
export * from './video-blacklist'
export * from './video-channels'
export * from './webfinger'
--- /dev/null
+import * as express from 'express'
+import 'express-validator'
+import { body, param } from 'express-validator/check'
+import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
+import { isVideoExist } from '../../helpers/custom-validators/videos'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import {
+ isVideoAbuseExist,
+ isVideoAbuseModerationCommentValid,
+ isVideoAbuseReasonValid,
+ isVideoAbuseStateValid
+} from '../../helpers/custom-validators/video-abuses'
+
+const videoAbuseReportValidator = [
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+ body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+ if (!await isVideoExist(req.params.videoId, res)) return
+
+ return next()
+ }
+]
+
+const videoAbuseGetValidator = [
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+ param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+ if (!await isVideoExist(req.params.videoId, res)) return
+ if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+
+ return next()
+ }
+]
+
+const videoAbuseUpdateValidator = [
+ param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+ param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
+ body('state')
+ .optional()
+ .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'),
+ body('moderationComment')
+ .optional()
+ .custom(isVideoAbuseModerationCommentValid).withMessage('Should have a valid video moderation comment'),
+
+ async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+ logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body })
+
+ if (areValidationErrors(req, res)) return
+ if (!await isVideoExist(req.params.videoId, res)) return
+ if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return
+
+ return next()
+ }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+ videoAbuseReportValidator,
+ videoAbuseGetValidator,
+ videoAbuseUpdateValidator
+}
import {
checkUserCanManageVideo,
isScheduleVideoUpdatePrivacyValid,
- isVideoAbuseReasonValid,
isVideoCategoryValid,
isVideoChannelOfAccountExist,
isVideoDescriptionValid,
}
]
-const videoAbuseReportValidator = [
- param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
- body('reason').custom(isVideoAbuseReasonValid).withMessage('Should have a valid reason'),
-
- async (req: express.Request, res: express.Response, next: express.NextFunction) => {
- logger.debug('Checking videoAbuseReport parameters', { parameters: req.body })
-
- if (areValidationErrors(req, res)) return
- if (!await isVideoExist(req.params.id, res)) return
-
- return next()
- }
-]
-
const videoRateValidator = [
param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
body('rating').custom(isVideoRatingTypeValid).withMessage('Should have a valid rate type'),
videosRemoveValidator,
videosShareValidator,
- videoAbuseReportValidator,
-
videoRateValidator,
getCommonVideoAttributes
-import { AfterCreate, AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import {
+ AfterCreate,
+ AllowNull,
+ BelongsTo,
+ Column,
+ CreatedAt,
+ DataType,
+ Default,
+ ForeignKey,
+ Is,
+ Model,
+ Table,
+ UpdatedAt
+} from 'sequelize-typescript'
import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
import { VideoAbuse } from '../../../shared/models/videos'
-import { isVideoAbuseReasonValid } from '../../helpers/custom-validators/videos'
+import {
+ isVideoAbuseModerationCommentValid,
+ isVideoAbuseReasonValid,
+ isVideoAbuseStateValid
+} from '../../helpers/custom-validators/video-abuses'
import { Emailer } from '../../lib/emailer'
import { AccountModel } from '../account/account'
import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
+import { VideoAbuseState } from '../../../shared'
+import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers'
@Table({
tableName: 'videoAbuse',
@Column
reason: string
+ @AllowNull(false)
+ @Default(null)
+ @Is('VideoAbuseState', value => throwIfNotValid(value, isVideoAbuseStateValid, 'state'))
+ @Column
+ state: VideoAbuseState
+
+ @AllowNull(true)
+ @Default(null)
+ @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max))
+ moderationComment: string
+
@CreatedAt
createdAt: Date
return Emailer.Instance.addVideoAbuseReportJob(instance.videoId)
}
+ static loadByIdAndVideoId (id: number, videoId: number) {
+ const query = {
+ where: {
+ id,
+ videoId
+ }
+ }
+ return VideoAbuseModel.findOne(query)
+ }
+
static listForApi (start: number, count: number, sort: string) {
const query = {
offset: start,
id: this.id,
reason: this.reason,
reporterAccount: this.Account.toFormattedJSON(),
+ state: {
+ id: this.state,
+ label: VideoAbuseModel.getStateLabel(this.state)
+ },
+ moderationComment: this.moderationComment,
video: {
id: this.Video.id,
uuid: this.Video.uuid,
object: this.Video.url
}
}
+
+ private static getStateLabel (id: number) {
+ return VIDEO_ABUSE_STATES[id] || 'Unknown'
+ }
}
video
}
}
+
private static getStateLabel (id: number) {
return VIDEO_IMPORT_STATES[id] || 'Unknown'
}
import 'mocha'
import {
- createUser, flushTests, killallServers, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
- uploadVideo, userLogin
+ createUser,
+ deleteVideoAbuse,
+ flushTests,
+ killallServers,
+ makeGetRequest,
+ makePostBodyRequest,
+ runServer,
+ ServerInfo,
+ setAccessTokensToServers,
+ updateVideoAbuse,
+ uploadVideo,
+ userLogin
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+import { VideoAbuseState } from '../../../../shared/models/videos'
describe('Test video abuses API validators', function () {
let server: ServerInfo
let userAccessToken = ''
+ let videoAbuseId: number
// ---------------------------------------------------------------
describe('When reporting a video abuse', function () {
const basePath = '/api/v1/videos/'
+ let path: string
+
+ before(() => {
+ path = basePath + server.video.id + '/abuse'
+ })
it('Should fail with nothing', async function () {
- const path = basePath + server.video.id + '/abuse'
const fields = {}
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a wrong video', async function () {
const wrongPath = '/api/v1/videos/blabla/abuse'
- const fields = {
- reason: 'my super reason'
- }
+ const fields = { reason: 'my super reason' }
+
await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
})
it('Should fail with a non authenticated user', async function () {
- const path = basePath + server.video.id + '/abuse'
- const fields = {
- reason: 'my super reason'
- }
+ const fields = { reason: 'my super reason' }
+
await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
})
it('Should fail with a reason too short', async function () {
- const path = basePath + server.video.id + '/abuse'
- const fields = {
- reason: 'h'
- }
+ const fields = { reason: 'h' }
+
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
it('Should fail with a reason too big', async function () {
- const path = basePath + server.video.id + '/abuse'
- const fields = {
- reason: 'super'.repeat(61)
- }
+ const fields = { reason: 'super'.repeat(61) }
+
await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
})
+
+ it('Should succeed with the correct parameters', async function () {
+ const fields = { reason: 'super reason' }
+
+ const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 })
+ videoAbuseId = res.body.videoAbuse.id
+ })
+ })
+
+ describe('When updating a video abuse', function () {
+ const basePath = '/api/v1/videos/'
+ let path: string
+
+ before(() => {
+ path = basePath + server.video.id + '/abuse/' + videoAbuseId
+ })
+
+ it('Should fail with a non authenticated user', async function () {
+ await updateVideoAbuse(server.url, 'blabla', server.video.uuid, videoAbuseId, {}, 401)
+ })
+
+ it('Should fail with a non admin user', async function () {
+ await updateVideoAbuse(server.url, userAccessToken, server.video.uuid, videoAbuseId, {}, 403)
+ })
+
+ it('Should fail with a bad video id or bad video abuse id', async function () {
+ await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, 45, {}, 404)
+ await updateVideoAbuse(server.url, server.accessToken, 52, videoAbuseId, {}, 404)
+ })
+
+ it('Should fail with a bad state', async function () {
+ const body = { state: 5 }
+ await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400)
+ })
+
+ it('Should fail with a bad moderation comment', async function () {
+ const body = { moderationComment: 'b'.repeat(305) }
+ await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400)
+ })
+
+ it('Should succeed with the correct params', async function () {
+ const body = { state: VideoAbuseState.ACCEPTED }
+ await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body)
+ })
+ })
+
+ describe('When deleting a video abuse', function () {
+ const basePath = '/api/v1/videos/'
+ let path: string
+
+ before(() => {
+ path = basePath + server.video.id + '/abuse/' + videoAbuseId
+ })
+
+ it('Should fail with a non authenticated user', async function () {
+ await deleteVideoAbuse(server.url, 'blabla', server.video.uuid, videoAbuseId, 401)
+ })
+
+ it('Should fail with a non admin user', async function () {
+ await deleteVideoAbuse(server.url, userAccessToken, server.video.uuid, videoAbuseId, 403)
+ })
+
+ it('Should fail with a bad video id or bad video abuse id', async function () {
+ await deleteVideoAbuse(server.url, server.accessToken, server.video.uuid, 45, 404)
+ await deleteVideoAbuse(server.url, server.accessToken, 52, videoAbuseId, 404)
+ })
+
+ it('Should succeed with the correct params', async function () {
+ await deleteVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId)
+ })
})
after(async function () {
import * as chai from 'chai'
import 'mocha'
-import { VideoAbuse } from '../../../../shared/models/videos'
+import { VideoAbuse, VideoAbuseState } from '../../../../shared/models/videos'
import {
+ deleteVideoAbuse,
flushAndRunMultipleServers,
getVideoAbusesList,
getVideosList,
reportVideoAbuse,
ServerInfo,
setAccessTokensToServers,
+ updateVideoAbuse,
uploadVideo
} from '../../utils/index'
import { doubleFollow } from '../../utils/server/follows'
describe('Test video abuses', function () {
let servers: ServerInfo[] = []
+ let abuseServer2: VideoAbuse
before(async function () {
this.timeout(50000)
await waitJobs(servers)
})
- it('Should have 2 video abuse on server 1 and 1 on server 2', async function () {
+ it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
expect(res1.body.total).to.equal(2)
expect(res1.body.data).to.be.an('array')
expect(abuse1.reporterAccount.name).to.equal('root')
expect(abuse1.reporterAccount.host).to.equal('localhost:9001')
expect(abuse1.video.id).to.equal(servers[0].video.id)
+ expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING)
+ expect(abuse1.state.label).to.equal('Pending')
+ expect(abuse1.moderationComment).to.be.null
const abuse2: VideoAbuse = res1.body.data[1]
expect(abuse2.reason).to.equal('my super bad reason 2')
expect(abuse2.reporterAccount.name).to.equal('root')
expect(abuse2.reporterAccount.host).to.equal('localhost:9001')
expect(abuse2.video.id).to.equal(servers[1].video.id)
+ expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING)
+ expect(abuse2.state.label).to.equal('Pending')
+ expect(abuse2.moderationComment).to.be.null
const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
expect(res2.body.total).to.equal(1)
expect(res2.body.data).to.be.an('array')
expect(res2.body.data.length).to.equal(1)
- const abuse3: VideoAbuse = res2.body.data[0]
- expect(abuse3.reason).to.equal('my super bad reason 2')
- expect(abuse3.reporterAccount.name).to.equal('root')
- expect(abuse3.reporterAccount.host).to.equal('localhost:9001')
+ abuseServer2 = res2.body.data[0]
+ expect(abuseServer2.reason).to.equal('my super bad reason 2')
+ expect(abuseServer2.reporterAccount.name).to.equal('root')
+ expect(abuseServer2.reporterAccount.host).to.equal('localhost:9001')
+ expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING)
+ expect(abuseServer2.state.label).to.equal('Pending')
+ expect(abuseServer2.moderationComment).to.be.null
+ })
+
+ it('Should update the state of a video abuse', async function () {
+ const body = { state: VideoAbuseState.REJECTED }
+ await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
+
+ const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
+ expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED)
+ })
+
+ it('Should add a moderation comment', async function () {
+ const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' }
+ await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body)
+
+ const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
+ expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED)
+ expect(res.body.data[0].moderationComment).to.equal('It is valid')
+ })
+
+ it('Should delete the video abuse', async function () {
+ await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id)
+
+ const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
+ expect(res.body.total).to.equal(0)
+ expect(res.body.data).to.be.an('array')
+ expect(res.body.data.length).to.equal(0)
})
after(async function () {
import * as request from 'supertest'
+import { VideoAbuseUpdate } from '../../../../shared/models/videos/video-abuse-update.model'
+import { makeDeleteRequest, makePutBodyRequest } from '..'
-function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 204) {
+function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
const path = '/api/v1/videos/' + videoId + '/abuse'
return request(url)
.expect('Content-Type', /json/)
}
+function updateVideoAbuse (
+ url: string,
+ token: string,
+ videoId: string | number,
+ videoAbuseId: number,
+ body: VideoAbuseUpdate,
+ statusCodeExpected = 204
+) {
+ const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
+
+ return makePutBodyRequest({
+ url,
+ token,
+ path,
+ fields: body,
+ statusCodeExpected
+ })
+}
+
+function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) {
+ const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
+
+ return makeDeleteRequest({
+ url,
+ token,
+ path,
+ statusCodeExpected
+ })
+}
+
// ---------------------------------------------------------------------------
export {
reportVideoAbuse,
- getVideoAbusesList
+ getVideoAbusesList,
+ updateVideoAbuse,
+ deleteVideoAbuse
}
export * from './user-video-rate-update.model'
export * from './user-video-rate.model'
export * from './user-video-rate.type'
+export * from './video-abuse-state.model'
export * from './video-abuse-create.model'
export * from './video-abuse.model'
export * from './video-blacklist.model'
--- /dev/null
+export enum VideoAbuseState {
+ PENDING = 1,
+ REJECTED = 2,
+ ACCEPTED = 3
+}
--- /dev/null
+import { VideoAbuseState } from './video-abuse-state.model'
+
+export interface VideoAbuseUpdate {
+ moderationComment?: string
+ state?: VideoAbuseState
+}
import { Account } from '../actors'
+import { VideoConstant } from './video-constant.model'
+import { VideoAbuseState } from './video-abuse-state.model'
export interface VideoAbuse {
id: number
reason: string
reporterAccount: Account
+
+ state: VideoConstant<VideoAbuseState>
+ moderationComment?: string
+
video: {
id: number
name: string
uuid: string
url: string
}
+
createdAt: Date
}