]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Update video channel routes
authorChocobozzz <me@florianbigard.com>
Tue, 24 Apr 2018 15:05:32 +0000 (17:05 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 24 Apr 2018 15:12:57 +0000 (17:12 +0200)
16 files changed:
CHANGELOG.md
server/controllers/api/accounts.ts
server/controllers/api/index.ts
server/controllers/api/video-channel.ts [new file with mode: 0644]
server/controllers/api/videos/channel.ts [deleted file]
server/controllers/api/videos/index.ts
server/controllers/feeds.ts
server/middlewares/validators/follows.ts
server/middlewares/validators/video-channels.ts
server/models/video/video.ts
server/tests/api/check-params/video-channels.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/video-channels.ts
server/tests/utils/videos/video-channels.ts
support/doc/api/html/index.html
support/doc/api/openapi.yaml

index 470b4f2956dd581ddea8df8a55b07c8c65aec830..ac3f61735e560b01df0d3a51b7f39d3d570c22d7 100644 (file)
@@ -5,6 +5,9 @@
 ### BREAKING CHANGES
 
  * Hide by default NSFW videos. Update the `instance.default_nsfw_policy` configuration to `blur` to keep the old behaviour
+ * Move video channels routes:
+   * `/videos/channels` routes to `/accounts/{accountId}/video-channels`
+   * `/videos/accounts/{accountId}/channels` route to `/accounts/{accountId}/video-channels`
  * PeerTube now listen on 127.0.0.1 by default
  * Use ISO 639 for language (*en*, *es*, *fr*...)
    * Tools (`import-videos`...) need the language ISO639 code instead of a number
index 06ab040339eb3f63dd528ff9370f906ff3915030..04c5897c5310e83b70339ac13720704cbac1c67f 100644 (file)
@@ -1,11 +1,30 @@
 import * as express from 'express'
-import { getFormattedObjects } from '../../helpers/utils'
-import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares'
+import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils'
+import {
+  asyncMiddleware,
+  authenticate,
+  listVideoAccountChannelsValidator,
+  optionalAuthenticate,
+  paginationValidator,
+  setDefaultPagination,
+  setDefaultSort,
+  videoChannelsAddValidator,
+  videoChannelsGetValidator,
+  videoChannelsRemoveValidator,
+  videoChannelsUpdateValidator
+} from '../../middlewares'
 import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators'
 import { AccountModel } from '../../models/account/account'
 import { VideoModel } from '../../models/video/video'
-import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
 import { isNSFWHidden } from '../../helpers/express-utils'
+import { VideoChannelModel } from '../../models/video/video-channel'
+import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
+import { sendUpdateActor } from '../../lib/activitypub/send'
+import { createVideoChannel } from '../../lib/video-channel'
+import { setAsyncActorKeys } from '../../lib/activitypub'
+import { sequelizeTypescript } from '../../initializers'
+import { logger } from '../../helpers/logger'
+import { retryTransactionWrapper } from '../../helpers/database-utils'
 
 const accountsRouter = express.Router()
 
@@ -29,7 +48,45 @@ accountsRouter.get('/:id/videos',
   setDefaultSort,
   setDefaultPagination,
   optionalAuthenticate,
-  asyncMiddleware(getAccountVideos)
+  asyncMiddleware(listAccountVideos)
+)
+
+accountsRouter.get('/:accountId/video-channels',
+  asyncMiddleware(listVideoAccountChannelsValidator),
+  asyncMiddleware(listVideoAccountChannels)
+)
+
+accountsRouter.post('/:accountId/video-channels',
+  authenticate,
+  videoChannelsAddValidator,
+  asyncMiddleware(addVideoChannelRetryWrapper)
+)
+
+accountsRouter.put('/:accountId/video-channels/:id',
+  authenticate,
+  asyncMiddleware(videoChannelsUpdateValidator),
+  updateVideoChannelRetryWrapper
+)
+
+accountsRouter.delete('/:accountId/video-channels/:id',
+  authenticate,
+  asyncMiddleware(videoChannelsRemoveValidator),
+  asyncMiddleware(removeVideoChannelRetryWrapper)
+)
+
+accountsRouter.get('/:accountId/video-channels/:id',
+  asyncMiddleware(videoChannelsGetValidator),
+  asyncMiddleware(getVideoChannel)
+)
+
+accountsRouter.get('/:accountId/video-channels/:id/videos',
+  asyncMiddleware(videoChannelsGetValidator),
+  paginationValidator,
+  videosSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  optionalAuthenticate,
+  asyncMiddleware(listVideoChannelVideos)
 )
 
 // ---------------------------------------------------------------------------
@@ -52,18 +109,142 @@ async function listAccounts (req: express.Request, res: express.Response, next:
   return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
 
-async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+// Wrapper to video channel add that retry the async function if there is a database error
+// We need this because we run the transaction in SERIALIZABLE isolation that can fail
+async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const options = {
+    arguments: [ req, res ],
+    errorMessage: 'Cannot insert the video video channel with many retries.'
+  }
+
+  const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
+  return res.json({
+    videoChannel: {
+      id: videoChannel.id
+    }
+  }).end()
+}
+
+async function addVideoChannel (req: express.Request, res: express.Response) {
+  const videoChannelInfo: VideoChannelCreate = req.body
+  const account: AccountModel = res.locals.oauth.token.User.Account
+
+  const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
+    return createVideoChannel(videoChannelInfo, account, t)
+  })
+
+  setAsyncActorKeys(videoChannelCreated.Actor)
+    .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
+
+  logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
+
+  return videoChannelCreated
+}
+
+async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const options = {
+    arguments: [ req, res ],
+    errorMessage: 'Cannot update the video with many retries.'
+  }
+
+  await retryTransactionWrapper(updateVideoChannel, options)
+
+  return res.type('json').status(204).end()
+}
+
+async function updateVideoChannel (req: express.Request, res: express.Response) {
+  const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
+  const videoChannelFieldsSave = videoChannelInstance.toJSON()
+  const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
+
+  try {
+    await sequelizeTypescript.transaction(async t => {
+      const sequelizeOptions = {
+        transaction: t
+      }
+
+      if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
+      if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
+      if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
+
+      const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
+      await sendUpdateActor(videoChannelInstanceUpdated, t)
+    })
+
+    logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
+  } catch (err) {
+    logger.debug('Cannot update the video channel.', { err })
+
+    // Force fields we want to update
+    // If the transaction is retried, sequelize will think the object has not changed
+    // So it will skip the SQL request, even if the last one was ROLLBACKed!
+    resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
+
+    throw err
+  }
+}
+
+async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const options = {
+    arguments: [ req, res ],
+    errorMessage: 'Cannot remove the video channel with many retries.'
+  }
+
+  await retryTransactionWrapper(removeVideoChannel, options)
+
+  return res.type('json').status(204).end()
+}
+
+async function removeVideoChannel (req: express.Request, res: express.Response) {
+  const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
+
+  return sequelizeTypescript.transaction(async t => {
+    await videoChannelInstance.destroy({ transaction: t })
+
+    logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
+  })
+
+}
+
+async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
+
+  return res.json(videoChannelWithVideos.toFormattedJSON())
+}
+
+async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
+
+  const resultList = await VideoModel.listForApi({
+    start: req.query.start,
+    count: req.query.count,
+    sort: req.query.sort,
+    hideNSFW: isNSFWHidden(res),
+    withFiles: false,
+    videoChannelId: videoChannelInstance.id
+  })
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+
+async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
   const account: AccountModel = res.locals.account
 
-  const resultList = await VideoModel.listForApi(
-    req.query.start as number,
-    req.query.count as number,
-    req.query.sort as VideoSortField,
-    isNSFWHidden(res),
-    null,
-    false,
-    account.id
-  )
+  const resultList = await VideoModel.listForApi({
+    start: req.query.start,
+    count: req.query.count,
+    sort: req.query.sort,
+    hideNSFW: isNSFWHidden(res),
+    withFiles: false,
+    accountId: account.id
+  })
 
   return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
