diff options
author | Florent <florent.git@zeteo.me> | 2022-08-10 09:53:39 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-10 09:53:39 +0200 |
commit | 2a491182e483b97afb1b65c908b23cb48d591807 (patch) | |
tree | ec13503216ad72a3ea8f1ce3b659899f8167fb47 /server/middlewares | |
parent | 06ac128958c489efe1008eeca1df683819bd2f18 (diff) | |
download | PeerTube-2a491182e483b97afb1b65c908b23cb48d591807.tar.gz PeerTube-2a491182e483b97afb1b65c908b23cb48d591807.tar.zst PeerTube-2a491182e483b97afb1b65c908b23cb48d591807.zip |
Channel sync (#5135)
* Add external channel URL for channel update / creation (#754)
* Disallow synchronisation if user has no video quota (#754)
* More constraints serverside (#754)
* Disable sync if server configuration does not allow HTTP import (#754)
* Working version synchronizing videos with a job (#754)
TODO: refactoring, too much code duplication
* More logs and try/catch (#754)
* Fix eslint error (#754)
* WIP: support synchronization time change (#754)
* New frontend #754
* WIP: Create sync front (#754)
* Enhance UI, sync creation form (#754)
* Warning message when HTTP upload is disallowed
* More consistent names (#754)
* Binding Front with API (#754)
* Add a /me API (#754)
* Improve list UI (#754)
* Implement creation and deletion routes (#754)
* Lint (#754)
* Lint again (#754)
* WIP: UI for triggering import existing videos (#754)
* Implement jobs for syncing and importing channels
* Don't sync videos before sync creation + avoid concurrency issue (#754)
* Cleanup (#754)
* Cleanup: OpenAPI + API rework (#754)
* Remove dead code (#754)
* Eslint (#754)
* Revert the mess with whitespaces in constants.ts (#754)
* Some fixes after rebase (#754)
* Several fixes after PR remarks (#754)
* Front + API: Rename video-channels-sync to video-channel-syncs (#754)
* Allow enabling channel sync through UI (#754)
* getChannelInfo (#754)
* Minor fixes: openapi + model + sql (#754)
* Simplified API validators (#754)
* Rename MChannelSync to MChannelSyncChannel (#754)
* Add command for VideoChannelSync (#754)
* Use synchronization.enabled config (#754)
* Check parameters test + some fixes (#754)
* Fix conflict mistake (#754)
* Restrict access to video channel sync list API (#754)
* Start adding unit test for synchronization (#754)
* Continue testing (#754)
* Tests finished + convertion of job to scheduler (#754)
* Add lastSyncAt field (#754)
* Fix externalRemoteUrl sort + creation date not well formatted (#754)
* Small fix (#754)
* Factorize addYoutubeDLImport and buildVideo (#754)
* Check duplicates on channel not on users (#754)
* factorize thumbnail generation (#754)
* Fetch error should return status 400 (#754)
* Separate video-channel-import and video-channel-sync-latest (#754)
* Bump DB migration version after rebase (#754)
* Prettier states in UI table (#754)
* Add DefaultScope in VideoChannelSyncModel (#754)
* Fix audit logs (#754)
* Ensure user can upload when importing channel + minor fixes (#754)
* Mark synchronization as failed on exception + typos (#754)
* Change REST API for importing videos into channel (#754)
* Add option for fully synchronize a chnanel (#754)
* Return a whole sync object on creation to avoid tricks in Front (#754)
* Various remarks (#754)
* Single quotes by default (#754)
* Rename synchronization to video_channel_synchronization
* Add check.latest_videos_count and max_per_user options (#754)
* Better channel rendering in list #754
* Allow sorting with channel name and state (#754)
* Add missing tests for channel imports (#754)
* Prefer using a parent job for channel sync
* Styling
* Client styling
Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/middlewares')
-rw-r--r-- | server/middlewares/validators/config.ts | 11 | ||||
-rw-r--r-- | server/middlewares/validators/sort.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/videos/index.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-channel-sync.ts | 66 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-channels.ts | 55 |
5 files changed, 117 insertions, 18 deletions
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 9ce47c5aa..f60103f48 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -66,6 +66,8 @@ const customConfigUpdateValidator = [ | |||
66 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), | 66 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), |
67 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), | 67 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), |
68 | 68 | ||
69 | body('import.videoChannelSynchronization.enabled').isBoolean().withMessage('Should have a valid synchronization enabled boolean'), | ||
70 | |||
69 | body('trending.videos.algorithms.default').exists().withMessage('Should have a valid default trending algorithm'), | 71 | body('trending.videos.algorithms.default').exists().withMessage('Should have a valid default trending algorithm'), |
70 | body('trending.videos.algorithms.enabled').exists().withMessage('Should have a valid array of enabled trending algorithms'), | 72 | body('trending.videos.algorithms.enabled').exists().withMessage('Should have a valid array of enabled trending algorithms'), |
71 | 73 | ||
@@ -110,6 +112,7 @@ const customConfigUpdateValidator = [ | |||
110 | if (areValidationErrors(req, res)) return | 112 | if (areValidationErrors(req, res)) return |
111 | if (!checkInvalidConfigIfEmailDisabled(req.body, res)) return | 113 | if (!checkInvalidConfigIfEmailDisabled(req.body, res)) return |
112 | if (!checkInvalidTranscodingConfig(req.body, res)) return | 114 | if (!checkInvalidTranscodingConfig(req.body, res)) return |
115 | if (!checkInvalidSynchronizationConfig(req.body, res)) return | ||
113 | if (!checkInvalidLiveConfig(req.body, res)) return | 116 | if (!checkInvalidLiveConfig(req.body, res)) return |
114 | if (!checkInvalidVideoStudioConfig(req.body, res)) return | 117 | if (!checkInvalidVideoStudioConfig(req.body, res)) return |
115 | 118 | ||
@@ -157,6 +160,14 @@ function checkInvalidTranscodingConfig (customConfig: CustomConfig, res: express | |||
157 | return true | 160 | return true |
158 | } | 161 | } |
159 | 162 | ||
163 | function checkInvalidSynchronizationConfig (customConfig: CustomConfig, res: express.Response) { | ||
164 | if (customConfig.import.videoChannelSynchronization.enabled && !customConfig.import.videos.http.enabled) { | ||
165 | res.fail({ message: 'You need to enable HTTP video import in order to enable channel synchronization' }) | ||
166 | return false | ||
167 | } | ||
168 | return true | ||
169 | } | ||
170 | |||
160 | function checkInvalidLiveConfig (customConfig: CustomConfig, res: express.Response) { | 171 | function checkInvalidLiveConfig (customConfig: CustomConfig, res: express.Response) { |
161 | if (customConfig.live.enabled === false) return true | 172 | if (customConfig.live.enabled === false) return true |
162 | 173 | ||
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index c9978e3b4..0354e3fc6 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -52,6 +52,7 @@ const videoPlaylistsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_PLAY | |||
52 | const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS) | 52 | const pluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.PLUGINS) |
53 | const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) | 53 | const availablePluginsSortValidator = checkSortFactory(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) |
54 | const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) | 54 | const videoRedundanciesSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) |
55 | const videoChannelSyncsSortValidator = checkSortFactory(SORTABLE_COLUMNS.VIDEO_CHANNEL_SYNCS) | ||
55 | 56 | ||
56 | const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) | 57 | const accountsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.ACCOUNT_FOLLOWERS) |
57 | const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) | 58 | const videoChannelsFollowersSortValidator = checkSortFactory(SORTABLE_COLUMNS.CHANNEL_FOLLOWERS) |
@@ -84,5 +85,6 @@ export { | |||
84 | videoPlaylistsSearchSortValidator, | 85 | videoPlaylistsSearchSortValidator, |
85 | accountsFollowersSortValidator, | 86 | accountsFollowersSortValidator, |
86 | videoChannelsFollowersSortValidator, | 87 | videoChannelsFollowersSortValidator, |
88 | videoChannelSyncsSortValidator, | ||
87 | pluginsSortValidator | 89 | pluginsSortValidator |
88 | } | 90 | } |
diff --git a/server/middlewares/validators/videos/index.ts b/server/middlewares/validators/videos/index.ts index 1dd7b5d2e..d225dfe45 100644 --- a/server/middlewares/validators/videos/index.ts +++ b/server/middlewares/validators/videos/index.ts | |||
@@ -14,3 +14,4 @@ export * from './video-stats' | |||
14 | export * from './video-studio' | 14 | export * from './video-studio' |
15 | export * from './video-transcoding' | 15 | export * from './video-transcoding' |
16 | export * from './videos' | 16 | export * from './videos' |
17 | export * from './video-channel-sync' | ||
diff --git a/server/middlewares/validators/videos/video-channel-sync.ts b/server/middlewares/validators/videos/video-channel-sync.ts new file mode 100644 index 000000000..b18498243 --- /dev/null +++ b/server/middlewares/validators/videos/video-channel-sync.ts | |||
@@ -0,0 +1,66 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param } from 'express-validator' | ||
3 | import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc' | ||
4 | import { logger } from '@server/helpers/logger' | ||
5 | import { CONFIG } from '@server/initializers/config' | ||
6 | import { VideoChannelModel } from '@server/models/video/video-channel' | ||
7 | import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' | ||
8 | import { HttpStatusCode, VideoChannelSyncCreate } from '@shared/models' | ||
9 | import { areValidationErrors, doesVideoChannelIdExist } from '../shared' | ||
10 | |||
11 | export const ensureSyncIsEnabled = (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
12 | if (!CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED) { | ||
13 | return res.fail({ | ||
14 | status: HttpStatusCode.FORBIDDEN_403, | ||
15 | message: 'Synchronization is impossible as video channel synchronization is not enabled on the server' | ||
16 | }) | ||
17 | } | ||
18 | |||
19 | return next() | ||
20 | } | ||
21 | |||
22 | export const videoChannelSyncValidator = [ | ||
23 | body('externalChannelUrl').custom(isUrlValid).withMessage('Should have a valid channel url'), | ||
24 | body('videoChannelId').isInt().withMessage('Should have a valid video channel id'), | ||
25 | |||
26 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
27 | logger.debug('Checking videoChannelSync parameters', { parameters: req.body }) | ||
28 | |||
29 | if (areValidationErrors(req, res)) return | ||
30 | |||
31 | const body: VideoChannelSyncCreate = req.body | ||
32 | if (!await doesVideoChannelIdExist(body.videoChannelId, res)) return | ||
33 | |||
34 | const count = await VideoChannelSyncModel.countByAccount(res.locals.videoChannel.accountId) | ||
35 | if (count >= CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.MAX_PER_USER) { | ||
36 | return res.fail({ | ||
37 | message: `You cannot create more than ${CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.MAX_PER_USER} channel synchronizations` | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | return next() | ||
42 | } | ||
43 | ] | ||
44 | |||
45 | export const ensureSyncExists = [ | ||
46 | param('id').exists().isInt().withMessage('Should have an sync id'), | ||
47 | |||
48 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
49 | if (areValidationErrors(req, res)) return | ||
50 | |||
51 | const syncId = parseInt(req.params.id, 10) | ||
52 | const sync = await VideoChannelSyncModel.loadWithChannel(syncId) | ||
53 | |||
54 | if (!sync) { | ||
55 | return res.fail({ | ||
56 | status: HttpStatusCode.NOT_FOUND_404, | ||
57 | message: 'Synchronization not found' | ||
58 | }) | ||
59 | } | ||
60 | |||
61 | res.locals.videoChannelSync = sync | ||
62 | res.locals.videoChannel = await VideoChannelModel.loadAndPopulateAccount(sync.videoChannelId) | ||
63 | |||
64 | return next() | ||
65 | } | ||
66 | ] | ||
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 3bfdebbb1..88f8b814d 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc' | ||
3 | import { CONFIG } from '@server/initializers/config' | 4 | import { CONFIG } from '@server/initializers/config' |
4 | import { MChannelAccountDefault } from '@server/types/models' | 5 | import { MChannelAccountDefault } from '@server/types/models' |
5 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' | 6 | import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' |
@@ -13,9 +14,9 @@ import { | |||
13 | import { logger } from '../../../helpers/logger' | 14 | import { logger } from '../../../helpers/logger' |
14 | import { ActorModel } from '../../../models/actor/actor' | 15 | import { ActorModel } from '../../../models/actor/actor' |
15 | import { VideoChannelModel } from '../../../models/video/video-channel' | 16 | import { VideoChannelModel } from '../../../models/video/video-channel' |
16 | import { areValidationErrors, doesVideoChannelNameWithHostExist } from '../shared' | 17 | import { areValidationErrors, checkUserQuota, doesVideoChannelNameWithHostExist } from '../shared' |
17 | 18 | ||
18 | const videoChannelsAddValidator = [ | 19 | export const videoChannelsAddValidator = [ |
19 | body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), | 20 | body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), |
20 | body('displayName').custom(isVideoChannelDisplayNameValid).withMessage('Should have a valid display name'), | 21 | body('displayName').custom(isVideoChannelDisplayNameValid).withMessage('Should have a valid display name'), |
21 | body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), | 22 | body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), |
@@ -45,7 +46,7 @@ const videoChannelsAddValidator = [ | |||
45 | } | 46 | } |
46 | ] | 47 | ] |
47 | 48 | ||
48 | const videoChannelsUpdateValidator = [ | 49 | export const videoChannelsUpdateValidator = [ |
49 | param('nameWithHost').exists().withMessage('Should have an video channel name with host'), | 50 | param('nameWithHost').exists().withMessage('Should have an video channel name with host'), |
50 | body('displayName') | 51 | body('displayName') |
51 | .optional() | 52 | .optional() |
@@ -69,7 +70,7 @@ const videoChannelsUpdateValidator = [ | |||
69 | } | 70 | } |
70 | ] | 71 | ] |
71 | 72 | ||
72 | const videoChannelsRemoveValidator = [ | 73 | export const videoChannelsRemoveValidator = [ |
73 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 74 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
74 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) | 75 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) |
75 | 76 | ||
@@ -79,7 +80,7 @@ const videoChannelsRemoveValidator = [ | |||
79 | } | 80 | } |
80 | ] | 81 | ] |
81 | 82 | ||
82 | const videoChannelsNameWithHostValidator = [ | 83 | export const videoChannelsNameWithHostValidator = [ |
83 | param('nameWithHost').exists().withMessage('Should have an video channel name with host'), | 84 | param('nameWithHost').exists().withMessage('Should have an video channel name with host'), |
84 | 85 | ||
85 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 86 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -93,7 +94,7 @@ const videoChannelsNameWithHostValidator = [ | |||
93 | } | 94 | } |
94 | ] | 95 | ] |
95 | 96 | ||
96 | const ensureIsLocalChannel = [ | 97 | export const ensureIsLocalChannel = [ |
97 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 98 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
98 | if (res.locals.videoChannel.Actor.isOwned() === false) { | 99 | if (res.locals.videoChannel.Actor.isOwned() === false) { |
99 | return res.fail({ | 100 | return res.fail({ |
@@ -106,7 +107,18 @@ const ensureIsLocalChannel = [ | |||
106 | } | 107 | } |
107 | ] | 108 | ] |
108 | 109 | ||
109 | const videoChannelStatsValidator = [ | 110 | export const ensureChannelOwnerCanUpload = [ |
111 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
112 | const channel = res.locals.videoChannel | ||
113 | const user = { id: channel.Account.userId } | ||
114 | |||
115 | if (!await checkUserQuota(user, 1, res)) return | ||
116 | |||
117 | next() | ||
118 | } | ||
119 | ] | ||
120 | |||
121 | export const videoChannelStatsValidator = [ | ||
110 | query('withStats') | 122 | query('withStats') |
111 | .optional() | 123 | .optional() |
112 | .customSanitizer(toBooleanOrNull) | 124 | .customSanitizer(toBooleanOrNull) |
@@ -118,7 +130,7 @@ const videoChannelStatsValidator = [ | |||
118 | } | 130 | } |
119 | ] | 131 | ] |
120 | 132 | ||
121 | const videoChannelsListValidator = [ | 133 | export const videoChannelsListValidator = [ |
122 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), | 134 | query('search').optional().not().isEmpty().withMessage('Should have a valid search'), |
123 | 135 | ||
124 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 136 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -130,17 +142,24 @@ const videoChannelsListValidator = [ | |||
130 | } | 142 | } |
131 | ] | 143 | ] |
132 | 144 | ||
133 | // --------------------------------------------------------------------------- | 145 | export const videoChannelImportVideosValidator = [ |
146 | body('externalChannelUrl').custom(isUrlValid).withMessage('Should have a valid channel url'), | ||
134 | 147 | ||
135 | export { | 148 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
136 | videoChannelsAddValidator, | 149 | logger.debug('Checking videoChannelImport parameters', { parameters: req.body }) |
137 | videoChannelsUpdateValidator, | 150 | |
138 | videoChannelsRemoveValidator, | 151 | if (areValidationErrors(req, res)) return |
139 | videoChannelsNameWithHostValidator, | 152 | |
140 | ensureIsLocalChannel, | 153 | if (!CONFIG.IMPORT.VIDEOS.HTTP.ENABLED) { |
141 | videoChannelsListValidator, | 154 | return res.fail({ |
142 | videoChannelStatsValidator | 155 | status: HttpStatusCode.FORBIDDEN_403, |
143 | } | 156 | message: 'Channel import is impossible as video upload via HTTP is not enabled on the server' |
157 | }) | ||
158 | } | ||
159 | |||
160 | return next() | ||
161 | } | ||
162 | ] | ||
144 | 163 | ||
145 | // --------------------------------------------------------------------------- | 164 | // --------------------------------------------------------------------------- |
146 | 165 | ||