From 80fdaf064562aff968f4c9cea1cf220bc12a70da Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 7 May 2020 14:58:24 +0200 Subject: [PATCH 1/1] Add moderation helpers to plugins --- scripts/create-import-video-file-job.ts | 2 +- scripts/create-transcoding-job.ts | 2 +- scripts/danger/clean/cleaner.ts | 2 +- scripts/migrations/peertube-2.1.ts | 2 +- scripts/optimize-old-videos.ts | 2 +- scripts/prune-storage.ts | 2 +- scripts/reset-password.ts | 2 +- scripts/update-host.ts | 2 +- server.ts | 2 +- server/controllers/api/users/my-history.ts | 2 +- server/controllers/api/videos/abuse.ts | 2 +- server/controllers/api/videos/blacklist.ts | 60 +----- server/controllers/api/videos/comment.ts | 2 +- server/controllers/api/videos/ownership.ts | 2 +- server/initializers/index.ts | 3 - .../activitypub/process/process-announce.ts | 2 +- .../lib/activitypub/process/process-create.ts | 2 +- .../lib/activitypub/process/process-delete.ts | 2 +- .../activitypub/process/process-dislike.ts | 2 +- .../lib/activitypub/process/process-flag.ts | 2 +- .../lib/activitypub/process/process-follow.ts | 2 +- .../lib/activitypub/process/process-like.ts | 2 +- .../lib/activitypub/process/process-reject.ts | 2 +- .../lib/activitypub/process/process-undo.ts | 2 +- .../lib/activitypub/process/process-update.ts | 2 +- server/lib/blocklist.ts | 4 +- .../job-queue/handlers/video-transcoding.ts | 2 +- server/lib/plugins/plugin-helpers.ts | 78 ++++++- server/lib/video-blacklist.ts | 80 ++++++- server/middlewares/validators/blocklist.ts | 5 +- server/models/server/server.ts | 7 + .../peertube-plugin-test-four/main.js | 107 +++++++-- server/tests/plugins/plugin-helpers.ts | 204 +++++++++++++++--- .../plugins/register-server-option.model.ts | 38 +++- 34 files changed, 484 insertions(+), 150 deletions(-) delete mode 100644 server/initializers/index.ts diff --git a/scripts/create-import-video-file-job.ts b/scripts/create-import-video-file-job.ts index 37738ca40..d71e82c14 100644 --- a/scripts/create-import-video-file-job.ts +++ b/scripts/create-import-video-file-job.ts @@ -4,7 +4,7 @@ registerTSPaths() import * as program from 'commander' import { resolve } from 'path' import { VideoModel } from '../server/models/video/video' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { JobQueue } from '../server/lib/job-queue' program diff --git a/scripts/create-transcoding-job.ts b/scripts/create-transcoding-job.ts index 1312e8952..7ee0050e3 100755 --- a/scripts/create-transcoding-job.ts +++ b/scripts/create-transcoding-job.ts @@ -3,7 +3,7 @@ registerTSPaths() import * as program from 'commander' import { VideoModel } from '../server/models/video/video' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { JobQueue } from '../server/lib/job-queue' import { computeResolutionsToTranscode } from '@server/helpers/ffmpeg-utils' import { VideoTranscodingPayload } from '@shared/models' diff --git a/scripts/danger/clean/cleaner.ts b/scripts/danger/clean/cleaner.ts index ed35ef79f..2d5366a91 100644 --- a/scripts/danger/clean/cleaner.ts +++ b/scripts/danger/clean/cleaner.ts @@ -3,7 +3,7 @@ registerTSPaths() import * as Promise from 'bluebird' import * as rimraf from 'rimraf' -import { initDatabaseModels, sequelizeTypescript } from '../../../server/initializers' +import { initDatabaseModels, sequelizeTypescript } from '../../../server/initializers/database' import { CONFIG } from '../../../server/initializers/config' initDatabaseModels(true) diff --git a/scripts/migrations/peertube-2.1.ts b/scripts/migrations/peertube-2.1.ts index 892497a88..e17e58166 100644 --- a/scripts/migrations/peertube-2.1.ts +++ b/scripts/migrations/peertube-2.1.ts @@ -1,7 +1,7 @@ import { registerTSPaths } from '../../server/helpers/register-ts-paths' registerTSPaths() -import { initDatabaseModels, sequelizeTypescript } from '../../server/initializers' +import { initDatabaseModels, sequelizeTypescript } from '../../server/initializers/database' import * as Sequelize from 'sequelize' import { join } from 'path' import { HLS_STREAMING_PLAYLIST_DIRECTORY, STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' diff --git a/scripts/optimize-old-videos.ts b/scripts/optimize-old-videos.ts index a84845068..9595efd9c 100644 --- a/scripts/optimize-old-videos.ts +++ b/scripts/optimize-old-videos.ts @@ -6,7 +6,7 @@ import { getDurationFromVideoFile, getVideoFileBitrate, getVideoFileFPS, getVide import { getMaxBitrate } from '../shared/models/videos' import { VideoModel } from '../server/models/video/video' import { optimizeOriginalVideofile } from '../server/lib/video-transcoding' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { basename, dirname } from 'path' import { copy, move, remove } from 'fs-extra' import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' diff --git a/scripts/prune-storage.ts b/scripts/prune-storage.ts index fa3d81744..2b04e906d 100755 --- a/scripts/prune-storage.ts +++ b/scripts/prune-storage.ts @@ -5,7 +5,7 @@ import * as prompt from 'prompt' import { join } from 'path' import { CONFIG } from '../server/initializers/config' import { VideoModel } from '../server/models/video/video' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { readdir, remove } from 'fs-extra' import { VideoRedundancyModel } from '../server/models/redundancy/video-redundancy' import * as Bluebird from 'bluebird' diff --git a/scripts/reset-password.ts b/scripts/reset-password.ts index 6126c3cd0..863537500 100755 --- a/scripts/reset-password.ts +++ b/scripts/reset-password.ts @@ -2,7 +2,7 @@ import { registerTSPaths } from '../server/helpers/register-ts-paths' registerTSPaths() import * as program from 'commander' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { UserModel } from '../server/models/account/user' import { isUserPasswordValid } from '../server/helpers/custom-validators/users' diff --git a/scripts/update-host.ts b/scripts/update-host.ts index 7b07dea04..2efe326a2 100755 --- a/scripts/update-host.ts +++ b/scripts/update-host.ts @@ -17,7 +17,7 @@ import { VideoCommentModel } from '../server/models/video/video-comment' import { AccountModel } from '../server/models/account/account' import { VideoChannelModel } from '../server/models/video/video-channel' import { VideoStreamingPlaylistModel } from '../server/models/video/video-streaming-playlist' -import { initDatabaseModels } from '../server/initializers' +import { initDatabaseModels } from '../server/initializers/database' import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' import { getServerActor } from '@server/models/application/application' diff --git a/server.ts b/server.ts index b14ebf623..e18e2be5c 100644 --- a/server.ts +++ b/server.ts @@ -84,7 +84,7 @@ migrate() loadLanguages() // ----------- PeerTube modules ----------- -import { installApplication } from './server/initializers' +import { installApplication } from './server/initializers/installer' import { Emailer } from './server/lib/emailer' import { JobQueue } from './server/lib/job-queue' import { VideosPreviewCache, VideosCaptionCache } from './server/lib/files-cache' diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 4da1f3496..77a15e5fc 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts @@ -9,7 +9,7 @@ import { } from '../../../middlewares' import { getFormattedObjects } from '../../../helpers/utils' import { UserVideoHistoryModel } from '../../../models/account/user-video-history' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' const myVideosHistoryRouter = express.Router() diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 3fe7f7e51..bce50aefb 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts @@ -2,7 +2,7 @@ import * as express from 'express' import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { asyncMiddleware, asyncRetryTransactionMiddleware, diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index abd09387c..3b25ceea2 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts @@ -1,7 +1,9 @@ import * as express from 'express' -import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' +import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist' +import { UserRight, VideoBlacklistCreate } from '../../../../shared' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' +import { sequelizeTypescript } from '../../../initializers/database' import { asyncMiddleware, authenticate, @@ -16,11 +18,6 @@ import { videosBlacklistUpdateValidator } from '../../../middlewares' import { VideoBlacklistModel } from '../../../models/video/video-blacklist' -import { sequelizeTypescript } from '../../../initializers' -import { Notifier } from '../../../lib/notifier' -import { sendDeleteVideo } from '../../../lib/activitypub/send' -import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' -import { MVideoBlacklistVideo } from '@server/typings/models' const blacklistRouter = express.Router() @@ -28,7 +25,7 @@ blacklistRouter.post('/:videoId/blacklist', authenticate, ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST), asyncMiddleware(videosBlacklistAddValidator), - asyncMiddleware(addVideoToBlacklist) + asyncMiddleware(addVideoToBlacklistController) ) blacklistRouter.get('/blacklist', @@ -64,29 +61,15 @@ export { // --------------------------------------------------------------------------- -async function addVideoToBlacklist (req: express.Request, res: express.Response) { +async function addVideoToBlacklistController (req: express.Request, res: express.Response) { const videoInstance = res.locals.videoAll const body: VideoBlacklistCreate = req.body - const toCreate = { - videoId: videoInstance.id, - unfederated: body.unfederate === true, - reason: body.reason, - type: VideoBlacklistType.MANUAL - } - - const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate) - blacklist.Video = videoInstance - - if (body.unfederate === true) { - await sendDeleteVideo(videoInstance, undefined) - } - - Notifier.Instance.notifyOnVideoBlacklist(blacklist) + await blacklistVideo(videoInstance, body) logger.info('Video %s blacklisted.', videoInstance.uuid) - return res.type('json').status(204).end() + return res.type('json').sendStatus(204) } async function updateVideoBlacklistController (req: express.Request, res: express.Response) { @@ -98,7 +81,7 @@ async function updateVideoBlacklistController (req: express.Request, res: expres return videoBlacklist.save({ transaction: t }) }) - return res.type('json').status(204).end() + return res.type('json').sendStatus(204) } async function listBlacklist (req: express.Request, res: express.Response) { @@ -117,32 +100,9 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex const videoBlacklist = res.locals.videoBlacklist const video = res.locals.videoAll - const videoBlacklistType = await sequelizeTypescript.transaction(async t => { - const unfederated = videoBlacklist.unfederated - const videoBlacklistType = videoBlacklist.type - - await videoBlacklist.destroy({ transaction: t }) - video.VideoBlacklist = undefined - - // Re federate the video - if (unfederated === true) { - await federateVideoIfNeeded(video, true, t) - } - - return videoBlacklistType - }) - - Notifier.Instance.notifyOnVideoUnblacklist(video) - - if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) { - Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video) - - // Delete on object so new video notifications will send - delete video.VideoBlacklist - Notifier.Instance.notifyOnNewVideoIfNeeded(video) - } + await unblacklistVideo(videoBlacklist, video) logger.info('Video %s removed from blacklist.', video.uuid) - return res.type('json').status(204).end() + return res.type('json').sendStatus(204) } diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index 5f3fed5c0..5070bb3c0 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts @@ -4,7 +4,7 @@ import { ResultList } from '../../../../shared/models' import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' import { logger } from '../../../helpers/logger' import { getFormattedObjects } from '../../../helpers/utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { buildFormattedCommentTree, createVideoComment, markCommentAsDeleted } from '../../../lib/video-comment' import { asyncMiddleware, diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index 190036f85..540a49010 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts @@ -1,6 +1,6 @@ import * as express from 'express' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { asyncMiddleware, asyncRetryTransactionMiddleware, diff --git a/server/initializers/index.ts b/server/initializers/index.ts deleted file mode 100644 index 0fc1a7363..000000000 --- a/server/initializers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './database' -export * from './installer' -export * from './migrator' diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index 7e22125d5..26427aaa1 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts @@ -1,6 +1,6 @@ import { ActivityAnnounce } from '../../../../shared/models/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { VideoShareModel } from '../../../models/video/video-share' import { forwardVideoRelatedActivity } from '../send/utils' import { getOrCreateVideoAndAccountAndChannel } from '../videos' diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index d375e29e3..566bf6992 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts @@ -2,7 +2,7 @@ import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../.. import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { resolveThread } from '../video-comments' import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { forwardVideoRelatedActivity } from '../send/utils' diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index e76132f91..7c8dc83e8 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts @@ -1,7 +1,7 @@ import { ActivityDelete } from '../../../../shared/models/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { ActorModel } from '../../../models/activitypub/actor' import { VideoModel } from '../../../models/video/video' import { VideoCommentModel } from '../../../models/video/video-comment' diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts index debd8a67c..fcdd0b86e 100644 --- a/server/lib/activitypub/process/process-dislike.ts +++ b/server/lib/activitypub/process/process-dislike.ts @@ -1,7 +1,7 @@ import { ActivityCreate, ActivityDislike } from '../../../../shared' import { DislikeObject } from '../../../../shared/models/activitypub/objects' import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { forwardVideoRelatedActivity } from '../send/utils' diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index e6e9084de..9a488a473 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts @@ -2,7 +2,7 @@ import { ActivityCreate, ActivityFlag, VideoAbuseState } from '../../../../share import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { VideoAbuseModel } from '../../../models/video/video-abuse' import { getOrCreateVideoAndAccountAndChannel } from '../videos' import { Notifier } from '../../notifier' diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 8f7828e41..950d421dd 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts @@ -1,7 +1,7 @@ import { ActivityFollow } from '../../../../shared/models/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { sendAccept, sendReject } from '../send' diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index 62be0de42..fba3c76a4 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts @@ -1,6 +1,6 @@ import { ActivityLike } from '../../../../shared/models/activitypub' import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { forwardVideoRelatedActivity } from '../send/utils' import { getOrCreateVideoAndAccountAndChannel } from '../videos' diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 00e9afa10..9804436a2 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts @@ -1,5 +1,5 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' import { APProcessorOptions } from '../../../typings/activitypub-processor.model' import { MActor } from '../../../typings/models' diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index 10643b2e9..9ef6a8a97 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts @@ -2,7 +2,7 @@ import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFile import { DislikeObject } from '../../../../shared/models/activitypub/objects' import { retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { ActorModel } from '../../../models/activitypub/actor' import { ActorFollowModel } from '../../../models/activitypub/actor-follow' diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 9579512b7..98ab0f83d 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts @@ -2,7 +2,7 @@ import { ActivityUpdate, CacheFileObject, VideoTorrentObject } from '../../../.. import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' import { logger } from '../../../helpers/logger' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { AccountModel } from '../../../models/account/account' import { ActorModel } from '../../../models/activitypub/actor' import { VideoChannelModel } from '../../../models/video/video-channel' diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index 28c69b46e..842eecb5b 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts @@ -1,7 +1,7 @@ -import { sequelizeTypescript } from '../initializers' +import { sequelizeTypescript } from '@server/initializers/database' +import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' import { AccountBlocklistModel } from '../models/account/account-blocklist' import { ServerBlocklistModel } from '../models/server/server-blocklist' -import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { return sequelizeTypescript.transaction(async t => { diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 04aac515f..46d52e1cf 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -10,7 +10,7 @@ import { VideoModel } from '../../../models/video/video' import { JobQueue } from '../job-queue' import { federateVideoIfNeeded } from '../../activitypub/videos' import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { sequelizeTypescript } from '../../../initializers' +import { sequelizeTypescript } from '../../../initializers/database' import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' import { Notifier } from '../../notifier' diff --git a/server/lib/plugins/plugin-helpers.ts b/server/lib/plugins/plugin-helpers.ts index 608207e05..de82b4918 100644 --- a/server/lib/plugins/plugin-helpers.ts +++ b/server/lib/plugins/plugin-helpers.ts @@ -3,6 +3,15 @@ import { sequelizeTypescript } from '@server/initializers/database' import { buildLogger } from '@server/helpers/logger' import { VideoModel } from '@server/models/video/video' import { WEBSERVER } from '@server/initializers/constants' +import { ServerModel } from '@server/models/server/server' +import { getServerActor } from '@server/models/application/application' +import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' +import { ServerBlocklistModel } from '@server/models/server/server-blocklist' +import { AccountModel } from '@server/models/account/account' +import { VideoBlacklistCreate } from '@shared/models' +import { blacklistVideo, unblacklistVideo } from '../video-blacklist' +import { VideoBlacklistModel } from '@server/models/video/video-blacklist' +import { AccountBlocklistModel } from '@server/models/account/account-blocklist' function buildPluginHelpers (npmName: string): PeerTubeHelpers { const logger = buildPluginLogger(npmName) @@ -12,11 +21,17 @@ function buildPluginHelpers (npmName: string): PeerTubeHelpers { const config = buildConfigHelpers() + const server = buildServerHelpers() + + const moderation = buildModerationHelpers() + return { logger, database, videos, - config + config, + moderation, + server } } @@ -36,8 +51,18 @@ function buildDatabaseHelpers () { } } +function buildServerHelpers () { + return { + getServerActor: () => getServerActor() + } +} + function buildVideosHelpers () { return { + loadByUrl: (url: string) => { + return VideoModel.loadByUrl(url) + }, + removeVideo: (id: number) => { return sequelizeTypescript.transaction(async t => { const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id, t) @@ -48,6 +73,57 @@ function buildVideosHelpers () { } } +function buildModerationHelpers () { + return { + blockServer: async (options: { byAccountId: number, hostToBlock: string }) => { + const serverToBlock = await ServerModel.loadOrCreateByHost(options.hostToBlock) + + await addServerInBlocklist(options.byAccountId, serverToBlock.id) + }, + + unblockServer: async (options: { byAccountId: number, hostToUnblock: string }) => { + const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(options.byAccountId, options.hostToUnblock) + if (!serverBlock) return + + await removeServerFromBlocklist(serverBlock) + }, + + blockAccount: async (options: { byAccountId: number, handleToBlock: string }) => { + const accountToBlock = await AccountModel.loadByNameWithHost(options.handleToBlock) + if (!accountToBlock) return + + await addAccountInBlocklist(options.byAccountId, accountToBlock.id) + }, + + unblockAccount: async (options: { byAccountId: number, handleToUnblock: string }) => { + const targetAccount = await AccountModel.loadByNameWithHost(options.handleToUnblock) + if (!targetAccount) return + + const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(options.byAccountId, targetAccount.id) + if (!accountBlock) return + + await removeAccountFromBlocklist(accountBlock) + }, + + blacklistVideo: async (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => { + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) + if (!video) return + + await blacklistVideo(video, options.createOptions) + }, + + unblacklistVideo: async (options: { videoIdOrUUID: number | string }) => { + const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) + if (!video) return + + const videoBlacklist = await VideoBlacklistModel.loadByVideoId(video.id) + if (!videoBlacklist) return + + await unblacklistVideo(videoBlacklist, video) + } + } +} + function buildConfigHelpers () { return { getWebserverUrl () { diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index 3b90b1b94..bd60c6201 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts @@ -1,12 +1,22 @@ import { Transaction } from 'sequelize' +import { sequelizeTypescript } from '@server/initializers/database' +import { + MUser, + MVideoAccountLight, + MVideoBlacklist, + MVideoBlacklistVideo, + MVideoFullLight, + MVideoWithBlacklistLight +} from '@server/typings/models' +import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../shared/models' +import { UserAdminFlag } from '../../shared/models/users/user-flag.model' +import { logger } from '../helpers/logger' import { CONFIG } from '../initializers/config' -import { UserRight, VideoBlacklistType } from '../../shared/models' import { VideoBlacklistModel } from '../models/video/video-blacklist' -import { logger } from '../helpers/logger' -import { UserAdminFlag } from '../../shared/models/users/user-flag.model' -import { Hooks } from './plugins/hooks' +import { sendDeleteVideo } from './activitypub/send' +import { federateVideoIfNeeded } from './activitypub/videos' import { Notifier } from './notifier' -import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' +import { Hooks } from './plugins/hooks' async function autoBlacklistVideoIfNeeded (parameters: { video: MVideoWithBlacklistLight @@ -49,6 +59,60 @@ async function autoBlacklistVideoIfNeeded (parameters: { return true } +async function blacklistVideo (videoInstance: MVideoAccountLight, options: VideoBlacklistCreate) { + const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create({ + videoId: videoInstance.id, + unfederated: options.unfederate === true, + reason: options.reason, + type: VideoBlacklistType.MANUAL + } + ) + blacklist.Video = videoInstance + + if (options.unfederate === true) { + await sendDeleteVideo(videoInstance, undefined) + } + + Notifier.Instance.notifyOnVideoBlacklist(blacklist) +} + +async function unblacklistVideo (videoBlacklist: MVideoBlacklist, video: MVideoFullLight) { + const videoBlacklistType = await sequelizeTypescript.transaction(async t => { + const unfederated = videoBlacklist.unfederated + const videoBlacklistType = videoBlacklist.type + + await videoBlacklist.destroy({ transaction: t }) + video.VideoBlacklist = undefined + + // Re federate the video + if (unfederated === true) { + await federateVideoIfNeeded(video, true, t) + } + + return videoBlacklistType + }) + + Notifier.Instance.notifyOnVideoUnblacklist(video) + + if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) { + Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video) + + // Delete on object so new video notifications will send + delete video.VideoBlacklist + Notifier.Instance.notifyOnNewVideoIfNeeded(video) + } +} + +// --------------------------------------------------------------------------- + +export { + autoBlacklistVideoIfNeeded, + blacklistVideo, + unblacklistVideo +} + +// --------------------------------------------------------------------------- + function autoBlacklistNeeded (parameters: { video: MVideoWithBlacklistLight isRemote: boolean @@ -66,9 +130,3 @@ function autoBlacklistNeeded (parameters: { return true } - -// --------------------------------------------------------------------------- - -export { - autoBlacklistVideoIfNeeded -} diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index c00a7e4df..27224ff9b 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts @@ -84,10 +84,7 @@ const blockServerValidator = [ .end() } - let server = await ServerModel.loadByHost(host) - if (!server) { - server = await ServerModel.create({ host }) - } + const server = await ServerModel.loadOrCreateByHost(host) res.locals.server = server diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 8b07115f1..5131257ec 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts @@ -71,6 +71,13 @@ export class ServerModel extends Model { return ServerModel.findOne(query) } + static async loadOrCreateByHost (host: string) { + let server = await ServerModel.loadByHost(host) + if (!server) server = await ServerModel.create({ host }) + + return server + } + isBlocked () { return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 } diff --git a/server/tests/fixtures/peertube-plugin-test-four/main.js b/server/tests/fixtures/peertube-plugin-test-four/main.js index 2e81550c1..067c3fe15 100644 --- a/server/tests/fixtures/peertube-plugin-test-four/main.js +++ b/server/tests/fixtures/peertube-plugin-test-four/main.js @@ -1,30 +1,70 @@ async function register ({ peertubeHelpers, - registerHook + registerHook, + getRouter }) { const logger = peertubeHelpers.logger logger.info('Hello world from plugin four') - const username = 'root' - const results = await peertubeHelpers.database.query( - 'SELECT "email" from "user" WHERE "username" = $username', - { - type: 'SELECT', - bind: { username } - } - ) + { + const username = 'root' + const results = await peertubeHelpers.database.query( + 'SELECT "email" from "user" WHERE "username" = $username', + { + type: 'SELECT', + bind: { username } + } + ) + + logger.info('root email is ' + results[0]['email']) + } + + { + registerHook({ + target: 'action:api.video.viewed', + handler: async ({ video }) => { + const videoFromDB = await peertubeHelpers.videos.loadByUrl(video.url) + logger.info('video from DB uuid is %s.', videoFromDB.uuid) + + await peertubeHelpers.videos.removeVideo(video.id) - logger.info('root email is ' + results[0]['email']) + logger.info('Video deleted by plugin four.') + } + }) + } - registerHook({ - target: 'action:api.video.viewed', - handler: async ({ video }) => { - await peertubeHelpers.videos.removeVideo(video.id) + { + const serverActor = await peertubeHelpers.server.getServerActor() + logger.info('server actor name is %s', serverActor.preferredUsername) + } - logger.info('Video deleted by plugin four.') + { + logger.info('server url is %s', peertubeHelpers.config.getWebserverUrl()) + } + + { + const actions = { + blockServer, + unblockServer, + blockAccount, + unblockAccount, + blacklist, + unblacklist } - }) + + const router = getRouter() + router.post('/commander', async (req, res) => { + try { + await actions[req.body.command](peertubeHelpers, req.body) + + res.sendStatus(204) + } catch (err) { + logger.error('Error in commander.', { err }) + res.sendStatus(500) + } + }) + } } async function unregister () { @@ -37,3 +77,38 @@ module.exports = { } // ########################################################################### + +async function blockServer (peertubeHelpers, body) { + const serverActor = await peertubeHelpers.server.getServerActor() + + await peertubeHelpers.moderation.blockServer({ byAccountId: serverActor.Account.id, hostToBlock: body.hostToBlock }) +} + +async function unblockServer (peertubeHelpers, body) { + const serverActor = await peertubeHelpers.server.getServerActor() + + await peertubeHelpers.moderation.unblockServer({ byAccountId: serverActor.Account.id, hostToUnblock: body.hostToUnblock }) +} + +async function blockAccount (peertubeHelpers, body) { + const serverActor = await peertubeHelpers.server.getServerActor() + + await peertubeHelpers.moderation.blockAccount({ byAccountId: serverActor.Account.id, handleToBlock: body.handleToBlock }) +} + +async function unblockAccount (peertubeHelpers, body) { + const serverActor = await peertubeHelpers.server.getServerActor() + + await peertubeHelpers.moderation.unblockAccount({ byAccountId: serverActor.Account.id, handleToUnblock: body.handleToUnblock }) +} + +async function blacklist (peertubeHelpers, body) { + await peertubeHelpers.moderation.blacklistVideo({ + videoIdOrUUID: body.videoUUID, + createOptions: body + }) +} + +async function unblacklist (peertubeHelpers, body) { + await peertubeHelpers.moderation.unblacklistVideo({ videoIdOrUUID: body.videoUUID }) +} diff --git a/server/tests/plugins/plugin-helpers.ts b/server/tests/plugins/plugin-helpers.ts index dfe8ebe55..0915603d0 100644 --- a/server/tests/plugins/plugin-helpers.ts +++ b/server/tests/plugins/plugin-helpers.ts @@ -1,66 +1,210 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import 'mocha' -import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' import { checkVideoFilesWereRemoved, + doubleFollow, getPluginTestPath, getVideo, installPlugin, + makePostBodyRequest, setAccessTokensToServers, uploadVideoAndGetId, - viewVideo + viewVideo, + getVideosList, + waitJobs } from '../../../shared/extra-utils' +import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' +import { expect } from 'chai' + +function postCommand (server: ServerInfo, command: string, bodyArg?: object) { + const body = { command } + if (bodyArg) Object.assign(body, bodyArg) + + return makePostBodyRequest({ + url: server.url, + path: '/plugins/test-four/router/commander', + fields: body, + statusCodeExpected: 204 + }) +} describe('Test plugin helpers', function () { - let server: ServerInfo + let servers: ServerInfo[] before(async function () { - this.timeout(30000) + this.timeout(60000) + + servers = await flushAndRunMultipleServers(2) + await setAccessTokensToServers(servers) - server = await flushAndRunServer(1) - await setAccessTokensToServers([ server ]) + await doubleFollow(servers[0], servers[1]) await installPlugin({ - url: server.url, - accessToken: server.accessToken, + url: servers[0].url, + accessToken: servers[0].accessToken, path: getPluginTestPath('-four') }) }) - it('Should have logged things', async function () { - await waitUntilLog(server, 'localhost:' + server.port + ' peertube-plugin-test-four', 1, false) - await waitUntilLog(server, 'Hello world from plugin four', 1) + describe('Logger', function () { + + it('Should have logged things', async function () { + await waitUntilLog(servers[0], 'localhost:' + servers[0].port + ' peertube-plugin-test-four', 1, false) + await waitUntilLog(servers[0], 'Hello world from plugin four', 1) + }) }) - it('Should have made a query', async function () { - await waitUntilLog(server, `root email is admin${server.internalServerNumber}@example.com`, 1) + describe('Database', function () { + + it('Should have made a query', async function () { + await waitUntilLog(servers[0], `root email is admin${servers[0].internalServerNumber}@example.com`) + }) + }) + + describe('Config', function () { + + it('Should have the correct webserver url', async function () { + await waitUntilLog(servers[0], `server url is http://localhost:${servers[0].port}`) + }) + }) + + describe('Server', function () { + + it('Should get the server actor', async function () { + await waitUntilLog(servers[0], 'server actor name is peertube') + }) + }) + + describe('Moderation', function () { + let videoUUIDServer1: string + + before(async function () { + this.timeout(15000) + + { + const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) + videoUUIDServer1 = res.uuid + } + + { + await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) + } + + await waitJobs(servers) + + const res = await getVideosList(servers[0].url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(2) + }) + + it('Should mute server 2', async function () { + this.timeout(10000) + await postCommand(servers[0], 'blockServer', { hostToBlock: `localhost:${servers[1].port}` }) + + const res = await getVideosList(servers[0].url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(1) + expect(videos[0].name).to.equal('video server 1') + }) + + it('Should unmute server 2', async function () { + await postCommand(servers[0], 'unblockServer', { hostToUnblock: `localhost:${servers[1].port}` }) + + const res = await getVideosList(servers[0].url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(2) + }) + + it('Should mute account of server 2', async function () { + await postCommand(servers[0], 'blockAccount', { handleToBlock: `root@localhost:${servers[1].port}` }) + + const res = await getVideosList(servers[0].url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(1) + expect(videos[0].name).to.equal('video server 1') + }) + + it('Should unmute account of server 2', async function () { + await postCommand(servers[0], 'unblockAccount', { handleToUnblock: `root@localhost:${servers[1].port}` }) + + const res = await getVideosList(servers[0].url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(2) + }) + + it('Should blacklist video', async function () { + this.timeout(10000) + + await postCommand(servers[0], 'blacklist', { videoUUID: videoUUIDServer1, unfederate: true }) + + await waitJobs(servers) + + for (const server of servers) { + const res = await getVideosList(server.url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(1) + expect(videos[0].name).to.equal('video server 2') + } + }) + + it('Should unblacklist video', async function () { + this.timeout(10000) + + await postCommand(servers[0], 'unblacklist', { videoUUID: videoUUIDServer1 }) + + await waitJobs(servers) + + for (const server of servers) { + const res = await getVideosList(server.url) + const videos = res.body.data + + expect(videos).to.have.lengthOf(2) + } + }) }) - it('Should remove a video after a view', async function () { - this.timeout(20000) + describe('Videos', function () { + let videoUUID: string + + before(async () => { + const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video1' }) + videoUUID = res.uuid + }) - const videoUUID = (await uploadVideoAndGetId({ server: server, videoName: 'video1' })).uuid + it('Should remove a video after a view', async function () { + this.timeout(20000) - // Should not throw -> video exists - await getVideo(server.url, videoUUID) - // Should delete the video - await viewVideo(server.url, videoUUID) + // Should not throw -> video exists + await getVideo(servers[0].url, videoUUID) + // Should delete the video + await viewVideo(servers[0].url, videoUUID) - await waitUntilLog(server, 'Video deleted by plugin four.', 1) + await waitUntilLog(servers[0], 'Video deleted by plugin four.') - try { - // Should throw because the video should have been deleted - await getVideo(server.url, videoUUID) - throw new Error('Video exists') - } catch (err) { - if (err.message.includes('exists')) throw err - } + try { + // Should throw because the video should have been deleted + await getVideo(servers[0].url, videoUUID) + throw new Error('Video exists') + } catch (err) { + if (err.message.includes('exists')) throw err + } - await checkVideoFilesWereRemoved(videoUUID, server.internalServerNumber) + await checkVideoFilesWereRemoved(videoUUID, servers[0].internalServerNumber) + }) + + it('Should have fetched the video by URL', async function () { + await waitUntilLog(servers[0], `video from DB uuid is ${videoUUID}`) + }) }) after(async function () { - await cleanupTests([ server ]) + await cleanupTests(servers) }) }) diff --git a/server/typings/plugins/register-server-option.model.ts b/server/typings/plugins/register-server-option.model.ts index 7f933b43a..8f1d66007 100644 --- a/server/typings/plugins/register-server-option.model.ts +++ b/server/typings/plugins/register-server-option.model.ts @@ -1,19 +1,23 @@ -import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' -import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' -import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' -import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' -import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' -import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' -import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' -import { Logger } from 'winston' +import * as Bluebird from 'bluebird' import { Router } from 'express' -import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' +import { Logger } from 'winston' +import { ActorModel } from '@server/models/activitypub/actor' +import { VideoBlacklistCreate } from '@shared/models' import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' +import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' import { RegisterServerAuthExternalOptions, RegisterServerAuthExternalResult, RegisterServerAuthPassOptions } from '@shared/models/plugins/register-server-auth.model' +import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' +import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' +import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' +import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' +import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' +import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' +import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' +import { MVideoThumbnail } from '../models' export type PeerTubeHelpers = { logger: Logger @@ -23,12 +27,28 @@ export type PeerTubeHelpers = { } videos: { + loadByUrl: (url: string) => Bluebird + removeVideo: (videoId: number) => Promise } config: { getWebserverUrl: () => string } + + moderation: { + blockServer: (options: { byAccountId: number, hostToBlock: string }) => Promise + unblockServer: (options: { byAccountId: number, hostToUnblock: string }) => Promise + blockAccount: (options: { byAccountId: number, handleToBlock: string }) => Promise + unblockAccount: (options: { byAccountId: number, handleToUnblock: string }) => Promise + + blacklistVideo: (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => Promise + unblacklistVideo: (options: { videoIdOrUUID: number | string }) => Promise + } + + server: { + getServerActor: () => Promise + } } export type RegisterServerOptions = { -- 2.41.0