aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-08-10 11:51:13 +0200
committerChocobozzz <me@florianbigard.com>2022-08-10 14:32:00 +0200
commita3b472a12ec6e57dbe2f650419f8064864686eab (patch)
treef36559488e34493c029b686772e986902150a647 /server
parent0567049a9819d67070aa6d548a75a7e632a4aaa4 (diff)
downloadPeerTube-a3b472a12ec6e57dbe2f650419f8064864686eab.tar.gz
PeerTube-a3b472a12ec6e57dbe2f650419f8064864686eab.tar.zst
PeerTube-a3b472a12ec6e57dbe2f650419f8064864686eab.zip
Add ability to list imports of a channel sync
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/users/me.ts11
-rw-r--r--server/controllers/api/video-channel.ts9
-rw-r--r--server/initializers/constants.ts2
-rw-r--r--server/initializers/migrations/0735-video-channel-sync-import-foreign-key.ts32
-rw-r--r--server/lib/job-queue/handlers/video-channel-import.ts20
-rw-r--r--server/lib/schedulers/video-channel-sync-latest-scheduler.ts4
-rw-r--r--server/lib/sync-channel.ts7
-rw-r--r--server/lib/video-import.ts3
-rw-r--r--server/middlewares/validators/shared/index.ts1
-rw-r--r--server/middlewares/validators/shared/video-channel-syncs.ts24
-rw-r--r--server/middlewares/validators/videos/video-channel-sync.ts16
-rw-r--r--server/middlewares/validators/videos/video-channels.ts14
-rw-r--r--server/middlewares/validators/videos/video-imports.ts19
-rw-r--r--server/models/video/video-import.ts79
-rw-r--r--server/tests/api/check-params/channel-import-videos.ts172
-rw-r--r--server/tests/api/check-params/index.ts1
-rw-r--r--server/tests/api/check-params/video-channels.ts113
-rw-r--r--server/tests/api/check-params/video-imports.ts9
-rw-r--r--server/tests/api/videos/channel-import-videos.ts72
-rw-r--r--server/tests/api/videos/video-channel-syncs.ts12
-rw-r--r--server/tests/api/videos/video-imports.ts9
21 files changed, 467 insertions, 162 deletions
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 595abcf95..00f580ee9 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -25,7 +25,13 @@ import {
25 usersUpdateMeValidator, 25 usersUpdateMeValidator,
26 usersVideoRatingValidator 26 usersVideoRatingValidator
27} from '../../../middlewares' 27} from '../../../middlewares'
28import { deleteMeValidator, usersVideosValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators' 28import {
29 deleteMeValidator,
30 getMyVideoImportsValidator,
31 usersVideosValidator,
32 videoImportsSortValidator,
33 videosSortValidator
34} from '../../../middlewares/validators'
29import { updateAvatarValidator } from '../../../middlewares/validators/actor-image' 35import { updateAvatarValidator } from '../../../middlewares/validators/actor-image'
30import { AccountModel } from '../../../models/account/account' 36import { AccountModel } from '../../../models/account/account'
31import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 37import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
@@ -60,6 +66,7 @@ meRouter.get('/me/videos/imports',
60 videoImportsSortValidator, 66 videoImportsSortValidator,
61 setDefaultSort, 67 setDefaultSort,
62 setDefaultPagination, 68 setDefaultPagination,
69 getMyVideoImportsValidator,
63 asyncMiddleware(getUserVideoImports) 70 asyncMiddleware(getUserVideoImports)
64) 71)
65 72
@@ -138,7 +145,7 @@ async function getUserVideoImports (req: express.Request, res: express.Response)
138 const resultList = await VideoImportModel.listUserVideoImportsForApi({ 145 const resultList = await VideoImportModel.listUserVideoImportsForApi({
139 userId: user.id, 146 userId: user.id,
140 147
141 ...pick(req.query, [ 'targetUrl', 'start', 'count', 'sort' ]) 148 ...pick(req.query, [ 'targetUrl', 'start', 'count', 'sort', 'search', 'videoChannelSyncId' ])
142 }) 149 })
143 150
144 return res.json(getFormattedObjects(resultList.data, resultList.total)) 151 return res.json(getFormattedObjects(resultList.data, resultList.total))
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 89c7181bd..94285a78d 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -6,7 +6,7 @@ import { ActorFollowModel } from '@server/models/actor/actor-follow'
6import { getServerActor } from '@server/models/application/application' 6import { getServerActor } from '@server/models/application/application'
7import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 7import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
8import { MChannelBannerAccountDefault } from '@server/types/models' 8import { MChannelBannerAccountDefault } from '@server/types/models'
9import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate } from '@shared/models' 9import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate, VideosImportInChannelCreate } from '@shared/models'
10import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 10import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
11import { resetSequelizeInstance } from '../../helpers/database-utils' 11import { resetSequelizeInstance } from '../../helpers/database-utils'
12import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 12import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
@@ -166,7 +166,7 @@ videoChannelRouter.get('/:nameWithHost/followers',
166videoChannelRouter.post('/:nameWithHost/import-videos', 166videoChannelRouter.post('/:nameWithHost/import-videos',
167 authenticate, 167 authenticate,
168 asyncMiddleware(videoChannelsNameWithHostValidator), 168 asyncMiddleware(videoChannelsNameWithHostValidator),
169 videoChannelImportVideosValidator, 169 asyncMiddleware(videoChannelImportVideosValidator),
170 ensureIsLocalChannel, 170 ensureIsLocalChannel,
171 ensureCanManageChannel, 171 ensureCanManageChannel,
172 asyncMiddleware(ensureChannelOwnerCanUpload), 172 asyncMiddleware(ensureChannelOwnerCanUpload),
@@ -418,13 +418,14 @@ async function listVideoChannelFollowers (req: express.Request, res: express.Res
418} 418}
419 419
420async function importVideosInChannel (req: express.Request, res: express.Response) { 420async function importVideosInChannel (req: express.Request, res: express.Response) {
421 const { externalChannelUrl } = req.body 421 const { externalChannelUrl } = req.body as VideosImportInChannelCreate
422 422
423 await JobQueue.Instance.createJob({ 423 await JobQueue.Instance.createJob({
424 type: 'video-channel-import', 424 type: 'video-channel-import',
425 payload: { 425 payload: {
426 externalChannelUrl, 426 externalChannelUrl,
427 videoChannelId: res.locals.videoChannel.id 427 videoChannelId: res.locals.videoChannel.id,
428 partOfChannelSyncId: res.locals.videoChannelSync?.id
428 } 429 }
429 }) 430 })
430 431
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 697a64d42..c2289ef36 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -25,7 +25,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
25 25
26// --------------------------------------------------------------------------- 26// ---------------------------------------------------------------------------
27 27
28const LAST_MIGRATION_VERSION = 730 28const LAST_MIGRATION_VERSION = 735
29 29
30// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
31 31
diff --git a/server/initializers/migrations/0735-video-channel-sync-import-foreign-key.ts b/server/initializers/migrations/0735-video-channel-sync-import-foreign-key.ts
new file mode 100644
index 000000000..ffe0b11ab
--- /dev/null
+++ b/server/initializers/migrations/0735-video-channel-sync-import-foreign-key.ts
@@ -0,0 +1,32 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 await utils.queryInterface.addColumn('videoImport', 'videoChannelSyncId', {
10 type: Sequelize.INTEGER,
11 defaultValue: null,
12 allowNull: true,
13 references: {
14 model: 'videoChannelSync',
15 key: 'id'
16 },
17 onUpdate: 'CASCADE',
18 onDelete: 'SET NULL'
19 }, { transaction: utils.transaction })
20}
21
22async function down (utils: {
23 queryInterface: Sequelize.QueryInterface
24 transaction: Sequelize.Transaction
25}) {
26 await utils.queryInterface.dropTable('videoChannelSync', { transaction: utils.transaction })
27}
28
29export {
30 up,
31 down
32}
diff --git a/server/lib/job-queue/handlers/video-channel-import.ts b/server/lib/job-queue/handlers/video-channel-import.ts
index 9bdb2d269..9aaad659e 100644
--- a/server/lib/job-queue/handlers/video-channel-import.ts
+++ b/server/lib/job-queue/handlers/video-channel-import.ts
@@ -3,6 +3,8 @@ import { logger } from '@server/helpers/logger'
3import { CONFIG } from '@server/initializers/config' 3import { CONFIG } from '@server/initializers/config'
4import { synchronizeChannel } from '@server/lib/sync-channel' 4import { synchronizeChannel } from '@server/lib/sync-channel'
5import { VideoChannelModel } from '@server/models/video/video-channel' 5import { VideoChannelModel } from '@server/models/video/video-channel'
6import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync'
7import { MChannelSync } from '@server/types/models'
6import { VideoChannelImportPayload } from '@shared/models' 8import { VideoChannelImportPayload } from '@shared/models'
7 9
8export async function processVideoChannelImport (job: Job) { 10export async function processVideoChannelImport (job: Job) {
@@ -12,13 +14,20 @@ export async function processVideoChannelImport (job: Job) {
12 14
13 // Channel import requires only http upload to be allowed 15 // Channel import requires only http upload to be allowed
14 if (!CONFIG.IMPORT.VIDEOS.HTTP.ENABLED) { 16 if (!CONFIG.IMPORT.VIDEOS.HTTP.ENABLED) {
15 logger.error('Cannot import channel as the HTTP upload is disabled') 17 throw new Error('Cannot import channel as the HTTP upload is disabled')
16 return
17 } 18 }
18 19
19 if (!CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED) { 20 if (!CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED) {
20 logger.error('Cannot import channel as the synchronization is disabled') 21 throw new Error('Cannot import channel as the synchronization is disabled')
21 return 22 }
23
24 let channelSync: MChannelSync
25 if (payload.partOfChannelSyncId) {
26 channelSync = await VideoChannelSyncModel.loadWithChannel(payload.partOfChannelSyncId)
27
28 if (!channelSync) {
29 throw new Error('Unlnown channel sync specified in videos channel import')
30 }
22 } 31 }
23 32
24 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(payload.videoChannelId) 33 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(payload.videoChannelId)
@@ -28,7 +37,8 @@ export async function processVideoChannelImport (job: Job) {
28 37
29 await synchronizeChannel({ 38 await synchronizeChannel({
30 channel: videoChannel, 39 channel: videoChannel,
31 externalChannelUrl: payload.externalChannelUrl 40 externalChannelUrl: payload.externalChannelUrl,
41 channelSync
32 }) 42 })
33 } catch (err) { 43 } catch (err) {
34 logger.error(`Failed to import channel ${videoChannel.name}`, { err }) 44 logger.error(`Failed to import channel ${videoChannel.name}`, { err })
diff --git a/server/lib/schedulers/video-channel-sync-latest-scheduler.ts b/server/lib/schedulers/video-channel-sync-latest-scheduler.ts
index fd9a35299..491ddaa87 100644
--- a/server/lib/schedulers/video-channel-sync-latest-scheduler.ts
+++ b/server/lib/schedulers/video-channel-sync-latest-scheduler.ts
@@ -36,10 +36,6 @@ export class VideoChannelSyncLatestScheduler extends AbstractScheduler {
36 36
37 const onlyAfter = sync.lastSyncAt || sync.createdAt 37 const onlyAfter = sync.lastSyncAt || sync.createdAt
38 38
39 sync.state = VideoChannelSyncState.PROCESSING
40 sync.lastSyncAt = new Date()
41 await sync.save()
42
43 await synchronizeChannel({ 39 await synchronizeChannel({
44 channel, 40 channel,
45 externalChannelUrl: sync.externalChannelUrl, 41 externalChannelUrl: sync.externalChannelUrl,
diff --git a/server/lib/sync-channel.ts b/server/lib/sync-channel.ts
index 50f80e6f9..eb5ca1703 100644
--- a/server/lib/sync-channel.ts
+++ b/server/lib/sync-channel.ts
@@ -18,6 +18,12 @@ export async function synchronizeChannel (options: {
18}) { 18}) {
19 const { channel, externalChannelUrl, videosCountLimit, onlyAfter, channelSync } = options 19 const { channel, externalChannelUrl, videosCountLimit, onlyAfter, channelSync } = options
20 20
21 if (channelSync) {
22 channelSync.state = VideoChannelSyncState.PROCESSING
23 channelSync.lastSyncAt = new Date()
24 await channelSync.save()
25 }
26
21 const user = await UserModel.loadByChannelActorId(channel.actorId) 27 const user = await UserModel.loadByChannelActorId(channel.actorId)
22 const youtubeDL = new YoutubeDLWrapper( 28 const youtubeDL = new YoutubeDLWrapper(
23 externalChannelUrl, 29 externalChannelUrl,
@@ -70,6 +76,7 @@ export async function synchronizeChannel (options: {
70 children.push(job) 76 children.push(job)
71 } 77 }
72 78
79 // Will update the channel sync status
73 const parent: CreateJobArgument = { 80 const parent: CreateJobArgument = {
74 type: 'after-video-channel-import', 81 type: 'after-video-channel-import',
75 payload: { 82 payload: {
diff --git a/server/lib/video-import.ts b/server/lib/video-import.ts
index fb9306967..de95116aa 100644
--- a/server/lib/video-import.ts
+++ b/server/lib/video-import.ts
@@ -206,7 +206,8 @@ async function buildYoutubeDLImport (options: {
206 videoImportAttributes: { 206 videoImportAttributes: {
207 targetUrl, 207 targetUrl,
208 state: VideoImportState.PENDING, 208 state: VideoImportState.PENDING,
209 userId: user.id 209 userId: user.id,
210 videoChannelSyncId: channelSync?.id
210 } 211 }
211 }) 212 })
212 213
diff --git a/server/middlewares/validators/shared/index.ts b/server/middlewares/validators/shared/index.ts
index fa89d05f2..bbd03b248 100644
--- a/server/middlewares/validators/shared/index.ts
+++ b/server/middlewares/validators/shared/index.ts
@@ -4,6 +4,7 @@ export * from './utils'
4export * from './video-blacklists' 4export * from './video-blacklists'
5export * from './video-captions' 5export * from './video-captions'
6export * from './video-channels' 6export * from './video-channels'
7export * from './video-channel-syncs'
7export * from './video-comments' 8export * from './video-comments'
8export * from './video-imports' 9export * from './video-imports'
9export * from './video-ownerships' 10export * from './video-ownerships'
diff --git a/server/middlewares/validators/shared/video-channel-syncs.ts b/server/middlewares/validators/shared/video-channel-syncs.ts
new file mode 100644
index 000000000..a6e51eb97
--- /dev/null
+++ b/server/middlewares/validators/shared/video-channel-syncs.ts
@@ -0,0 +1,24 @@
1import express from 'express'
2import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync'
3import { HttpStatusCode } from '@shared/models'
4
5async function doesVideoChannelSyncIdExist (id: number, res: express.Response) {
6 const sync = await VideoChannelSyncModel.loadWithChannel(+id)
7
8 if (!sync) {
9 res.fail({
10 status: HttpStatusCode.NOT_FOUND_404,
11 message: 'Video channel sync not found'
12 })
13 return false
14 }
15
16 res.locals.videoChannelSync = sync
17 return true
18}
19
20// ---------------------------------------------------------------------------
21
22export {
23 doesVideoChannelSyncIdExist
24}
diff --git a/server/middlewares/validators/videos/video-channel-sync.ts b/server/middlewares/validators/videos/video-channel-sync.ts
index b18498243..081f09bba 100644
--- a/server/middlewares/validators/videos/video-channel-sync.ts
+++ b/server/middlewares/validators/videos/video-channel-sync.ts
@@ -3,10 +3,10 @@ import { body, param } from 'express-validator'
3import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc' 3import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
4import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { VideoChannelModel } from '@server/models/video/video-channel'
7import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync' 6import { VideoChannelSyncModel } from '@server/models/video/video-channel-sync'
8import { HttpStatusCode, VideoChannelSyncCreate } from '@shared/models' 7import { HttpStatusCode, VideoChannelSyncCreate } from '@shared/models'
9import { areValidationErrors, doesVideoChannelIdExist } from '../shared' 8import { areValidationErrors, doesVideoChannelIdExist } from '../shared'
9import { doesVideoChannelSyncIdExist } from '../shared/video-channel-syncs'
10 10
11export const ensureSyncIsEnabled = (req: express.Request, res: express.Response, next: express.NextFunction) => { 11export const ensureSyncIsEnabled = (req: express.Request, res: express.Response, next: express.NextFunction) => {
12 if (!CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED) { 12 if (!CONFIG.IMPORT.VIDEO_CHANNEL_SYNCHRONIZATION.ENABLED) {
@@ -48,18 +48,8 @@ export const ensureSyncExists = [
48 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 48 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
49 if (areValidationErrors(req, res)) return 49 if (areValidationErrors(req, res)) return
50 50
51 const syncId = parseInt(req.params.id, 10) 51 if (!await doesVideoChannelSyncIdExist(+req.params.id, res)) return
52 const sync = await VideoChannelSyncModel.loadWithChannel(syncId) 52 if (!await doesVideoChannelIdExist(res.locals.videoChannelSync.videoChannelId, res)) return
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 53
64 return next() 54 return next()
65 } 55 }
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index 88f8b814d..d53c777fa 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -3,8 +3,9 @@ import { body, param, query } from 'express-validator'
3import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc' 3import { isUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
4import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
5import { MChannelAccountDefault } from '@server/types/models' 5import { MChannelAccountDefault } from '@server/types/models'
6import { VideosImportInChannelCreate } from '@shared/models'
6import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 7import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
7import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' 8import { isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
8import { 9import {
9 isVideoChannelDescriptionValid, 10 isVideoChannelDescriptionValid,
10 isVideoChannelDisplayNameValid, 11 isVideoChannelDisplayNameValid,
@@ -15,6 +16,7 @@ import { logger } from '../../../helpers/logger'
15import { ActorModel } from '../../../models/actor/actor' 16import { ActorModel } from '../../../models/actor/actor'
16import { VideoChannelModel } from '../../../models/video/video-channel' 17import { VideoChannelModel } from '../../../models/video/video-channel'
17import { areValidationErrors, checkUserQuota, doesVideoChannelNameWithHostExist } from '../shared' 18import { areValidationErrors, checkUserQuota, doesVideoChannelNameWithHostExist } from '../shared'
19import { doesVideoChannelSyncIdExist } from '../shared/video-channel-syncs'
18 20
19export const videoChannelsAddValidator = [ 21export const videoChannelsAddValidator = [
20 body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), 22 body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'),
@@ -145,11 +147,17 @@ export const videoChannelsListValidator = [
145export const videoChannelImportVideosValidator = [ 147export const videoChannelImportVideosValidator = [
146 body('externalChannelUrl').custom(isUrlValid).withMessage('Should have a valid channel url'), 148 body('externalChannelUrl').custom(isUrlValid).withMessage('Should have a valid channel url'),
147 149
148 (req: express.Request, res: express.Response, next: express.NextFunction) => { 150 body('videoChannelSyncId')
151 .optional()
152 .custom(isIdValid).withMessage('Should have a valid channel sync id'),
153
154 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
149 logger.debug('Checking videoChannelImport parameters', { parameters: req.body }) 155 logger.debug('Checking videoChannelImport parameters', { parameters: req.body })
150 156
151 if (areValidationErrors(req, res)) return 157 if (areValidationErrors(req, res)) return
152 158
159 const body: VideosImportInChannelCreate = req.body
160
153 if (!CONFIG.IMPORT.VIDEOS.HTTP.ENABLED) { 161 if (!CONFIG.IMPORT.VIDEOS.HTTP.ENABLED) {
154 return res.fail({ 162 return res.fail({
155 status: HttpStatusCode.FORBIDDEN_403, 163 status: HttpStatusCode.FORBIDDEN_403,
@@ -157,6 +165,8 @@ export const videoChannelImportVideosValidator = [
157 }) 165 })
158 } 166 }
159 167
168 if (body.videoChannelSyncId && !await doesVideoChannelSyncIdExist(body.videoChannelSyncId, res)) return
169
160 return next() 170 return next()
161 } 171 }
162] 172]
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 9c6d213c4..3115acb21 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -1,5 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { body, param } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { isResolvingToUnicastOnly } from '@server/helpers/dns' 3import { isResolvingToUnicastOnly } from '@server/helpers/dns'
4import { isPreImportVideoAccepted } from '@server/lib/moderation' 4import { isPreImportVideoAccepted } from '@server/lib/moderation'
5import { Hooks } from '@server/lib/plugins/hooks' 5import { Hooks } from '@server/lib/plugins/hooks'
@@ -92,6 +92,20 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
92 } 92 }
93]) 93])
94 94
95const getMyVideoImportsValidator = [
96 query('videoChannelSyncId')
97 .optional()
98 .custom(isIdValid).withMessage('Should have correct videoChannelSync id'),
99
100 (req: express.Request, res: express.Response, next: express.NextFunction) => {
101 logger.debug('Checking getMyVideoImportsValidator parameters', { parameters: req.params })
102
103 if (areValidationErrors(req, res)) return
104
105 return next()
106 }
107]
108
95const videoImportDeleteValidator = [ 109const videoImportDeleteValidator = [
96 param('id') 110 param('id')
97 .custom(isIdValid).withMessage('Should have correct import id'), 111 .custom(isIdValid).withMessage('Should have correct import id'),
@@ -143,7 +157,8 @@ const videoImportCancelValidator = [
143export { 157export {
144 videoImportAddValidator, 158 videoImportAddValidator,
145 videoImportCancelValidator, 159 videoImportCancelValidator,
146 videoImportDeleteValidator 160 videoImportDeleteValidator,
161 getMyVideoImportsValidator
147} 162}
148 163
149// --------------------------------------------------------------------------- 164// ---------------------------------------------------------------------------
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index b8e941623..da6b92c7a 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -1,4 +1,4 @@
1import { Op, WhereOptions } from 'sequelize' 1import { IncludeOptions, Op, WhereOptions } from 'sequelize'
2import { 2import {
3 AfterUpdate, 3 AfterUpdate,
4 AllowNull, 4 AllowNull,
@@ -22,8 +22,17 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help
22import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 22import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
23import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' 23import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
24import { UserModel } from '../user/user' 24import { UserModel } from '../user/user'
25import { getSort, throwIfNotValid } from '../utils' 25import { getSort, searchAttribute, throwIfNotValid } from '../utils'
26import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' 26import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
27import { VideoChannelSyncModel } from './video-channel-sync'
28
29const defaultVideoScope = () => {
30 return VideoModel.scope([
31 VideoModelScopeNames.WITH_ACCOUNT_DETAILS,
32 VideoModelScopeNames.WITH_TAGS,
33 VideoModelScopeNames.WITH_THUMBNAILS
34 ])
35}
27 36
28@DefaultScope(() => ({ 37@DefaultScope(() => ({
29 include: [ 38 include: [
@@ -32,11 +41,11 @@ import { ScopeNames as VideoModelScopeNames, VideoModel } from './video'
32 required: true 41 required: true
33 }, 42 },
34 { 43 {
35 model: VideoModel.scope([ 44 model: defaultVideoScope(),
36 VideoModelScopeNames.WITH_ACCOUNT_DETAILS, 45 required: false
37 VideoModelScopeNames.WITH_TAGS, 46 },
38 VideoModelScopeNames.WITH_THUMBNAILS 47 {
39 ]), 48 model: VideoChannelSyncModel.unscoped(),
40 required: false 49 required: false
41 } 50 }
42 ] 51 ]
@@ -113,6 +122,18 @@ export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportMo
113 }) 122 })
114 Video: VideoModel 123 Video: VideoModel
115 124
125 @ForeignKey(() => VideoChannelSyncModel)
126 @Column
127 videoChannelSyncId: number
128
129 @BelongsTo(() => VideoChannelSyncModel, {
130 foreignKey: {
131 allowNull: true
132 },
133 onDelete: 'set null'
134 })
135 VideoChannelSync: VideoChannelSyncModel
136
116 @AfterUpdate 137 @AfterUpdate
117 static deleteVideoIfFailed (instance: VideoImportModel, options) { 138 static deleteVideoIfFailed (instance: VideoImportModel, options) {
118 if (instance.state === VideoImportState.FAILED) { 139 if (instance.state === VideoImportState.FAILED) {
@@ -132,23 +153,44 @@ export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportMo
132 count: number 153 count: number
133 sort: string 154 sort: string
134 155
156 search?: string
135 targetUrl?: string 157 targetUrl?: string
158 videoChannelSyncId?: number
136 }) { 159 }) {
137 const { userId, start, count, sort, targetUrl } = options 160 const { userId, start, count, sort, targetUrl, videoChannelSyncId, search } = options
138 161
139 const where: WhereOptions = { userId } 162 const where: WhereOptions = { userId }
163 const include: IncludeOptions[] = [
164 {
165 attributes: [ 'id' ],
166 model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
167 required: true
168 },
169 {
170 model: VideoChannelSyncModel.unscoped(),
171 required: false
172 }
173 ]
140 174
141 if (targetUrl) where['targetUrl'] = targetUrl 175 if (targetUrl) where['targetUrl'] = targetUrl
176 if (videoChannelSyncId) where['videoChannelSyncId'] = videoChannelSyncId
177
178 if (search) {
179 include.push({
180 model: defaultVideoScope(),
181 required: true,
182 where: searchAttribute(search, 'name')
183 })
184 } else {
185 include.push({
186 model: defaultVideoScope(),
187 required: false
188 })
189 }
142 190
143 const query = { 191 const query = {
144 distinct: true, 192 distinct: true,
145 include: [ 193 include,
146 {
147 attributes: [ 'id' ],
148 model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query
149 required: true
150 }
151 ],
152 offset: start, 194 offset: start,
153 limit: count, 195 limit: count,
154 order: getSort(sort), 196 order: getSort(sort),
@@ -196,6 +238,10 @@ export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportMo
196 ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) }) 238 ? Object.assign(this.Video.toFormattedJSON(videoFormatOptions), { tags: this.Video.Tags.map(t => t.name) })
197 : undefined 239 : undefined
198 240
241 const videoChannelSync = this.VideoChannelSync
242 ? { id: this.VideoChannelSync.id, externalChannelUrl: this.VideoChannelSync.externalChannelUrl }
243 : undefined
244
199 return { 245 return {
200 id: this.id, 246 id: this.id,
201 247
@@ -210,7 +256,8 @@ export class VideoImportModel extends Model<Partial<AttributesOnly<VideoImportMo
210 error: this.error, 256 error: this.error,
211 updatedAt: this.updatedAt.toISOString(), 257 updatedAt: this.updatedAt.toISOString(),
212 createdAt: this.createdAt.toISOString(), 258 createdAt: this.createdAt.toISOString(),
213 video 259 video,
260 videoChannelSync
214 } 261 }
215 } 262 }
216 263
diff --git a/server/tests/api/check-params/channel-import-videos.ts b/server/tests/api/check-params/channel-import-videos.ts
new file mode 100644
index 000000000..90d61f20a
--- /dev/null
+++ b/server/tests/api/check-params/channel-import-videos.ts
@@ -0,0 +1,172 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import { FIXTURE_URLS } from '@server/tests/shared'
5import { areHttpImportTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode } from '@shared/models'
7import { ChannelsCommand, cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
8
9describe('Test videos import in a channel API validator', function () {
10 let server: PeerTubeServer
11 const userInfo = {
12 accessToken: '',
13 channelName: 'fake_channel',
14 id: -1,
15 videoQuota: -1,
16 videoQuotaDaily: -1
17 }
18 let command: ChannelsCommand
19
20 // ---------------------------------------------------------------
21
22 before(async function () {
23 this.timeout(30000)
24
25 server = await createSingleServer(1)
26
27 await setAccessTokensToServers([ server ])
28
29 const userCreds = {
30 username: 'fake',
31 password: 'fake_password'
32 }
33
34 {
35 const user = await server.users.create({ username: userCreds.username, password: userCreds.password })
36 userInfo.id = user.id
37 userInfo.accessToken = await server.login.getAccessToken(userCreds)
38 }
39
40 command = server.channels
41 })
42
43 it('Should fail when HTTP upload is disabled', async function () {
44 await server.config.disableImports()
45
46 await command.importVideos({
47 channelName: 'super_channel',
48 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
49 token: server.accessToken,
50 expectedStatus: HttpStatusCode.FORBIDDEN_403
51 })
52
53 await server.config.enableImports()
54 })
55
56 it('Should fail when externalChannelUrl is not provided', async function () {
57 await command.importVideos({
58 channelName: 'super_channel',
59 externalChannelUrl: null,
60 token: server.accessToken,
61 expectedStatus: HttpStatusCode.BAD_REQUEST_400
62 })
63 })
64
65 it('Should fail when externalChannelUrl is malformed', async function () {
66 await command.importVideos({
67 channelName: 'super_channel',
68 externalChannelUrl: 'not-a-url',
69 token: server.accessToken,
70 expectedStatus: HttpStatusCode.BAD_REQUEST_400
71 })
72 })
73
74 it('Should fail with a bad sync id', async function () {
75 await command.importVideos({
76 channelName: 'super_channel',
77 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
78 videoChannelSyncId: 'toto' as any,
79 token: server.accessToken,
80 expectedStatus: HttpStatusCode.BAD_REQUEST_400
81 })
82 })
83
84 it('Should fail with a unknown sync id', async function () {
85 await command.importVideos({
86 channelName: 'super_channel',
87 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
88 videoChannelSyncId: 42,
89 token: server.accessToken,
90 expectedStatus: HttpStatusCode.NOT_FOUND_404
91 })
92 })
93
94 it('Should fail with no authentication', async function () {
95 await command.importVideos({
96 channelName: 'super_channel',
97 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
98 token: null,
99 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
100 })
101 })
102
103 it('Should fail when sync is not owned by the user', async function () {
104 await command.importVideos({
105 channelName: 'super_channel',
106 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
107 token: userInfo.accessToken,
108 expectedStatus: HttpStatusCode.FORBIDDEN_403
109 })
110 })
111
112 it('Should fail when the user has no quota', async function () {
113 await server.users.update({
114 userId: userInfo.id,
115 videoQuota: 0
116 })
117
118 await command.importVideos({
119 channelName: 'fake_channel',
120 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
121 token: userInfo.accessToken,
122 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
123 })
124
125 await server.users.update({
126 userId: userInfo.id,
127 videoQuota: userInfo.videoQuota
128 })
129 })
130
131 it('Should fail when the user has no daily quota', async function () {
132 await server.users.update({
133 userId: userInfo.id,
134 videoQuotaDaily: 0
135 })
136
137 await command.importVideos({
138 channelName: 'fake_channel',
139 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
140 token: userInfo.accessToken,
141 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
142 })
143
144 await server.users.update({
145 userId: userInfo.id,
146 videoQuotaDaily: userInfo.videoQuotaDaily
147 })
148 })
149
150 it('Should succeed when sync is run by its owner', async function () {
151 if (!areHttpImportTestsDisabled()) return
152
153 await command.importVideos({
154 channelName: 'fake_channel',
155 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
156 token: userInfo.accessToken
157 })
158 })
159
160 it('Should succeed when sync is run with root and for another user\'s channel', async function () {
161 if (!areHttpImportTestsDisabled()) return
162
163 await command.importVideos({
164 channelName: 'fake_channel',
165 externalChannelUrl: FIXTURE_URLS.youtubeChannel
166 })
167 })
168
169 after(async function () {
170 await cleanupTests([ server ])
171 })
172})
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts
index 5f1168b53..149305f49 100644
--- a/server/tests/api/check-params/index.ts
+++ b/server/tests/api/check-params/index.ts
@@ -28,6 +28,7 @@ import './video-comments'
28import './video-files' 28import './video-files'
29import './video-imports' 29import './video-imports'
30import './video-channel-syncs' 30import './video-channel-syncs'
31import './channel-import-videos'
31import './video-playlists' 32import './video-playlists'
32import './video-source' 33import './video-source'
33import './video-studio' 34import './video-studio'
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index 337ea1dd4..9024126c0 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -3,8 +3,8 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination, FIXTURE_URLS } from '@server/tests/shared' 6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
7import { areHttpImportTestsDisabled, buildAbsoluteFixturePath } from '@shared/core-utils' 7import { buildAbsoluteFixturePath } from '@shared/core-utils'
8import { HttpStatusCode, VideoChannelUpdate } from '@shared/models' 8import { HttpStatusCode, VideoChannelUpdate } from '@shared/models'
9import { 9import {
10 ChannelsCommand, 10 ChannelsCommand,
@@ -354,115 +354,6 @@ describe('Test video channels API validator', function () {
354 }) 354 })
355 }) 355 })
356 356
357 describe('When triggering full synchronization', function () {
358
359 it('Should fail when HTTP upload is disabled', async function () {
360 await server.config.disableImports()
361
362 await command.importVideos({
363 channelName: 'super_channel',
364 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
365 token: server.accessToken,
366 expectedStatus: HttpStatusCode.FORBIDDEN_403
367 })
368
369 await server.config.enableImports()
370 })
371
372 it('Should fail when externalChannelUrl is not provided', async function () {
373 await command.importVideos({
374 channelName: 'super_channel',
375 externalChannelUrl: null,
376 token: server.accessToken,
377 expectedStatus: HttpStatusCode.BAD_REQUEST_400
378 })
379 })
380
381 it('Should fail when externalChannelUrl is malformed', async function () {
382 await command.importVideos({
383 channelName: 'super_channel',
384 externalChannelUrl: 'not-a-url',
385 token: server.accessToken,
386 expectedStatus: HttpStatusCode.BAD_REQUEST_400
387 })
388 })
389
390 it('Should fail with no authentication', async function () {
391 await command.importVideos({
392 channelName: 'super_channel',
393 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
394 token: null,
395 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
396 })
397 })
398
399 it('Should fail when sync is not owned by the user', async function () {
400 await command.importVideos({
401 channelName: 'super_channel',
402 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
403 token: userInfo.accessToken,
404 expectedStatus: HttpStatusCode.FORBIDDEN_403
405 })
406 })
407
408 it('Should fail when the user has no quota', async function () {
409 await server.users.update({
410 userId: userInfo.id,
411 videoQuota: 0
412 })
413
414 await command.importVideos({
415 channelName: 'fake_channel',
416 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
417 token: userInfo.accessToken,
418 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
419 })
420
421 await server.users.update({
422 userId: userInfo.id,
423 videoQuota: userInfo.videoQuota
424 })
425 })
426
427 it('Should fail when the user has no daily quota', async function () {
428 await server.users.update({
429 userId: userInfo.id,
430 videoQuotaDaily: 0
431 })
432
433 await command.importVideos({
434 channelName: 'fake_channel',
435 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
436 token: userInfo.accessToken,
437 expectedStatus: HttpStatusCode.PAYLOAD_TOO_LARGE_413
438 })
439
440 await server.users.update({
441 userId: userInfo.id,
442 videoQuotaDaily: userInfo.videoQuotaDaily
443 })
444 })
445
446 it('Should succeed when sync is run by its owner', async function () {
447 if (!areHttpImportTestsDisabled()) return
448
449 await command.importVideos({
450 channelName: 'fake_channel',
451 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
452 token: userInfo.accessToken
453 })
454 })
455
456 it('Should succeed when sync is run with root and for another user\'s channel', async function () {
457 if (!areHttpImportTestsDisabled()) return
458
459 await command.importVideos({
460 channelName: 'fake_channel',
461 externalChannelUrl: FIXTURE_URLS.youtubeChannel
462 })
463 })
464 })
465
466 describe('When deleting a video channel', function () { 357 describe('When deleting a video channel', function () {
467 it('Should fail with a non authenticated user', async function () { 358 it('Should fail with a non authenticated user', async function () {
468 await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 }) 359 await command.delete({ token: 'coucou', channelName: 'super_channel', expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts
index 5cdd0d925..85382b261 100644
--- a/server/tests/api/check-params/video-imports.ts
+++ b/server/tests/api/check-params/video-imports.ts
@@ -59,6 +59,15 @@ describe('Test video imports API validator', function () {
59 await checkBadSortPagination(server.url, myPath, server.accessToken) 59 await checkBadSortPagination(server.url, myPath, server.accessToken)
60 }) 60 })
61 61
62 it('Should fail with a bad videoChannelSyncId param', async function () {
63 await makeGetRequest({
64 url: server.url,
65 path: myPath,
66 query: { videoChannelSyncId: 'toto' },
67 token: server.accessToken
68 })
69 })
70
62 it('Should success with the correct parameters', async function () { 71 it('Should success with the correct parameters', async function () {
63 await makeGetRequest({ url: server.url, path: myPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken }) 72 await makeGetRequest({ url: server.url, path: myPath, expectedStatus: HttpStatusCode.OK_200, token: server.accessToken })
64 }) 73 })
diff --git a/server/tests/api/videos/channel-import-videos.ts b/server/tests/api/videos/channel-import-videos.ts
index f7540e1ba..7cfd02fbb 100644
--- a/server/tests/api/videos/channel-import-videos.ts
+++ b/server/tests/api/videos/channel-import-videos.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
1import { expect } from 'chai' 3import { expect } from 'chai'
2import { FIXTURE_URLS } from '@server/tests/shared' 4import { FIXTURE_URLS } from '@server/tests/shared'
3import { areHttpImportTestsDisabled } from '@shared/core-utils' 5import { areHttpImportTestsDisabled } from '@shared/core-utils'
@@ -29,7 +31,7 @@ describe('Test videos import in a channel', function () {
29 await server.config.enableChannelSync() 31 await server.config.enableChannelSync()
30 }) 32 })
31 33
32 it('Should import a whole channel', async function () { 34 it('Should import a whole channel without specifying the sync id', async function () {
33 this.timeout(240_000) 35 this.timeout(240_000)
34 36
35 await server.channels.importVideos({ channelName: server.store.channel.name, externalChannelUrl: FIXTURE_URLS.youtubeChannel }) 37 await server.channels.importVideos({ channelName: server.store.channel.name, externalChannelUrl: FIXTURE_URLS.youtubeChannel })
@@ -39,6 +41,74 @@ describe('Test videos import in a channel', function () {
39 expect(videos.total).to.equal(2) 41 expect(videos.total).to.equal(2)
40 }) 42 })
41 43
44 it('These imports should not have a sync id', async function () {
45 const { total, data } = await server.imports.getMyVideoImports()
46
47 expect(total).to.equal(2)
48 expect(data).to.have.lengthOf(2)
49
50 for (const videoImport of data) {
51 expect(videoImport.videoChannelSync).to.not.exist
52 }
53 })
54
55 it('Should import a whole channel and specifying the sync id', async function () {
56 this.timeout(240_000)
57
58 {
59 server.store.channel.name = 'channel2'
60 const { id } = await server.channels.create({ attributes: { name: server.store.channel.name } })
61 server.store.channel.id = id
62 }
63
64 {
65 const attributes = {
66 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
67 videoChannelId: server.store.channel.id
68 }
69
70 const { videoChannelSync } = await server.channelSyncs.create({ attributes })
71 server.store.videoChannelSync = videoChannelSync
72
73 await waitJobs(server)
74 }
75
76 await server.channels.importVideos({
77 channelName: server.store.channel.name,
78 externalChannelUrl: FIXTURE_URLS.youtubeChannel,
79 videoChannelSyncId: server.store.videoChannelSync.id
80 })
81
82 await waitJobs(server)
83 })
84
85 it('These imports should have a sync id', async function () {
86 const { total, data } = await server.imports.getMyVideoImports()
87
88 expect(total).to.equal(4)
89 expect(data).to.have.lengthOf(4)
90
91 const importsWithSyncId = data.filter(i => !!i.videoChannelSync)
92 expect(importsWithSyncId).to.have.lengthOf(2)
93
94 for (const videoImport of importsWithSyncId) {
95 expect(videoImport.videoChannelSync).to.exist
96 expect(videoImport.videoChannelSync.id).to.equal(server.store.videoChannelSync.id)
97 }
98 })
99
100 it('Should be able to filter imports by this sync id', async function () {
101 const { total, data } = await server.imports.getMyVideoImports({ videoChannelSyncId: server.store.videoChannelSync.id })
102
103 expect(total).to.equal(2)
104 expect(data).to.have.lengthOf(2)
105
106 for (const videoImport of data) {
107 expect(videoImport.videoChannelSync).to.exist
108 expect(videoImport.videoChannelSync.id).to.equal(server.store.videoChannelSync.id)
109 }
110 })
111
42 after(async function () { 112 after(async function () {
43 await server?.kill() 113 await server?.kill()
44 }) 114 })
diff --git a/server/tests/api/videos/video-channel-syncs.ts b/server/tests/api/videos/video-channel-syncs.ts
index 229c01f68..835d3cb09 100644
--- a/server/tests/api/videos/video-channel-syncs.ts
+++ b/server/tests/api/videos/video-channel-syncs.ts
@@ -23,7 +23,10 @@ describe('Test channel synchronizations', function () {
23 describe('Sync using ' + mode, function () { 23 describe('Sync using ' + mode, function () {
24 let server: PeerTubeServer 24 let server: PeerTubeServer
25 let command: ChannelSyncsCommand 25 let command: ChannelSyncsCommand
26
26 let startTestDate: Date 27 let startTestDate: Date
28
29 let rootChannelSyncId: number
27 const userInfo = { 30 const userInfo = {
28 accessToken: '', 31 accessToken: '',
29 username: 'user1', 32 username: 'user1',
@@ -90,6 +93,7 @@ describe('Test channel synchronizations', function () {
90 token: server.accessToken, 93 token: server.accessToken,
91 expectedStatus: HttpStatusCode.OK_200 94 expectedStatus: HttpStatusCode.OK_200
92 }) 95 })
96 rootChannelSyncId = videoChannelSync.id
93 97
94 // Ensure any missing video not already fetched will be considered as new 98 // Ensure any missing video not already fetched will be considered as new
95 await changeDateForSync(videoChannelSync.id, '1970-01-01') 99 await changeDateForSync(videoChannelSync.id, '1970-01-01')
@@ -208,6 +212,14 @@ describe('Test channel synchronizations', function () {
208 } 212 }
209 }) 213 })
210 214
215 it('Should list imports of a channel synchronization', async function () {
216 const { total, data } = await server.imports.getMyVideoImports({ videoChannelSyncId: rootChannelSyncId })
217
218 expect(total).to.equal(1)
219 expect(data).to.have.lengthOf(1)
220 expect(data[0].video.name).to.equal('test')
221 })
222
211 it('Should remove user\'s channel synchronizations', async function () { 223 it('Should remove user\'s channel synchronizations', async function () {
212 await command.delete({ channelSyncId: userInfo.syncId }) 224 await command.delete({ channelSyncId: userInfo.syncId })
213 225
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index a487062a2..f082d4bd7 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -228,6 +228,15 @@ describe('Test video imports', function () {
228 expect(videoImports[0].targetUrl).to.equal(FIXTURE_URLS.youtube) 228 expect(videoImports[0].targetUrl).to.equal(FIXTURE_URLS.youtube)
229 }) 229 })
230 230
231 it('Should search in my imports', async function () {
232 const { total, data: videoImports } = await servers[0].imports.getMyVideoImports({ search: 'peertube2' })
233 expect(total).to.equal(1)
234 expect(videoImports).to.have.lengthOf(1)
235
236 expect(videoImports[0].magnetUri).to.equal(FIXTURE_URLS.magnet)
237 expect(videoImports[0].video.name).to.equal('super peertube2 video')
238 })
239
231 it('Should have the video listed on the two instances', async function () { 240 it('Should have the video listed on the two instances', async function () {
232 this.timeout(120_000) 241 this.timeout(120_000)
233 242