diff options
-rw-r--r-- | server/controllers/api/video-channel.ts | 24 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 9 | ||||
-rw-r--r-- | server/lib/model-loaders/video.ts | 10 | ||||
-rw-r--r-- | server/tests/fixtures/peertube-plugin-test/main.js | 27 | ||||
-rw-r--r-- | server/tests/plugins/action-hooks.ts | 33 | ||||
-rw-r--r-- | server/tests/plugins/filter-hooks.ts | 24 | ||||
-rw-r--r-- | shared/models/plugins/server/server-hook.model.ts | 14 |
7 files changed, 124 insertions, 17 deletions
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', | |||
126 | 126 | ||
127 | videoChannelRouter.get('/:nameWithHost', | 127 | videoChannelRouter.get('/:nameWithHost', |
128 | asyncMiddleware(videoChannelsNameWithHostValidator), | 128 | asyncMiddleware(videoChannelsNameWithHostValidator), |
129 | getVideoChannel | 129 | asyncMiddleware(getVideoChannel) |
130 | ) | 130 | ) |
131 | 131 | ||
132 | videoChannelRouter.get('/:nameWithHost/video-playlists', | 132 | videoChannelRouter.get('/:nameWithHost/video-playlists', |
@@ -171,12 +171,19 @@ export { | |||
171 | 171 | ||
172 | async function listVideoChannels (req: express.Request, res: express.Response) { | 172 | async function listVideoChannels (req: express.Request, res: express.Response) { |
173 | const serverActor = await getServerActor() | 173 | const serverActor = await getServerActor() |
174 | const resultList = await VideoChannelModel.listForApi({ | 174 | |
175 | const apiOptions = await Hooks.wrapObject({ | ||
175 | actorId: serverActor.id, | 176 | actorId: serverActor.id, |
176 | start: req.query.start, | 177 | start: req.query.start, |
177 | count: req.query.count, | 178 | count: req.query.count, |
178 | sort: req.query.sort | 179 | sort: req.query.sort |
179 | }) | 180 | }, 'filter:api.video-channels.list.params') |
181 | |||
182 | const resultList = await Hooks.wrapPromiseFun( | ||
183 | VideoChannelModel.listForApi, | ||
184 | apiOptions, | ||
185 | 'filter:api.video-channels.list.result' | ||
186 | ) | ||
180 | 187 | ||
181 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 188 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
182 | } | 189 | } |
@@ -243,6 +250,8 @@ async function addVideoChannel (req: express.Request, res: express.Response) { | |||
243 | auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())) | 250 | auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())) |
244 | logger.info('Video channel %s created.', videoChannelCreated.Actor.url) | 251 | logger.info('Video channel %s created.', videoChannelCreated.Actor.url) |
245 | 252 | ||
253 | Hooks.runAction('action:api.video-channel.created', { videoChannel: videoChannelCreated, req, res }) | ||
254 | |||
246 | return res.json({ | 255 | return res.json({ |
247 | videoChannel: { | 256 | videoChannel: { |
248 | id: videoChannelCreated.id | 257 | id: videoChannelCreated.id |
@@ -281,6 +290,8 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
281 | oldVideoChannelAuditKeys | 290 | oldVideoChannelAuditKeys |
282 | ) | 291 | ) |
283 | 292 | ||
293 | Hooks.runAction('action:api.video-channel.updated', { videoChannel: videoChannelInstanceUpdated, req, res }) | ||
294 | |||
284 | logger.info('Video channel %s updated.', videoChannelInstance.Actor.url) | 295 | logger.info('Video channel %s updated.', videoChannelInstance.Actor.url) |
285 | }) | 296 | }) |
286 | } catch (err) { | 297 | } catch (err) { |
@@ -310,6 +321,8 @@ async function removeVideoChannel (req: express.Request, res: express.Response) | |||
310 | 321 | ||
311 | await videoChannelInstance.destroy({ transaction: t }) | 322 | await videoChannelInstance.destroy({ transaction: t }) |
312 | 323 | ||
324 | Hooks.runAction('action:api.video-channel.deleted', { videoChannel: videoChannelInstance, req, res }) | ||
325 | |||
313 | auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) | 326 | auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) |
314 | logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url) | 327 | logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url) |
315 | }) | 328 | }) |
@@ -317,8 +330,9 @@ async function removeVideoChannel (req: express.Request, res: express.Response) | |||
317 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() | 330 | return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() |
318 | } | 331 | } |
319 | 332 | ||
320 | function getVideoChannel (req: express.Request, res: express.Response) { | 333 | async function getVideoChannel (req: express.Request, res: express.Response) { |
321 | const videoChannel = res.locals.videoChannel | 334 | const id = res.locals.videoChannel.id |
335 | const videoChannel = await Hooks.wrapObject(res.locals.videoChannel, 'filter:api.video-channel.get.result', { id }) | ||
322 | 336 | ||
323 | if (videoChannel.isOutdated()) { | 337 | if (videoChannel.isOutdated()) { |
324 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannel.Actor.url } }) | 338 | 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', | |||
110 | optionalAuthenticate, | 110 | optionalAuthenticate, |
111 | asyncMiddleware(videosCustomGetValidator('for-api')), | 111 | asyncMiddleware(videosCustomGetValidator('for-api')), |
112 | asyncMiddleware(checkVideoFollowConstraints), | 112 | asyncMiddleware(checkVideoFollowConstraints), |
113 | getVideo | 113 | asyncMiddleware(getVideo) |
114 | ) | 114 | ) |
115 | 115 | ||
116 | videosRouter.delete('/:id', | 116 | videosRouter.delete('/:id', |
@@ -144,8 +144,11 @@ function listVideoPrivacies (_req: express.Request, res: express.Response) { | |||
144 | res.json(VIDEO_PRIVACIES) | 144 | res.json(VIDEO_PRIVACIES) |
145 | } | 145 | } |
146 | 146 | ||
147 | function getVideo (_req: express.Request, res: express.Response) { | 147 | async function getVideo (_req: express.Request, res: express.Response) { |
148 | const video = res.locals.videoAPI | 148 | const videoId = res.locals.videoAPI.id |
149 | const userId = res.locals.oauth?.token.User.id | ||
150 | |||
151 | const video = await Hooks.wrapObject(res.locals.videoAPI, 'filter:api.video.get.result', { id: videoId, userId }) | ||
149 | 152 | ||
150 | if (video.isOutdated()) { | 153 | if (video.isOutdated()) { |
151 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) | 154 | 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 { | |||
7 | MVideoImmutable, | 7 | MVideoImmutable, |
8 | MVideoThumbnail | 8 | MVideoThumbnail |
9 | } from '@server/types/models' | 9 | } from '@server/types/models' |
10 | import { Hooks } from '../plugins/hooks' | 10 | |
11 | 11 | ||
12 | type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes' | 12 | type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes' |
13 | 13 | ||
@@ -27,13 +27,7 @@ function loadVideo ( | |||
27 | userId?: number | 27 | userId?: number |
28 | ): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> { | 28 | ): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> { |
29 | 29 | ||
30 | if (fetchType === 'for-api') { | 30 | if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId }) |
31 | return Hooks.wrapPromiseFun( | ||
32 | VideoModel.loadForGetAPI, | ||
33 | { id, userId }, | ||
34 | 'filter:api.video.get.result' | ||
35 | ) | ||
36 | } | ||
37 | 31 | ||
38 | if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId) | 32 | if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId) |
39 | 33 | ||
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 | |||
7 | 'action:api.video.uploaded', | 7 | 'action:api.video.uploaded', |
8 | 'action:api.video.viewed', | 8 | 'action:api.video.viewed', |
9 | 9 | ||
10 | 'action:api.video-channel.created', | ||
11 | 'action:api.video-channel.updated', | ||
12 | 'action:api.video-channel.deleted', | ||
13 | |||
10 | 'action:api.live-video.created', | 14 | 'action:api.live-video.created', |
11 | 15 | ||
12 | 'action:api.video-thread.created', | 16 | 'action:api.video-thread.created', |
@@ -93,6 +97,29 @@ async function register ({ registerHook, registerSetting, settingsManager, stora | |||
93 | } | 97 | } |
94 | }) | 98 | }) |
95 | 99 | ||
100 | // --------------------------------------------------------------------------- | ||
101 | |||
102 | registerHook({ | ||
103 | target: 'filter:api.video-channels.list.params', | ||
104 | handler: obj => addToCount(obj, 1) | ||
105 | }) | ||
106 | |||
107 | registerHook({ | ||
108 | target: 'filter:api.video-channels.list.result', | ||
109 | handler: obj => addToTotal(obj, 1) | ||
110 | }) | ||
111 | |||
112 | registerHook({ | ||
113 | target: 'filter:api.video-channel.get.result', | ||
114 | handler: channel => { | ||
115 | channel.name += ' <3' | ||
116 | |||
117 | return channel | ||
118 | } | ||
119 | }) | ||
120 | |||
121 | // --------------------------------------------------------------------------- | ||
122 | |||
96 | for (const hook of [ 'filter:api.video.upload.accept.result', 'filter:api.live-video.create.accept.result' ]) { | 123 | for (const hook of [ 'filter:api.video.upload.accept.result', 'filter:api.live-video.create.accept.result' ]) { |
97 | registerHook({ | 124 | registerHook({ |
98 | target: hook, | 125 | 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 () { | |||
65 | 65 | ||
66 | await checkHook('action:api.video.viewed') | 66 | await checkHook('action:api.video.viewed') |
67 | }) | 67 | }) |
68 | |||
69 | it('Should run action:api.video.deleted', async function () { | ||
70 | await servers[0].videos.remove({ id: videoUUID }) | ||
71 | |||
72 | await checkHook('action:api.video.deleted') | ||
73 | }) | ||
74 | |||
75 | after(async function () { | ||
76 | const { uuid } = await servers[0].videos.quickUpload({ name: 'video' }) | ||
77 | videoUUID = uuid | ||
78 | }) | ||
79 | }) | ||
80 | |||
81 | describe('Video channel hooks', function () { | ||
82 | const channelName = 'my_super_channel' | ||
83 | |||
84 | it('Should run action:api.video-channel.created', async function () { | ||
85 | await servers[0].channels.create({ attributes: { name: channelName } }) | ||
86 | |||
87 | await checkHook('action:api.video-channel.created') | ||
88 | }) | ||
89 | |||
90 | it('Should run action:api.video-channel.updated', async function () { | ||
91 | await servers[0].channels.update({ channelName, attributes: { displayName: 'my display name' } }) | ||
92 | |||
93 | await checkHook('action:api.video-channel.updated') | ||
94 | }) | ||
95 | |||
96 | it('Should run action:api.video-channel.deleted', async function () { | ||
97 | await servers[0].channels.delete({ channelName }) | ||
98 | |||
99 | await checkHook('action:api.video-channel.deleted') | ||
100 | }) | ||
68 | }) | 101 | }) |
69 | 102 | ||
70 | describe('Live hooks', function () { | 103 | 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 () { | |||
295 | await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.result', 3) | 295 | await servers[0].servers.waitUntilLog('Run hook filter:api.overviews.videos.list.result', 3) |
296 | }) | 296 | }) |
297 | 297 | ||
298 | describe('Should run filter:video.auto-blacklist.result', function () { | 298 | describe('filter:video.auto-blacklist.result', function () { |
299 | 299 | ||
300 | async function checkIsBlacklisted (id: number | string, value: boolean) { | 300 | async function checkIsBlacklisted (id: number | string, value: boolean) { |
301 | const video = await servers[0].videos.getWithToken({ id }) | 301 | const video = await servers[0].videos.getWithToken({ id }) |
@@ -691,6 +691,28 @@ describe('Test plugin filter hooks', function () { | |||
691 | }) | 691 | }) |
692 | }) | 692 | }) |
693 | 693 | ||
694 | describe('Video channel filters', async function () { | ||
695 | |||
696 | it('Should run filter:api.video-channels.list.params', async function () { | ||
697 | const { data } = await servers[0].channels.list({ start: 0, count: 0 }) | ||
698 | |||
699 | // plugin do +1 to the count parameter | ||
700 | expect(data).to.have.lengthOf(1) | ||
701 | }) | ||
702 | |||
703 | it('Should run filter:api.video-channels.list.result', async function () { | ||
704 | const { total } = await servers[0].channels.list({ start: 0, count: 1 }) | ||
705 | |||
706 | // plugin do +1 to the total parameter | ||
707 | expect(total).to.equal(4) | ||
708 | }) | ||
709 | |||
710 | it('Should run filter:api.video-channel.get.result', async function () { | ||
711 | const channel = await servers[0].channels.get({ channelName: 'root_channel' }) | ||
712 | expect(channel.displayName).to.equal('Main root channel <3') | ||
713 | }) | ||
714 | }) | ||
715 | |||
694 | after(async function () { | 716 | after(async function () { |
695 | await cleanupTests(servers) | 717 | await cleanupTests(servers) |
696 | }) | 718 | }) |
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 = { | |||
45 | // Used to get detailed video information (video watch page for example) | 45 | // Used to get detailed video information (video watch page for example) |
46 | 'filter:api.video.get.result': true, | 46 | 'filter:api.video.get.result': true, |
47 | 47 | ||
48 | // Filter params/results when listing video channels | ||
49 | 'filter:api.video-channels.list.params': true, | ||
50 | 'filter:api.video-channels.list.result': true, | ||
51 | |||
52 | // Filter the result when getting a video channel | ||
53 | 'filter:api.video-channel.get.result': true, | ||
54 | |||
48 | // Filter the result of the accept upload/live, import via torrent/url functions | 55 | // Filter the result of the accept upload/live, import via torrent/url functions |
49 | // If this function returns false then the upload is aborted with an error | 56 | // If this function returns false then the upload is aborted with an error |
50 | 'filter:api.video.upload.accept.result': true, | 57 | 'filter:api.video.upload.accept.result': true, |
@@ -116,6 +123,13 @@ export const serverActionHookObject = { | |||
116 | // Fired when a local video is viewed | 123 | // Fired when a local video is viewed |
117 | 'action:api.video.viewed': true, | 124 | 'action:api.video.viewed': true, |
118 | 125 | ||
126 | // Fired when a video channel is created | ||
127 | 'action:api.video-channel.created': true, | ||
128 | // Fired when a video channel is updated | ||
129 | 'action:api.video-channel.updated': true, | ||
130 | // Fired when a video channel is deleted | ||
131 | 'action:api.video-channel.deleted': true, | ||
132 | |||
119 | // Fired when a live video is created | 133 | // Fired when a live video is created |
120 | 'action:api.live-video.created': true, | 134 | 'action:api.live-video.created': true, |
121 | 135 | ||