index 964d5d04c3c893b13b9855111f53be26220acfb6..8f63b9535c7f76b9c6bc94626be1c9289c631a9e 100644 (file)
@@ -7,6 +7,7 @@ import { usersRouter } from './users'
 import { accountsRouter } from './accounts'
 import { videosRouter } from './videos'
 import { badRequest } from '../../helpers/express-utils'
+import { videoChannelRouter } from './video-channel'
 
 const apiRouter = express.Router()
 
@@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter)
 apiRouter.use('/config', configRouter)
 apiRouter.use('/users', usersRouter)
 apiRouter.use('/accounts', accountsRouter)
+apiRouter.use('/video-channels', videoChannelRouter)
 apiRouter.use('/videos', videosRouter)
 apiRouter.use('/jobs', jobsRouter)
 apiRouter.use('/ping', pong)
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
new file mode 100644 (file)
index 0000000..d572737
--- /dev/null
@@ -0,0 +1,34 @@
+import * as express from 'express'
+import { getFormattedObjects } from '../../helpers/utils'
+import {
+  asyncMiddleware,
+  paginationValidator,
+  setDefaultPagination,
+  setDefaultSort,
+  videoChannelsSortValidator
+} from '../../middlewares'
+import { VideoChannelModel } from '../../models/video/video-channel'
+
+const videoChannelRouter = express.Router()
+
+videoChannelRouter.get('/',
+  paginationValidator,
+  videoChannelsSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  asyncMiddleware(listVideoChannels)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  videoChannelRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
deleted file mode 100644 (file)
index e547d37..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-import * as express from 'express'
-import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
-import { retryTransactionWrapper } from '../../../helpers/database-utils'
-import { logger } from '../../../helpers/logger'
-import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils'
-import { sequelizeTypescript } from '../../../initializers'
-import { setAsyncActorKeys } from '../../../lib/activitypub'
-import { sendUpdateActor } from '../../../lib/activitypub/send'
-import { createVideoChannel } from '../../../lib/video-channel'
-import {
-  asyncMiddleware, authenticate, listVideoAccountChannelsValidator, paginationValidator, setDefaultSort, setDefaultPagination,
-  videoChannelsAddValidator, videoChannelsGetValidator, videoChannelsRemoveValidator, videoChannelsSortValidator,
-  videoChannelsUpdateValidator
-} from '../../../middlewares'
-import { AccountModel } from '../../../models/account/account'
-import { VideoChannelModel } from '../../../models/video/video-channel'
-
-const videoChannelRouter = express.Router()
-
-videoChannelRouter.get('/channels',
-  paginationValidator,
-  videoChannelsSortValidator,
-  setDefaultSort,
-  setDefaultPagination,
-  asyncMiddleware(listVideoChannels)
-)
-
-videoChannelRouter.get('/accounts/:accountId/channels',
-  asyncMiddleware(listVideoAccountChannelsValidator),
-  asyncMiddleware(listVideoAccountChannels)
-)
-
-videoChannelRouter.post('/channels',
-  authenticate,
-  videoChannelsAddValidator,
-  asyncMiddleware(addVideoChannelRetryWrapper)
-)
-
-videoChannelRouter.put('/channels/:id',
-  authenticate,
-  asyncMiddleware(videoChannelsUpdateValidator),
-  updateVideoChannelRetryWrapper
-)
-
-videoChannelRouter.delete('/channels/:id',
-  authenticate,
-  asyncMiddleware(videoChannelsRemoveValidator),
-  asyncMiddleware(removeVideoChannelRetryWrapper)
-)
-
-videoChannelRouter.get('/channels/:id',
-  asyncMiddleware(videoChannelsGetValidator),
-  asyncMiddleware(getVideoChannel)
-)
-
-// ---------------------------------------------------------------------------
-
-export {
-  videoChannelRouter
-}
-
-// ---------------------------------------------------------------------------
-
-async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort)
-
-  return res.json(getFormattedObjects(resultList.data, resultList.total))
-}
-
-async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
-
-  return res.json(getFormattedObjects(resultList.data, resultList.total))
-}
-
-// Wrapper to video channel add that retry the async function if there is a database error
-// We need this because we run the transaction in SERIALIZABLE isolation that can fail
-async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const options = {
-    arguments: [ req, res ],
-    errorMessage: 'Cannot insert the video video channel with many retries.'
-  }
-
-  const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
-  return res.json({
-    videoChannel: {
-      id: videoChannel.id
-    }
-  }).end()
-}
-
-async function addVideoChannel (req: express.Request, res: express.Response) {
-  const videoChannelInfo: VideoChannelCreate = req.body
-  const account: AccountModel = res.locals.oauth.token.User.Account
-
-  const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
-    return createVideoChannel(videoChannelInfo, account, t)
-  })
-
-  setAsyncActorKeys(videoChannelCreated.Actor)
-    .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
-
-  logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
-
-  return videoChannelCreated
-}
-
-async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const options = {
-    arguments: [ req, res ],
-    errorMessage: 'Cannot update the video with many retries.'
-  }
-
-  await retryTransactionWrapper(updateVideoChannel, options)
-
-  return res.type('json').status(204).end()
-}
-
-async function updateVideoChannel (req: express.Request, res: express.Response) {
-  const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
-  const videoChannelFieldsSave = videoChannelInstance.toJSON()
-  const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
-
-  try {
-    await sequelizeTypescript.transaction(async t => {
-      const sequelizeOptions = {
-        transaction: t
-      }
-
-      if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
-      if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
-      if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
-
-      const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
-      await sendUpdateActor(videoChannelInstanceUpdated, t)
-    })
-
-    logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
-  } catch (err) {
-    logger.debug('Cannot update the video channel.', { err })
-
-    // Force fields we want to update
-    // If the transaction is retried, sequelize will think the object has not changed
-    // So it will skip the SQL request, even if the last one was ROLLBACKed!
-    resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
-
-    throw err
-  }
-}
-
-async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const options = {
-    arguments: [ req, res ],
-    errorMessage: 'Cannot remove the video channel with many retries.'
-  }
-
-  await retryTransactionWrapper(removeVideoChannel, options)
-
-  return res.type('json').status(204).end()
-}
-
-async function removeVideoChannel (req: express.Request, res: express.Response) {
-  const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
-
-  return sequelizeTypescript.transaction(async t => {
-    await videoChannelInstance.destroy({ transaction: t })
-
-    logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
-  })
-
-}
-
-async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
-
-  return res.json(videoChannelWithVideos.toFormattedJSON())
-}
index 61b6c58260f526d74763c60dda5f67d478388f9c..4b3198a749033b60e4eb356d1d2c2095a5c44dc5 100644 (file)
@@ -42,7 +42,6 @@ import { VideoModel } from '../../../models/video/video'
 import { VideoFileModel } from '../../../models/video/video-file'
 import { abuseVideoRouter } from './abuse'
 import { blacklistRouter } from './blacklist'
-import { videoChannelRouter } from './channel'
 import { videoCommentRouter } from './comment'
 import { rateVideoRouter } from './rate'
 import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
@@ -72,7 +71,6 @@ const reqVideoFileUpdate = createReqFiles(
 videosRouter.use('/', abuseVideoRouter)
 videosRouter.use('/', blacklistRouter)
 videosRouter.use('/', rateVideoRouter)
-videosRouter.use('/', videoChannelRouter)
 videosRouter.use('/', videoCommentRouter)
 
 videosRouter.get('/categories', listVideoCategories)
@@ -397,13 +395,14 @@ async function getVideoDescription (req: express.Request, res: express.Response)
 }
 
 async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const resultList = await VideoModel.listForApi(
-    req.query.start as number,
-    req.query.count as number,
-    req.query.sort as VideoSortField,
-    isNSFWHidden(res),
-    req.query.filter as VideoFilter
-  )
+  const resultList = await VideoModel.listForApi({
+    start: req.query.start,
+    count: req.query.count,
+    sort: req.query.sort,
+    hideNSFW: isNSFWHidden(res),
+    filter: req.query.filter as VideoFilter,
+    withFiles: false
+  })
 
   return res.json(getFormattedObjects(resultList.data, resultList.total))
 }
