diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 2454b1ec9..411ec8630 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts @@ -126,7 +126,7 @@ videoChannelRouter.delete('/:nameWithHost', videoChannelRouter.get('/:nameWithHost', asyncMiddleware(videoChannelsNameWithHostValidator), - getVideoChannel + asyncMiddleware(getVideoChannel) ) videoChannelRouter.get('/:nameWithHost/video-playlists', @@ -171,12 +171,19 @@ export { async function listVideoChannels (req: express.Request, res: express.Response) { const serverActor = await getServerActor() - const resultList = await VideoChannelModel.listForApi({ + + const apiOptions = await Hooks.wrapObject({ actorId: serverActor.id, start: req.query.start, count: req.query.count, sort: req.query.sort - }) + }, 'filter:api.video-channels.list.params') + + const resultList = await Hooks.wrapPromiseFun( + VideoChannelModel.listForApi, + apiOptions, + 'filter:api.video-channels.list.result' + ) return res.json(getFormattedObjects(resultList.data, resultList.total)) } @@ -243,6 +250,8 @@ async function addVideoChannel (req: express.Request, res: express.Response) { auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())) logger.info('Video channel %s created.', videoChannelCreated.Actor.url) + Hooks.runAction('action:api.video-channel.created', { videoChannel: videoChannelCreated, req, res }) + return res.json({ videoChannel: { id: videoChannelCreated.id @@ -281,6 +290,8 @@ async function updateVideoChannel (req: express.Request, res: express.Response) oldVideoChannelAuditKeys ) + Hooks.runAction('action:api.video-channel.updated', { videoChannel: videoChannelInstanceUpdated, req, res }) + logger.info('Video channel %s updated.', videoChannelInstance.Actor.url) }) } catch (err) { @@ -310,6 +321,8 @@ async function removeVideoChannel (req: express.Request, res: express.Response) await videoChannelInstance.destroy({ transaction: t }) + Hooks.runAction('action:api.video-channel.deleted', { videoChannel: videoChannelInstance, req, res }) + auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url) }) @@ -317,8 +330,9 @@ async function removeVideoChannel (req: express.Request, res: express.Response) return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() } -function getVideoChannel (req: express.Request, res: express.Response) { - const videoChannel = res.locals.videoChannel +async function getVideoChannel (req: express.Request, res: express.Response) { + const id = res.locals.videoChannel.id + const videoChannel = await Hooks.wrapObject(res.locals.videoChannel, 'filter:api.video-channel.get.result', { id }) if (videoChannel.isOutdated()) { JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannel.Actor.url } }) diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index d4e08293e..eca72c397 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts @@ -110,7 +110,7 @@ videosRouter.get('/:id', optionalAuthenticate, asyncMiddleware(videosCustomGetValidator('for-api')), asyncMiddleware(checkVideoFollowConstraints), - getVideo + asyncMiddleware(getVideo) ) videosRouter.delete('/:id', @@ -144,8 +144,11 @@ function listVideoPrivacies (_req: express.Request, res: express.Response) { res.json(VIDEO_PRIVACIES) } -function getVideo (_req: express.Request, res: express.Response) { - const video = res.locals.videoAPI +async function getVideo (_req: express.Request, res: express.Response) { + const videoId = res.locals.videoAPI.id + const userId = res.locals.oauth?.token.User.id + + const video = await Hooks.wrapObject(res.locals.videoAPI, 'filter:api.video.get.result', { id: videoId, userId }) if (video.isOutdated()) { JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) diff --git a/server/lib/model-loaders/video.ts b/server/lib/model-loaders/video.ts index cef6a367c..a64389a89 100644 --- a/server/lib/model-loaders/video.ts +++ b/server/lib/model-loaders/video.ts @@ -7,7 +7,7 @@ import { MVideoImmutable, MVideoThumbnail } from '@server/types/models' -import { Hooks } from '../plugins/hooks' + type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes' @@ -27,13 +27,7 @@ function loadVideo ( userId?: number ): Promise { - if (fetchType === 'for-api') { - return Hooks.wrapPromiseFun( - VideoModel.loadForGetAPI, - { id, userId }, - 'filter:api.video.get.result' - ) - } + if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId }) if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId) diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js index c395ac7aa..8d7584eba 100644 --- a/server/tests/fixtures/peertube-plugin-test/main.js +++ b/server/tests/fixtures/peertube-plugin-test/main.js @@ -7,6 +7,10 @@ async function register ({ registerHook, registerSetting, settingsManager, stora 'action:api.video.uploaded', 'action:api.video.viewed', + 'action:api.video-channel.created', + 'action:api.video-channel.updated', + 'action:api.video-channel.deleted', + 'action:api.live-video.created', 'action:api.video-thread.created', @@ -93,6 +97,29 @@ async function register ({ registerHook, registerSetting, settingsManager, stora } }) + // --------------------------------------------------------------------------- + + registerHook({ + target: 'filter:api.video-channels.list.params', + handler: obj => addToCount(obj, 1) + }) + + registerHook({ + target: 'filter:api.video-channels.list.result', + handler: obj => addToTotal(obj, 1) + }) + + registerHook({ + target: 'filter:api.video-channel.get.result', + handler: channel => { + channel.name += ' <3' + + return channel + } + }) + + // --------------------------------------------------------------------------- + for (const hook of [ 'filter:api.video.upload.accept.result', 'filter:api.live-video.create.accept.result' ]) { registerHook({ target: hook, diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index 57ede2701..209db95a4 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts @@ -65,6 +65,39 @@ describe('Test plugin action hooks', function () { await checkHook('action:api.video.viewed') }) + + it('Should run action:api.video.deleted', async function () { + await servers[0].videos.remove({ id: videoUUID }) + + await checkHook('action:api.video.deleted') + }) + + after(async function () { + const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) + videoUUID = uuid + }) + }) + + describe('Video channel hooks', function () { + const channelName = 'my_super_channel' + + it('Should run action:api.video-channel.created', async function () { + await servers[0].channels.create({ attributes: { name: channelName } }) + + await checkHook('action:api.video-channel.created') + }) + + it('Should run action:api.video-channel.updated', async function () { + await servers[0].channels.update({ channelName, attributes: { displayName: 'my display name' } }) + + await checkHook('action:api.video-channel.updated') + }) + + it('Should run action:api.video-channel.deleted', async function () { + await servers[0].channels.delete({ channelName }) + + await checkHook('action:api.video-channel.deleted') + }) }) describe('Live hooks', function () { diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 5ed24a58e..015459ead 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts @@ -295,7 +295,7 @@ describe('Test plugin filter hooks', function () { await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.result', 3) }) - describe('Should run filter:video.auto-blacklist.result', function () { + describe('filter:video.auto-blacklist.result', function () { async function checkIsBlacklisted (id: number | string, value: boolean) { const video = await servers[0].videos.getWithToken({ id }) @@ -691,6 +691,28 @@ describe('Test plugin filter hooks', function () { }) }) + describe('Video channel filters', async function () { + + it('Should run filter:api.video-channels.list.params', async function () { + const { data } = await servers[0].channels.list({ start: 0, count: 0 }) + + // plugin do +1 to the count parameter + expect(data).to.have.lengthOf(1) + }) + + it('Should run filter:api.video-channels.list.result', async function () { + const { total } = await servers[0].channels.list({ start: 0, count: 1 }) + + // plugin do +1 to the total parameter + expect(total).to.equal(4) + }) + + it('Should run filter:api.video-channel.get.result', async function () { + const channel = await servers[0].channels.get({ channelName: 'root_channel' }) + expect(channel.displayName).to.equal('Main root channel <3') + }) + }) + after(async function () { await cleanupTests(servers) }) diff --git a/shared/models/plugins/server/server-hook.model.ts b/shared/models/plugins/server/server-hook.model.ts index 08da95177..5f3a5be10 100644 --- a/shared/models/plugins/server/server-hook.model.ts +++ b/shared/models/plugins/server/server-hook.model.ts @@ -45,6 +45,13 @@ export const serverFilterHookObject = { // Used to get detailed video information (video watch page for example) 'filter:api.video.get.result': true, + // Filter params/results when listing video channels + 'filter:api.video-channels.list.params': true, + 'filter:api.video-channels.list.result': true, + + // Filter the result when getting a video channel + 'filter:api.video-channel.get.result': true, + // Filter the result of the accept upload/live, import via torrent/url functions // If this function returns false then the upload is aborted with an error 'filter:api.video.upload.accept.result': true, @@ -116,6 +123,13 @@ export const serverActionHookObject = { // Fired when a local video is viewed 'action:api.video.viewed': true, + // Fired when a video channel is created + 'action:api.video-channel.created': true, + // Fired when a video channel is updated + 'action:api.video-channel.updated': true, + // Fired when a video channel is deleted + 'action:api.video-channel.deleted': true, + // Fired when a live video is created 'action:api.live-video.created': true,