aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/abuse.ts81
-rw-r--r--server/controllers/api/users/index.ts26
-rw-r--r--server/controllers/api/users/my-abuses.ts48
-rw-r--r--server/controllers/api/videos/abuse.ts8
-rw-r--r--server/helpers/audit-logger.ts4
-rw-r--r--server/helpers/custom-validators/abuses.ts8
-rw-r--r--server/initializers/constants.ts3
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/lib/emailer.ts4
-rw-r--r--server/lib/moderation.ts2
-rw-r--r--server/lib/notifier.ts6
-rw-r--r--server/middlewares/user-right.ts6
-rw-r--r--server/middlewares/validators/abuse.ts105
-rw-r--r--server/models/abuse/abuse-message.ts103
-rw-r--r--server/models/abuse/abuse-query-builder.ts15
-rw-r--r--server/models/abuse/abuse.ts182
-rw-r--r--server/tests/api/check-params/abuses.ts153
-rw-r--r--server/tests/api/moderation/abuses.ts307
-rw-r--r--server/tests/api/users/users.ts4
-rw-r--r--server/tests/api/videos/video-abuse.ts18
-rw-r--r--server/types/models/moderation/abuse-message.ts20
-rw-r--r--server/types/models/moderation/abuse.ts8
-rw-r--r--server/types/models/moderation/index.ts1
-rw-r--r--server/typings/express/index.d.ts2
24 files changed, 931 insertions, 185 deletions
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts
index 04a0c06e3..50d068157 100644
--- a/server/controllers/api/abuse.ts
+++ b/server/controllers/api/abuse.ts
@@ -1,20 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation' 2import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation'
3import { AbuseModel } from '@server/models/abuse/abuse' 3import { AbuseModel } from '@server/models/abuse/abuse'
4import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
4import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
5import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared' 6import { AbuseCreate, abusePredefinedReasonsMap, AbuseState, UserRight } from '../../../shared'
6import { getFormattedObjects } from '../../helpers/utils' 7import { getFormattedObjects } from '../../helpers/utils'
7import { sequelizeTypescript } from '../../initializers/database' 8import { sequelizeTypescript } from '../../initializers/database'
8import { 9import {
9 abuseGetValidator, 10 abuseGetValidator,
10 abuseListValidator, 11 abuseListForAdminsValidator,
11 abuseReportValidator, 12 abuseReportValidator,
12 abusesSortValidator, 13 abusesSortValidator,
13 abuseUpdateValidator, 14 abuseUpdateValidator,
15 addAbuseMessageValidator,
14 asyncMiddleware, 16 asyncMiddleware,
15 asyncRetryTransactionMiddleware, 17 asyncRetryTransactionMiddleware,
16 authenticate, 18 authenticate,
19 deleteAbuseMessageValidator,
17 ensureUserHasRight, 20 ensureUserHasRight,
21 getAbuseValidator,
18 paginationValidator, 22 paginationValidator,
19 setDefaultPagination, 23 setDefaultPagination,
20 setDefaultSort 24 setDefaultSort
@@ -30,8 +34,8 @@ abuseRouter.get('/',
30 abusesSortValidator, 34 abusesSortValidator,
31 setDefaultSort, 35 setDefaultSort,
32 setDefaultPagination, 36 setDefaultPagination,
33 abuseListValidator, 37 abuseListForAdminsValidator,
34 asyncMiddleware(listAbuses) 38 asyncMiddleware(listAbusesForAdmins)
35) 39)
36abuseRouter.put('/:id', 40abuseRouter.put('/:id',
37 authenticate, 41 authenticate,
@@ -51,13 +55,33 @@ abuseRouter.delete('/:id',
51 asyncRetryTransactionMiddleware(deleteAbuse) 55 asyncRetryTransactionMiddleware(deleteAbuse)
52) 56)
53 57
58abuseRouter.get('/:id/messages',
59 authenticate,
60 asyncMiddleware(getAbuseValidator),
61 asyncRetryTransactionMiddleware(listAbuseMessages)
62)
63
64abuseRouter.post('/:id/messages',
65 authenticate,
66 asyncMiddleware(getAbuseValidator),
67 addAbuseMessageValidator,
68 asyncRetryTransactionMiddleware(addAbuseMessage)
69)
70
71abuseRouter.delete('/:id/messages/:messageId',
72 authenticate,
73 asyncMiddleware(getAbuseValidator),
74 asyncMiddleware(deleteAbuseMessageValidator),
75 asyncRetryTransactionMiddleware(deleteAbuseMessage)
76)
77
54// --------------------------------------------------------------------------- 78// ---------------------------------------------------------------------------
55 79
56export { 80export {
57 abuseRouter, 81 abuseRouter,
58 82
59 // FIXME: deprecated in 2.3. Remove these exports 83 // FIXME: deprecated in 2.3. Remove these exports
60 listAbuses, 84 listAbusesForAdmins,
61 updateAbuse, 85 updateAbuse,
62 deleteAbuse, 86 deleteAbuse,
63 reportAbuse 87 reportAbuse
@@ -65,11 +89,11 @@ export {
65 89
66// --------------------------------------------------------------------------- 90// ---------------------------------------------------------------------------
67 91
68async function listAbuses (req: express.Request, res: express.Response) { 92async function listAbusesForAdmins (req: express.Request, res: express.Response) {
69 const user = res.locals.oauth.token.user 93 const user = res.locals.oauth.token.user
70 const serverActor = await getServerActor() 94 const serverActor = await getServerActor()
71 95
72 const resultList = await AbuseModel.listForApi({ 96 const resultList = await AbuseModel.listForAdminApi({
73 start: req.query.start, 97 start: req.query.start,
74 count: req.query.count, 98 count: req.query.count,
75 sort: req.query.sort, 99 sort: req.query.sort,
@@ -87,7 +111,10 @@ async function listAbuses (req: express.Request, res: express.Response) {
87 user 111 user
88 }) 112 })
89 113
90 return res.json(getFormattedObjects(resultList.data, resultList.total)) 114 return res.json({
115 total: resultList.total,
116 data: resultList.data.map(d => d.toFormattedAdminJSON())
117 })
91} 118}
92 119
93async function updateAbuse (req: express.Request, res: express.Response) { 120async function updateAbuse (req: express.Request, res: express.Response) {
@@ -100,6 +127,8 @@ async function updateAbuse (req: express.Request, res: express.Response) {
100 return abuse.save({ transaction: t }) 127 return abuse.save({ transaction: t })
101 }) 128 })
102 129
130 // TODO: Notification
131
103 // Do not send the delete to other instances, we updated OUR copy of this abuse 132 // Do not send the delete to other instances, we updated OUR copy of this abuse
104 133
105 return res.type('json').status(204).end() 134 return res.type('json').status(204).end()
@@ -166,3 +195,41 @@ async function reportAbuse (req: express.Request, res: express.Response) {
166 195
167 return res.json({ abuse: { id } }) 196 return res.json({ abuse: { id } })
168} 197}
198
199async function listAbuseMessages (req: express.Request, res: express.Response) {
200 const abuse = res.locals.abuse
201
202 const resultList = await AbuseMessageModel.listForApi(abuse.id)
203
204 return res.json(getFormattedObjects(resultList.data, resultList.total))
205}
206
207async function addAbuseMessage (req: express.Request, res: express.Response) {
208 const abuse = res.locals.abuse
209 const user = res.locals.oauth.token.user
210
211 const abuseMessage = await AbuseMessageModel.create({
212 message: req.body.message,
213 byModerator: abuse.reporterAccountId !== user.Account.id,
214 accountId: user.Account.id,
215 abuseId: abuse.id
216 })
217
218 // TODO: Notification
219
220 return res.json({
221 abuseMessage: {
222 id: abuseMessage.id
223 }
224 })
225}
226
227async function deleteAbuseMessage (req: express.Request, res: express.Response) {
228 const abuseMessage = res.locals.abuseMessage
229
230 await sequelizeTypescript.transaction(t => {
231 return abuseMessage.destroy({ transaction: t })
232 })
233
234 return res.sendStatus(204)
235}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 5939f6125..d339c2a1c 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -1,10 +1,20 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as RateLimit from 'express-rate-limit' 2import * as RateLimit from 'express-rate-limit'
3import { tokensRouter } from '@server/controllers/api/users/token'
4import { Hooks } from '@server/lib/plugins/hooks'
5import { MUser, MUserAccountDefault } from '@server/types/models'
3import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' 6import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
7import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
8import { UserRegister } from '../../../../shared/models/users/user-register.model'
9import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
4import { logger } from '../../../helpers/logger' 10import { logger } from '../../../helpers/logger'
5import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' 11import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
12import { CONFIG } from '../../../initializers/config'
6import { WEBSERVER } from '../../../initializers/constants' 13import { WEBSERVER } from '../../../initializers/constants'
14import { sequelizeTypescript } from '../../../initializers/database'
7import { Emailer } from '../../../lib/emailer' 15import { Emailer } from '../../../lib/emailer'
16import { Notifier } from '../../../lib/notifier'
17import { deleteUserToken } from '../../../lib/oauth-model'
8import { Redis } from '../../../lib/redis' 18import { Redis } from '../../../lib/redis'
9import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user' 19import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
10import { 20import {
@@ -18,9 +28,9 @@ import {
18 setDefaultPagination, 28 setDefaultPagination,
19 setDefaultSort, 29 setDefaultSort,
20 userAutocompleteValidator, 30 userAutocompleteValidator,
21 usersListValidator,
22 usersAddValidator, 31 usersAddValidator,
23 usersGetValidator, 32 usersGetValidator,
33 usersListValidator,
24 usersRegisterValidator, 34 usersRegisterValidator,
25 usersRemoveValidator, 35 usersRemoveValidator,
26 usersSortValidator, 36 usersSortValidator,
@@ -35,22 +45,13 @@ import {
35 usersVerifyEmailValidator 45 usersVerifyEmailValidator
36} from '../../../middlewares/validators' 46} from '../../../middlewares/validators'
37import { UserModel } from '../../../models/account/user' 47import { UserModel } from '../../../models/account/user'
38import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
39import { meRouter } from './me' 48import { meRouter } from './me'
40import { deleteUserToken } from '../../../lib/oauth-model' 49import { myAbusesRouter } from './my-abuses'
41import { myBlocklistRouter } from './my-blocklist' 50import { myBlocklistRouter } from './my-blocklist'
42import { myVideoPlaylistsRouter } from './my-video-playlists'
43import { myVideosHistoryRouter } from './my-history' 51import { myVideosHistoryRouter } from './my-history'
44import { myNotificationsRouter } from './my-notifications' 52import { myNotificationsRouter } from './my-notifications'
45import { Notifier } from '../../../lib/notifier'
46import { mySubscriptionsRouter } from './my-subscriptions' 53import { mySubscriptionsRouter } from './my-subscriptions'
47import { CONFIG } from '../../../initializers/config' 54import { myVideoPlaylistsRouter } from './my-video-playlists'
48import { sequelizeTypescript } from '../../../initializers/database'
49import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
50import { UserRegister } from '../../../../shared/models/users/user-register.model'
51import { MUser, MUserAccountDefault } from '@server/types/models'
52import { Hooks } from '@server/lib/plugins/hooks'
53import { tokensRouter } from '@server/controllers/api/users/token'
54 55
55const auditLogger = auditLoggerFactory('users') 56const auditLogger = auditLoggerFactory('users')
56 57
@@ -72,6 +73,7 @@ usersRouter.use('/', mySubscriptionsRouter)
72usersRouter.use('/', myBlocklistRouter) 73usersRouter.use('/', myBlocklistRouter)
73usersRouter.use('/', myVideosHistoryRouter) 74usersRouter.use('/', myVideosHistoryRouter)
74usersRouter.use('/', myVideoPlaylistsRouter) 75usersRouter.use('/', myVideoPlaylistsRouter)
76usersRouter.use('/', myAbusesRouter)
75usersRouter.use('/', meRouter) 77usersRouter.use('/', meRouter)
76 78
77usersRouter.get('/autocomplete', 79usersRouter.get('/autocomplete',
diff --git a/server/controllers/api/users/my-abuses.ts b/server/controllers/api/users/my-abuses.ts
new file mode 100644
index 000000000..e43fc483e
--- /dev/null
+++ b/server/controllers/api/users/my-abuses.ts
@@ -0,0 +1,48 @@
1import * as express from 'express'
2import { AbuseModel } from '@server/models/abuse/abuse'
3import {
4 abuseListForUserValidator,
5 abusesSortValidator,
6 asyncMiddleware,
7 authenticate,
8 paginationValidator,
9 setDefaultPagination,
10 setDefaultSort
11} from '../../../middlewares'
12
13const myAbusesRouter = express.Router()
14
15myAbusesRouter.get('/me/abuses',
16 authenticate,
17 paginationValidator,
18 abusesSortValidator,
19 setDefaultSort,
20 setDefaultPagination,
21 abuseListForUserValidator,
22 asyncMiddleware(listMyAbuses)
23)
24
25// ---------------------------------------------------------------------------
26
27export {
28 myAbusesRouter
29}
30
31// ---------------------------------------------------------------------------
32
33async function listMyAbuses (req: express.Request, res: express.Response) {
34 const resultList = await AbuseModel.listForUserApi({
35 start: req.query.start,
36 count: req.query.count,
37 sort: req.query.sort,
38 id: req.query.id,
39 search: req.query.search,
40 state: req.query.state,
41 user: res.locals.oauth.token.User
42 })
43
44 return res.json({
45 total: resultList.total,
46 data: resultList.data.map(d => d.toFormattedAdminJSON())
47 })
48}
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index b92a66360..9c4d00849 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { AbuseModel } from '@server/models/abuse/abuse' 2import { AbuseModel } from '@server/models/abuse/abuse'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared' 4import { AbuseCreate, UserRight, VideoAbuseCreate } from '../../../../shared'
5import { getFormattedObjects } from '../../../helpers/utils'
6import { 5import {
7 abusesSortValidator, 6 abusesSortValidator,
8 asyncMiddleware, 7 asyncMiddleware,
@@ -63,7 +62,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
63 const user = res.locals.oauth.token.user 62 const user = res.locals.oauth.token.user
64 const serverActor = await getServerActor() 63 const serverActor = await getServerActor()
65 64
66 const resultList = await AbuseModel.listForApi({ 65 const resultList = await AbuseModel.listForAdminApi({
67 start: req.query.start, 66 start: req.query.start,
68 count: req.query.count, 67 count: req.query.count,
69 sort: req.query.sort, 68 sort: req.query.sort,
@@ -81,7 +80,10 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
81 user 80 user
82 }) 81 })
83 82
84 return res.json(getFormattedObjects(resultList.data, resultList.total)) 83 return res.json({
84 total: resultList.total,
85 data: resultList.data.map(d => d.toFormattedAdminJSON())
86 })
85} 87}
86 88
87async function updateVideoAbuse (req: express.Request, res: express.Response) { 89async function updateVideoAbuse (req: express.Request, res: express.Response) {
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index 954b0b69d..6aae5e821 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -5,7 +5,7 @@ import { chain } from 'lodash'
5import * as path from 'path' 5import * as path from 'path'
6import * as winston from 'winston' 6import * as winston from 'winston'
7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' 7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants'
8import { Abuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' 8import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared'
9import { CustomConfig } from '../../shared/models/server/custom-config.model' 9import { CustomConfig } from '../../shared/models/server/custom-config.model'
10import { VideoComment } from '../../shared/models/videos/video-comment.model' 10import { VideoComment } from '../../shared/models/videos/video-comment.model'
11import { CONFIG } from '../initializers/config' 11import { CONFIG } from '../initializers/config'
@@ -219,7 +219,7 @@ const abuseKeysToKeep = [
219 'createdAt' 219 'createdAt'
220] 220]
221class AbuseAuditView extends EntityAuditView { 221class AbuseAuditView extends EntityAuditView {
222 constructor (private readonly abuse: Abuse) { 222 constructor (private readonly abuse: AdminAbuse) {
223 super(abuseKeysToKeep, 'abuse', abuse) 223 super(abuseKeysToKeep, 'abuse', abuse)
224 } 224 }
225} 225}
diff --git a/server/helpers/custom-validators/abuses.ts b/server/helpers/custom-validators/abuses.ts
index 0ca06a252..0ddde4b06 100644
--- a/server/helpers/custom-validators/abuses.ts
+++ b/server/helpers/custom-validators/abuses.ts
@@ -4,6 +4,7 @@ import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
4import { exists, isArray } from './misc' 4import { exists, isArray } from './misc'
5 5
6const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES 6const ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSES
7const ABUSE_MESSAGES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.ABUSE_MESSAGES
7 8
8function isAbuseReasonValid (value: string) { 9function isAbuseReasonValid (value: string) {
9 return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON) 10 return exists(value) && validator.isLength(value, ABUSES_CONSTRAINTS_FIELDS.REASON)
@@ -46,13 +47,18 @@ function isAbuseVideoIsValid (value: AbuseVideoIs) {
46 ) 47 )
47} 48}
48 49
50function isAbuseMessageValid (value: string) {
51 return exists(value) && validator.isLength(value, ABUSE_MESSAGES_CONSTRAINTS_FIELDS.MESSAGE)
52}
53
49// --------------------------------------------------------------------------- 54// ---------------------------------------------------------------------------
50 55
51export { 56export {
52 isAbuseReasonValid, 57 isAbuseReasonValid,
53 isAbuseFilterValid, 58 isAbuseFilterValid,
54 isAbusePredefinedReasonValid, 59 isAbusePredefinedReasonValid,
55 areAbusePredefinedReasonsValid as isAbusePredefinedReasonsValid, 60 isAbuseMessageValid,
61 areAbusePredefinedReasonsValid,
56 isAbuseTimestampValid, 62 isAbuseTimestampValid,
57 isAbuseTimestampCoherent, 63 isAbuseTimestampCoherent,
58 isAbuseModerationCommentValid, 64 isAbuseModerationCommentValid,
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index fd5bf5868..a40a22395 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -206,6 +206,9 @@ const CONSTRAINTS_FIELDS = {
206 REASON: { min: 2, max: 3000 }, // Length 206 REASON: { min: 2, max: 3000 }, // Length
207 MODERATION_COMMENT: { min: 2, max: 3000 } // Length 207 MODERATION_COMMENT: { min: 2, max: 3000 } // Length
208 }, 208 },
209 ABUSE_MESSAGES: {
210 MESSAGE: { min: 2, max: 3000 } // Length
211 },
209 VIDEO_BLACKLIST: { 212 VIDEO_BLACKLIST: {
210 REASON: { min: 2, max: 300 } // Length 213 REASON: { min: 2, max: 300 } // Length
211 }, 214 },
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 0775f1fad..8ce32f6fa 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -1,6 +1,7 @@
1import { QueryTypes, Transaction } from 'sequelize' 1import { QueryTypes, Transaction } from 'sequelize'
2import { Sequelize as SequelizeTypescript } from 'sequelize-typescript' 2import { Sequelize as SequelizeTypescript } from 'sequelize-typescript'
3import { AbuseModel } from '@server/models/abuse/abuse' 3import { AbuseModel } from '@server/models/abuse/abuse'
4import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
4import { VideoAbuseModel } from '@server/models/abuse/video-abuse' 5import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
5import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' 6import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
6import { isTestInstance } from '../helpers/core-utils' 7import { isTestInstance } from '../helpers/core-utils'
@@ -87,6 +88,7 @@ async function initDatabaseModels (silent: boolean) {
87 TagModel, 88 TagModel,
88 AccountVideoRateModel, 89 AccountVideoRateModel,
89 UserModel, 90 UserModel,
91 AbuseMessageModel,
90 AbuseModel, 92 AbuseModel,
91 VideoCommentAbuseModel, 93 VideoCommentAbuseModel,
92 VideoAbuseModel, 94 VideoAbuseModel,
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 48ba7421e..c6ad03328 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -5,7 +5,7 @@ import { join } from 'path'
5import { VideoChannelModel } from '@server/models/video/video-channel' 5import { VideoChannelModel } from '@server/models/video/video-channel'
6import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' 6import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
7import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import' 7import { MVideoImport, MVideoImportVideo } from '@server/types/models/video/video-import'
8import { Abuse, EmailPayload } from '@shared/models' 8import { UserAbuse, EmailPayload } from '@shared/models'
9import { SendEmailOptions } from '../../shared/models/server/emailer.model' 9import { SendEmailOptions } from '../../shared/models/server/emailer.model'
10import { isTestInstance, root } from '../helpers/core-utils' 10import { isTestInstance, root } from '../helpers/core-utils'
11import { bunyanLogger, logger } from '../helpers/logger' 11import { bunyanLogger, logger } from '../helpers/logger'
@@ -283,7 +283,7 @@ class Emailer {
283 } 283 }
284 284
285 addAbuseModeratorsNotification (to: string[], parameters: { 285 addAbuseModeratorsNotification (to: string[], parameters: {
286 abuse: Abuse 286 abuse: UserAbuse
287 abuseInstance: MAbuseFull 287 abuseInstance: MAbuseFull
288 reporter: string 288 reporter: string
289 }) { 289 }) {
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index 4fc9cd747..b140d5aa9 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -213,7 +213,7 @@ async function createAbuse (options: {
213 await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction) 213 await sendAbuse(reporterAccount.Actor, abuseInstance, abuseInstance.FlaggedAccount, transaction)
214 } 214 }
215 215
216 const abuseJSON = abuseInstance.toFormattedJSON() 216 const abuseJSON = abuseInstance.toFormattedAdminJSON()
217 auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) 217 auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON))
218 218
219 Notifier.Instance.notifyOnNewAbuse({ 219 Notifier.Instance.notifyOnNewAbuse({
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index c567e1c20..8f165d2fd 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -10,7 +10,7 @@ import {
10} from '@server/types/models/user' 10} from '@server/types/models/user'
11import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist' 11import { MVideoBlacklistLightVideo, MVideoBlacklistVideo } from '@server/types/models/video/video-blacklist'
12import { MVideoImportVideo } from '@server/types/models/video/video-import' 12import { MVideoImportVideo } from '@server/types/models/video/video-import'
13import { Abuse } from '@shared/models' 13import { UserAbuse } from '@shared/models'
14import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' 14import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
15import { VideoPrivacy, VideoState } from '../../shared/models/videos' 15import { VideoPrivacy, VideoState } from '../../shared/models/videos'
16import { logger } from '../helpers/logger' 16import { logger } from '../helpers/logger'
@@ -73,7 +73,7 @@ class Notifier {
73 .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 }))
74 } 74 }
75 75
76 notifyOnNewAbuse (parameters: { abuse: Abuse, abuseInstance: MAbuseFull, reporter: string }): void { 76 notifyOnNewAbuse (parameters: { abuse: UserAbuse, abuseInstance: MAbuseFull, reporter: string }): void {
77 this.notifyModeratorsOfNewAbuse(parameters) 77 this.notifyModeratorsOfNewAbuse(parameters)
78 .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err })) 78 .catch(err => logger.error('Cannot notify of new abuse %d.', parameters.abuseInstance.id, { err }))
79 } 79 }
@@ -350,7 +350,7 @@ class Notifier {
350 } 350 }
351 351
352 private async notifyModeratorsOfNewAbuse (parameters: { 352 private async notifyModeratorsOfNewAbuse (parameters: {
353 abuse: Abuse 353 abuse: UserAbuse
354 abuseInstance: MAbuseFull 354 abuseInstance: MAbuseFull
355 reporter: string 355 reporter: string
356 }) { 356 }) {
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts
index 4da7b9802..4d836485c 100644
--- a/server/middlewares/user-right.ts
+++ b/server/middlewares/user-right.ts
@@ -9,11 +9,7 @@ function ensureUserHasRight (userRight: UserRight) {
9 const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.` 9 const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.`
10 logger.info(message) 10 logger.info(message)
11 11
12 return res.status(403) 12 return res.status(403).json({ error: message })
13 .json({
14 error: message
15 })
16 .end()
17 } 13 }
18 14
19 return next() 15 return next()
diff --git a/server/middlewares/validators/abuse.ts b/server/middlewares/validators/abuse.ts
index 966d1f7fb..cb0bc658a 100644
--- a/server/middlewares/validators/abuse.ts
+++ b/server/middlewares/validators/abuse.ts
@@ -2,8 +2,9 @@ import * as express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { 3import {
4 isAbuseFilterValid, 4 isAbuseFilterValid,
5 isAbuseMessageValid,
5 isAbuseModerationCommentValid, 6 isAbuseModerationCommentValid,
6 isAbusePredefinedReasonsValid, 7 areAbusePredefinedReasonsValid,
7 isAbusePredefinedReasonValid, 8 isAbusePredefinedReasonValid,
8 isAbuseReasonValid, 9 isAbuseReasonValid,
9 isAbuseStateValid, 10 isAbuseStateValid,
@@ -15,7 +16,8 @@ import { exists, isIdOrUUIDValid, isIdValid, toIntOrNull } from '@server/helpers
15import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments' 16import { doesCommentIdExist } from '@server/helpers/custom-validators/video-comments'
16import { logger } from '@server/helpers/logger' 17import { logger } from '@server/helpers/logger'
17import { doesAbuseExist, doesAccountIdExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares' 18import { doesAbuseExist, doesAccountIdExist, doesVideoAbuseExist, doesVideoExist } from '@server/helpers/middlewares'
18import { AbuseCreate } from '@shared/models' 19import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
20import { AbuseCreate, UserRight } from '@shared/models'
19import { areValidationErrors } from './utils' 21import { areValidationErrors } from './utils'
20 22
21const abuseReportValidator = [ 23const abuseReportValidator = [
@@ -53,7 +55,7 @@ const abuseReportValidator = [
53 55
54 body('predefinedReasons') 56 body('predefinedReasons')
55 .optional() 57 .optional()
56 .custom(isAbusePredefinedReasonsValid) 58 .custom(areAbusePredefinedReasonsValid)
57 .withMessage('Should have a valid list of predefined reasons'), 59 .withMessage('Should have a valid list of predefined reasons'),
58 60
59 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 61 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
@@ -111,7 +113,7 @@ const abuseUpdateValidator = [
111 } 113 }
112] 114]
113 115
114const abuseListValidator = [ 116const abuseListForAdminsValidator = [
115 query('id') 117 query('id')
116 .optional() 118 .optional()
117 .custom(isIdValid).withMessage('Should have a valid id'), 119 .custom(isIdValid).withMessage('Should have a valid id'),
@@ -146,7 +148,7 @@ const abuseListValidator = [
146 .custom(exists).withMessage('Should have a valid video channel search'), 148 .custom(exists).withMessage('Should have a valid video channel search'),
147 149
148 (req: express.Request, res: express.Response, next: express.NextFunction) => { 150 (req: express.Request, res: express.Response, next: express.NextFunction) => {
149 logger.debug('Checking abuseListValidator parameters', { parameters: req.body }) 151 logger.debug('Checking abuseListForAdminsValidator parameters', { parameters: req.body })
150 152
151 if (areValidationErrors(req, res)) return 153 if (areValidationErrors(req, res)) return
152 154
@@ -154,6 +156,91 @@ const abuseListValidator = [
154 } 156 }
155] 157]
156 158
159const abuseListForUserValidator = [
160 query('id')
161 .optional()
162 .custom(isIdValid).withMessage('Should have a valid id'),
163
164 query('search')
165 .optional()
166 .custom(exists).withMessage('Should have a valid search'),
167
168 query('state')
169 .optional()
170 .custom(isAbuseStateValid).withMessage('Should have a valid abuse state'),
171
172 (req: express.Request, res: express.Response, next: express.NextFunction) => {
173 logger.debug('Checking abuseListForUserValidator parameters', { parameters: req.body })
174
175 if (areValidationErrors(req, res)) return
176
177 return next()
178 }
179]
180
181const getAbuseValidator = [
182 param('id').custom(isIdValid).not().isEmpty().withMessage('Should have a valid id'),
183
184 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
185 logger.debug('Checking getAbuseValidator parameters', { parameters: req.body })
186
187 if (areValidationErrors(req, res)) return
188 if (!await doesAbuseExist(req.params.id, res)) return
189
190 const user = res.locals.oauth.token.user
191 const abuse = res.locals.abuse
192
193 if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuse.reporterAccountId !== user.Account.id) {
194 const message = `User ${user.username} does not have right to get abuse ${abuse.id}`
195 logger.warn(message)
196
197 return res.status(403).json({ error: message })
198 }
199
200 return next()
201 }
202]
203
204const addAbuseMessageValidator = [
205 body('message').custom(isAbuseMessageValid).not().isEmpty().withMessage('Should have a valid abuse message'),
206
207 (req: express.Request, res: express.Response, next: express.NextFunction) => {
208 logger.debug('Checking addAbuseMessageValidator parameters', { parameters: req.body })
209
210 if (areValidationErrors(req, res)) return
211
212 return next()
213 }
214]
215
216const deleteAbuseMessageValidator = [
217 param('messageId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid message id'),
218
219 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
220 logger.debug('Checking deleteAbuseMessageValidator parameters', { parameters: req.body })
221
222 if (areValidationErrors(req, res)) return
223
224 const user = res.locals.oauth.token.user
225 const abuse = res.locals.abuse
226
227 const messageId = parseInt(req.params.messageId + '', 10)
228 const abuseMessage = await AbuseMessageModel.loadByIdAndAbuseId(messageId, abuse.id)
229
230 if (!abuseMessage) {
231 return res.status(404).json({ error: 'Abuse message not found' })
232 }
233
234 if (user.hasRight(UserRight.MANAGE_ABUSES) !== true && abuseMessage.accountId !== user.Account.id) {
235 return res.status(403).json({ error: 'Cannot delete this abuse message' })
236 }
237
238 res.locals.abuseMessage = abuseMessage
239
240 return next()
241 }
242]
243
157// FIXME: deprecated in 2.3. Remove these validators 244// FIXME: deprecated in 2.3. Remove these validators
158 245
159const videoAbuseReportValidator = [ 246const videoAbuseReportValidator = [
@@ -167,7 +254,7 @@ const videoAbuseReportValidator = [
167 .withMessage('Should have a valid reason'), 254 .withMessage('Should have a valid reason'),
168 body('predefinedReasons') 255 body('predefinedReasons')
169 .optional() 256 .optional()
170 .custom(isAbusePredefinedReasonsValid) 257 .custom(areAbusePredefinedReasonsValid)
171 .withMessage('Should have a valid list of predefined reasons'), 258 .withMessage('Should have a valid list of predefined reasons'),
172 body('startAt') 259 body('startAt')
173 .optional() 260 .optional()
@@ -266,10 +353,14 @@ const videoAbuseListValidator = [
266// --------------------------------------------------------------------------- 353// ---------------------------------------------------------------------------
267 354
268export { 355export {
269 abuseListValidator, 356 abuseListForAdminsValidator,
270 abuseReportValidator, 357 abuseReportValidator,
271 abuseGetValidator, 358 abuseGetValidator,
359 addAbuseMessageValidator,
272 abuseUpdateValidator, 360 abuseUpdateValidator,
361 deleteAbuseMessageValidator,
362 abuseListForUserValidator,
363 getAbuseValidator,
273 videoAbuseReportValidator, 364 videoAbuseReportValidator,
274 videoAbuseGetValidator, 365 videoAbuseGetValidator,
275 videoAbuseUpdateValidator, 366 videoAbuseUpdateValidator,
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts
new file mode 100644
index 000000000..f7721c87d
--- /dev/null
+++ b/server/models/abuse/abuse-message.ts
@@ -0,0 +1,103 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses'
3import { AbuseMessage } from '@shared/models'
4import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
5import { throwIfNotValid, getSort } from '../utils'
6import { AbuseModel } from './abuse'
7import { MAbuseMessageFormattable, MAbuseMessage } from '@server/types/models'
8
9@Table({
10 tableName: 'abuseMessage',
11 indexes: [
12 {
13 fields: [ 'abuseId' ]
14 },
15 {
16 fields: [ 'accountId' ]
17 }
18 ]
19})
20export class AbuseMessageModel extends Model<AbuseMessageModel> {
21
22 @AllowNull(false)
23 @Is('AbuseMessage', value => throwIfNotValid(value, isAbuseMessageValid, 'message'))
24 @Column(DataType.TEXT)
25 message: string
26
27 @AllowNull(false)
28 @Column
29 byModerator: boolean
30
31 @CreatedAt
32 createdAt: Date
33
34 @UpdatedAt
35 updatedAt: Date
36
37 @ForeignKey(() => AccountModel)
38 @Column
39 accountId: number
40
41 @BelongsTo(() => AccountModel, {
42 foreignKey: {
43 name: 'accountId',
44 allowNull: true
45 },
46 onDelete: 'set null'
47 })
48 Account: AccountModel
49
50 @ForeignKey(() => AbuseModel)
51 @Column
52 abuseId: number
53
54 @BelongsTo(() => AbuseModel, {
55 foreignKey: {
56 name: 'abuseId',
57 allowNull: false
58 },
59 onDelete: 'cascade'
60 })
61 Abuse: AbuseModel
62
63 static listForApi (abuseId: number) {
64 const options = {
65 where: { abuseId },
66
67 order: getSort('createdAt'),
68
69 include: [
70 {
71 model: AccountModel.scope(AccountScopeNames.SUMMARY),
72 required: false
73 }
74 ]
75 }
76
77 return AbuseMessageModel.findAndCountAll(options)
78 .then(({ rows, count }) => ({ data: rows, total: count }))
79 }
80
81 static loadByIdAndAbuseId (messageId: number, abuseId: number): Promise<MAbuseMessage> {
82 return AbuseMessageModel.findOne({
83 where: {
84 id: messageId,
85 abuseId
86 }
87 })
88 }
89
90 toFormattedJSON (this: MAbuseMessageFormattable): AbuseMessage {
91 const account = this.Account
92 ? this.Account.toFormattedSummaryJSON()
93 : null
94
95 return {
96 id: this.id,
97 byModerator: this.byModerator,
98 message: this.message,
99
100 account
101 }
102 }
103}
diff --git a/server/models/abuse/abuse-query-builder.ts b/server/models/abuse/abuse-query-builder.ts
index 5fddcf3c4..9d7cb75aa 100644
--- a/server/models/abuse/abuse-query-builder.ts
+++ b/server/models/abuse/abuse-query-builder.ts
@@ -26,8 +26,10 @@ export type BuildAbusesQueryOptions = {
26 state?: AbuseState 26 state?: AbuseState
27 27
28 // accountIds 28 // accountIds
29 serverAccountId: number 29 serverAccountId?: number
30 userAccountId: number 30 userAccountId?: number
31
32 reporterAccountId?: number
31} 33}
32 34
33function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') { 35function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' | 'id') {
@@ -45,7 +47,14 @@ function buildAbuseListQuery (options: BuildAbusesQueryOptions, type: 'count' |
45 'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"' 47 'LEFT JOIN "videoComment" ON "commentAbuse"."videoCommentId" = "videoComment"."id"'
46 ] 48 ]
47 49
48 whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')') 50 if (options.serverAccountId || options.userAccountId) {
51 whereAnd.push('"abuse"."reporterAccountId" NOT IN (' + buildBlockedAccountSQL([ options.serverAccountId, options.userAccountId ]) + ')')
52 }
53
54 if (options.reporterAccountId) {
55 whereAnd.push('"abuse"."reporterAccountId" = :reporterAccountId')
56 replacements.reporterAccountId = options.reporterAccountId
57 }
49 58
50 if (options.search) { 59 if (options.search) {
51 const searchWhereOr = [ 60 const searchWhereOr = [
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index bd96cf79c..7002502d5 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -18,7 +18,6 @@ import {
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' 19import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
20import { 20import {
21 Abuse,
22 AbuseFilter, 21 AbuseFilter,
23 AbuseObject, 22 AbuseObject,
24 AbusePredefinedReasons, 23 AbusePredefinedReasons,
@@ -26,11 +25,14 @@ import {
26 AbusePredefinedReasonsString, 25 AbusePredefinedReasonsString,
27 AbuseState, 26 AbuseState,
28 AbuseVideoIs, 27 AbuseVideoIs,
29 VideoAbuse, 28 AdminVideoAbuse,
30 VideoCommentAbuse 29 AdminAbuse,
30 AdminVideoCommentAbuse,
31 UserAbuse,
32 UserVideoAbuse
31} from '@shared/models' 33} from '@shared/models'
32import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' 34import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
33import { MAbuse, MAbuseAP, MAbuseFormattable, MUserAccountId } from '../../types/models' 35import { MAbuse, MAbuseAdminFormattable, MAbuseAP, MUserAccountId, MAbuseUserFormattable } from '../../types/models'
34import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
35import { getSort, throwIfNotValid } from '../utils' 37import { getSort, throwIfNotValid } from '../utils'
36import { ThumbnailModel } from '../video/thumbnail' 38import { ThumbnailModel } from '../video/thumbnail'
@@ -52,6 +54,16 @@ export enum ScopeNames {
52 attributes: { 54 attributes: {
53 include: [ 55 include: [
54 [ 56 [
57 literal(
58 '(' +
59 'SELECT count(*) ' +
60 'FROM "abuseMessage" ' +
61 'WHERE "abuseId" = "AbuseModel"."id"' +
62 ')'
63 ),
64 'countMessages'
65 ],
66 [
55 // we don't care about this count for deleted videos, so there are not included 67 // we don't care about this count for deleted videos, so there are not included
56 literal( 68 literal(
57 '(' + 69 '(' +
@@ -285,7 +297,7 @@ export class AbuseModel extends Model<AbuseModel> {
285 return AbuseModel.findOne(query) 297 return AbuseModel.findOne(query)
286 } 298 }
287 299
288 static async listForApi (parameters: { 300 static async listForAdminApi (parameters: {
289 start: number 301 start: number
290 count: number 302 count: number
291 sort: string 303 sort: string
@@ -353,71 +365,98 @@ export class AbuseModel extends Model<AbuseModel> {
353 return { total, data } 365 return { total, data }
354 } 366 }
355 367
356 toFormattedJSON (this: MAbuseFormattable): Abuse { 368 static async listForUserApi (parameters: {
357 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons) 369 user: MUserAccountId
358 370
359 const countReportsForVideo = this.get('countReportsForVideo') as number 371 start: number
360 const nthReportForVideo = this.get('nthReportForVideo') as number 372 count: number
373 sort: string
361 374
362 const countReportsForReporter = this.get('countReportsForReporter') as number 375 id?: number
363 const countReportsForReportee = this.get('countReportsForReportee') as number 376 search?: string
377 state?: AbuseState
378 }) {
379 const {
380 start,
381 count,
382 sort,
383 search,
384 user,
385 state,
386 id
387 } = parameters
364 388
365 let video: VideoAbuse = null 389 const queryOptions: BuildAbusesQueryOptions = {
366 let comment: VideoCommentAbuse = null 390 start,
391 count,
392 sort,
393 id,
394 search,
395 state,
396 reporterAccountId: user.Account.id
397 }
398
399 const [ total, data ] = await Promise.all([
400 AbuseModel.internalCountForApi(queryOptions),
401 AbuseModel.internalListForApi(queryOptions)
402 ])
403
404 return { total, data }
405 }
367 406
368 if (this.VideoAbuse) { 407 buildBaseVideoCommentAbuse (this: MAbuseUserFormattable) {
369 const abuseModel = this.VideoAbuse 408 if (!this.VideoCommentAbuse) return null
370 const entity = abuseModel.Video || abuseModel.deletedVideo
371 409
372 video = { 410 const abuseModel = this.VideoCommentAbuse
373 id: entity.id, 411 const entity = abuseModel.VideoComment
374 uuid: entity.uuid,
375 name: entity.name,
376 nsfw: entity.nsfw,
377 412
378 startAt: abuseModel.startAt, 413 return {
379 endAt: abuseModel.endAt, 414 id: entity.id,
415 threadId: entity.getThreadId(),
380 416
381 deleted: !abuseModel.Video, 417 text: entity.text ?? '',
382 blacklisted: abuseModel.Video?.isBlacklisted() || false,
383 thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
384 418
385 channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel, 419 deleted: entity.isDeleted(),
386 420
387 countReports: countReportsForVideo, 421 video: {
388 nthReport: nthReportForVideo 422 id: entity.Video.id,
423 name: entity.Video.name,
424 uuid: entity.Video.uuid
389 } 425 }
390 } 426 }
427 }
391 428
392 if (this.VideoCommentAbuse) { 429 buildBaseVideoAbuse (this: MAbuseUserFormattable): UserVideoAbuse {
393 const abuseModel = this.VideoCommentAbuse 430 if (!this.VideoAbuse) return null
394 const entity = abuseModel.VideoComment
395 431
396 comment = { 432 const abuseModel = this.VideoAbuse
397 id: entity.id, 433 const entity = abuseModel.Video || abuseModel.deletedVideo
398 threadId: entity.getThreadId(),
399 434
400 text: entity.text ?? '', 435 return {
436 id: entity.id,
437 uuid: entity.uuid,
438 name: entity.name,
439 nsfw: entity.nsfw,
401 440
402 deleted: entity.isDeleted(), 441 startAt: abuseModel.startAt,
442 endAt: abuseModel.endAt,
403 443
404 video: { 444 deleted: !abuseModel.Video,
405 id: entity.Video.id, 445 blacklisted: abuseModel.Video?.isBlacklisted() || false,
406 name: entity.Video.name, 446 thumbnailPath: abuseModel.Video?.getMiniatureStaticPath(),
407 uuid: entity.Video.uuid 447
408 } 448 channel: abuseModel.Video?.VideoChannel.toFormattedJSON() || abuseModel.deletedVideo?.channel,
409 }
410 } 449 }
450 }
451
452 buildBaseAbuse (this: MAbuseUserFormattable, countMessages: number): UserAbuse {
453 const predefinedReasons = AbuseModel.getPredefinedReasonsStrings(this.predefinedReasons)
411 454
412 return { 455 return {
413 id: this.id, 456 id: this.id,
414 reason: this.reason, 457 reason: this.reason,
415 predefinedReasons, 458 predefinedReasons,
416 459
417 reporterAccount: this.ReporterAccount
418 ? this.ReporterAccount.toFormattedJSON()
419 : null,
420
421 flaggedAccount: this.FlaggedAccount 460 flaggedAccount: this.FlaggedAccount
422 ? this.FlaggedAccount.toFormattedJSON() 461 ? this.FlaggedAccount.toFormattedJSON()
423 : null, 462 : null,
@@ -429,11 +468,41 @@ export class AbuseModel extends Model<AbuseModel> {
429 468
430 moderationComment: this.moderationComment, 469 moderationComment: this.moderationComment,
431 470
471 countMessages,
472
473 createdAt: this.createdAt,
474 updatedAt: this.updatedAt
475 }
476 }
477
478 toFormattedAdminJSON (this: MAbuseAdminFormattable): AdminAbuse {
479 const countReportsForVideo = this.get('countReportsForVideo') as number
480 const nthReportForVideo = this.get('nthReportForVideo') as number
481
482 const countReportsForReporter = this.get('countReportsForReporter') as number
483 const countReportsForReportee = this.get('countReportsForReportee') as number
484
485 const countMessages = this.get('countMessages') as number
486
487 const baseVideo = this.buildBaseVideoAbuse()
488 const video: AdminVideoAbuse = baseVideo
489 ? Object.assign(baseVideo, {
490 countReports: countReportsForVideo,
491 nthReport: nthReportForVideo
492 })
493 : null
494
495 const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
496
497 const abuse = this.buildBaseAbuse(countMessages || 0)
498
499 return Object.assign(abuse, {
432 video, 500 video,
433 comment, 501 comment,
434 502
435 createdAt: this.createdAt, 503 reporterAccount: this.ReporterAccount
436 updatedAt: this.updatedAt, 504 ? this.ReporterAccount.toFormattedJSON()
505 : null,
437 506
438 countReportsForReporter: (countReportsForReporter || 0), 507 countReportsForReporter: (countReportsForReporter || 0),
439 countReportsForReportee: (countReportsForReportee || 0), 508 countReportsForReportee: (countReportsForReportee || 0),
@@ -443,7 +512,20 @@ export class AbuseModel extends Model<AbuseModel> {
443 endAt: null, 512 endAt: null,
444 count: countReportsForVideo || 0, 513 count: countReportsForVideo || 0,
445 nth: nthReportForVideo || 0 514 nth: nthReportForVideo || 0
446 } 515 })
516 }
517
518 toFormattedUserJSON (this: MAbuseUserFormattable): UserAbuse {
519 const countMessages = this.get('countMessages') as number
520
521 const video = this.buildBaseVideoAbuse()
522 const comment: AdminVideoCommentAbuse = this.buildBaseVideoCommentAbuse()
523 const abuse = this.buildBaseAbuse(countMessages || 0)
524
525 return Object.assign(abuse, {
526 video,
527 comment
528 })
447 } 529 }
448 530
449 toActivityPubObject (this: MAbuseAP): AbuseObject { 531 toActivityPubObject (this: MAbuseAP): AbuseObject {
diff --git a/server/tests/api/check-params/abuses.ts b/server/tests/api/check-params/abuses.ts
index 8964c0ab2..5e1d66c25 100644
--- a/server/tests/api/check-params/abuses.ts
+++ b/server/tests/api/check-params/abuses.ts
@@ -13,7 +13,11 @@ import {
13 setAccessTokensToServers, 13 setAccessTokensToServers,
14 updateAbuse, 14 updateAbuse,
15 uploadVideo, 15 uploadVideo,
16 userLogin 16 userLogin,
17 generateUserAccessToken,
18 addAbuseMessage,
19 listAbuseMessages,
20 deleteAbuseMessage
17} from '../../../../shared/extra-utils' 21} from '../../../../shared/extra-utils'
18import { 22import {
19 checkBadCountPagination, 23 checkBadCountPagination,
@@ -26,7 +30,9 @@ describe('Test abuses API validators', function () {
26 30
27 let server: ServerInfo 31 let server: ServerInfo
28 let userAccessToken = '' 32 let userAccessToken = ''
33 let userAccessToken2 = ''
29 let abuseId: number 34 let abuseId: number
35 let messageId: number
30 36
31 // --------------------------------------------------------------- 37 // ---------------------------------------------------------------
32 38
@@ -42,11 +48,15 @@ describe('Test abuses API validators', function () {
42 await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) 48 await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
43 userAccessToken = await userLogin(server, { username, password }) 49 userAccessToken = await userLogin(server, { username, password })
44 50
51 {
52 userAccessToken2 = await generateUserAccessToken(server, 'user_2')
53 }
54
45 const res = await uploadVideo(server.url, server.accessToken, {}) 55 const res = await uploadVideo(server.url, server.accessToken, {})
46 server.video = res.body.video 56 server.video = res.body.video
47 }) 57 })
48 58
49 describe('When listing abuses', function () { 59 describe('When listing abuses for admins', function () {
50 const path = basePath 60 const path = basePath
51 61
52 it('Should fail with a bad start pagination', async function () { 62 it('Should fail with a bad start pagination', async function () {
@@ -113,47 +123,89 @@ describe('Test abuses API validators', function () {
113 }) 123 })
114 }) 124 })
115 125
126 describe('When listing abuses for users', function () {
127 const path = '/api/v1/users/me/abuses'
128
129 it('Should fail with a bad start pagination', async function () {
130 await checkBadStartPagination(server.url, path, userAccessToken)
131 })
132
133 it('Should fail with a bad count pagination', async function () {
134 await checkBadCountPagination(server.url, path, userAccessToken)
135 })
136
137 it('Should fail with an incorrect sort', async function () {
138 await checkBadSortPagination(server.url, path, userAccessToken)
139 })
140
141 it('Should fail with a non authenticated user', async function () {
142 await makeGetRequest({
143 url: server.url,
144 path,
145 statusCodeExpected: 401
146 })
147 })
148
149 it('Should fail with a bad id filter', async function () {
150 await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { id: 'toto' } })
151 })
152
153 it('Should fail with a bad state filter', async function () {
154 await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { state: 'toto' } })
155 await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { state: 0 } })
156 })
157
158 it('Should succeed with the correct params', async function () {
159 const query = {
160 id: 13,
161 state: 2
162 }
163
164 await makeGetRequest({ url: server.url, path, token: userAccessToken, query, statusCodeExpected: 200 })
165 })
166 })
167
116 describe('When reporting an abuse', function () { 168 describe('When reporting an abuse', function () {
117 const path = basePath 169 const path = basePath
118 170
119 it('Should fail with nothing', async function () { 171 it('Should fail with nothing', async function () {
120 const fields = {} 172 const fields = {}
121 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 173 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
122 }) 174 })
123 175
124 it('Should fail with a wrong video', async function () { 176 it('Should fail with a wrong video', async function () {
125 const fields = { video: { id: 'blabla' }, reason: 'my super reason' } 177 const fields = { video: { id: 'blabla' }, reason: 'my super reason' }
126 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) 178 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
127 }) 179 })
128 180
129 it('Should fail with an unknown video', async function () { 181 it('Should fail with an unknown video', async function () {
130 const fields = { video: { id: 42 }, reason: 'my super reason' } 182 const fields = { video: { id: 42 }, reason: 'my super reason' }
131 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) 183 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
132 }) 184 })
133 185
134 it('Should fail with a wrong comment', async function () { 186 it('Should fail with a wrong comment', async function () {
135 const fields = { comment: { id: 'blabla' }, reason: 'my super reason' } 187 const fields = { comment: { id: 'blabla' }, reason: 'my super reason' }
136 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) 188 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
137 }) 189 })
138 190
139 it('Should fail with an unknown comment', async function () { 191 it('Should fail with an unknown comment', async function () {
140 const fields = { comment: { id: 42 }, reason: 'my super reason' } 192 const fields = { comment: { id: 42 }, reason: 'my super reason' }
141 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) 193 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
142 }) 194 })
143 195
144 it('Should fail with a wrong account', async function () { 196 it('Should fail with a wrong account', async function () {
145 const fields = { account: { id: 'blabla' }, reason: 'my super reason' } 197 const fields = { account: { id: 'blabla' }, reason: 'my super reason' }
146 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields }) 198 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields })
147 }) 199 })
148 200
149 it('Should fail with an unknown account', async function () { 201 it('Should fail with an unknown account', async function () {
150 const fields = { account: { id: 42 }, reason: 'my super reason' } 202 const fields = { account: { id: 42 }, reason: 'my super reason' }
151 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 404 }) 203 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 404 })
152 }) 204 })
153 205
154 it('Should fail with not account, comment or video', async function () { 206 it('Should fail with not account, comment or video', async function () {
155 const fields = { reason: 'my super reason' } 207 const fields = { reason: 'my super reason' }
156 await makePostBodyRequest({ url: server.url, path: path, token: server.accessToken, fields, statusCodeExpected: 400 }) 208 await makePostBodyRequest({ url: server.url, path: path, token: userAccessToken, fields, statusCodeExpected: 400 })
157 }) 209 })
158 210
159 it('Should fail with a non authenticated user', async function () { 211 it('Should fail with a non authenticated user', async function () {
@@ -165,38 +217,38 @@ describe('Test abuses API validators', function () {
165 it('Should fail with a reason too short', async function () { 217 it('Should fail with a reason too short', async function () {
166 const fields = { video: { id: server.video.id }, reason: 'h' } 218 const fields = { video: { id: server.video.id }, reason: 'h' }
167 219
168 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 220 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
169 }) 221 })
170 222
171 it('Should fail with a too big reason', async function () { 223 it('Should fail with a too big reason', async function () {
172 const fields = { video: { id: server.video.id }, reason: 'super'.repeat(605) } 224 const fields = { video: { id: server.video.id }, reason: 'super'.repeat(605) }
173 225
174 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 226 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
175 }) 227 })
176 228
177 it('Should succeed with the correct parameters (basic)', async function () { 229 it('Should succeed with the correct parameters (basic)', async function () {
178 const fields: AbuseCreate = { video: { id: server.video.id }, reason: 'my super reason' } 230 const fields: AbuseCreate = { video: { id: server.video.id }, reason: 'my super reason' }
179 231
180 const res = await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) 232 const res = await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 200 })
181 abuseId = res.body.abuse.id 233 abuseId = res.body.abuse.id
182 }) 234 })
183 235
184 it('Should fail with a wrong predefined reason', async function () { 236 it('Should fail with a wrong predefined reason', async function () {
185 const fields = { video: { id: server.video.id }, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] } 237 const fields = { video: { id: server.video.id }, reason: 'my super reason', predefinedReasons: [ 'wrongPredefinedReason' ] }
186 238
187 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 239 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
188 }) 240 })
189 241
190 it('Should fail with negative timestamps', async function () { 242 it('Should fail with negative timestamps', async function () {
191 const fields = { video: { id: server.video.id, startAt: -1 }, reason: 'my super reason' } 243 const fields = { video: { id: server.video.id, startAt: -1 }, reason: 'my super reason' }
192 244
193 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 245 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
194 }) 246 })
195 247
196 it('Should fail mith misordered startAt/endAt', async function () { 248 it('Should fail mith misordered startAt/endAt', async function () {
197 const fields = { video: { id: server.video.id, startAt: 5, endAt: 1 }, reason: 'my super reason' } 249 const fields = { video: { id: server.video.id, startAt: 5, endAt: 1 }, reason: 'my super reason' }
198 250
199 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 251 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields })
200 }) 252 })
201 253
202 it('Should succeed with the corret parameters (advanced)', async function () { 254 it('Should succeed with the corret parameters (advanced)', async function () {
@@ -210,7 +262,7 @@ describe('Test abuses API validators', function () {
210 predefinedReasons: [ 'serverRules' ] 262 predefinedReasons: [ 'serverRules' ]
211 } 263 }
212 264
213 await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 200 }) 265 await makePostBodyRequest({ url: server.url, path, token: userAccessToken, fields, statusCodeExpected: 200 })
214 }) 266 })
215 }) 267 })
216 268
@@ -244,6 +296,73 @@ describe('Test abuses API validators', function () {
244 }) 296 })
245 }) 297 })
246 298
299 describe('When creating an abuse message', function () {
300 const message = 'my super message'
301
302 it('Should fail with an invalid abuse id', async function () {
303 await addAbuseMessage(server.url, userAccessToken2, 888, message, 404)
304 })
305
306 it('Should fail with a non authenticated user', async function () {
307 await addAbuseMessage(server.url, 'fake_token', abuseId, message, 401)
308 })
309
310 it('Should fail with an invalid logged in user', async function () {
311 await addAbuseMessage(server.url, userAccessToken2, abuseId, message, 403)
312 })
313
314 it('Should fail with an invalid message', async function () {
315 await addAbuseMessage(server.url, userAccessToken, abuseId, 'a'.repeat(5000), 400)
316 })
317
318 it('Should suceed with the correct params', async function () {
319 const res = await addAbuseMessage(server.url, userAccessToken, abuseId, message)
320 messageId = res.body.abuseMessage.id
321 })
322 })
323
324 describe('When listing abuse message', function () {
325
326 it('Should fail with an invalid abuse id', async function () {
327 await listAbuseMessages(server.url, userAccessToken, 888, 404)
328 })
329
330 it('Should fail with a non authenticated user', async function () {
331 await listAbuseMessages(server.url, 'fake_token', abuseId, 401)
332 })
333
334 it('Should fail with an invalid logged in user', async function () {
335 await listAbuseMessages(server.url, userAccessToken2, abuseId, 403)
336 })
337
338 it('Should succeed with the correct params', async function () {
339 await listAbuseMessages(server.url, userAccessToken, abuseId)
340 })
341 })
342
343 describe('When deleting an abuse message', function () {
344
345 it('Should fail with an invalid abuse id', async function () {
346 await deleteAbuseMessage(server.url, userAccessToken, 888, messageId, 404)
347 })
348
349 it('Should fail with an invalid message id', async function () {
350 await deleteAbuseMessage(server.url, userAccessToken, abuseId, 888, 404)
351 })
352
353 it('Should fail with a non authenticated user', async function () {
354 await deleteAbuseMessage(server.url, 'fake_token', abuseId, messageId, 401)
355 })
356
357 it('Should fail with an invalid logged in user', async function () {
358 await deleteAbuseMessage(server.url, userAccessToken2, abuseId, messageId, 403)
359 })
360
361 it('Should succeed with the correct params', async function () {
362 await deleteAbuseMessage(server.url, userAccessToken, abuseId, messageId)
363 })
364 })
365
247 describe('When deleting a video abuse', function () { 366 describe('When deleting a video abuse', function () {
248 367
249 it('Should fail with a non authenticated user', async function () { 368 it('Should fail with a non authenticated user', async function () {
diff --git a/server/tests/api/moderation/abuses.ts b/server/tests/api/moderation/abuses.ts
index f186f7ea0..601125fdf 100644
--- a/server/tests/api/moderation/abuses.ts
+++ b/server/tests/api/moderation/abuses.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { Abuse, AbuseFilter, AbusePredefinedReasonsString, AbuseState, VideoComment, Account } from '@shared/models' 5import { AbuseFilter, AbusePredefinedReasonsString, AbuseState, Account, AdminAbuse, UserAbuse, VideoComment, AbuseMessage } from '@shared/models'
6import { 6import {
7 addVideoCommentThread, 7 addVideoCommentThread,
8 cleanupTests, 8 cleanupTests,
@@ -10,11 +10,15 @@ import {
10 deleteAbuse, 10 deleteAbuse,
11 deleteVideoComment, 11 deleteVideoComment,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 getAbusesList, 13 generateUserAccessToken,
14 getAccount,
15 getAdminAbusesList,
16 getUserAbusesList,
14 getVideoCommentThreads, 17 getVideoCommentThreads,
15 getVideoIdFromUUID, 18 getVideoIdFromUUID,
16 getVideosList, 19 getVideosList,
17 immutableAssign, 20 immutableAssign,
21 removeUser,
18 removeVideo, 22 removeVideo,
19 reportAbuse, 23 reportAbuse,
20 ServerInfo, 24 ServerInfo,
@@ -23,9 +27,9 @@ import {
23 uploadVideo, 27 uploadVideo,
24 uploadVideoAndGetId, 28 uploadVideoAndGetId,
25 userLogin, 29 userLogin,
26 getAccount, 30 addAbuseMessage,
27 removeUser, 31 listAbuseMessages,
28 generateUserAccessToken 32 deleteAbuseMessage
29} from '../../../../shared/extra-utils/index' 33} from '../../../../shared/extra-utils/index'
30import { doubleFollow } from '../../../../shared/extra-utils/server/follows' 34import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
31import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 35import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -40,8 +44,8 @@ const expect = chai.expect
40 44
41describe('Test abuses', function () { 45describe('Test abuses', function () {
42 let servers: ServerInfo[] = [] 46 let servers: ServerInfo[] = []
43 let abuseServer1: Abuse 47 let abuseServer1: AdminAbuse
44 let abuseServer2: Abuse 48 let abuseServer2: AdminAbuse
45 49
46 before(async function () { 50 before(async function () {
47 this.timeout(50000) 51 this.timeout(50000)
@@ -87,7 +91,7 @@ describe('Test abuses', function () {
87 }) 91 })
88 92
89 it('Should not have abuses', async function () { 93 it('Should not have abuses', async function () {
90 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 94 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
91 95
92 expect(res.body.total).to.equal(0) 96 expect(res.body.total).to.equal(0)
93 expect(res.body.data).to.be.an('array') 97 expect(res.body.data).to.be.an('array')
@@ -105,13 +109,13 @@ describe('Test abuses', function () {
105 }) 109 })
106 110
107 it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { 111 it('Should have 1 video abuses on server 1 and 0 on server 2', async function () {
108 const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 112 const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
109 113
110 expect(res1.body.total).to.equal(1) 114 expect(res1.body.total).to.equal(1)
111 expect(res1.body.data).to.be.an('array') 115 expect(res1.body.data).to.be.an('array')
112 expect(res1.body.data.length).to.equal(1) 116 expect(res1.body.data.length).to.equal(1)
113 117
114 const abuse: Abuse = res1.body.data[0] 118 const abuse: AdminAbuse = res1.body.data[0]
115 expect(abuse.reason).to.equal('my super bad reason') 119 expect(abuse.reason).to.equal('my super bad reason')
116 120
117 expect(abuse.reporterAccount.name).to.equal('root') 121 expect(abuse.reporterAccount.name).to.equal('root')
@@ -131,7 +135,7 @@ describe('Test abuses', function () {
131 expect(abuse.countReportsForReporter).to.equal(1) 135 expect(abuse.countReportsForReporter).to.equal(1)
132 expect(abuse.countReportsForReportee).to.equal(1) 136 expect(abuse.countReportsForReportee).to.equal(1)
133 137
134 const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) 138 const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
135 expect(res2.body.total).to.equal(0) 139 expect(res2.body.total).to.equal(0)
136 expect(res2.body.data).to.be.an('array') 140 expect(res2.body.data).to.be.an('array')
137 expect(res2.body.data.length).to.equal(0) 141 expect(res2.body.data.length).to.equal(0)
@@ -141,19 +145,20 @@ describe('Test abuses', function () {
141 this.timeout(10000) 145 this.timeout(10000)
142 146
143 const reason = 'my super bad reason 2' 147 const reason = 'my super bad reason 2'
144 await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId: servers[1].video.id, reason }) 148 const videoId = await getVideoIdFromUUID(servers[0].url, servers[1].video.uuid)
149 await reportAbuse({ url: servers[0].url, token: servers[0].accessToken, videoId, reason })
145 150
146 // We wait requests propagation 151 // We wait requests propagation
147 await waitJobs(servers) 152 await waitJobs(servers)
148 }) 153 })
149 154
150 it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { 155 it('Should have 2 video abuses on server 1 and 1 on server 2', async function () {
151 const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 156 const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
152 157
153 expect(res1.body.total).to.equal(2) 158 expect(res1.body.total).to.equal(2)
154 expect(res1.body.data.length).to.equal(2) 159 expect(res1.body.data.length).to.equal(2)
155 160
156 const abuse1: Abuse = res1.body.data[0] 161 const abuse1: AdminAbuse = res1.body.data[0]
157 expect(abuse1.reason).to.equal('my super bad reason') 162 expect(abuse1.reason).to.equal('my super bad reason')
158 expect(abuse1.reporterAccount.name).to.equal('root') 163 expect(abuse1.reporterAccount.name).to.equal('root')
159 expect(abuse1.reporterAccount.host).to.equal(servers[0].host) 164 expect(abuse1.reporterAccount.host).to.equal(servers[0].host)
@@ -171,7 +176,7 @@ describe('Test abuses', function () {
171 expect(abuse1.state.label).to.equal('Pending') 176 expect(abuse1.state.label).to.equal('Pending')
172 expect(abuse1.moderationComment).to.be.null 177 expect(abuse1.moderationComment).to.be.null
173 178
174 const abuse2: Abuse = res1.body.data[1] 179 const abuse2: AdminAbuse = res1.body.data[1]
175 expect(abuse2.reason).to.equal('my super bad reason 2') 180 expect(abuse2.reason).to.equal('my super bad reason 2')
176 181
177 expect(abuse2.reporterAccount.name).to.equal('root') 182 expect(abuse2.reporterAccount.name).to.equal('root')
@@ -188,7 +193,7 @@ describe('Test abuses', function () {
188 expect(abuse2.state.label).to.equal('Pending') 193 expect(abuse2.state.label).to.equal('Pending')
189 expect(abuse2.moderationComment).to.be.null 194 expect(abuse2.moderationComment).to.be.null
190 195
191 const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) 196 const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
192 expect(res2.body.total).to.equal(1) 197 expect(res2.body.total).to.equal(1)
193 expect(res2.body.data.length).to.equal(1) 198 expect(res2.body.data.length).to.equal(1)
194 199
@@ -213,7 +218,7 @@ describe('Test abuses', function () {
213 await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'will mute this' }) 218 await reportAbuse({ url: servers[1].url, token: servers[1].accessToken, videoId, reason: 'will mute this' })
214 await waitJobs(servers) 219 await waitJobs(servers)
215 220
216 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 221 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
217 expect(res.body.total).to.equal(3) 222 expect(res.body.total).to.equal(3)
218 } 223 }
219 224
@@ -222,7 +227,7 @@ describe('Test abuses', function () {
222 { 227 {
223 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) 228 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
224 229
225 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 230 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
226 expect(res.body.total).to.equal(2) 231 expect(res.body.total).to.equal(2)
227 232
228 const abuse = res.body.data.find(a => a.reason === 'will mute this') 233 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -232,7 +237,7 @@ describe('Test abuses', function () {
232 { 237 {
233 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) 238 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
234 239
235 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 240 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
236 expect(res.body.total).to.equal(3) 241 expect(res.body.total).to.equal(3)
237 } 242 }
238 }) 243 })
@@ -243,7 +248,7 @@ describe('Test abuses', function () {
243 { 248 {
244 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host) 249 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
245 250
246 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 251 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
247 expect(res.body.total).to.equal(2) 252 expect(res.body.total).to.equal(2)
248 253
249 const abuse = res.body.data.find(a => a.reason === 'will mute this') 254 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -253,7 +258,7 @@ describe('Test abuses', function () {
253 { 258 {
254 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock) 259 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
255 260
256 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 261 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
257 expect(res.body.total).to.equal(3) 262 expect(res.body.total).to.equal(3)
258 } 263 }
259 }) 264 })
@@ -265,11 +270,11 @@ describe('Test abuses', function () {
265 270
266 await waitJobs(servers) 271 await waitJobs(servers)
267 272
268 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) 273 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
269 expect(res.body.total).to.equal(2, "wrong number of videos returned") 274 expect(res.body.total).to.equal(2, "wrong number of videos returned")
270 expect(res.body.data).to.have.lengthOf(2, "wrong number of videos returned") 275 expect(res.body.data).to.have.lengthOf(2, "wrong number of videos returned")
271 276
272 const abuse: Abuse = res.body.data[0] 277 const abuse: AdminAbuse = res.body.data[0]
273 expect(abuse.id).to.equal(abuseServer2.id, "wrong origin server id for first video") 278 expect(abuse.id).to.equal(abuseServer2.id, "wrong origin server id for first video")
274 expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") 279 expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id")
275 expect(abuse.video.channel).to.exist 280 expect(abuse.video.channel).to.exist
@@ -303,8 +308,8 @@ describe('Test abuses', function () {
303 await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: reason4 }) 308 await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: reason4 })
304 309
305 { 310 {
306 const res2 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 311 const res2 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
307 const abuses = res2.body.data as Abuse[] 312 const abuses = res2.body.data as AdminAbuse[]
308 313
309 const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id) 314 const abuseVideo3 = res2.body.data.find(a => a.video.id === video3.id)
310 expect(abuseVideo3).to.not.be.undefined 315 expect(abuseVideo3).to.not.be.undefined
@@ -333,10 +338,10 @@ describe('Test abuses', function () {
333 endAt: 5 338 endAt: 5
334 })).body.abuse 339 })).body.abuse
335 340
336 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 341 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
337 342
338 { 343 {
339 const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) 344 const abuse = (res.body.data as AdminAbuse[]).find(a => a.id === createdAbuse.id)
340 expect(abuse.reason).to.equals(reason5) 345 expect(abuse.reason).to.equals(reason5)
341 expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported") 346 expect(abuse.predefinedReasons).to.deep.equals(predefinedReasons5, "predefined reasons do not match the one reported")
342 expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported") 347 expect(abuse.video.startAt).to.equal(1, "starting timestamp doesn't match the one reported")
@@ -352,14 +357,14 @@ describe('Test abuses', function () {
352 await waitJobs(servers) 357 await waitJobs(servers)
353 358
354 { 359 {
355 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken }) 360 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken })
356 expect(res.body.total).to.equal(1) 361 expect(res.body.total).to.equal(1)
357 expect(res.body.data.length).to.equal(1) 362 expect(res.body.data.length).to.equal(1)
358 expect(res.body.data[0].id).to.not.equal(abuseServer2.id) 363 expect(res.body.data[0].id).to.not.equal(abuseServer2.id)
359 } 364 }
360 365
361 { 366 {
362 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken }) 367 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken })
363 expect(res.body.total).to.equal(6) 368 expect(res.body.total).to.equal(6)
364 } 369 }
365 }) 370 })
@@ -367,7 +372,7 @@ describe('Test abuses', function () {
367 it('Should list and filter video abuses', async function () { 372 it('Should list and filter video abuses', async function () {
368 this.timeout(10000) 373 this.timeout(10000)
369 374
370 async function list (query: Omit<Parameters<typeof getAbusesList>[0], 'url' | 'token'>) { 375 async function list (query: Omit<Parameters<typeof getAdminAbusesList>[0], 'url' | 'token'>) {
371 const options = { 376 const options = {
372 url: servers[0].url, 377 url: servers[0].url,
373 token: servers[0].accessToken 378 token: servers[0].accessToken
@@ -375,9 +380,9 @@ describe('Test abuses', function () {
375 380
376 Object.assign(options, query) 381 Object.assign(options, query)
377 382
378 const res = await getAbusesList(options) 383 const res = await getAdminAbusesList(options)
379 384
380 return res.body.data as Abuse[] 385 return res.body.data as AdminAbuse[]
381 } 386 }
382 387
383 expect(await list({ id: 56 })).to.have.lengthOf(0) 388 expect(await list({ id: 56 })).to.have.lengthOf(0)
@@ -446,12 +451,12 @@ describe('Test abuses', function () {
446 it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () { 451 it('Should have 1 comment abuse on server 1 and 0 on server 2', async function () {
447 { 452 {
448 const comment = await getComment(servers[0].url, servers[0].video.id) 453 const comment = await getComment(servers[0].url, servers[0].video.id)
449 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) 454 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
450 455
451 expect(res.body.total).to.equal(1) 456 expect(res.body.total).to.equal(1)
452 expect(res.body.data).to.have.lengthOf(1) 457 expect(res.body.data).to.have.lengthOf(1)
453 458
454 const abuse: Abuse = res.body.data[0] 459 const abuse: AdminAbuse = res.body.data[0]
455 expect(abuse.reason).to.equal('it is a bad comment') 460 expect(abuse.reason).to.equal('it is a bad comment')
456 461
457 expect(abuse.reporterAccount.name).to.equal('root') 462 expect(abuse.reporterAccount.name).to.equal('root')
@@ -471,7 +476,7 @@ describe('Test abuses', function () {
471 } 476 }
472 477
473 { 478 {
474 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) 479 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
475 expect(res.body.total).to.equal(0) 480 expect(res.body.total).to.equal(0)
476 expect(res.body.data.length).to.equal(0) 481 expect(res.body.data.length).to.equal(0)
477 } 482 }
@@ -491,16 +496,16 @@ describe('Test abuses', function () {
491 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () { 496 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
492 const commentServer2 = await getComment(servers[0].url, servers[1].video.id) 497 const commentServer2 = await getComment(servers[0].url, servers[1].video.id)
493 498
494 const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) 499 const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
495 expect(res1.body.total).to.equal(2) 500 expect(res1.body.total).to.equal(2)
496 expect(res1.body.data.length).to.equal(2) 501 expect(res1.body.data.length).to.equal(2)
497 502
498 const abuse: Abuse = res1.body.data[0] 503 const abuse: AdminAbuse = res1.body.data[0]
499 expect(abuse.reason).to.equal('it is a bad comment') 504 expect(abuse.reason).to.equal('it is a bad comment')
500 expect(abuse.countReportsForReporter).to.equal(6) 505 expect(abuse.countReportsForReporter).to.equal(6)
501 expect(abuse.countReportsForReportee).to.equal(5) 506 expect(abuse.countReportsForReportee).to.equal(5)
502 507
503 const abuse2: Abuse = res1.body.data[1] 508 const abuse2: AdminAbuse = res1.body.data[1]
504 509
505 expect(abuse2.reason).to.equal('it is a really bad comment') 510 expect(abuse2.reason).to.equal('it is a really bad comment')
506 511
@@ -523,7 +528,7 @@ describe('Test abuses', function () {
523 expect(abuse2.countReportsForReporter).to.equal(6) 528 expect(abuse2.countReportsForReporter).to.equal(6)
524 expect(abuse2.countReportsForReportee).to.equal(2) 529 expect(abuse2.countReportsForReportee).to.equal(2)
525 530
526 const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) 531 const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
527 expect(res2.body.total).to.equal(1) 532 expect(res2.body.total).to.equal(1)
528 expect(res2.body.data.length).to.equal(1) 533 expect(res2.body.data.length).to.equal(1)
529 534
@@ -550,11 +555,11 @@ describe('Test abuses', function () {
550 555
551 await waitJobs(servers) 556 await waitJobs(servers)
552 557
553 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) 558 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
554 expect(res.body.total).to.equal(2) 559 expect(res.body.total).to.equal(2)
555 expect(res.body.data).to.have.lengthOf(2) 560 expect(res.body.data).to.have.lengthOf(2)
556 561
557 const abuse = (res.body.data as Abuse[]).find(a => a.comment?.id === commentServer2.id) 562 const abuse = (res.body.data as AdminAbuse[]).find(a => a.comment?.id === commentServer2.id)
558 expect(abuse).to.not.be.undefined 563 expect(abuse).to.not.be.undefined
559 564
560 expect(abuse.comment.text).to.be.empty 565 expect(abuse.comment.text).to.be.empty
@@ -570,36 +575,46 @@ describe('Test abuses', function () {
570 await waitJobs(servers) 575 await waitJobs(servers)
571 576
572 { 577 {
573 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) 578 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
574 expect(res.body.total).to.equal(0) 579 expect(res.body.total).to.equal(0)
575 expect(res.body.data.length).to.equal(0) 580 expect(res.body.data.length).to.equal(0)
576 } 581 }
577 582
578 { 583 {
579 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' }) 584 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment' })
580 expect(res.body.total).to.equal(2) 585 expect(res.body.total).to.equal(2)
581 } 586 }
582 }) 587 })
583 588
584 it('Should list and filter video abuses', async function () { 589 it('Should list and filter video abuses', async function () {
585 { 590 {
586 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'foo' }) 591 const res = await getAdminAbusesList({
592 url: servers[0].url,
593 token: servers[0].accessToken,
594 filter: 'comment',
595 searchReportee: 'foo'
596 })
587 expect(res.body.total).to.equal(0) 597 expect(res.body.total).to.equal(0)
588 } 598 }
589 599
590 { 600 {
591 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'comment', searchReportee: 'ot' }) 601 const res = await getAdminAbusesList({
602 url: servers[0].url,
603 token: servers[0].accessToken,
604 filter: 'comment',
605 searchReportee: 'ot'
606 })
592 expect(res.body.total).to.equal(2) 607 expect(res.body.total).to.equal(2)
593 } 608 }
594 609
595 { 610 {
596 const baseParams = { url: servers[0].url, token: servers[0].accessToken, filter: 'comment' as AbuseFilter, start: 1, count: 1 } 611 const baseParams = { url: servers[0].url, token: servers[0].accessToken, filter: 'comment' as AbuseFilter, start: 1, count: 1 }
597 612
598 const res1 = await getAbusesList(immutableAssign(baseParams, { sort: 'createdAt' })) 613 const res1 = await getAdminAbusesList(immutableAssign(baseParams, { sort: 'createdAt' }))
599 expect(res1.body.data).to.have.lengthOf(1) 614 expect(res1.body.data).to.have.lengthOf(1)
600 expect(res1.body.data[0].comment.text).to.be.empty 615 expect(res1.body.data[0].comment.text).to.be.empty
601 616
602 const res2 = await getAbusesList(immutableAssign(baseParams, { sort: '-createdAt' })) 617 const res2 = await getAdminAbusesList(immutableAssign(baseParams, { sort: '-createdAt' }))
603 expect(res2.body.data).to.have.lengthOf(1) 618 expect(res2.body.data).to.have.lengthOf(1)
604 expect(res2.body.data[0].comment.text).to.equal('comment server 1') 619 expect(res2.body.data[0].comment.text).to.equal('comment server 1')
605 } 620 }
@@ -638,12 +653,12 @@ describe('Test abuses', function () {
638 653
639 it('Should have 1 account abuse on server 1 and 0 on server 2', async function () { 654 it('Should have 1 account abuse on server 1 and 0 on server 2', async function () {
640 { 655 {
641 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) 656 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
642 657
643 expect(res.body.total).to.equal(1) 658 expect(res.body.total).to.equal(1)
644 expect(res.body.data).to.have.lengthOf(1) 659 expect(res.body.data).to.have.lengthOf(1)
645 660
646 const abuse: Abuse = res.body.data[0] 661 const abuse: AdminAbuse = res.body.data[0]
647 expect(abuse.reason).to.equal('it is a bad account') 662 expect(abuse.reason).to.equal('it is a bad account')
648 663
649 expect(abuse.reporterAccount.name).to.equal('root') 664 expect(abuse.reporterAccount.name).to.equal('root')
@@ -657,7 +672,7 @@ describe('Test abuses', function () {
657 } 672 }
658 673
659 { 674 {
660 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' }) 675 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'comment' })
661 expect(res.body.total).to.equal(0) 676 expect(res.body.total).to.equal(0)
662 expect(res.body.data.length).to.equal(0) 677 expect(res.body.data.length).to.equal(0)
663 } 678 }
@@ -675,14 +690,14 @@ describe('Test abuses', function () {
675 }) 690 })
676 691
677 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () { 692 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
678 const res1 = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) 693 const res1 = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
679 expect(res1.body.total).to.equal(2) 694 expect(res1.body.total).to.equal(2)
680 expect(res1.body.data.length).to.equal(2) 695 expect(res1.body.data.length).to.equal(2)
681 696
682 const abuse: Abuse = res1.body.data[0] 697 const abuse: AdminAbuse = res1.body.data[0]
683 expect(abuse.reason).to.equal('it is a bad account') 698 expect(abuse.reason).to.equal('it is a bad account')
684 699
685 const abuse2: Abuse = res1.body.data[1] 700 const abuse2: AdminAbuse = res1.body.data[1]
686 expect(abuse2.reason).to.equal('it is a really bad account') 701 expect(abuse2.reason).to.equal('it is a really bad account')
687 702
688 expect(abuse2.reporterAccount.name).to.equal('root') 703 expect(abuse2.reporterAccount.name).to.equal('root')
@@ -696,7 +711,7 @@ describe('Test abuses', function () {
696 711
697 expect(abuse2.moderationComment).to.be.null 712 expect(abuse2.moderationComment).to.be.null
698 713
699 const res2 = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' }) 714 const res2 = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
700 expect(res2.body.total).to.equal(1) 715 expect(res2.body.total).to.equal(1)
701 expect(res2.body.data.length).to.equal(1) 716 expect(res2.body.data.length).to.equal(1)
702 717
@@ -721,11 +736,11 @@ describe('Test abuses', function () {
721 736
722 await waitJobs(servers) 737 await waitJobs(servers)
723 738
724 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) 739 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
725 expect(res.body.total).to.equal(2) 740 expect(res.body.total).to.equal(2)
726 expect(res.body.data).to.have.lengthOf(2) 741 expect(res.body.data).to.have.lengthOf(2)
727 742
728 const abuse = (res.body.data as Abuse[]).find(a => a.reason === 'it is a really bad account') 743 const abuse = (res.body.data as AdminAbuse[]).find(a => a.reason === 'it is a really bad account')
729 expect(abuse).to.not.be.undefined 744 expect(abuse).to.not.be.undefined
730 }) 745 })
731 746
@@ -737,13 +752,13 @@ describe('Test abuses', function () {
737 await waitJobs(servers) 752 await waitJobs(servers)
738 753
739 { 754 {
740 const res = await getAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' }) 755 const res = await getAdminAbusesList({ url: servers[1].url, token: servers[1].accessToken, filter: 'account' })
741 expect(res.body.total).to.equal(0) 756 expect(res.body.total).to.equal(0)
742 expect(res.body.data.length).to.equal(0) 757 expect(res.body.data.length).to.equal(0)
743 } 758 }
744 759
745 { 760 {
746 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' }) 761 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, filter: 'account' })
747 expect(res.body.total).to.equal(2) 762 expect(res.body.total).to.equal(2)
748 763
749 abuseServer1 = res.body.data[0] 764 abuseServer1 = res.body.data[0]
@@ -757,7 +772,7 @@ describe('Test abuses', function () {
757 const body = { state: AbuseState.REJECTED } 772 const body = { state: AbuseState.REJECTED }
758 await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body) 773 await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
759 774
760 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id }) 775 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
761 expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED) 776 expect(res.body.data[0].state.id).to.equal(AbuseState.REJECTED)
762 }) 777 })
763 778
@@ -765,12 +780,184 @@ describe('Test abuses', function () {
765 const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' } 780 const body = { state: AbuseState.ACCEPTED, moderationComment: 'It is valid' }
766 await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body) 781 await updateAbuse(servers[0].url, servers[0].accessToken, abuseServer1.id, body)
767 782
768 const res = await getAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id }) 783 const res = await getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, id: abuseServer1.id })
769 expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED) 784 expect(res.body.data[0].state.id).to.equal(AbuseState.ACCEPTED)
770 expect(res.body.data[0].moderationComment).to.equal('It is valid') 785 expect(res.body.data[0].moderationComment).to.equal('It is valid')
771 }) 786 })
772 }) 787 })
773 788
789 describe('My abuses', async function () {
790 let abuseId1: number
791 let userAccessToken: string
792
793 before(async function () {
794 userAccessToken = await generateUserAccessToken(servers[0], 'user_42')
795
796 await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId: servers[0].video.id, reason: 'user reason 1' })
797
798 const videoId = await getVideoIdFromUUID(servers[0].url, servers[1].video.uuid)
799 await reportAbuse({ url: servers[0].url, token: userAccessToken, videoId, reason: 'user reason 2' })
800 })
801
802 it('Should correctly list my abuses', async function () {
803 {
804 const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 0, count: 5, sort: 'createdAt' })
805 expect(res.body.total).to.equal(2)
806
807 const abuses: UserAbuse[] = res.body.data
808 expect(abuses[0].reason).to.equal('user reason 1')
809 expect(abuses[1].reason).to.equal('user reason 2')
810
811 abuseId1 = abuses[0].id
812 }
813
814 {
815 const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 1, count: 1, sort: 'createdAt' })
816 expect(res.body.total).to.equal(2)
817
818 const abuses: UserAbuse[] = res.body.data
819 expect(abuses[0].reason).to.equal('user reason 2')
820 }
821
822 {
823 const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 1, count: 1, sort: '-createdAt' })
824 expect(res.body.total).to.equal(2)
825
826 const abuses: UserAbuse[] = res.body.data
827 expect(abuses[0].reason).to.equal('user reason 1')
828 }
829 })
830
831 it('Should correctly filter my abuses by id', async function () {
832 const res = await getUserAbusesList({ url: servers[0].url, token: userAccessToken, id: abuseId1 })
833
834 expect(res.body.total).to.equal(1)
835
836 const abuses: UserAbuse[] = res.body.data
837 expect(abuses[0].reason).to.equal('user reason 1')
838 })
839
840 it('Should correctly filter my abuses by search', async function () {
841 const res = await getUserAbusesList({
842 url: servers[0].url,
843 token: userAccessToken,
844 search: 'server 2'
845 })
846
847 expect(res.body.total).to.equal(1)
848
849 const abuses: UserAbuse[] = res.body.data
850 expect(abuses[0].reason).to.equal('user reason 2')
851 })
852
853 it('Should correctly filter my abuses by state', async function () {
854 const body = { state: AbuseState.REJECTED }
855 await updateAbuse(servers[0].url, servers[0].accessToken, abuseId1, body)
856
857 const res = await getUserAbusesList({
858 url: servers[0].url,
859 token: userAccessToken,
860 state: AbuseState.REJECTED
861 })
862
863 expect(res.body.total).to.equal(1)
864
865 const abuses: UserAbuse[] = res.body.data
866 expect(abuses[0].reason).to.equal('user reason 1')
867 })
868 })
869
870 describe('Abuse messages', async function () {
871 let abuseId: number
872 let userAccessToken: string
873 let abuseMessageUserId: number
874 let abuseMessageModerationId: number
875
876 before(async function () {
877 userAccessToken = await generateUserAccessToken(servers[0], 'user_43')
878
879 const res = await reportAbuse({
880 url: servers[0].url,
881 token: userAccessToken,
882 videoId: servers[0].video.id,
883 reason: 'user 43 reason 1'
884 })
885
886 abuseId = res.body.abuse.id
887 })
888
889 it('Should create some messages on the abuse', async function () {
890 await addAbuseMessage(servers[0].url, userAccessToken, abuseId, 'message 1')
891 await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, 'message 2')
892 await addAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, 'message 3')
893 await addAbuseMessage(servers[0].url, userAccessToken, abuseId, 'message 4')
894 })
895
896 it('Should have the correct messages count when listing abuses', async function () {
897 const results = await Promise.all([
898 getAdminAbusesList({ url: servers[0].url, token: servers[0].accessToken, start: 0, count: 50 }),
899 getUserAbusesList({ url: servers[0].url, token: userAccessToken, start: 0, count: 50 })
900 ])
901
902 for (const res of results) {
903 const abuses: AdminAbuse[] = res.body.data
904 const abuse = abuses.find(a => a.id === abuseId)
905 expect(abuse.countMessages).to.equal(4)
906 }
907 })
908
909 it('Should correctly list messages of this abuse', async function () {
910 const results = await Promise.all([
911 listAbuseMessages(servers[0].url, servers[0].accessToken, abuseId),
912 listAbuseMessages(servers[0].url, userAccessToken, abuseId)
913 ])
914
915 for (const res of results) {
916 expect(res.body.total).to.equal(4)
917
918 const abuseMessages: AbuseMessage[] = res.body.data
919
920 expect(abuseMessages[0].message).to.equal('message 1')
921 expect(abuseMessages[0].byModerator).to.be.false
922 expect(abuseMessages[0].account.name).to.equal('user_43')
923
924 abuseMessageUserId = abuseMessages[0].id
925
926 expect(abuseMessages[1].message).to.equal('message 2')
927 expect(abuseMessages[1].byModerator).to.be.true
928 expect(abuseMessages[1].account.name).to.equal('root')
929
930 expect(abuseMessages[2].message).to.equal('message 3')
931 expect(abuseMessages[2].byModerator).to.be.true
932 expect(abuseMessages[2].account.name).to.equal('root')
933 abuseMessageModerationId = abuseMessages[2].id
934
935 expect(abuseMessages[3].message).to.equal('message 4')
936 expect(abuseMessages[3].byModerator).to.be.false
937 expect(abuseMessages[3].account.name).to.equal('user_43')
938 }
939 })
940
941 it('Should delete messages', async function () {
942 await deleteAbuseMessage(servers[0].url, servers[0].accessToken, abuseId, abuseMessageModerationId)
943 await deleteAbuseMessage(servers[0].url, userAccessToken, abuseId, abuseMessageUserId)
944
945 const results = await Promise.all([
946 listAbuseMessages(servers[0].url, servers[0].accessToken, abuseId),
947 listAbuseMessages(servers[0].url, userAccessToken, abuseId)
948 ])
949
950 for (const res of results) {
951 expect(res.body.total).to.equal(2)
952
953 const abuseMessages: AbuseMessage[] = res.body.data
954
955 expect(abuseMessages[0].message).to.equal('message 2')
956 expect(abuseMessages[1].message).to.equal('message 4')
957 }
958 })
959 })
960
774 after(async function () { 961 after(async function () {
775 await cleanupTests(servers) 962 await cleanupTests(servers)
776 }) 963 })
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index ea74bde6a..edb0b4bb3 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -11,8 +11,8 @@ import {
11 createUser, 11 createUser,
12 deleteMe, 12 deleteMe,
13 flushAndRunServer, 13 flushAndRunServer,
14 getAbusesList,
15 getAccountRatings, 14 getAccountRatings,
15 getAdminAbusesList,
16 getBlacklistedVideosList, 16 getBlacklistedVideosList,
17 getCustomConfig, 17 getCustomConfig,
18 getMyUserInformation, 18 getMyUserInformation,
@@ -928,7 +928,7 @@ describe('Test users', function () {
928 const reason = 'my super bad reason' 928 const reason = 'my super bad reason'
929 await reportAbuse({ url: server.url, token: user17AccessToken, videoId, reason }) 929 await reportAbuse({ url: server.url, token: user17AccessToken, videoId, reason })
930 930
931 const res1 = await getAbusesList({ url: server.url, token: server.accessToken }) 931 const res1 = await getAdminAbusesList({ url: server.url, token: server.accessToken })
932 const abuseId = res1.body.data[0].id 932 const abuseId = res1.body.data[0].id
933 933
934 const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) 934 const res2 = 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 baeb543e0..0b6a0e8ae 100644
--- a/server/tests/api/videos/video-abuse.ts
+++ b/server/tests/api/videos/video-abuse.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { Abuse, AbusePredefinedReasonsString, AbuseState } from '@shared/models' 5import { AbusePredefinedReasonsString, AbuseState, AdminAbuse } from '@shared/models'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser,
@@ -33,7 +33,7 @@ const expect = chai.expect
33 33
34describe('Test video abuses', function () { 34describe('Test video abuses', function () {
35 let servers: ServerInfo[] = [] 35 let servers: ServerInfo[] = []
36 let abuseServer2: Abuse 36 let abuseServer2: AdminAbuse
37 37
38 before(async function () { 38 before(async function () {
39 this.timeout(50000) 39 this.timeout(50000)
@@ -97,7 +97,7 @@ describe('Test video abuses', function () {
97 expect(res1.body.data).to.be.an('array') 97 expect(res1.body.data).to.be.an('array')
98 expect(res1.body.data.length).to.equal(1) 98 expect(res1.body.data.length).to.equal(1)
99 99
100 const abuse: Abuse = res1.body.data[0] 100 const abuse: AdminAbuse = res1.body.data[0]
101 expect(abuse.reason).to.equal('my super bad reason') 101 expect(abuse.reason).to.equal('my super bad reason')
102 expect(abuse.reporterAccount.name).to.equal('root') 102 expect(abuse.reporterAccount.name).to.equal('root')
103 expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) 103 expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@@ -130,7 +130,7 @@ describe('Test video abuses', function () {
130 expect(res1.body.data).to.be.an('array') 130 expect(res1.body.data).to.be.an('array')
131 expect(res1.body.data.length).to.equal(2) 131 expect(res1.body.data.length).to.equal(2)
132 132
133 const abuse1: Abuse = res1.body.data[0] 133 const abuse1: AdminAbuse = res1.body.data[0]
134 expect(abuse1.reason).to.equal('my super bad reason') 134 expect(abuse1.reason).to.equal('my super bad reason')
135 expect(abuse1.reporterAccount.name).to.equal('root') 135 expect(abuse1.reporterAccount.name).to.equal('root')
136 expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port) 136 expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@@ -141,7 +141,7 @@ describe('Test video abuses', function () {
141 expect(abuse1.video.countReports).to.equal(1) 141 expect(abuse1.video.countReports).to.equal(1)
142 expect(abuse1.video.nthReport).to.equal(1) 142 expect(abuse1.video.nthReport).to.equal(1)
143 143
144 const abuse2: Abuse = res1.body.data[1] 144 const abuse2: AdminAbuse = res1.body.data[1]
145 expect(abuse2.reason).to.equal('my super bad reason 2') 145 expect(abuse2.reason).to.equal('my super bad reason 2')
146 expect(abuse2.reporterAccount.name).to.equal('root') 146 expect(abuse2.reporterAccount.name).to.equal('root')
147 expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port) 147 expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
@@ -245,7 +245,7 @@ describe('Test video abuses', function () {
245 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")
246 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")
247 247
248 const abuse: Abuse = res.body.data[0] 248 const abuse: AdminAbuse = res.body.data[0]
249 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")
250 expect(abuse.video.channel).to.exist 250 expect(abuse.video.channel).to.exist
251 expect(abuse.video.deleted).to.be.true 251 expect(abuse.video.deleted).to.be.true
@@ -279,7 +279,7 @@ describe('Test video abuses', function () {
279 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 })
280 280
281 { 281 {
282 for (const abuse of res2.body.data as Abuse[]) { 282 for (const abuse of res2.body.data as AdminAbuse[]) {
283 if (abuse.video.id === video3.id) { 283 if (abuse.video.id === video3.id) {
284 expect(abuse.video.countReports).to.equal(1, "wrong reports count for video 3") 284 expect(abuse.video.countReports).to.equal(1, "wrong reports count for video 3")
285 expect(abuse.video.nthReport).to.equal(1, "wrong report position in report list for video 3") 285 expect(abuse.video.nthReport).to.equal(1, "wrong report position in report list for video 3")
@@ -311,7 +311,7 @@ describe('Test video abuses', function () {
311 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 })
312 312
313 { 313 {
314 const abuse = (res.body.data as Abuse[]).find(a => a.id === createdAbuse.id) 314 const abuse = (res.body.data as AdminAbuse[]).find(a => a.id === createdAbuse.id)
315 expect(abuse.reason).to.equals(reason5) 315 expect(abuse.reason).to.equals(reason5)
316 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")
317 expect(abuse.video.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")
@@ -350,7 +350,7 @@ describe('Test video abuses', function () {
350 350
351 const res = await getVideoAbusesList(options) 351 const res = await getVideoAbusesList(options)
352 352
353 return res.body.data as Abuse[] 353 return res.body.data as AdminAbuse[]
354 } 354 }
355 355
356 expect(await list({ id: 56 })).to.have.lengthOf(0) 356 expect(await list({ id: 56 })).to.have.lengthOf(0)
diff --git a/server/types/models/moderation/abuse-message.ts b/server/types/models/moderation/abuse-message.ts
new file mode 100644
index 000000000..565eca706
--- /dev/null
+++ b/server/types/models/moderation/abuse-message.ts
@@ -0,0 +1,20 @@
1import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
2import { PickWith } from '@shared/core-utils'
3import { AbuseModel } from '../../../models/abuse/abuse'
4import { MAccountFormattable } from '../account'
5
6type Use<K extends keyof AbuseMessageModel, M> = PickWith<AbuseMessageModel, K, M>
7
8// ############################################################################
9
10export type MAbuseMessage = Omit<AbuseMessageModel, 'Account' | 'Abuse' | 'toFormattedJSON'>
11
12export type MAbuseMessageId = Pick<AbuseModel, 'id'>
13
14// ############################################################################
15
16// Format for API
17
18export type MAbuseMessageFormattable =
19 MAbuseMessage &
20 Use<'Account', MAccountFormattable>
diff --git a/server/types/models/moderation/abuse.ts b/server/types/models/moderation/abuse.ts
index a0bf4b08f..39ef50771 100644
--- a/server/types/models/moderation/abuse.ts
+++ b/server/types/models/moderation/abuse.ts
@@ -95,9 +95,15 @@ export type MAbuseFull =
95 95
96// Format for API or AP object 96// Format for API or AP object
97 97
98export type MAbuseFormattable = 98export type MAbuseAdminFormattable =
99 MAbuse & 99 MAbuse &
100 Use<'ReporterAccount', MAccountFormattable> & 100 Use<'ReporterAccount', MAccountFormattable> &
101 Use<'FlaggedAccount', MAccountFormattable> & 101 Use<'FlaggedAccount', MAccountFormattable> &
102 Use<'VideoAbuse', MVideoAbuseFormattable> & 102 Use<'VideoAbuse', MVideoAbuseFormattable> &
103 Use<'VideoCommentAbuse', MCommentAbuseFormattable> 103 Use<'VideoCommentAbuse', MCommentAbuseFormattable>
104
105export type MAbuseUserFormattable =
106 MAbuse &
107 Use<'FlaggedAccount', MAccountFormattable> &
108 Use<'VideoAbuse', MVideoAbuseFormattable> &
109 Use<'VideoCommentAbuse', MCommentAbuseFormattable>
diff --git a/server/types/models/moderation/index.ts b/server/types/models/moderation/index.ts
index 8bea1708f..1ed91b249 100644
--- a/server/types/models/moderation/index.ts
+++ b/server/types/models/moderation/index.ts
@@ -1 +1,2 @@
1export * from './abuse' 1export * from './abuse'
2export * from './abuse-message'
diff --git a/server/typings/express/index.d.ts b/server/typings/express/index.d.ts
index 7595e6d86..452c6e1a0 100644
--- a/server/typings/express/index.d.ts
+++ b/server/typings/express/index.d.ts
@@ -1,6 +1,7 @@
1import { RegisterServerAuthExternalOptions } from '@server/types' 1import { RegisterServerAuthExternalOptions } from '@server/types'
2import { 2import {
3 MAbuse, 3 MAbuse,
4 MAbuseMessage,
4 MAccountBlocklist, 5 MAccountBlocklist,
5 MActorUrl, 6 MActorUrl,
6 MStreamingPlaylist, 7 MStreamingPlaylist,
@@ -78,6 +79,7 @@ declare module 'express' {
78 videoCaption?: MVideoCaptionVideo 79 videoCaption?: MVideoCaptionVideo
79 80
80 abuse?: MAbuse 81 abuse?: MAbuse
82 abuseMessage?: MAbuseMessage
81 83
82 videoStreamingPlaylist?: MStreamingPlaylist 84 videoStreamingPlaylist?: MStreamingPlaylist
83 85