Add moderation helpers to plugins
This commit is contained in:
parent
3cc665f48f
commit
80fdaf0645
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export * from './database'
|
||||
export * from './installer'
|
||||
export * from './migrator'
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import { Transaction } from 'sequelize'
|
||||
import { CONFIG } from '../initializers/config'
|
||||
import { UserRight, VideoBlacklistType } from '../../shared/models'
|
||||
import { VideoBlacklistModel } from '../models/video/video-blacklist'
|
||||
import { logger } from '../helpers/logger'
|
||||
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 { Hooks } from './plugins/hooks'
|
||||
import { logger } from '../helpers/logger'
|
||||
import { CONFIG } from '../initializers/config'
|
||||
import { VideoBlacklistModel } from '../models/video/video-blacklist'
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -71,6 +71,13 @@ export class ServerModel extends Model<ServerModel> {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -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('Video deleted by plugin four.')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const serverActor = await peertubeHelpers.server.getServerActor()
|
||||
logger.info('server actor name is %s', serverActor.preferredUsername)
|
||||
}
|
||||
|
||||
{
|
||||
logger.info('server url is %s', peertubeHelpers.config.getWebserverUrl())
|
||||
}
|
||||
|
||||
{
|
||||
const actions = {
|
||||
blockServer,
|
||||
unblockServer,
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
blacklist,
|
||||
unblacklist
|
||||
}
|
||||
)
|
||||
|
||||
logger.info('root email is ' + results[0]['email'])
|
||||
const router = getRouter()
|
||||
router.post('/commander', async (req, res) => {
|
||||
try {
|
||||
await actions[req.body.command](peertubeHelpers, req.body)
|
||||
|
||||
registerHook({
|
||||
target: 'action:api.video.viewed',
|
||||
handler: async ({ video }) => {
|
||||
await peertubeHelpers.videos.removeVideo(video.id)
|
||||
|
||||
logger.info('Video deleted by plugin four.')
|
||||
}
|
||||
})
|
||||
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 })
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
server = await flushAndRunServer(1)
|
||||
await setAccessTokensToServers([ server ])
|
||||
servers = await flushAndRunMultipleServers(2)
|
||||
await setAccessTokensToServers(servers)
|
||||
|
||||
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`)
|
||||
})
|
||||
})
|
||||
|
||||
it('Should remove a video after a view', async function () {
|
||||
this.timeout(20000)
|
||||
describe('Config', function () {
|
||||
|
||||
const videoUUID = (await uploadVideoAndGetId({ server: server, videoName: 'video1' })).uuid
|
||||
it('Should have the correct webserver url', async function () {
|
||||
await waitUntilLog(servers[0], `server url is http://localhost:${servers[0].port}`)
|
||||
})
|
||||
})
|
||||
|
||||
// Should not throw -> video exists
|
||||
await getVideo(server.url, videoUUID)
|
||||
// Should delete the video
|
||||
await viewVideo(server.url, videoUUID)
|
||||
describe('Server', function () {
|
||||
|
||||
await waitUntilLog(server, 'Video deleted by plugin four.', 1)
|
||||
it('Should get the server actor', async function () {
|
||||
await waitUntilLog(servers[0], 'server actor name is peertube')
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
describe('Moderation', function () {
|
||||
let videoUUIDServer1: string
|
||||
|
||||
await checkVideoFilesWereRemoved(videoUUID, server.internalServerNumber)
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('Videos', function () {
|
||||
let videoUUID: string
|
||||
|
||||
before(async () => {
|
||||
const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video1' })
|
||||
videoUUID = res.uuid
|
||||
})
|
||||
|
||||
it('Should remove a video after a view', async function () {
|
||||
this.timeout(20000)
|
||||
|
||||
// Should not throw -> video exists
|
||||
await getVideo(servers[0].url, videoUUID)
|
||||
// Should delete the video
|
||||
await viewVideo(servers[0].url, videoUUID)
|
||||
|
||||
await waitUntilLog(servers[0], 'Video deleted by plugin four.')
|
||||
|
||||
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, 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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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<MVideoThumbnail>
|
||||
|
||||
removeVideo: (videoId: number) => Promise<void>
|
||||
}
|
||||
|
||||
config: {
|
||||
getWebserverUrl: () => string
|
||||
}
|
||||
|
||||
moderation: {
|
||||
blockServer: (options: { byAccountId: number, hostToBlock: string }) => Promise<void>
|
||||
unblockServer: (options: { byAccountId: number, hostToUnblock: string }) => Promise<void>
|
||||
blockAccount: (options: { byAccountId: number, handleToBlock: string }) => Promise<void>
|
||||
unblockAccount: (options: { byAccountId: number, handleToUnblock: string }) => Promise<void>
|
||||
|
||||
blacklistVideo: (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => Promise<void>
|
||||
unblacklistVideo: (options: { videoIdOrUUID: number | string }) => Promise<void>
|
||||
}
|
||||
|
||||
server: {
|
||||
getServerActor: () => Promise<ActorModel>
|
||||
}
|
||||
}
|
||||
|
||||
export type RegisterServerOptions = {
|
||||
|
|
Loading…
Reference in New Issue