index 6a6af3e0910f9d50d4ebb17a29e97fb475098445..7dcaf7004f311e29bac7d43d701ff5d6e11161ed 100644 (file)
@@ -33,15 +33,15 @@ async function generateFeed (req: express.Request, res: express.Response, next:
   const account: AccountModel = res.locals.account
   const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
 
-  const resultList = await VideoModel.listForApi(
+  const resultList = await VideoModel.listForApi({
     start,
-    FEEDS.COUNT,
-    req.query.sort as VideoSortField,
+    count: FEEDS.COUNT,
+    sort: req.query.sort,
     hideNSFW,
-    req.query.filter,
-    true,
-    account ? account.id : null
-  )
+    filter: req.query.filter,
+    withFiles: true,
+    accountId: account ? account.id : null
+  })
 
   // Adding video items to the feed, one at a time
   resultList.data.forEach(video => {
index 991a2e1757f8a2b8098e5c572849036d95f8f177..bdf39eb9c543389b58139d87e8e5339e822b6c54 100644 (file)
@@ -16,7 +16,7 @@ const followValidator = [
     if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
       return res.status(400)
         .json({
-          error: 'Cannot follow non HTTPS web server.'
+          error: 'Cannot follow on a non HTTPS web server.'
         })
         .end()
     }
index fe42105e871f35b84169b8b54834a4c6e0ff34eb..e3a11a41b58aff00f221f571b93e8a8340367a87 100644 (file)
@@ -26,6 +26,7 @@ const listVideoAccountChannelsValidator = [
 ]
 
 const videoChannelsAddValidator = [
+  param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
   body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
   body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
   body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -41,6 +42,7 @@ const videoChannelsAddValidator = [
 
 const videoChannelsUpdateValidator = [
   param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
   body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
   body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
   body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -49,6 +51,7 @@ const videoChannelsUpdateValidator = [
     logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
 
     if (areValidationErrors(req, res)) return
+    if (!await isAccountIdExist(req.params.accountId, res)) return
     if (!await isVideoChannelExist(req.params.id, res)) return
 
     // We need to make additional checks
@@ -70,11 +73,13 @@ const videoChannelsUpdateValidator = [
 
 const videoChannelsRemoveValidator = [
   param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
+    if (!await isAccountIdExist(req.params.accountId, res)) return
     if (!await isVideoChannelExist(req.params.id, res)) return
 
     // Check if the user who did the request is able to delete the video
@@ -87,11 +92,14 @@ const videoChannelsRemoveValidator = [
 
 const videoChannelsGetValidator = [
   param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
+  param('accountId').optional().custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking videoChannelsGet parameters', { parameters: req.params })
 
     if (areValidationErrors(req, res)) return
+    // On some routes, accountId is optional (for example in the ActivityPub route)
+    if (req.params.accountId && !await isAccountIdExist(req.params.accountId, res)) return
     if (!await isVideoChannelExist(req.params.id, res)) return
 
     return next()
index 2ad9c00dd482fa6f9cc9a41550bb68e26a6ae472..7ababbf232e4c0e3c5c8a1e77e51a5c1d5509d45 100644 (file)
@@ -95,7 +95,14 @@ enum ScopeNames {
 }
 
 @Scopes({
-  [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => {
+  [ScopeNames.AVAILABLE_FOR_LIST]: (options: {
+    actorId: number,
+    hideNSFW: boolean,
+    filter?: VideoFilter,
+    withFiles?: boolean,
+    accountId?: number,
+    videoChannelId?: number
+  }) => {
     const accountInclude = {
       attributes: [ 'name' ],
       model: AccountModel.unscoped(),
@@ -106,7 +113,7 @@ enum ScopeNames {
           attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
           model: ActorModel.unscoped(),
           required: true,
-          where: VideoModel.buildActorWhereWithFilter(filter),
+          where: VideoModel.buildActorWhereWithFilter(options.filter),
           include: [
             {
               attributes: [ 'host' ],
@@ -122,6 +129,18 @@ enum ScopeNames {
       ]
     }
 
+    const videoChannelInclude = {
+      attributes: [ 'name', 'description' ],
+      model: VideoChannelModel.unscoped(),
+      required: true,
+      where: {},
+      include: [
+        accountInclude
+      ]
+    }
+
+    // Force actorId to be a number to avoid SQL injections
+    const actorIdNumber = parseInt(options.actorId.toString(), 10)
     const query: IFindOptions<VideoModel> = {
       where: {
         id: {
@@ -132,32 +151,23 @@ enum ScopeNames {
             '(' +
             'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
             'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
-            'WHERE "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) +
+            'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
             ' UNION ' +
             'SELECT "video"."id" AS "id" FROM "video" ' +
             'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
             'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
             'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
             'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
-            'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) +
+            'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + actorIdNumber +
             ')'
           )
         },
         privacy: VideoPrivacy.PUBLIC
       },
-      include: [
-        {
-          attributes: [ 'name', 'description' ],
-          model: VideoChannelModel.unscoped(),
-          required: true,
-          include: [
-            accountInclude
-          ]
-        }
-      ]
+      include: [ videoChannelInclude ]
     }
 
-    if (withFiles === true) {
+    if (options.withFiles === true) {
       query.include.push({
         model: VideoFileModel.unscoped(),
         required: true
@@ -165,13 +175,19 @@ enum ScopeNames {
     }
 
     // Hide nsfw videos?
-    if (hideNSFW === true) {
+    if (options.hideNSFW === true) {
       query.where['nsfw'] = false
     }
 
-    if (accountId) {
+    if (options.accountId) {
       accountInclude.where = {
-        id: accountId
+        id: options.accountId
+      }
+    }
+
+    if (options.videoChannelId) {
+      videoChannelInclude.where = {
+        id: options.videoChannelId
       }
     }
 
@@ -697,23 +713,37 @@ export class VideoModel extends Model<VideoModel> {
     })
   }
 
-  static async listForApi (
+  static async listForApi (options: {
     start: number,
     count: number,
     sort: string,
     hideNSFW: boolean,
+    withFiles: boolean,
     filter?: VideoFilter,
-    withFiles = false,
-    accountId?: number
-  ) {
+    accountId?: number,
+    videoChannelId?: number
+  }) {
     const query = {
-      offset: start,
-      limit: count,
-      order: getSort(sort)
+      offset: options.start,
+      limit: options.count,
+      order: getSort(options.sort)
     }
 
     const serverActor = await getServerActor()
-    return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] })
+    const scopes = {
+      method: [
+        ScopeNames.AVAILABLE_FOR_LIST, {
+          actorId: serverActor.id,
+          hideNSFW: options.hideNSFW,
+          filter: options.filter,
+          withFiles: options.withFiles,
+          accountId: options.accountId,
+          videoChannelId: options.videoChannelId
+        }
+      ]
+    }
+
+    return VideoModel.scope(scopes)
       .findAndCountAll(query)
       .then(({ rows, count }) => {
         return {
@@ -750,8 +780,16 @@ export class VideoModel extends Model<VideoModel> {
     }
 
     const serverActor = await getServerActor()
+    const scopes = {
+      method: [
+        ScopeNames.AVAILABLE_FOR_LIST, {
+          actorId: serverActor.id,
+          hideNSFW
+        }
+      ]
+    }
 
-    return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW ] })
+    return VideoModel.scope(scopes)
       .findAndCountAll(query)
       .then(({ rows, count }) => {
         return {
index 43c5462ee8028861724b32311af96d554d5314e2..acb6bdd57ead1bd48dfbeae9f0c3abb20ab2c7db 100644 (file)
@@ -4,15 +4,29 @@ import * as chai from 'chai'
 import { omit } from 'lodash'
 import 'mocha'
 import {
-  createUser, deleteVideoChannel, flushTests, getAccountVideoChannelsList, getVideoChannelsList, immutableAssign, killallServers,
-  makeGetRequest, makePostBodyRequest, makePutBodyRequest, runServer, ServerInfo, setAccessTokensToServers, userLogin
+  createUser,
+  deleteVideoChannel,
+  flushTests,
+  getAccountVideoChannelsList,
+  getVideoChannelsList,
+  immutableAssign,
+  killallServers,
+  makeGetRequest,
+  makePostBodyRequest,
+  makePutBodyRequest,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  userLogin
 } from '../../utils'
 import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+import { getAccountsList } from '../../utils/users/accounts'
 
 const expect = chai.expect
 
 describe('Test videos API validator', function () {
-  const path = '/api/v1/videos/channels'
+  const videoChannelPath = '/api/v1/video-channels'
+  const accountPath = '/api/v1/accounts/'
   let server: ServerInfo
   let accessTokenUser: string
 
@@ -37,15 +51,15 @@ describe('Test videos API validator', function () {
 
   describe('When listing a video channels', function () {
     it('Should fail with a bad start pagination', async function () {
-      await checkBadStartPagination(server.url, path, server.accessToken)
+      await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
     })
 
     it('Should fail with a bad count pagination', async function () {
-      await checkBadCountPagination(server.url, path, server.accessToken)
+      await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
     })
 
     it('Should fail with an incorrect sort', async function () {
-      await checkBadSortPagination(server.url, path, server.accessToken)
+      await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
     })
   })
 
@@ -60,12 +74,20 @@ describe('Test videos API validator', function () {
   })
 
   describe('When adding a video channel', function () {
+    let path: string
+
     const baseCorrectParams = {
       name: 'hello',
       description: 'super description',
       support: 'super support text'
     }
 
+    before(async function () {
+      const res = await getAccountsList(server.url)
+      const accountId = res.body.data[0].id
+      path = accountPath + accountId + '/video-channels'
+    })
+
     it('Should fail with a non authenticated user', async function () {
       await makePostBodyRequest({ url: server.url, path, token: 'none', fields: baseCorrectParams, statusCodeExpected: 401 })
     })
@@ -107,22 +129,27 @@ describe('Test videos API validator', function () {
   })
 
   describe('When updating a video channel', function () {
+    let path: string
+
     const baseCorrectParams = {
       name: 'hello',
       description: 'super description'
     }
 
-    let videoChannelId
-
     before(async function () {
-      const res = await getVideoChannelsList(server.url, 0, 1)
-      videoChannelId = res.body.data[0].id
+      const res1 = await getVideoChannelsList(server.url, 0, 1)
+      const videoChannelId = res1.body.data[0].id
+
+      const res2 = await getAccountsList(server.url)
+      const accountId = res2.body.data[0].id
+
+      path = accountPath + accountId + '/video-channels/' + videoChannelId
     })
 
     it('Should fail with a non authenticated user', async function () {
       await makePutBodyRequest({
         url: server.url,
-        path: path + '/' + videoChannelId,
+        path,
         token: 'hi',
         fields: baseCorrectParams,
         statusCodeExpected: 401
@@ -132,7 +159,7 @@ describe('Test videos API validator', function () {
     it('Should fail with another authenticated user', async function () {
       await makePutBodyRequest({
         url: server.url,
-        path: path + '/' + videoChannelId,
+        path,
         token: accessTokenUser,
         fields: baseCorrectParams,
         statusCodeExpected: 403
@@ -141,23 +168,23 @@ describe('Test videos API validator', function () {
 
     it('Should fail with a long name', async function () {
       const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(25) })
-      await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields })
+      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
     it('Should fail with a long description', async function () {
       const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(60) })
-      await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields })
+      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
     it('Should fail with a long support text', async function () {
       const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
-      await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields })
+      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
     it('Should succeed with the correct parameters', async function () {
       await makePutBodyRequest({
         url: server.url,
-        path: path + '/' + videoChannelId,
+        path,
         token: server.accessToken,
         fields: baseCorrectParams,
         statusCodeExpected: 204
@@ -166,17 +193,23 @@ describe('Test videos API validator', function () {
   })
 
   describe('When getting a video channel', function () {
+    let basePath: string
     let videoChannelId: number
 
     before(async function () {
-      const res = await getVideoChannelsList(server.url, 0, 1)
-      videoChannelId = res.body.data[0].id
+      const res1 = await getVideoChannelsList(server.url, 0, 1)
+      videoChannelId = res1.body.data[0].id
+
+      const res2 = await getAccountsList(server.url)
+      const accountId = res2.body.data[0].id
+
+      basePath = accountPath + accountId + '/video-channels'
     })
 
     it('Should return the list of the video channels with nothing', async function () {
       const res = await makeGetRequest({
         url: server.url,
-        path,
+        path: basePath,
         statusCodeExpected: 200
       })
 
@@ -186,7 +219,7 @@ describe('Test videos API validator', function () {
     it('Should fail without a correct uuid', async function () {
       await makeGetRequest({
         url: server.url,
-        path: path + '/coucou',
+        path: basePath + '/coucou',
         statusCodeExpected: 400
       })
     })
@@ -194,7 +227,7 @@ describe('Test videos API validator', function () {
     it('Should return 404 with an incorrect video channel', async function () {
       await makeGetRequest({
         url: server.url,
-        path: path + '/4da6fde3-88f7-4d16-b119-108df5630b06',
+        path: basePath + '/4da6fde3-88f7-4d16-b119-108df5630b06',
         statusCodeExpected: 404
       })
     })
@@ -202,7 +235,7 @@ describe('Test videos API validator', function () {
     it('Should succeed with the correct parameters', async function () {
       await makeGetRequest({
         url: server.url,
-        path: path + '/' + videoChannelId,
+        path: basePath + '/' + videoChannelId,
         statusCodeExpected: 200
       })
     })
@@ -210,33 +243,41 @@ describe('Test videos API validator', function () {
 
   describe('When deleting a video channel', function () {
     let videoChannelId: number
+    let accountId: number
 
     before(async function () {
-      const res = await getVideoChannelsList(server.url, 0, 1)
-      videoChannelId = res.body.data[0].id
+      const res1 = await getVideoChannelsList(server.url, 0, 1)
+      videoChannelId = res1.body.data[0].id
+
+      const res2 = await getAccountsList(server.url)
+      accountId = res2.body.data[0].id
     })
 
     it('Should fail with a non authenticated user', async function () {
-      await deleteVideoChannel(server.url, 'coucou', videoChannelId, 401)
+      await deleteVideoChannel(server.url, 'coucou', accountId, videoChannelId, 401)
     })
 
     it('Should fail with another authenticated user', async function () {
-      await deleteVideoChannel(server.url, accessTokenUser, videoChannelId, 403)
+      await deleteVideoChannel(server.url, accessTokenUser, accountId, videoChannelId, 403)
+    })
+
+    it('Should fail with an unknown account id', async function () {
+      await deleteVideoChannel(server.url, server.accessToken, 454554,videoChannelId, 404)
     })
 
-    it('Should fail with an unknown id', async function () {
-      await deleteVideoChannel(server.url, server.accessToken, 454554, 404)
+    it('Should fail with an unknown video channel id', async function () {
+      await deleteVideoChannel(server.url, server.accessToken, accountId,454554, 404)
     })
 
     it('Should succeed with the correct parameters', async function () {
-      await deleteVideoChannel(server.url, server.accessToken, videoChannelId)
+      await deleteVideoChannel(server.url, server.accessToken, accountId, videoChannelId)
     })
 
     it('Should fail to delete the last user video channel', async function () {
       const res = await getVideoChannelsList(server.url, 0, 1)
       videoChannelId = res.body.data[0].id
 
-      await deleteVideoChannel(server.url, server.accessToken, videoChannelId, 409)
+      await deleteVideoChannel(server.url, server.accessToken, accountId, videoChannelId, 409)
     })
   })
 
index 2563939ecdf4c24a5add56f22aacbdd9d6afe56b..6238cdc0858e92e18e6f9d3ee7549ecd6896a923 100644 (file)
@@ -39,6 +39,7 @@ import {
   getVideoCommentThreads,
   getVideoThreadComments
 } from '../../utils/videos/video-comments'
+import { getAccountsList } from '../../utils/users/accounts'
 
 const expect = chai.expect
 
@@ -46,6 +47,7 @@ describe('Test multiple servers', function () {
   let servers: ServerInfo[] = []
   const toRemove = []
   let videoUUID = ''
+  let accountId: number
   let videoChannelId: number
 
   before(async function () {
@@ -56,13 +58,20 @@ describe('Test multiple servers', function () {
     // Get the access tokens
     await setAccessTokensToServers(servers)
 
-    const videoChannel = {
-      name: 'my channel',
-      description: 'super channel'
+    {
+      const res = await getAccountsList(servers[0].url)
+      accountId = res.body.data[0].id
+    }
+
+    {
+      const videoChannel = {
+        name: 'my channel',
+        description: 'super channel'
+      }
+      await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, accountId, videoChannel)
+      const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1)
+      videoChannelId = channelRes.body.data[ 0 ].id
     }
-    await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
-    const channelRes = await getVideoChannelsList(servers[0].url, 0, 1)
-    videoChannelId = channelRes.body.data[0].id
 
     // Server 1 and server 2 follow each other
     await doubleFollow(servers[0], servers[1])
index b9c9bbf3cbd25efa99e29d9d50fa62005564fd7d..a7552a83a7f7cbee253d280e4f917c7e4ec68d81 100644 (file)
@@ -17,12 +17,14 @@ import {
   setAccessTokensToServers,
   updateVideoChannel
 } from '../../utils/index'
+import { getAccountsList } from '../../utils/users/accounts'
 
 const expect = chai.expect
 
 describe('Test video channels', function () {
   let servers: ServerInfo[]
   let userInfo: User
+  let accountId: number
   let videoChannelId: number
 
   before(async function () {
@@ -35,6 +37,11 @@ describe('Test video channels', function () {
     await setAccessTokensToServers(servers)
     await doubleFollow(servers[0], servers[1])
 
+    {
+      const res = await getAccountsList(servers[0].url)
+      accountId = res.body.data[0].id
+    }
+
     await wait(5000)
   })
 
@@ -54,7 +61,7 @@ describe('Test video channels', function () {
       description: 'super video channel description',
       support: 'super video channel support text'
     }
-    const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
+    const res = await addVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannel)
     videoChannelId = res.body.videoChannel.id
 
     // The channel is 1 is propagated to servers 2
@@ -120,7 +127,7 @@ describe('Test video channels', function () {
       support: 'video channel support text updated'
     }
 
-    await updateVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId, videoChannelAttributes)
+    await updateVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannelId, videoChannelAttributes)
 
     await wait(3000)
   })
@@ -139,7 +146,7 @@ describe('Test video channels', function () {
   })
 
   it('Should get video channel', async function () {
-    const res = await getVideoChannel(servers[0].url, videoChannelId)
+    const res = await getVideoChannel(servers[0].url, accountId, videoChannelId)
 
     const videoChannel = res.body
     expect(videoChannel.displayName).to.equal('video channel updated')
@@ -148,7 +155,7 @@ describe('Test video channels', function () {
   })
 
   it('Should delete video channel', async function () {
-    await deleteVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId)
+    await deleteVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannelId)
   })
 
   it('Should have video channel deleted', async function () {
index 2d095d8ab25fbabe8d26ac8d527ced1c6e4806d4..cfc541431d52f788884794de5c977f0d3ec2b4b4 100644 (file)
@@ -7,7 +7,7 @@ type VideoChannelAttributes = {
 }
 
 function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
-  const path = '/api/v1/videos/channels'
+  const path = '/api/v1/video-channels'
 
   const req = request(url)
     .get(path)
@@ -22,7 +22,7 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
 }
 
 function getAccountVideoChannelsList (url: string, accountId: number | string, specialStatus = 200) {
-  const path = '/api/v1/videos/accounts/' + accountId + '/channels'
+  const path = '/api/v1/accounts/' + accountId + '/video-channels'
 
   return request(url)
     .get(path)
@@ -31,8 +31,14 @@ function getAccountVideoChannelsList (url: string, accountId: number | string, s
     .expect('Content-Type', /json/)
 }
 
-function addVideoChannel (url: string, token: string, videoChannelAttributesArg: VideoChannelAttributes, expectedStatus = 200) {
-  const path = '/api/v1/videos/channels'
+function addVideoChannel (
+  url: string,
+  token: string,
+  accountId: number,
+  videoChannelAttributesArg: VideoChannelAttributes,
+  expectedStatus = 200
+) {
+  const path = '/api/v1/accounts/' + accountId + '/video-channels/'
 
   // Default attributes
   let attributes = {
@@ -50,9 +56,16 @@ function addVideoChannel (url: string, token: string, videoChannelAttributesArg:
     .expect(expectedStatus)
 }
 
-function updateVideoChannel (url: string, token: string, channelId: number, attributes: VideoChannelAttributes, expectedStatus = 204) {
+function updateVideoChannel (
+  url: string,
+  token: string,
+  accountId: number,
+  channelId: number,
+  attributes: VideoChannelAttributes,
+  expectedStatus = 204
+) {
   const body = {}
-  const path = '/api/v1/videos/channels/' + channelId
+  const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
 
   if (attributes.name) body['name'] = attributes.name
   if (attributes.description) body['description'] = attributes.description
@@ -66,18 +79,18 @@ function updateVideoChannel (url: string, token: string, channelId: number, attr
     .expect(expectedStatus)
 }
 
-function deleteVideoChannel (url: string, token: string, channelId: number, expectedStatus = 204) {
-  const path = '/api/v1/videos/channels/'
+function deleteVideoChannel (url: string, token: string, accountId: number, channelId: number, expectedStatus = 204) {
+  const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
 
   return request(url)
-    .delete(path + channelId)
+    .delete(path)
     .set('Accept', 'application/json')
     .set('Authorization', 'Bearer ' + token)
     .expect(expectedStatus)
 }
 
-function getVideoChannel (url: string, channelId: number) {
-  const path = '/api/v1/videos/channels/' + channelId
+function getVideoChannel (url: string, accountId: number, channelId: number) {
+  const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
 
   return request(url)
     .get(path)
index bf9430e799bf49792c56a9ee21a1ccc4ea316bf8..23162c307cbe254bc62d67e883b7acc0c8f1efe8 100644 (file)
               </li>
             </ul>
           </section>
+          <section>
+            <a href="#tag-Feeds">Feeds</a>
+            <ul>
+              <li>
+                <a href="#operation--feeds-videos.-format--get"> GET /feeds/videos.{format} </a>
+              </li>
+            </ul>
+          </section>
           <section>
             <a href="#tag-Job">Job</a>
             <ul>
             <a href="#tag-VideoChannel">VideoChannel</a>
             <ul>
               <li>
-                <a href="#operation--videos-channels-get"> GET /videos/channels </a>
+                <a href="#operation--video-channels-get"> GET /video-channels </a>
               </li>
               <li>
-                <a href="#operation--videos-channels-post"> POST /videos/channels </a>
+                <a href="#operation--accounts--accountId--video-channels-get"> GET /accounts/{accountId}/video-channels </a>
               </li>
               <li>
-                <a href="#operation--videos-channels--id--get"> GET /videos/channels/{id} </a>
+                <a href="#operation--accounts--accountId--video-channels-post"> POST /accounts/{accountId}/video-channels </a>
               </li>
               <li>
-                <a href="#operation--videos-channels--id--put"> PUT /videos/channels/{id} </a>
+                <a href="#operation--account--accountId--video-channels--id--get"> GET /account/{accountId}/video-channels/{id} </a>
               </li>
               <li>
-                <a href="#operation--videos-channels--id--delete"> DELETE /videos/channels/{id} </a>
+                <a href="#operation--account--accountId--video-channels--id--put"> PUT /account/{accountId}/video-channels/{id} </a>
               </li>
               <li>
-                <a href="#operation--videos-accounts--accountId--channels-get"> GET /videos/accounts/{accountId}/channels </a>
+                <a href="#operation--account--accountId--video-channels--id--delete"> DELETE /account/{accountId}/video-channels/{id} </a>
               </li>
             </ul>
           </section>
             </ul>
           </section>
           <h5>Schema Definitions</h5>
-          <a href="#definition-VideoConstant"> VideoConstant </a>
+          <a href="#definition-VideoConstantNumber"> VideoConstantNumber </a>
+          <a href="#definition-VideoConstantString"> VideoConstantString </a>
           <a href="#definition-VideoPrivacy"> VideoPrivacy </a>
           <a href="#definition-Video"> Video </a>
           <a href="#definition-VideoAbuse"> VideoAbuse </a>
               </div>
             </div>
           </div>
+          <h1 id="tag-Feeds" class="swagger-summary-tag" data-traverse-target="tag-Feeds">Feeds</h1>
+          <div id="operation--feeds-videos.-format--get" class="operation panel" data-traverse-target="operation--feeds-videos.-format--get">
+            <!-- <section class="operation-tags row"> -->
+            <!-- <div class="doc-copy"> -->
+            <div class="operation-tags">
+              <a class="label" href="#tag-Feeds">Feeds</a>
+              <!---->
+            </div>
+            <!-- </div> -->
+            <!-- </section> -->
+            <h2 class="operation-title">
+              <span class="operation-name">
+                <span class="operation-name">GET</span>
+                <span class="operation-path">/feeds/videos.{format}</span>
+              </span>
+            </h2>
+            <div class="doc-row">
+              <div class="doc-copy">
+                <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">format</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-enum" title="Possible values">
+                          <span class="json-property-enum-item json-property-enum-default-value">xml</span>,
+                          <span class="json-property-enum-item">atom</span>,
+                          <span class="json-property-enum-item">json</span>
+                        </span>
+                        <span class="json-property-range" title="Value limits"></span>
+                        <span class="json-property-default-value" title="Default value">xml</span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The format expected (xml defaults to RSS 2.0, atom to ATOM 1.0 and json to JSON FEED 1.0</p>
+                    </div>
+                  </div>
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <div class="prop-subtitle"> in query </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">number</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The id of the local account to filter to (beware, users IDs and not actors IDs which will return empty feeds</p>
+                    </div>
+                  </div>
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountName</div>
+                      <div class="prop-subtitle"> in query </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The name of the local account to filter to</p>
+                    </div>
+                  </div>
+                </section>
+              </div>
+              <div class="doc-examples"></div>
+            </div>
+            <div class="doc-row">
+              <div class="doc-copy">
+                <section class="swagger-responses">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">200 OK</div>
+                    </div>
+                    <div class="prop-value">
+                      <p>successful operation</p>
+                    </div>
+                  </div>
+                </section>
+              </div>
+              <div class="doc-examples">
+                <h5>Response Content-Types:
+                  <span>application/atom+xml, application/rss+xml, application/json</span>
+                </h5>
+              </div>
+            </div>
+          </div>
           <h1 id="tag-Job" class="swagger-summary-tag" data-traverse-target="tag-Job">Job</h1>
           <div id="operation--jobs-get" class="operation panel" data-traverse-target="operation--jobs-get">
             <!-- <section class="operation-tags row"> -->
               <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
             },
             <span class="hljs-attr">&quot;language&quot;</span>: {
-              <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+              <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
               <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
             },
             <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
             <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
           },
           <span class="hljs-attr">&quot;language&quot;</span>: {
-            <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+            <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
             <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
           },
           <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
               <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
             },
             <span class="hljs-attr">&quot;language&quot;</span>: {
-              <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+              <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
               <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
             },
             <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;language&quot;</span>: {
-      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;language&quot;</span>: {
-      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;language&quot;</span>: {
-      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+      <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
       <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
     },
     <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
                       <div class="prop-title">language</div>
                       <div class="prop-subtitle"> in formData </div>
                       <div class="prop-subtitle">
-                        <span class="json-property-type">number</span>
+                        <span class="json-property-type">string</span>
                         <span class="json-property-range" title="Value limits"></span>
                       </div>
                     </div>
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;language&quot;</span>: {
-    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;language&quot;</span>: {
-    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
                       <div class="prop-title">language</div>
                       <div class="prop-subtitle"> in formData </div>
                       <div class="prop-subtitle">
-                        <span class="json-property-type">number</span>
+                        <span class="json-property-type">string</span>
                         <span class="json-property-range" title="Value limits"></span>
                       </div>
                     </div>
             </div>
           </div>
           <h1 id="tag-VideoChannel" class="swagger-summary-tag" data-traverse-target="tag-VideoChannel">VideoChannel</h1>
-          <div id="operation--videos-channels-get" class="operation panel" data-traverse-target="operation--videos-channels-get">
+          <div id="operation--video-channels-get" class="operation panel" data-traverse-target="operation--video-channels-get">
             <!-- <section class="operation-tags row"> -->
             <!-- <div class="doc-copy"> -->
             <div class="operation-tags">
             <h2 class="operation-title">
               <span class="operation-name">
                 <span class="operation-name">GET</span>
-                <span class="operation-path">/videos/channels</span>
+                <span class="operation-path">/video-channels</span>
               </span>
             </h2>
             <div class="doc-row">
           <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
         },
         <span class="hljs-attr">&quot;language&quot;</span>: {
+          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+        },
+        <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
+        <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;thumbnailPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;previewPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;embedPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;views&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;likes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;dislikes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;nsfw&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
+        <span class="hljs-attr">&quot;account&quot;</span>: {
+          <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;url&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;host&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;avatar&quot;</span>: {
+            <span class="hljs-attr">&quot;path&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+            <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+            <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+          }
+        }
+      }
+    ]
+  }
+]
+</code></pre>
+                  <!-- </div> -->
+                </section>
+              </div>
+            </div>
+          </div>
+          <div id="operation--accounts--accountId--video-channels-get" class="operation panel" data-traverse-target="operation--accounts--accountId--video-channels-get">
+            <!-- <section class="operation-tags row"> -->
+            <!-- <div class="doc-copy"> -->
+            <div class="operation-tags">
+              <a class="label" href="#tag-VideoChannel">VideoChannel</a>
+              <!---->
+            </div>
+            <!-- </div> -->
+            <!-- </section> -->
+            <h2 class="operation-title">
+              <span class="operation-name">
+                <span class="operation-name">GET</span>
+                <span class="operation-path">/accounts/{accountId}/video-channels</span>
+              </span>
+            </h2>
+            <div class="doc-row">
+              <div class="doc-copy">
+                <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The account id </p>
+                    </div>
+                  </div>
+                </section>
+              </div>
+              <div class="doc-examples"></div>
+            </div>
+            <div class="doc-row">
+              <div class="doc-copy">
+                <section class="swagger-responses">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">200 OK</div>
+                      <div class="prop-ref">
+                        <span class="json-schema-ref-array">
+                          <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
+                        </span>
+                      </div>
+                      <!-- <span class="swagger-global"></span> <span class="json-schema-reference"><a href=""></a></span> -->
+                    </div>
+                    <div class="prop-value">
+                      <p>successful operation</p>
+                    </div>
+                  </div>
+                  <div class="prop-row prop-inner">
+                    <div class="prop-name">type</div>
+                    <div class="prop-value">
+                      <span class="json-property-type">
+                        <span class="json-schema-ref-array">
+                          <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
+                        </span>
+                      </span>
+                      <span class="json-property-range" title="Value limits"></span>
+                    </div>
+                  </div>
+                </section>
+              </div>
+              <div class="doc-examples">
+                <h5>Response Content-Types:
+                  <span>application/json</span>
+                </h5>
+                <section>
+                  <h5>Response Example
+                    <span>(200 OK)</span>
+                  </h5>
+                  <!-- <div class="hljs"> --><pre><code class="hljs lang-json">[
+  {
+    <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+    <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+    <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
+    <span class="hljs-attr">&quot;owner&quot;</span>: {
+      <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+      <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+    },
+    <span class="hljs-attr">&quot;videos&quot;</span>: [
+      {
+        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;publishedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+        <span class="hljs-attr">&quot;category&quot;</span>: {
           <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
           <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
         },
+        <span class="hljs-attr">&quot;licence&quot;</span>: {
+          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+        },
+        <span class="hljs-attr">&quot;language&quot;</span>: {
+          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+        },
         <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
         <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
         <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
               </div>
             </div>
           </div>
-          <div id="operation--videos-channels-post" class="operation panel" data-traverse-target="operation--videos-channels-post">
+          <div id="operation--accounts--accountId--video-channels-post" class="operation panel" data-traverse-target="operation--accounts--accountId--video-channels-post">
             <!-- <section class="operation-tags row"> -->
             <!-- <div class="doc-copy"> -->
             <div class="operation-tags">
             <h2 class="operation-title">
               <span class="operation-name">
                 <span class="operation-name">POST</span>
-                <span class="operation-path">/videos/channels</span>
+                <span class="operation-path">/accounts/{accountId}/video-channels</span>
               </span>
             </h2>
             <div class="doc-row">
                     </div>
                   </div>
                 </section>
+                <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The account id </p>
+                    </div>
+                  </div>
+                </section>
               </div>
               <div class="doc-examples">
                 <section>
               </div>
             </div>
           </div>
-          <div id="operation--videos-channels--id--get" class="operation panel" data-traverse-target="operation--videos-channels--id--get">
+          <div id="operation--account--accountId--video-channels--id--get" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--get">
             <!-- <section class="operation-tags row"> -->
             <!-- <div class="doc-copy"> -->
             <div class="operation-tags">
             <h2 class="operation-title">
               <span class="operation-name">
                 <span class="operation-name">GET</span>
-                <span class="operation-path">/videos/channels/{id}</span>
+                <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
               </span>
             </h2>
             <div class="doc-row">
               <div class="doc-copy">
                 <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The account id </p>
+                    </div>
+                  </div>
                   <div class="prop-row prop-group">
                     <div class="prop-name">
                       <div class="prop-title">id</div>
                       </div>
                     </div>
                     <div class="prop-value">
-                      <p>The video id </p>
+                      <p>The video channel id </p>
                     </div>
                   </div>
                 </section>
         <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
       },
       <span class="hljs-attr">&quot;language&quot;</span>: {
-        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
         <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
       },
       <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
               </div>
             </div>
           </div>
-          <div id="operation--videos-channels--id--put" class="operation panel" data-traverse-target="operation--videos-channels--id--put">
+          <div id="operation--account--accountId--video-channels--id--put" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--put">
             <!-- <section class="operation-tags row"> -->
             <!-- <div class="doc-copy"> -->
             <div class="operation-tags">
             <h2 class="operation-title">
               <span class="operation-name">
                 <span class="operation-name">PUT</span>
-                <span class="operation-path">/videos/channels/{id}</span>
+                <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
               </span>
             </h2>
             <div class="doc-row">
                   </div>
                 </section>
                 <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The account id </p>
+                    </div>
+                  </div>
                   <div class="prop-row prop-group">
                     <div class="prop-name">
                       <div class="prop-title">id</div>
                       </div>
                     </div>
                     <div class="prop-value">
-                      <p>The video id </p>
+                      <p>The video channel id </p>
                     </div>
                   </div>
                 </section>
               </div>
             </div>
           </div>
-          <div id="operation--videos-channels--id--delete" class="operation panel" data-traverse-target="operation--videos-channels--id--delete">
+          <div id="operation--account--accountId--video-channels--id--delete" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--delete">
             <!-- <section class="operation-tags row"> -->
             <!-- <div class="doc-copy"> -->
             <div class="operation-tags">
             <h2 class="operation-title">
               <span class="operation-name">
                 <span class="operation-name">DELETE</span>
-                <span class="operation-path">/videos/channels/{id}</span>
+                <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
               </span>
             </h2>
             <div class="doc-row">
               <div class="doc-copy">
                 <section class="swagger-request-params">
+                  <div class="prop-row prop-group">
+                    <div class="prop-name">
+                      <div class="prop-title">accountId</div>
+                      <span class="json-property-required"></span>
+                      <div class="prop-subtitle"> in path </div>
+                      <div class="prop-subtitle">
+                        <span class="json-property-type">string</span>
+                        <span class="json-property-range" title="Value limits"></span>
+                      </div>
+                    </div>
+                    <div class="prop-value">
+                      <p>The account id </p>
+                    </div>
+                  </div>
                   <div class="prop-row prop-group">
                     <div class="prop-name">
                       <div class="prop-title">id</div>
                       </div>
                     </div>
                     <div class="prop-value">
-                      <p>The video id </p>
+                      <p>The video channel id </p>
                     </div>
                   </div>
                 </section>
               </div>
             </div>
           </div>
-          <div id="operation--videos-accounts--accountId--channels-get" class="operation panel" data-traverse-target="operation--videos-accounts--accountId--channels-get">
-            <!-- <section class="operation-tags row"> -->
-            <!-- <div class="doc-copy"> -->
-            <div class="operation-tags">
-              <a class="label" href="#tag-VideoChannel">VideoChannel</a>
-              <!---->
-            </div>
-            <!-- </div> -->
-            <!-- </section> -->
-            <h2 class="operation-title">
-              <span class="operation-name">
-                <span class="operation-name">GET</span>
-                <span class="operation-path">/videos/accounts/{accountId}/channels</span>
-              </span>
-            </h2>
-            <div class="doc-row">
-              <div class="doc-copy">
-                <section class="swagger-request-params">
-                  <div class="prop-row prop-group">
-                    <div class="prop-name">
-                      <div class="prop-title">accountId</div>
-                      <span class="json-property-required"></span>
-                      <div class="prop-subtitle"> in path </div>
-                      <div class="prop-subtitle">
-                        <span class="json-property-type">string</span>
-                        <span class="json-property-range" title="Value limits"></span>
-                      </div>
-                    </div>
-                    <div class="prop-value">
-                      <p>The account id </p>
-                    </div>
-                  </div>
-                </section>
-              </div>
-              <div class="doc-examples"></div>
-            </div>
-            <div class="doc-row">
-              <div class="doc-copy">
-                <section class="swagger-responses">
-                  <div class="prop-row prop-group">
-                    <div class="prop-name">
-                      <div class="prop-title">200 OK</div>
-                      <div class="prop-ref">
-                        <span class="json-schema-ref-array">
-                          <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
-                        </span>
-                      </div>
-                      <!-- <span class="swagger-global"></span> <span class="json-schema-reference"><a href=""></a></span> -->
-                    </div>
-                    <div class="prop-value">
-                      <p>successful operation</p>
-                    </div>
-                  </div>
-                  <div class="prop-row prop-inner">
-                    <div class="prop-name">type</div>
-                    <div class="prop-value">
-                      <span class="json-property-type">
-                        <span class="json-schema-ref-array">
-                          <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
-                        </span>
-                      </span>
-                      <span class="json-property-range" title="Value limits"></span>
-                    </div>
-                  </div>
-                </section>
-              </div>
-              <div class="doc-examples">
-                <h5>Response Content-Types:
-                  <span>application/json</span>
-                </h5>
-                <section>
-                  <h5>Response Example
-                    <span>(200 OK)</span>
-                  </h5>
-                  <!-- <div class="hljs"> --><pre><code class="hljs lang-json">[
-  {
-    <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-    <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-    <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
-    <span class="hljs-attr">&quot;owner&quot;</span>: {
-      <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-      <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
-    },
-    <span class="hljs-attr">&quot;videos&quot;</span>: [
-      {
-        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-        <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;publishedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;category&quot;</span>: {
-          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
-        },
-        <span class="hljs-attr">&quot;licence&quot;</span>: {
-          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
-        },
-        <span class="hljs-attr">&quot;language&quot;</span>: {
-          <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-          <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
-        },
-        <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-        <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
-        <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;thumbnailPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;previewPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;embedPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-        <span class="hljs-attr">&quot;views&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-        <span class="hljs-attr">&quot;likes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-        <span class="hljs-attr">&quot;dislikes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
-        <span class="hljs-attr">&quot;nsfw&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
-        <span class="hljs-attr">&quot;account&quot;</span>: {
-          <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-          <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-          <span class="hljs-attr">&quot;url&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-          <span class="hljs-attr">&quot;host&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-          <span class="hljs-attr">&quot;avatar&quot;</span>: {
-            <span class="hljs-attr">&quot;path&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-            <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
-            <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
-          }
-        }
-      }
-    ]
-  }
-]
-</code></pre>
-                  <!-- </div> -->
-                </section>
-              </div>
-            </div>
-          </div>
           <h1 id="tag-VideoComment" class="swagger-summary-tag" data-traverse-target="tag-VideoComment">VideoComment</h1>
           <div id="operation--videos--videoId--comment-threads-get" class="operation panel" data-traverse-target="operation--videos--videoId--comment-threads-get">
             <!-- <section class="operation-tags row"> -->
             </div>
           </div>
           <h1>Schema Definitions</h1>
-          <div id="definition-VideoConstant" class="definition panel" data-traverse-target="definition-VideoConstant">
+          <div id="definition-VideoConstantNumber" class="definition panel" data-traverse-target="definition-VideoConstantNumber">
             <h2 class="panel-title">
-              <a name="/definitions/VideoConstant"></a>VideoConstant:
+              <a name="/definitions/VideoConstantNumber"></a>VideoConstantNumber:
               <!-- <span class="json-property-type"><span class="json-property-type">object</span>
               <span class="json-property-range" title="Value limits"></span>
               
   <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
   <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
 }
+</code></pre>
+                  <!-- </div> -->
+                </section>
+              </div>
+            </div>
+          </div>
+          <div id="definition-VideoConstantString" class="definition panel" data-traverse-target="definition-VideoConstantString">
+            <h2 class="panel-title">
+              <a name="/definitions/VideoConstantString"></a>VideoConstantString:
+              <!-- <span class="json-property-type"><span class="json-property-type">object</span>
+              <span class="json-property-range" title="Value limits"></span>
+              
+              
+              </span> -->
+            </h2>
+            <div class="doc-row">
+              <div class="doc-copy">
+                <section class="json-schema-properties">
+                  <dl>
+                    <dt data-property-name="id">
+                      <span class="json-property-name">id:</span>
+                      <span class="json-property-type">string</span>
+                      <span class="json-property-range" title="Value limits"></span>
+                    </dt>
+                    <dt data-property-name="label">
+                      <span class="json-property-name">label:</span>
+                      <span class="json-property-type">string</span>
+                      <span class="json-property-range" title="Value limits"></span>
+                    </dt>
+                  </dl>
+                </section>
+              </div>
+              <div class="doc-examples">
+                <section>
+                  <h5>Example</h5>
+                  <!-- <div class="hljs"> --><pre><code class="hljs lang-json">{
+  <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
+  <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
+}
 </code></pre>
                   <!-- </div> -->
                 </section>
                       <span class="json-property-name">category:</span>
                       <span class="json-property-type">
                         <span class="">
-                          <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a>
+                          <a class="json-schema-ref" href="#/definitions/VideoConstantNumber">VideoConstantNumber</a>
                         </span>
                       </span>
                       <span class="json-property-range" title="Value limits"></span>
                       <span class="json-property-name">licence:</span>
                       <span class="json-property-type">
                         <span class="">
-                          <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a>
+                          <a class="json-schema-ref" href="#/definitions/VideoConstantNumber">VideoConstantNumber</a>
                         </span>
                       </span>
                       <span class="json-property-range" title="Value limits"></span>
                       <span class="json-property-name">language:</span>
                       <span class="json-property-type">
                         <span class="">
-                          <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a>
+                          <a class="json-schema-ref" href="#/definitions/VideoConstantString">VideoConstantString</a>
                         </span>
                       </span>
                       <span class="json-property-range" title="Value limits"></span>
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;language&quot;</span>: {
-    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+    <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
     <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
   },
   <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
         <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
       },
       <span class="hljs-attr">&quot;language&quot;</span>: {
-        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+        <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
         <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
       },
       <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
             <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
           },
           <span class="hljs-attr">&quot;language&quot;</span>: {
-            <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
+            <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
             <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
           },
           <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
index 10f60175dd7b3b0dd84f0dfe7a8a0e97321e5fd0..4a1f06d005fe206cd88c84b498f3b6cd5a5edebf 100644 (file)
@@ -91,7 +91,7 @@ paths:
           in: path
           required: true
           type: string
-          enum: ['xml', 'atom' 'json']
+          enum: [ 'xml', 'atom', 'json']
           default: 'xml'
           description: 'The format expected (xml defaults to RSS 2.0, atom to ATOM 1.0 and json to JSON FEED 1.0'
         - name: accountId
@@ -967,7 +967,7 @@ paths:
             type: array
             items:
               $ref: '#/definitions/VideoBlacklist'
-  /videos/channels:
+  /video-channels:
     get:
       tags:
         - VideoChannel
@@ -998,6 +998,27 @@ paths:
             type: array
             items:
               $ref: '#/definitions/VideoChannel'
+  /accounts/{accountId}/video-channels:
+    get:
+      tags:
+        - VideoChannel
+      consumes:
+        - application/json
+      produces:
+        - application/json
+      parameters:
+        - name: accountId
+          in: path
+          required: true
+          type: string
+          description: 'The account id '
+      responses:
+        '200':
+          description: successful operation
+          schema:
+            type: array
+            items:
+              $ref: '#/definitions/VideoChannel'
     post:
       security:
         - OAuth2: [ ]
@@ -1008,6 +1029,11 @@ paths:
       produces:
         - application/json
       parameters:
+        - name: accountId
+          in: path
+          required: true
+          type: string
+          description: 'The account id '
         - in: body
           name: body
           schema:
@@ -1015,7 +1041,7 @@ paths:
       responses:
         '204':
           description: successful operation
-  "/videos/channels/{id}":
+  "/account/{accountId}/video-channels/{id}":
     get:
       tags:
         - VideoChannel
@@ -1024,11 +1050,16 @@ paths:
       produces:
         - application/json
       parameters:
+        - name: accountId
+          in: path
+          required: true
+          type: string
+          description: 'The account id '
         - name: id
           in: path
           required: true
           type: string
-          description: 'The video id '
+          description: 'The video channel id '
       responses:
         '200':
           description: successful operation
@@ -1044,11 +1075,16 @@ paths:
       produces:
         - application/json
       parameters:
+        - name: accountId
+          in: path
+          required: true
+          type: string
+          description: 'The account id '
         - name: id
           in: path
           required: true
           type: string
-          description: 'The video id '
+          description: 'The video channel id '
         - in: body
           name: body
           schema:
@@ -1066,35 +1102,19 @@ paths:
       produces:
         - application/json
       parameters:
-        - name: id
+        - name: accountId
           in: path
           required: true
           type: string
-          description: 'The video id '
-      responses:
-        '204':
-          description: successful operation
-  /videos/accounts/{accountId}/channels:
-    get:
-      tags:
-        - VideoChannel
-      consumes:
-        - application/json
-      produces:
-        - application/json
-      parameters:
-        - name: accountId
+          description: 'The account id '
+        - name: id
           in: path
           required: true
           type: string
-          description: 'The account id '
+          description: 'The video channel id '
       responses:
-        '200':
+        '204':
           description: successful operation
-          schema:
-            type: array
-            items:
-              $ref: '#/definitions/VideoChannel'
   "/videos/{videoId}/comment-threads":
     get:
       tags: