aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--server/controllers/api/accounts.ts209
-rw-r--r--server/controllers/api/index.ts2
-rw-r--r--server/controllers/api/video-channel.ts34
-rw-r--r--server/controllers/api/videos/channel.ts177
-rw-r--r--server/controllers/api/videos/index.ts17
-rw-r--r--server/controllers/feeds.ts14
-rw-r--r--server/middlewares/validators/follows.ts2
-rw-r--r--server/middlewares/validators/video-channels.ts8
-rw-r--r--server/models/video/video.ts92
-rw-r--r--server/tests/api/check-params/video-channels.ts101
-rw-r--r--server/tests/api/videos/multiple-servers.ts21
-rw-r--r--server/tests/api/videos/video-channels.ts15
-rw-r--r--server/tests/utils/videos/video-channels.ts35
-rw-r--r--support/doc/api/html/index.html543
-rw-r--r--support/doc/api/openapi.yaml72
16 files changed, 859 insertions, 486 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 470b4f295..ac3f61735 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@
5### BREAKING CHANGES 5### BREAKING CHANGES
6 6
7 * Hide by default NSFW videos. Update the `instance.default_nsfw_policy` configuration to `blur` to keep the old behaviour 7 * Hide by default NSFW videos. Update the `instance.default_nsfw_policy` configuration to `blur` to keep the old behaviour
8 * Move video channels routes:
9 * `/videos/channels` routes to `/accounts/{accountId}/video-channels`
10 * `/videos/accounts/{accountId}/channels` route to `/accounts/{accountId}/video-channels`
8 * PeerTube now listen on 127.0.0.1 by default 11 * PeerTube now listen on 127.0.0.1 by default
9 * Use ISO 639 for language (*en*, *es*, *fr*...) 12 * Use ISO 639 for language (*en*, *es*, *fr*...)
10 * Tools (`import-videos`...) need the language ISO639 code instead of a number 13 * Tools (`import-videos`...) need the language ISO639 code instead of a number
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 06ab04033..04c5897c5 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,11 +1,30 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils'
3import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares' 3import {
4 asyncMiddleware,
5 authenticate,
6 listVideoAccountChannelsValidator,
7 optionalAuthenticate,
8 paginationValidator,
9 setDefaultPagination,
10 setDefaultSort,
11 videoChannelsAddValidator,
12 videoChannelsGetValidator,
13 videoChannelsRemoveValidator,
14 videoChannelsUpdateValidator
15} from '../../middlewares'
4import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' 16import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators'
5import { AccountModel } from '../../models/account/account' 17import { AccountModel } from '../../models/account/account'
6import { VideoModel } from '../../models/video/video' 18import { VideoModel } from '../../models/video/video'
7import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type'
8import { isNSFWHidden } from '../../helpers/express-utils' 19import { isNSFWHidden } from '../../helpers/express-utils'
20import { VideoChannelModel } from '../../models/video/video-channel'
21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22import { sendUpdateActor } from '../../lib/activitypub/send'
23import { createVideoChannel } from '../../lib/video-channel'
24import { setAsyncActorKeys } from '../../lib/activitypub'
25import { sequelizeTypescript } from '../../initializers'
26import { logger } from '../../helpers/logger'
27import { retryTransactionWrapper } from '../../helpers/database-utils'
9 28
10const accountsRouter = express.Router() 29const accountsRouter = express.Router()
11 30
@@ -29,7 +48,45 @@ accountsRouter.get('/:id/videos',
29 setDefaultSort, 48 setDefaultSort,
30 setDefaultPagination, 49 setDefaultPagination,
31 optionalAuthenticate, 50 optionalAuthenticate,
32 asyncMiddleware(getAccountVideos) 51 asyncMiddleware(listAccountVideos)
52)
53
54accountsRouter.get('/:accountId/video-channels',
55 asyncMiddleware(listVideoAccountChannelsValidator),
56 asyncMiddleware(listVideoAccountChannels)
57)
58
59accountsRouter.post('/:accountId/video-channels',
60 authenticate,
61 videoChannelsAddValidator,
62 asyncMiddleware(addVideoChannelRetryWrapper)
63)
64
65accountsRouter.put('/:accountId/video-channels/:id',
66 authenticate,
67 asyncMiddleware(videoChannelsUpdateValidator),
68 updateVideoChannelRetryWrapper
69)
70
71accountsRouter.delete('/:accountId/video-channels/:id',
72 authenticate,
73 asyncMiddleware(videoChannelsRemoveValidator),
74 asyncMiddleware(removeVideoChannelRetryWrapper)
75)
76
77accountsRouter.get('/:accountId/video-channels/:id',
78 asyncMiddleware(videoChannelsGetValidator),
79 asyncMiddleware(getVideoChannel)
80)
81
82accountsRouter.get('/:accountId/video-channels/:id/videos',
83 asyncMiddleware(videoChannelsGetValidator),
84 paginationValidator,
85 videosSortValidator,
86 setDefaultSort,
87 setDefaultPagination,
88 optionalAuthenticate,
89 asyncMiddleware(listVideoChannelVideos)
33) 90)
34 91
35// --------------------------------------------------------------------------- 92// ---------------------------------------------------------------------------
@@ -52,18 +109,142 @@ async function listAccounts (req: express.Request, res: express.Response, next:
52 return res.json(getFormattedObjects(resultList.data, resultList.total)) 109 return res.json(getFormattedObjects(resultList.data, resultList.total))
53} 110}
54 111
55async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 112async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
113 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
114
115 return res.json(getFormattedObjects(resultList.data, resultList.total))
116}
117
118// Wrapper to video channel add that retry the async function if there is a database error
119// We need this because we run the transaction in SERIALIZABLE isolation that can fail
120async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
121 const options = {
122 arguments: [ req, res ],
123 errorMessage: 'Cannot insert the video video channel with many retries.'
124 }
125
126 const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
127 return res.json({
128 videoChannel: {
129 id: videoChannel.id
130 }
131 }).end()
132}
133
134async function addVideoChannel (req: express.Request, res: express.Response) {
135 const videoChannelInfo: VideoChannelCreate = req.body
136 const account: AccountModel = res.locals.oauth.token.User.Account
137
138 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
139 return createVideoChannel(videoChannelInfo, account, t)
140 })
141
142 setAsyncActorKeys(videoChannelCreated.Actor)
143 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
144
145 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
146
147 return videoChannelCreated
148}
149
150async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
151 const options = {
152 arguments: [ req, res ],
153 errorMessage: 'Cannot update the video with many retries.'
154 }
155
156 await retryTransactionWrapper(updateVideoChannel, options)
157
158 return res.type('json').status(204).end()
159}
160
161async function updateVideoChannel (req: express.Request, res: express.Response) {
162 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
163 const videoChannelFieldsSave = videoChannelInstance.toJSON()
164 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
165
166 try {
167 await sequelizeTypescript.transaction(async t => {
168 const sequelizeOptions = {
169 transaction: t
170 }
171
172 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
173 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
174 if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
175
176 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
177 await sendUpdateActor(videoChannelInstanceUpdated, t)
178 })
179
180 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
181 } catch (err) {
182 logger.debug('Cannot update the video channel.', { err })
183
184 // Force fields we want to update
185 // If the transaction is retried, sequelize will think the object has not changed
186 // So it will skip the SQL request, even if the last one was ROLLBACKed!
187 resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
188
189 throw err
190 }
191}
192
193async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
194 const options = {
195 arguments: [ req, res ],
196 errorMessage: 'Cannot remove the video channel with many retries.'
197 }
198
199 await retryTransactionWrapper(removeVideoChannel, options)
200
201 return res.type('json').status(204).end()
202}
203
204async function removeVideoChannel (req: express.Request, res: express.Response) {
205 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
206
207 return sequelizeTypescript.transaction(async t => {
208 await videoChannelInstance.destroy({ transaction: t })
209
210 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
211 })
212
213}
214
215async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
216 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
217
218 return res.json(videoChannelWithVideos.toFormattedJSON())
219}
220
221async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
222 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
223
224 const resultList = await VideoModel.listForApi({
225 start: req.query.start,
226 count: req.query.count,
227 sort: req.query.sort,
228 hideNSFW: isNSFWHidden(res),
229 withFiles: false,
230 videoChannelId: videoChannelInstance.id
231 })
232
233 return res.json(getFormattedObjects(resultList.data, resultList.total))
234}
235
236
237async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
56 const account: AccountModel = res.locals.account 238 const account: AccountModel = res.locals.account
57 239
58 const resultList = await VideoModel.listForApi( 240 const resultList = await VideoModel.listForApi({
59 req.query.start as number, 241 start: req.query.start,
60 req.query.count as number, 242 count: req.query.count,
61 req.query.sort as VideoSortField, 243 sort: req.query.sort,
62 isNSFWHidden(res), 244 hideNSFW: isNSFWHidden(res),
63 null, 245 withFiles: false,
64 false, 246 accountId: account.id
65 account.id 247 })
66 )
67 248
68 return res.json(getFormattedObjects(resultList.data, resultList.total)) 249 return res.json(getFormattedObjects(resultList.data, resultList.total))
69} 250}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 964d5d04c..8f63b9535 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -7,6 +7,7 @@ import { usersRouter } from './users'
7import { accountsRouter } from './accounts' 7import { accountsRouter } from './accounts'
8import { videosRouter } from './videos' 8import { videosRouter } from './videos'
9import { badRequest } from '../../helpers/express-utils' 9import { badRequest } from '../../helpers/express-utils'
10import { videoChannelRouter } from './video-channel'
10 11
11const apiRouter = express.Router() 12const apiRouter = express.Router()
12 13
@@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter)
15apiRouter.use('/config', configRouter) 16apiRouter.use('/config', configRouter)
16apiRouter.use('/users', usersRouter) 17apiRouter.use('/users', usersRouter)
17apiRouter.use('/accounts', accountsRouter) 18apiRouter.use('/accounts', accountsRouter)
19apiRouter.use('/video-channels', videoChannelRouter)
18apiRouter.use('/videos', videosRouter) 20apiRouter.use('/videos', videosRouter)
19apiRouter.use('/jobs', jobsRouter) 21apiRouter.use('/jobs', jobsRouter)
20apiRouter.use('/ping', pong) 22apiRouter.use('/ping', pong)
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
new file mode 100644
index 000000000..d57273747
--- /dev/null
+++ b/server/controllers/api/video-channel.ts
@@ -0,0 +1,34 @@
1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils'
3import {
4 asyncMiddleware,
5 paginationValidator,
6 setDefaultPagination,
7 setDefaultSort,
8 videoChannelsSortValidator
9} from '../../middlewares'
10import { VideoChannelModel } from '../../models/video/video-channel'
11
12const videoChannelRouter = express.Router()
13
14videoChannelRouter.get('/',
15 paginationValidator,
16 videoChannelsSortValidator,
17 setDefaultSort,
18 setDefaultPagination,
19 asyncMiddleware(listVideoChannels)
20)
21
22// ---------------------------------------------------------------------------
23
24export {
25 videoChannelRouter
26}
27
28// ---------------------------------------------------------------------------
29
30async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
31 const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort)
32
33 return res.json(getFormattedObjects(resultList.data, resultList.total))
34}
diff --git a/server/controllers/api/videos/channel.ts b/server/controllers/api/videos/channel.ts
deleted file mode 100644
index e547d375f..000000000
--- a/server/controllers/api/videos/channel.ts
+++ /dev/null
@@ -1,177 +0,0 @@
1import * as express from 'express'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger'
5import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils'
6import { sequelizeTypescript } from '../../../initializers'
7import { setAsyncActorKeys } from '../../../lib/activitypub'
8import { sendUpdateActor } from '../../../lib/activitypub/send'
9import { createVideoChannel } from '../../../lib/video-channel'
10import {
11 asyncMiddleware, authenticate, listVideoAccountChannelsValidator, paginationValidator, setDefaultSort, setDefaultPagination,
12 videoChannelsAddValidator, videoChannelsGetValidator, videoChannelsRemoveValidator, videoChannelsSortValidator,
13 videoChannelsUpdateValidator
14} from '../../../middlewares'
15import { AccountModel } from '../../../models/account/account'
16import { VideoChannelModel } from '../../../models/video/video-channel'
17
18const videoChannelRouter = express.Router()
19
20videoChannelRouter.get('/channels',
21 paginationValidator,
22 videoChannelsSortValidator,
23 setDefaultSort,
24 setDefaultPagination,
25 asyncMiddleware(listVideoChannels)
26)
27
28videoChannelRouter.get('/accounts/:accountId/channels',
29 asyncMiddleware(listVideoAccountChannelsValidator),
30 asyncMiddleware(listVideoAccountChannels)
31)
32
33videoChannelRouter.post('/channels',
34 authenticate,
35 videoChannelsAddValidator,
36 asyncMiddleware(addVideoChannelRetryWrapper)
37)
38
39videoChannelRouter.put('/channels/:id',
40 authenticate,
41 asyncMiddleware(videoChannelsUpdateValidator),
42 updateVideoChannelRetryWrapper
43)
44
45videoChannelRouter.delete('/channels/:id',
46 authenticate,
47 asyncMiddleware(videoChannelsRemoveValidator),
48 asyncMiddleware(removeVideoChannelRetryWrapper)
49)
50
51videoChannelRouter.get('/channels/:id',
52 asyncMiddleware(videoChannelsGetValidator),
53 asyncMiddleware(getVideoChannel)
54)
55
56// ---------------------------------------------------------------------------
57
58export {
59 videoChannelRouter
60}
61
62// ---------------------------------------------------------------------------
63
64async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
65 const resultList = await VideoChannelModel.listForApi(req.query.start, req.query.count, req.query.sort)
66
67 return res.json(getFormattedObjects(resultList.data, resultList.total))
68}
69
70async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) {
71 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
72
73 return res.json(getFormattedObjects(resultList.data, resultList.total))
74}
75
76// Wrapper to video channel add that retry the async function if there is a database error
77// We need this because we run the transaction in SERIALIZABLE isolation that can fail
78async function addVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
79 const options = {
80 arguments: [ req, res ],
81 errorMessage: 'Cannot insert the video video channel with many retries.'
82 }
83
84 const videoChannel = await retryTransactionWrapper(addVideoChannel, options)
85 return res.json({
86 videoChannel: {
87 id: videoChannel.id
88 }
89 }).end()
90}
91
92async function addVideoChannel (req: express.Request, res: express.Response) {
93 const videoChannelInfo: VideoChannelCreate = req.body
94 const account: AccountModel = res.locals.oauth.token.User.Account
95
96 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
97 return createVideoChannel(videoChannelInfo, account, t)
98 })
99
100 setAsyncActorKeys(videoChannelCreated.Actor)
101 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err }))
102
103 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid)
104
105 return videoChannelCreated
106}
107
108async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
109 const options = {
110 arguments: [ req, res ],
111 errorMessage: 'Cannot update the video with many retries.'
112 }
113
114 await retryTransactionWrapper(updateVideoChannel, options)
115
116 return res.type('json').status(204).end()
117}
118
119async function updateVideoChannel (req: express.Request, res: express.Response) {
120 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel
121 const videoChannelFieldsSave = videoChannelInstance.toJSON()
122 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
123
124 try {
125 await sequelizeTypescript.transaction(async t => {
126 const sequelizeOptions = {
127 transaction: t
128 }
129
130 if (videoChannelInfoToUpdate.name !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.name)
131 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description)
132 if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support)
133
134 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
135 await sendUpdateActor(videoChannelInstanceUpdated, t)
136 })
137
138 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
139 } catch (err) {
140 logger.debug('Cannot update the video channel.', { err })
141
142 // Force fields we want to update
143 // If the transaction is retried, sequelize will think the object has not changed
144 // So it will skip the SQL request, even if the last one was ROLLBACKed!
145 resetSequelizeInstance(videoChannelInstance, videoChannelFieldsSave)
146
147 throw err
148 }
149}
150
151async function removeVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
152 const options = {
153 arguments: [ req, res ],
154 errorMessage: 'Cannot remove the video channel with many retries.'
155 }
156
157 await retryTransactionWrapper(removeVideoChannel, options)
158
159 return res.type('json').status(204).end()
160}
161
162async function removeVideoChannel (req: express.Request, res: express.Response) {
163 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
164
165 return sequelizeTypescript.transaction(async t => {
166 await videoChannelInstance.destroy({ transaction: t })
167
168 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid)
169 })
170
171}
172
173async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
174 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
175
176 return res.json(videoChannelWithVideos.toFormattedJSON())
177}
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 61b6c5826..4b3198a74 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -42,7 +42,6 @@ import { VideoModel } from '../../../models/video/video'
42import { VideoFileModel } from '../../../models/video/video-file' 42import { VideoFileModel } from '../../../models/video/video-file'
43import { abuseVideoRouter } from './abuse' 43import { abuseVideoRouter } from './abuse'
44import { blacklistRouter } from './blacklist' 44import { blacklistRouter } from './blacklist'
45import { videoChannelRouter } from './channel'
46import { videoCommentRouter } from './comment' 45import { videoCommentRouter } from './comment'
47import { rateVideoRouter } from './rate' 46import { rateVideoRouter } from './rate'
48import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 47import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
@@ -72,7 +71,6 @@ const reqVideoFileUpdate = createReqFiles(
72videosRouter.use('/', abuseVideoRouter) 71videosRouter.use('/', abuseVideoRouter)
73videosRouter.use('/', blacklistRouter) 72videosRouter.use('/', blacklistRouter)
74videosRouter.use('/', rateVideoRouter) 73videosRouter.use('/', rateVideoRouter)
75videosRouter.use('/', videoChannelRouter)
76videosRouter.use('/', videoCommentRouter) 74videosRouter.use('/', videoCommentRouter)
77 75
78videosRouter.get('/categories', listVideoCategories) 76videosRouter.get('/categories', listVideoCategories)
@@ -397,13 +395,14 @@ async function getVideoDescription (req: express.Request, res: express.Response)
397} 395}
398 396
399async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 397async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
400 const resultList = await VideoModel.listForApi( 398 const resultList = await VideoModel.listForApi({
401 req.query.start as number, 399 start: req.query.start,
402 req.query.count as number, 400 count: req.query.count,
403 req.query.sort as VideoSortField, 401 sort: req.query.sort,
404 isNSFWHidden(res), 402 hideNSFW: isNSFWHidden(res),
405 req.query.filter as VideoFilter 403 filter: req.query.filter as VideoFilter,
406 ) 404 withFiles: false
405 })
407 406
408 return res.json(getFormattedObjects(resultList.data, resultList.total)) 407 return res.json(getFormattedObjects(resultList.data, resultList.total))
409} 408}
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 6a6af3e09..7dcaf7004 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -33,15 +33,15 @@ async function generateFeed (req: express.Request, res: express.Response, next:
33 const account: AccountModel = res.locals.account 33 const account: AccountModel = res.locals.account
34 const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list' 34 const hideNSFW = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY === 'do_not_list'
35 35
36 const resultList = await VideoModel.listForApi( 36 const resultList = await VideoModel.listForApi({
37 start, 37 start,
38 FEEDS.COUNT, 38 count: FEEDS.COUNT,
39 req.query.sort as VideoSortField, 39 sort: req.query.sort,
40 hideNSFW, 40 hideNSFW,
41 req.query.filter, 41 filter: req.query.filter,
42 true, 42 withFiles: true,
43 account ? account.id : null 43 accountId: account ? account.id : null
44 ) 44 })
45 45
46 // Adding video items to the feed, one at a time 46 // Adding video items to the feed, one at a time
47 resultList.data.forEach(video => { 47 resultList.data.forEach(video => {
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts
index 991a2e175..bdf39eb9c 100644
--- a/server/middlewares/validators/follows.ts
+++ b/server/middlewares/validators/follows.ts
@@ -16,7 +16,7 @@ const followValidator = [
16 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') { 16 if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') {
17 return res.status(400) 17 return res.status(400)
18 .json({ 18 .json({
19 error: 'Cannot follow non HTTPS web server.' 19 error: 'Cannot follow on a non HTTPS web server.'
20 }) 20 })
21 .end() 21 .end()
22 } 22 }
diff --git a/server/middlewares/validators/video-channels.ts b/server/middlewares/validators/video-channels.ts
index fe42105e8..e3a11a41b 100644
--- a/server/middlewares/validators/video-channels.ts
+++ b/server/middlewares/validators/video-channels.ts
@@ -26,6 +26,7 @@ const listVideoAccountChannelsValidator = [
26] 26]
27 27
28const videoChannelsAddValidator = [ 28const videoChannelsAddValidator = [
29 param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
29 body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'), 30 body('name').custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
30 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), 31 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
31 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), 32 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -41,6 +42,7 @@ const videoChannelsAddValidator = [
41 42
42const videoChannelsUpdateValidator = [ 43const videoChannelsUpdateValidator = [
43 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 44 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
45 param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
44 body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'), 46 body('name').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid name'),
45 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), 47 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
46 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), 48 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
@@ -49,6 +51,7 @@ const videoChannelsUpdateValidator = [
49 logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) 51 logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
50 52
51 if (areValidationErrors(req, res)) return 53 if (areValidationErrors(req, res)) return
54 if (!await isAccountIdExist(req.params.accountId, res)) return
52 if (!await isVideoChannelExist(req.params.id, res)) return 55 if (!await isVideoChannelExist(req.params.id, res)) return
53 56
54 // We need to make additional checks 57 // We need to make additional checks
@@ -70,11 +73,13 @@ const videoChannelsUpdateValidator = [
70 73
71const videoChannelsRemoveValidator = [ 74const videoChannelsRemoveValidator = [
72 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 75 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
76 param('accountId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
73 77
74 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 78 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
75 logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) 79 logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
76 80
77 if (areValidationErrors(req, res)) return 81 if (areValidationErrors(req, res)) return
82 if (!await isAccountIdExist(req.params.accountId, res)) return
78 if (!await isVideoChannelExist(req.params.id, res)) return 83 if (!await isVideoChannelExist(req.params.id, res)) return
79 84
80 // Check if the user who did the request is able to delete the video 85 // Check if the user who did the request is able to delete the video
@@ -87,11 +92,14 @@ const videoChannelsRemoveValidator = [
87 92
88const videoChannelsGetValidator = [ 93const videoChannelsGetValidator = [
89 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), 94 param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
95 param('accountId').optional().custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid account id'),
90 96
91 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 97 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
92 logger.debug('Checking videoChannelsGet parameters', { parameters: req.params }) 98 logger.debug('Checking videoChannelsGet parameters', { parameters: req.params })
93 99
94 if (areValidationErrors(req, res)) return 100 if (areValidationErrors(req, res)) return
101 // On some routes, accountId is optional (for example in the ActivityPub route)
102 if (req.params.accountId && !await isAccountIdExist(req.params.accountId, res)) return
95 if (!await isVideoChannelExist(req.params.id, res)) return 103 if (!await isVideoChannelExist(req.params.id, res)) return
96 104
97 return next() 105 return next()
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 2ad9c00dd..7ababbf23 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -95,7 +95,14 @@ enum ScopeNames {
95} 95}
96 96
97@Scopes({ 97@Scopes({
98 [ScopeNames.AVAILABLE_FOR_LIST]: (actorId: number, hideNSFW: boolean, filter?: VideoFilter, withFiles?: boolean, accountId?: number) => { 98 [ScopeNames.AVAILABLE_FOR_LIST]: (options: {
99 actorId: number,
100 hideNSFW: boolean,
101 filter?: VideoFilter,
102 withFiles?: boolean,
103 accountId?: number,
104 videoChannelId?: number
105 }) => {
99 const accountInclude = { 106 const accountInclude = {
100 attributes: [ 'name' ], 107 attributes: [ 'name' ],
101 model: AccountModel.unscoped(), 108 model: AccountModel.unscoped(),
@@ -106,7 +113,7 @@ enum ScopeNames {
106 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], 113 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
107 model: ActorModel.unscoped(), 114 model: ActorModel.unscoped(),
108 required: true, 115 required: true,
109 where: VideoModel.buildActorWhereWithFilter(filter), 116 where: VideoModel.buildActorWhereWithFilter(options.filter),
110 include: [ 117 include: [
111 { 118 {
112 attributes: [ 'host' ], 119 attributes: [ 'host' ],
@@ -122,6 +129,18 @@ enum ScopeNames {
122 ] 129 ]
123 } 130 }
124 131
132 const videoChannelInclude = {
133 attributes: [ 'name', 'description' ],
134 model: VideoChannelModel.unscoped(),
135 required: true,
136 where: {},
137 include: [
138 accountInclude
139 ]
140 }
141
142 // Force actorId to be a number to avoid SQL injections
143 const actorIdNumber = parseInt(options.actorId.toString(), 10)
125 const query: IFindOptions<VideoModel> = { 144 const query: IFindOptions<VideoModel> = {
126 where: { 145 where: {
127 id: { 146 id: {
@@ -132,32 +151,23 @@ enum ScopeNames {
132 '(' + 151 '(' +
133 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 152 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
134 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 153 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
135 'WHERE "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + 154 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
136 ' UNION ' + 155 ' UNION ' +
137 'SELECT "video"."id" AS "id" FROM "video" ' + 156 'SELECT "video"."id" AS "id" FROM "video" ' +
138 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 157 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
139 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + 158 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' +
140 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + 159 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' +
141 'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + 160 'LEFT JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' +
142 'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + parseInt(actorId.toString(), 10) + 161 'WHERE "actor"."serverId" IS NULL OR "actorFollow"."actorId" = ' + actorIdNumber +
143 ')' 162 ')'
144 ) 163 )
145 }, 164 },
146 privacy: VideoPrivacy.PUBLIC 165 privacy: VideoPrivacy.PUBLIC
147 }, 166 },
148 include: [ 167 include: [ videoChannelInclude ]
149 {
150 attributes: [ 'name', 'description' ],
151 model: VideoChannelModel.unscoped(),
152 required: true,
153 include: [
154 accountInclude
155 ]
156 }
157 ]
158 } 168 }
159 169
160 if (withFiles === true) { 170 if (options.withFiles === true) {
161 query.include.push({ 171 query.include.push({
162 model: VideoFileModel.unscoped(), 172 model: VideoFileModel.unscoped(),
163 required: true 173 required: true
@@ -165,13 +175,19 @@ enum ScopeNames {
165 } 175 }
166 176
167 // Hide nsfw videos? 177 // Hide nsfw videos?
168 if (hideNSFW === true) { 178 if (options.hideNSFW === true) {
169 query.where['nsfw'] = false 179 query.where['nsfw'] = false
170 } 180 }
171 181
172 if (accountId) { 182 if (options.accountId) {
173 accountInclude.where = { 183 accountInclude.where = {
174 id: accountId 184 id: options.accountId
185 }
186 }
187
188 if (options.videoChannelId) {
189 videoChannelInclude.where = {
190 id: options.videoChannelId
175 } 191 }
176 } 192 }
177 193
@@ -697,23 +713,37 @@ export class VideoModel extends Model<VideoModel> {
697 }) 713 })
698 } 714 }
699 715
700 static async listForApi ( 716 static async listForApi (options: {
701 start: number, 717 start: number,
702 count: number, 718 count: number,
703 sort: string, 719 sort: string,
704 hideNSFW: boolean, 720 hideNSFW: boolean,
721 withFiles: boolean,
705 filter?: VideoFilter, 722 filter?: VideoFilter,
706 withFiles = false, 723 accountId?: number,
707 accountId?: number 724 videoChannelId?: number
708 ) { 725 }) {
709 const query = { 726 const query = {
710 offset: start, 727 offset: options.start,
711 limit: count, 728 limit: options.count,
712 order: getSort(sort) 729 order: getSort(options.sort)
713 } 730 }
714 731
715 const serverActor = await getServerActor() 732 const serverActor = await getServerActor()
716 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW, filter, withFiles, accountId ] }) 733 const scopes = {
734 method: [
735 ScopeNames.AVAILABLE_FOR_LIST, {
736 actorId: serverActor.id,
737 hideNSFW: options.hideNSFW,
738 filter: options.filter,
739 withFiles: options.withFiles,
740 accountId: options.accountId,
741 videoChannelId: options.videoChannelId
742 }
743 ]
744 }
745
746 return VideoModel.scope(scopes)
717 .findAndCountAll(query) 747 .findAndCountAll(query)
718 .then(({ rows, count }) => { 748 .then(({ rows, count }) => {
719 return { 749 return {
@@ -750,8 +780,16 @@ export class VideoModel extends Model<VideoModel> {
750 } 780 }
751 781
752 const serverActor = await getServerActor() 782 const serverActor = await getServerActor()
783 const scopes = {
784 method: [
785 ScopeNames.AVAILABLE_FOR_LIST, {
786 actorId: serverActor.id,
787 hideNSFW
788 }
789 ]
790 }
753 791
754 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST, serverActor.id, hideNSFW ] }) 792 return VideoModel.scope(scopes)
755 .findAndCountAll(query) 793 .findAndCountAll(query)
756 .then(({ rows, count }) => { 794 .then(({ rows, count }) => {
757 return { 795 return {
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index 43c5462ee..acb6bdd57 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -4,15 +4,29 @@ import * as chai from 'chai'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import 'mocha' 5import 'mocha'
6import { 6import {
7 createUser, deleteVideoChannel, flushTests, getAccountVideoChannelsList, getVideoChannelsList, immutableAssign, killallServers, 7 createUser,
8 makeGetRequest, makePostBodyRequest, makePutBodyRequest, runServer, ServerInfo, setAccessTokensToServers, userLogin 8 deleteVideoChannel,
9 flushTests,
10 getAccountVideoChannelsList,
11 getVideoChannelsList,
12 immutableAssign,
13 killallServers,
14 makeGetRequest,
15 makePostBodyRequest,
16 makePutBodyRequest,
17 runServer,
18 ServerInfo,
19 setAccessTokensToServers,
20 userLogin
9} from '../../utils' 21} from '../../utils'
10import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' 22import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
23import { getAccountsList } from '../../utils/users/accounts'
11 24
12const expect = chai.expect 25const expect = chai.expect
13 26
14describe('Test videos API validator', function () { 27describe('Test videos API validator', function () {
15 const path = '/api/v1/videos/channels' 28 const videoChannelPath = '/api/v1/video-channels'
29 const accountPath = '/api/v1/accounts/'
16 let server: ServerInfo 30 let server: ServerInfo
17 let accessTokenUser: string 31 let accessTokenUser: string
18 32
@@ -37,15 +51,15 @@ describe('Test videos API validator', function () {
37 51
38 describe('When listing a video channels', function () { 52 describe('When listing a video channels', function () {
39 it('Should fail with a bad start pagination', async function () { 53 it('Should fail with a bad start pagination', async function () {
40 await checkBadStartPagination(server.url, path, server.accessToken) 54 await checkBadStartPagination(server.url, videoChannelPath, server.accessToken)
41 }) 55 })
42 56
43 it('Should fail with a bad count pagination', async function () { 57 it('Should fail with a bad count pagination', async function () {
44 await checkBadCountPagination(server.url, path, server.accessToken) 58 await checkBadCountPagination(server.url, videoChannelPath, server.accessToken)
45 }) 59 })
46 60
47 it('Should fail with an incorrect sort', async function () { 61 it('Should fail with an incorrect sort', async function () {
48 await checkBadSortPagination(server.url, path, server.accessToken) 62 await checkBadSortPagination(server.url, videoChannelPath, server.accessToken)
49 }) 63 })
50 }) 64 })
51 65
@@ -60,12 +74,20 @@ describe('Test videos API validator', function () {
60 }) 74 })
61 75
62 describe('When adding a video channel', function () { 76 describe('When adding a video channel', function () {
77 let path: string
78
63 const baseCorrectParams = { 79 const baseCorrectParams = {
64 name: 'hello', 80 name: 'hello',
65 description: 'super description', 81 description: 'super description',
66 support: 'super support text' 82 support: 'super support text'
67 } 83 }
68 84
85 before(async function () {
86 const res = await getAccountsList(server.url)
87 const accountId = res.body.data[0].id
88 path = accountPath + accountId + '/video-channels'
89 })
90
69 it('Should fail with a non authenticated user', async function () { 91 it('Should fail with a non authenticated user', async function () {
70 await makePostBodyRequest({ url: server.url, path, token: 'none', fields: baseCorrectParams, statusCodeExpected: 401 }) 92 await makePostBodyRequest({ url: server.url, path, token: 'none', fields: baseCorrectParams, statusCodeExpected: 401 })
71 }) 93 })
@@ -107,22 +129,27 @@ describe('Test videos API validator', function () {
107 }) 129 })
108 130
109 describe('When updating a video channel', function () { 131 describe('When updating a video channel', function () {
132 let path: string
133
110 const baseCorrectParams = { 134 const baseCorrectParams = {
111 name: 'hello', 135 name: 'hello',
112 description: 'super description' 136 description: 'super description'
113 } 137 }
114 138
115 let videoChannelId
116
117 before(async function () { 139 before(async function () {
118 const res = await getVideoChannelsList(server.url, 0, 1) 140 const res1 = await getVideoChannelsList(server.url, 0, 1)
119 videoChannelId = res.body.data[0].id 141 const videoChannelId = res1.body.data[0].id
142
143 const res2 = await getAccountsList(server.url)
144 const accountId = res2.body.data[0].id
145
146 path = accountPath + accountId + '/video-channels/' + videoChannelId
120 }) 147 })
121 148
122 it('Should fail with a non authenticated user', async function () { 149 it('Should fail with a non authenticated user', async function () {
123 await makePutBodyRequest({ 150 await makePutBodyRequest({
124 url: server.url, 151 url: server.url,
125 path: path + '/' + videoChannelId, 152 path,
126 token: 'hi', 153 token: 'hi',
127 fields: baseCorrectParams, 154 fields: baseCorrectParams,
128 statusCodeExpected: 401 155 statusCodeExpected: 401
@@ -132,7 +159,7 @@ describe('Test videos API validator', function () {
132 it('Should fail with another authenticated user', async function () { 159 it('Should fail with another authenticated user', async function () {
133 await makePutBodyRequest({ 160 await makePutBodyRequest({
134 url: server.url, 161 url: server.url,
135 path: path + '/' + videoChannelId, 162 path,
136 token: accessTokenUser, 163 token: accessTokenUser,
137 fields: baseCorrectParams, 164 fields: baseCorrectParams,
138 statusCodeExpected: 403 165 statusCodeExpected: 403
@@ -141,23 +168,23 @@ describe('Test videos API validator', function () {
141 168
142 it('Should fail with a long name', async function () { 169 it('Should fail with a long name', async function () {
143 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(25) }) 170 const fields = immutableAssign(baseCorrectParams, { name: 'super'.repeat(25) })
144 await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields }) 171 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
145 }) 172 })
146 173
147 it('Should fail with a long description', async function () { 174 it('Should fail with a long description', async function () {
148 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(60) }) 175 const fields = immutableAssign(baseCorrectParams, { description: 'super'.repeat(60) })
149 await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields }) 176 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
150 }) 177 })
151 178
152 it('Should fail with a long support text', async function () { 179 it('Should fail with a long support text', async function () {
153 const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) }) 180 const fields = immutableAssign(baseCorrectParams, { support: 'super'.repeat(70) })
154 await makePutBodyRequest({ url: server.url, path: path + '/' + videoChannelId, token: server.accessToken, fields }) 181 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
155 }) 182 })
156 183
157 it('Should succeed with the correct parameters', async function () { 184 it('Should succeed with the correct parameters', async function () {
158 await makePutBodyRequest({ 185 await makePutBodyRequest({
159 url: server.url, 186 url: server.url,
160 path: path + '/' + videoChannelId, 187 path,
161 token: server.accessToken, 188 token: server.accessToken,
162 fields: baseCorrectParams, 189 fields: baseCorrectParams,
163 statusCodeExpected: 204 190 statusCodeExpected: 204
@@ -166,17 +193,23 @@ describe('Test videos API validator', function () {
166 }) 193 })
167 194
168 describe('When getting a video channel', function () { 195 describe('When getting a video channel', function () {
196 let basePath: string
169 let videoChannelId: number 197 let videoChannelId: number
170 198
171 before(async function () { 199 before(async function () {
172 const res = await getVideoChannelsList(server.url, 0, 1) 200 const res1 = await getVideoChannelsList(server.url, 0, 1)
173 videoChannelId = res.body.data[0].id 201 videoChannelId = res1.body.data[0].id
202
203 const res2 = await getAccountsList(server.url)
204 const accountId = res2.body.data[0].id
205
206 basePath = accountPath + accountId + '/video-channels'
174 }) 207 })
175 208
176 it('Should return the list of the video channels with nothing', async function () { 209 it('Should return the list of the video channels with nothing', async function () {
177 const res = await makeGetRequest({ 210 const res = await makeGetRequest({
178 url: server.url, 211 url: server.url,
179 path, 212 path: basePath,
180 statusCodeExpected: 200 213 statusCodeExpected: 200
181 }) 214 })
182 215
@@ -186,7 +219,7 @@ describe('Test videos API validator', function () {
186 it('Should fail without a correct uuid', async function () { 219 it('Should fail without a correct uuid', async function () {
187 await makeGetRequest({ 220 await makeGetRequest({
188 url: server.url, 221 url: server.url,
189 path: path + '/coucou', 222 path: basePath + '/coucou',
190 statusCodeExpected: 400 223 statusCodeExpected: 400
191 }) 224 })
192 }) 225 })
@@ -194,7 +227,7 @@ describe('Test videos API validator', function () {
194 it('Should return 404 with an incorrect video channel', async function () { 227 it('Should return 404 with an incorrect video channel', async function () {
195 await makeGetRequest({ 228 await makeGetRequest({
196 url: server.url, 229 url: server.url,
197 path: path + '/4da6fde3-88f7-4d16-b119-108df5630b06', 230 path: basePath + '/4da6fde3-88f7-4d16-b119-108df5630b06',
198 statusCodeExpected: 404 231 statusCodeExpected: 404
199 }) 232 })
200 }) 233 })
@@ -202,7 +235,7 @@ describe('Test videos API validator', function () {
202 it('Should succeed with the correct parameters', async function () { 235 it('Should succeed with the correct parameters', async function () {
203 await makeGetRequest({ 236 await makeGetRequest({
204 url: server.url, 237 url: server.url,
205 path: path + '/' + videoChannelId, 238 path: basePath + '/' + videoChannelId,
206 statusCodeExpected: 200 239 statusCodeExpected: 200
207 }) 240 })
208 }) 241 })
@@ -210,33 +243,41 @@ describe('Test videos API validator', function () {
210 243
211 describe('When deleting a video channel', function () { 244 describe('When deleting a video channel', function () {
212 let videoChannelId: number 245 let videoChannelId: number
246 let accountId: number
213 247
214 before(async function () { 248 before(async function () {
215 const res = await getVideoChannelsList(server.url, 0, 1) 249 const res1 = await getVideoChannelsList(server.url, 0, 1)
216 videoChannelId = res.body.data[0].id 250 videoChannelId = res1.body.data[0].id
251
252 const res2 = await getAccountsList(server.url)
253 accountId = res2.body.data[0].id
217 }) 254 })
218 255
219 it('Should fail with a non authenticated user', async function () { 256 it('Should fail with a non authenticated user', async function () {
220 await deleteVideoChannel(server.url, 'coucou', videoChannelId, 401) 257 await deleteVideoChannel(server.url, 'coucou', accountId, videoChannelId, 401)
221 }) 258 })
222 259
223 it('Should fail with another authenticated user', async function () { 260 it('Should fail with another authenticated user', async function () {
224 await deleteVideoChannel(server.url, accessTokenUser, videoChannelId, 403) 261 await deleteVideoChannel(server.url, accessTokenUser, accountId, videoChannelId, 403)
262 })
263
264 it('Should fail with an unknown account id', async function () {
265 await deleteVideoChannel(server.url, server.accessToken, 454554,videoChannelId, 404)
225 }) 266 })
226 267
227 it('Should fail with an unknown id', async function () { 268 it('Should fail with an unknown video channel id', async function () {
228 await deleteVideoChannel(server.url, server.accessToken, 454554, 404) 269 await deleteVideoChannel(server.url, server.accessToken, accountId,454554, 404)
229 }) 270 })
230 271
231 it('Should succeed with the correct parameters', async function () { 272 it('Should succeed with the correct parameters', async function () {
232 await deleteVideoChannel(server.url, server.accessToken, videoChannelId) 273 await deleteVideoChannel(server.url, server.accessToken, accountId, videoChannelId)
233 }) 274 })
234 275
235 it('Should fail to delete the last user video channel', async function () { 276 it('Should fail to delete the last user video channel', async function () {
236 const res = await getVideoChannelsList(server.url, 0, 1) 277 const res = await getVideoChannelsList(server.url, 0, 1)
237 videoChannelId = res.body.data[0].id 278 videoChannelId = res.body.data[0].id
238 279
239 await deleteVideoChannel(server.url, server.accessToken, videoChannelId, 409) 280 await deleteVideoChannel(server.url, server.accessToken, accountId, videoChannelId, 409)
240 }) 281 })
241 }) 282 })
242 283
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 2563939ec..6238cdc08 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -39,6 +39,7 @@ import {
39 getVideoCommentThreads, 39 getVideoCommentThreads,
40 getVideoThreadComments 40 getVideoThreadComments
41} from '../../utils/videos/video-comments' 41} from '../../utils/videos/video-comments'
42import { getAccountsList } from '../../utils/users/accounts'
42 43
43const expect = chai.expect 44const expect = chai.expect
44 45
@@ -46,6 +47,7 @@ describe('Test multiple servers', function () {
46 let servers: ServerInfo[] = [] 47 let servers: ServerInfo[] = []
47 const toRemove = [] 48 const toRemove = []
48 let videoUUID = '' 49 let videoUUID = ''
50 let accountId: number
49 let videoChannelId: number 51 let videoChannelId: number
50 52
51 before(async function () { 53 before(async function () {
@@ -56,13 +58,20 @@ describe('Test multiple servers', function () {
56 // Get the access tokens 58 // Get the access tokens
57 await setAccessTokensToServers(servers) 59 await setAccessTokensToServers(servers)
58 60
59 const videoChannel = { 61 {
60 name: 'my channel', 62 const res = await getAccountsList(servers[0].url)
61 description: 'super channel' 63 accountId = res.body.data[0].id
64 }
65
66 {
67 const videoChannel = {
68 name: 'my channel',
69 description: 'super channel'
70 }
71 await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, accountId, videoChannel)
72 const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1)
73 videoChannelId = channelRes.body.data[ 0 ].id
62 } 74 }
63 await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
64 const channelRes = await getVideoChannelsList(servers[0].url, 0, 1)
65 videoChannelId = channelRes.body.data[0].id
66 75
67 // Server 1 and server 2 follow each other 76 // Server 1 and server 2 follow each other
68 await doubleFollow(servers[0], servers[1]) 77 await doubleFollow(servers[0], servers[1])
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index b9c9bbf3c..a7552a83a 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -17,12 +17,14 @@ import {
17 setAccessTokensToServers, 17 setAccessTokensToServers,
18 updateVideoChannel 18 updateVideoChannel
19} from '../../utils/index' 19} from '../../utils/index'
20import { getAccountsList } from '../../utils/users/accounts'
20 21
21const expect = chai.expect 22const expect = chai.expect
22 23
23describe('Test video channels', function () { 24describe('Test video channels', function () {
24 let servers: ServerInfo[] 25 let servers: ServerInfo[]
25 let userInfo: User 26 let userInfo: User
27 let accountId: number
26 let videoChannelId: number 28 let videoChannelId: number
27 29
28 before(async function () { 30 before(async function () {
@@ -35,6 +37,11 @@ describe('Test video channels', function () {
35 await setAccessTokensToServers(servers) 37 await setAccessTokensToServers(servers)
36 await doubleFollow(servers[0], servers[1]) 38 await doubleFollow(servers[0], servers[1])
37 39
40 {
41 const res = await getAccountsList(servers[0].url)
42 accountId = res.body.data[0].id
43 }
44
38 await wait(5000) 45 await wait(5000)
39 }) 46 })
40 47
@@ -54,7 +61,7 @@ describe('Test video channels', function () {
54 description: 'super video channel description', 61 description: 'super video channel description',
55 support: 'super video channel support text' 62 support: 'super video channel support text'
56 } 63 }
57 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) 64 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannel)
58 videoChannelId = res.body.videoChannel.id 65 videoChannelId = res.body.videoChannel.id
59 66
60 // The channel is 1 is propagated to servers 2 67 // The channel is 1 is propagated to servers 2
@@ -120,7 +127,7 @@ describe('Test video channels', function () {
120 support: 'video channel support text updated' 127 support: 'video channel support text updated'
121 } 128 }
122 129
123 await updateVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId, videoChannelAttributes) 130 await updateVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannelId, videoChannelAttributes)
124 131
125 await wait(3000) 132 await wait(3000)
126 }) 133 })
@@ -139,7 +146,7 @@ describe('Test video channels', function () {
139 }) 146 })
140 147
141 it('Should get video channel', async function () { 148 it('Should get video channel', async function () {
142 const res = await getVideoChannel(servers[0].url, videoChannelId) 149 const res = await getVideoChannel(servers[0].url, accountId, videoChannelId)
143 150
144 const videoChannel = res.body 151 const videoChannel = res.body
145 expect(videoChannel.displayName).to.equal('video channel updated') 152 expect(videoChannel.displayName).to.equal('video channel updated')
@@ -148,7 +155,7 @@ describe('Test video channels', function () {
148 }) 155 })
149 156
150 it('Should delete video channel', async function () { 157 it('Should delete video channel', async function () {
151 await deleteVideoChannel(servers[0].url, servers[0].accessToken, videoChannelId) 158 await deleteVideoChannel(servers[0].url, servers[0].accessToken, accountId, videoChannelId)
152 }) 159 })
153 160
154 it('Should have video channel deleted', async function () { 161 it('Should have video channel deleted', async function () {
diff --git a/server/tests/utils/videos/video-channels.ts b/server/tests/utils/videos/video-channels.ts
index 2d095d8ab..cfc541431 100644
--- a/server/tests/utils/videos/video-channels.ts
+++ b/server/tests/utils/videos/video-channels.ts
@@ -7,7 +7,7 @@ type VideoChannelAttributes = {
7} 7}
8 8
9function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { 9function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
10 const path = '/api/v1/videos/channels' 10 const path = '/api/v1/video-channels'
11 11
12 const req = request(url) 12 const req = request(url)
13 .get(path) 13 .get(path)
@@ -22,7 +22,7 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
22} 22}
23 23
24function getAccountVideoChannelsList (url: string, accountId: number | string, specialStatus = 200) { 24function getAccountVideoChannelsList (url: string, accountId: number | string, specialStatus = 200) {
25 const path = '/api/v1/videos/accounts/' + accountId + '/channels' 25 const path = '/api/v1/accounts/' + accountId + '/video-channels'
26 26
27 return request(url) 27 return request(url)
28 .get(path) 28 .get(path)
@@ -31,8 +31,14 @@ function getAccountVideoChannelsList (url: string, accountId: number | string, s
31 .expect('Content-Type', /json/) 31 .expect('Content-Type', /json/)
32} 32}
33 33
34function addVideoChannel (url: string, token: string, videoChannelAttributesArg: VideoChannelAttributes, expectedStatus = 200) { 34function addVideoChannel (
35 const path = '/api/v1/videos/channels' 35 url: string,
36 token: string,
37 accountId: number,
38 videoChannelAttributesArg: VideoChannelAttributes,
39 expectedStatus = 200
40) {
41 const path = '/api/v1/accounts/' + accountId + '/video-channels/'
36 42
37 // Default attributes 43 // Default attributes
38 let attributes = { 44 let attributes = {
@@ -50,9 +56,16 @@ function addVideoChannel (url: string, token: string, videoChannelAttributesArg:
50 .expect(expectedStatus) 56 .expect(expectedStatus)
51} 57}
52 58
53function updateVideoChannel (url: string, token: string, channelId: number, attributes: VideoChannelAttributes, expectedStatus = 204) { 59function updateVideoChannel (
60 url: string,
61 token: string,
62 accountId: number,
63 channelId: number,
64 attributes: VideoChannelAttributes,
65 expectedStatus = 204
66) {
54 const body = {} 67 const body = {}
55 const path = '/api/v1/videos/channels/' + channelId 68 const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
56 69
57 if (attributes.name) body['name'] = attributes.name 70 if (attributes.name) body['name'] = attributes.name
58 if (attributes.description) body['description'] = attributes.description 71 if (attributes.description) body['description'] = attributes.description
@@ -66,18 +79,18 @@ function updateVideoChannel (url: string, token: string, channelId: number, attr
66 .expect(expectedStatus) 79 .expect(expectedStatus)
67} 80}
68 81
69function deleteVideoChannel (url: string, token: string, channelId: number, expectedStatus = 204) { 82function deleteVideoChannel (url: string, token: string, accountId: number, channelId: number, expectedStatus = 204) {
70 const path = '/api/v1/videos/channels/' 83 const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
71 84
72 return request(url) 85 return request(url)
73 .delete(path + channelId) 86 .delete(path)
74 .set('Accept', 'application/json') 87 .set('Accept', 'application/json')
75 .set('Authorization', 'Bearer ' + token) 88 .set('Authorization', 'Bearer ' + token)
76 .expect(expectedStatus) 89 .expect(expectedStatus)
77} 90}
78 91
79function getVideoChannel (url: string, channelId: number) { 92function getVideoChannel (url: string, accountId: number, channelId: number) {
80 const path = '/api/v1/videos/channels/' + channelId 93 const path = '/api/v1/accounts/' + accountId + '/video-channels/' + channelId
81 94
82 return request(url) 95 return request(url)
83 .get(path) 96 .get(path)
diff --git a/support/doc/api/html/index.html b/support/doc/api/html/index.html
index bf9430e79..23162c307 100644
--- a/support/doc/api/html/index.html
+++ b/support/doc/api/html/index.html
@@ -41,6 +41,14 @@
41 </ul> 41 </ul>
42 </section> 42 </section>
43 <section> 43 <section>
44 <a href="#tag-Feeds">Feeds</a>
45 <ul>
46 <li>
47 <a href="#operation--feeds-videos.-format--get"> GET /feeds/videos.{format} </a>
48 </li>
49 </ul>
50 </section>
51 <section>
44 <a href="#tag-Job">Job</a> 52 <a href="#tag-Job">Job</a>
45 <ul> 53 <ul>
46 <li> 54 <li>
@@ -176,22 +184,22 @@
176 <a href="#tag-VideoChannel">VideoChannel</a> 184 <a href="#tag-VideoChannel">VideoChannel</a>
177 <ul> 185 <ul>
178 <li> 186 <li>
179 <a href="#operation--videos-channels-get"> GET /videos/channels </a> 187 <a href="#operation--video-channels-get"> GET /video-channels </a>
180 </li> 188 </li>
181 <li> 189 <li>
182 <a href="#operation--videos-channels-post"> POST /videos/channels </a> 190 <a href="#operation--accounts--accountId--video-channels-get"> GET /accounts/{accountId}/video-channels </a>
183 </li> 191 </li>
184 <li> 192 <li>
185 <a href="#operation--videos-channels--id--get"> GET /videos/channels/{id} </a> 193 <a href="#operation--accounts--accountId--video-channels-post"> POST /accounts/{accountId}/video-channels </a>
186 </li> 194 </li>
187 <li> 195 <li>
188 <a href="#operation--videos-channels--id--put"> PUT /videos/channels/{id} </a> 196 <a href="#operation--account--accountId--video-channels--id--get"> GET /account/{accountId}/video-channels/{id} </a>
189 </li> 197 </li>
190 <li> 198 <li>
191 <a href="#operation--videos-channels--id--delete"> DELETE /videos/channels/{id} </a> 199 <a href="#operation--account--accountId--video-channels--id--put"> PUT /account/{accountId}/video-channels/{id} </a>
192 </li> 200 </li>
193 <li> 201 <li>
194 <a href="#operation--videos-accounts--accountId--channels-get"> GET /videos/accounts/{accountId}/channels </a> 202 <a href="#operation--account--accountId--video-channels--id--delete"> DELETE /account/{accountId}/video-channels/{id} </a>
195 </li> 203 </li>
196 </ul> 204 </ul>
197 </section> 205 </section>
@@ -224,7 +232,8 @@
224 </ul> 232 </ul>
225 </section> 233 </section>
226 <h5>Schema Definitions</h5> 234 <h5>Schema Definitions</h5>
227 <a href="#definition-VideoConstant"> VideoConstant </a> 235 <a href="#definition-VideoConstantNumber"> VideoConstantNumber </a>
236 <a href="#definition-VideoConstantString"> VideoConstantString </a>
228 <a href="#definition-VideoPrivacy"> VideoPrivacy </a> 237 <a href="#definition-VideoPrivacy"> VideoPrivacy </a>
229 <a href="#definition-Video"> Video </a> 238 <a href="#definition-Video"> Video </a>
230 <a href="#definition-VideoAbuse"> VideoAbuse </a> 239 <a href="#definition-VideoAbuse"> VideoAbuse </a>
@@ -605,6 +614,95 @@
605 </div> 614 </div>
606 </div> 615 </div>
607 </div> 616 </div>
617 <h1 id="tag-Feeds" class="swagger-summary-tag" data-traverse-target="tag-Feeds">Feeds</h1>
618 <div id="operation--feeds-videos.-format--get" class="operation panel" data-traverse-target="operation--feeds-videos.-format--get">
619 <!-- <section class="operation-tags row"> -->
620 <!-- <div class="doc-copy"> -->
621 <div class="operation-tags">
622 <a class="label" href="#tag-Feeds">Feeds</a>
623 <!---->
624 </div>
625 <!-- </div> -->
626 <!-- </section> -->
627 <h2 class="operation-title">
628 <span class="operation-name">
629 <span class="operation-name">GET</span>
630 <span class="operation-path">/feeds/videos.{format}</span>
631 </span>
632 </h2>
633 <div class="doc-row">
634 <div class="doc-copy">
635 <section class="swagger-request-params">
636 <div class="prop-row prop-group">
637 <div class="prop-name">
638 <div class="prop-title">format</div>
639 <span class="json-property-required"></span>
640 <div class="prop-subtitle"> in path </div>
641 <div class="prop-subtitle">
642 <span class="json-property-type">string</span>
643 <span class="json-property-enum" title="Possible values">
644 <span class="json-property-enum-item json-property-enum-default-value">xml</span>,
645 <span class="json-property-enum-item">atom</span>,
646 <span class="json-property-enum-item">json</span>
647 </span>
648 <span class="json-property-range" title="Value limits"></span>
649 <span class="json-property-default-value" title="Default value">xml</span>
650 </div>
651 </div>
652 <div class="prop-value">
653 <p>The format expected (xml defaults to RSS 2.0, atom to ATOM 1.0 and json to JSON FEED 1.0</p>
654 </div>
655 </div>
656 <div class="prop-row prop-group">
657 <div class="prop-name">
658 <div class="prop-title">accountId</div>
659 <div class="prop-subtitle"> in query </div>
660 <div class="prop-subtitle">
661 <span class="json-property-type">number</span>
662 <span class="json-property-range" title="Value limits"></span>
663 </div>
664 </div>
665 <div class="prop-value">
666 <p>The id of the local account to filter to (beware, users IDs and not actors IDs which will return empty feeds</p>
667 </div>
668 </div>
669 <div class="prop-row prop-group">
670 <div class="prop-name">
671 <div class="prop-title">accountName</div>
672 <div class="prop-subtitle"> in query </div>
673 <div class="prop-subtitle">
674 <span class="json-property-type">string</span>
675 <span class="json-property-range" title="Value limits"></span>
676 </div>
677 </div>
678 <div class="prop-value">
679 <p>The name of the local account to filter to</p>
680 </div>
681 </div>
682 </section>
683 </div>
684 <div class="doc-examples"></div>
685 </div>
686 <div class="doc-row">
687 <div class="doc-copy">
688 <section class="swagger-responses">
689 <div class="prop-row prop-group">
690 <div class="prop-name">
691 <div class="prop-title">200 OK</div>
692 </div>
693 <div class="prop-value">
694 <p>successful operation</p>
695 </div>
696 </div>
697 </section>
698 </div>
699 <div class="doc-examples">
700 <h5>Response Content-Types:
701 <span>application/atom+xml, application/rss+xml, application/json</span>
702 </h5>
703 </div>
704 </div>
705 </div>
608 <h1 id="tag-Job" class="swagger-summary-tag" data-traverse-target="tag-Job">Job</h1> 706 <h1 id="tag-Job" class="swagger-summary-tag" data-traverse-target="tag-Job">Job</h1>
609 <div id="operation--jobs-get" class="operation panel" data-traverse-target="operation--jobs-get"> 707 <div id="operation--jobs-get" class="operation panel" data-traverse-target="operation--jobs-get">
610 <!-- <section class="operation-tags row"> --> 708 <!-- <section class="operation-tags row"> -->
@@ -1518,7 +1616,7 @@
1518 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 1616 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
1519 }, 1617 },
1520 <span class="hljs-attr">&quot;language&quot;</span>: { 1618 <span class="hljs-attr">&quot;language&quot;</span>: {
1521 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 1619 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
1522 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 1620 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
1523 }, 1621 },
1524 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 1622 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -1770,7 +1868,7 @@
1770 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 1868 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
1771 }, 1869 },
1772 <span class="hljs-attr">&quot;language&quot;</span>: { 1870 <span class="hljs-attr">&quot;language&quot;</span>: {
1773 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 1871 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
1774 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 1872 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
1775 }, 1873 },
1776 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 1874 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -2047,7 +2145,7 @@
2047 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2145 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2048 }, 2146 },
2049 <span class="hljs-attr">&quot;language&quot;</span>: { 2147 <span class="hljs-attr">&quot;language&quot;</span>: {
2050 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 2148 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
2051 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2149 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2052 }, 2150 },
2053 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 2151 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -2488,7 +2586,7 @@
2488 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2586 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2489 }, 2587 },
2490 <span class="hljs-attr">&quot;language&quot;</span>: { 2588 <span class="hljs-attr">&quot;language&quot;</span>: {
2491 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 2589 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
2492 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2590 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2493 }, 2591 },
2494 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 2592 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -2825,7 +2923,7 @@
2825 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2923 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2826 }, 2924 },
2827 <span class="hljs-attr">&quot;language&quot;</span>: { 2925 <span class="hljs-attr">&quot;language&quot;</span>: {
2828 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 2926 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
2829 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 2927 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
2830 }, 2928 },
2831 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 2929 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -3206,7 +3304,7 @@
3206 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3304 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3207 }, 3305 },
3208 <span class="hljs-attr">&quot;language&quot;</span>: { 3306 <span class="hljs-attr">&quot;language&quot;</span>: {
3209 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 3307 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
3210 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3308 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3211 }, 3309 },
3212 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 3310 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -3329,7 +3427,7 @@
3329 <div class="prop-title">language</div> 3427 <div class="prop-title">language</div>
3330 <div class="prop-subtitle"> in formData </div> 3428 <div class="prop-subtitle"> in formData </div>
3331 <div class="prop-subtitle"> 3429 <div class="prop-subtitle">
3332 <span class="json-property-type">number</span> 3430 <span class="json-property-type">string</span>
3333 <span class="json-property-range" title="Value limits"></span> 3431 <span class="json-property-range" title="Value limits"></span>
3334 </div> 3432 </div>
3335 </div> 3433 </div>
@@ -3478,7 +3576,7 @@
3478 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3576 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3479 }, 3577 },
3480 <span class="hljs-attr">&quot;language&quot;</span>: { 3578 <span class="hljs-attr">&quot;language&quot;</span>: {
3481 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 3579 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
3482 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3580 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3483 }, 3581 },
3484 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 3582 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -3611,7 +3709,7 @@
3611 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3709 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3612 }, 3710 },
3613 <span class="hljs-attr">&quot;language&quot;</span>: { 3711 <span class="hljs-attr">&quot;language&quot;</span>: {
3614 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 3712 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
3615 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 3713 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
3616 }, 3714 },
3617 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 3715 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -3942,7 +4040,7 @@
3942 <div class="prop-title">language</div> 4040 <div class="prop-title">language</div>
3943 <div class="prop-subtitle"> in formData </div> 4041 <div class="prop-subtitle"> in formData </div>
3944 <div class="prop-subtitle"> 4042 <div class="prop-subtitle">
3945 <span class="json-property-type">number</span> 4043 <span class="json-property-type">string</span>
3946 <span class="json-property-range" title="Value limits"></span> 4044 <span class="json-property-range" title="Value limits"></span>
3947 </div> 4045 </div>
3948 </div> 4046 </div>
@@ -4665,7 +4763,7 @@
4665 </div> 4763 </div>
4666 </div> 4764 </div>
4667 <h1 id="tag-VideoChannel" class="swagger-summary-tag" data-traverse-target="tag-VideoChannel">VideoChannel</h1> 4765 <h1 id="tag-VideoChannel" class="swagger-summary-tag" data-traverse-target="tag-VideoChannel">VideoChannel</h1>
4668 <div id="operation--videos-channels-get" class="operation panel" data-traverse-target="operation--videos-channels-get"> 4766 <div id="operation--video-channels-get" class="operation panel" data-traverse-target="operation--video-channels-get">
4669 <!-- <section class="operation-tags row"> --> 4767 <!-- <section class="operation-tags row"> -->
4670 <!-- <div class="doc-copy"> --> 4768 <!-- <div class="doc-copy"> -->
4671 <div class="operation-tags"> 4769 <div class="operation-tags">
@@ -4677,7 +4775,7 @@
4677 <h2 class="operation-title"> 4775 <h2 class="operation-title">
4678 <span class="operation-name"> 4776 <span class="operation-name">
4679 <span class="operation-name">GET</span> 4777 <span class="operation-name">GET</span>
4680 <span class="operation-path">/videos/channels</span> 4778 <span class="operation-path">/video-channels</span>
4681 </span> 4779 </span>
4682 </h2> 4780 </h2>
4683 <div class="doc-row"> 4781 <div class="doc-row">
@@ -4789,9 +4887,144 @@
4789 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 4887 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
4790 }, 4888 },
4791 <span class="hljs-attr">&quot;language&quot;</span>: { 4889 <span class="hljs-attr">&quot;language&quot;</span>: {
4890 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4891 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
4892 },
4893 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4894 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4895 <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
4896 <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
4897 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4898 <span class="hljs-attr">&quot;thumbnailPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4899 <span class="hljs-attr">&quot;previewPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4900 <span class="hljs-attr">&quot;embedPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4901 <span class="hljs-attr">&quot;views&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
4902 <span class="hljs-attr">&quot;likes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
4903 <span class="hljs-attr">&quot;dislikes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
4904 <span class="hljs-attr">&quot;nsfw&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
4905 <span class="hljs-attr">&quot;account&quot;</span>: {
4906 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4907 <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4908 <span class="hljs-attr">&quot;url&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4909 <span class="hljs-attr">&quot;host&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4910 <span class="hljs-attr">&quot;avatar&quot;</span>: {
4911 <span class="hljs-attr">&quot;path&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4912 <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4913 <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
4914 }
4915 }
4916 }
4917 ]
4918 }
4919]
4920</code></pre>
4921 <!-- </div> -->
4922 </section>
4923 </div>
4924 </div>
4925 </div>
4926 <div id="operation--accounts--accountId--video-channels-get" class="operation panel" data-traverse-target="operation--accounts--accountId--video-channels-get">
4927 <!-- <section class="operation-tags row"> -->
4928 <!-- <div class="doc-copy"> -->
4929 <div class="operation-tags">
4930 <a class="label" href="#tag-VideoChannel">VideoChannel</a>
4931 <!---->
4932 </div>
4933 <!-- </div> -->
4934 <!-- </section> -->
4935 <h2 class="operation-title">
4936 <span class="operation-name">
4937 <span class="operation-name">GET</span>
4938 <span class="operation-path">/accounts/{accountId}/video-channels</span>
4939 </span>
4940 </h2>
4941 <div class="doc-row">
4942 <div class="doc-copy">
4943 <section class="swagger-request-params">
4944 <div class="prop-row prop-group">
4945 <div class="prop-name">
4946 <div class="prop-title">accountId</div>
4947 <span class="json-property-required"></span>
4948 <div class="prop-subtitle"> in path </div>
4949 <div class="prop-subtitle">
4950 <span class="json-property-type">string</span>
4951 <span class="json-property-range" title="Value limits"></span>
4952 </div>
4953 </div>
4954 <div class="prop-value">
4955 <p>The account id </p>
4956 </div>
4957 </div>
4958 </section>
4959 </div>
4960 <div class="doc-examples"></div>
4961 </div>
4962 <div class="doc-row">
4963 <div class="doc-copy">
4964 <section class="swagger-responses">
4965 <div class="prop-row prop-group">
4966 <div class="prop-name">
4967 <div class="prop-title">200 OK</div>
4968 <div class="prop-ref">
4969 <span class="json-schema-ref-array">
4970 <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
4971 </span>
4972 </div>
4973 <!-- <span class="swagger-global"></span> <span class="json-schema-reference"><a href=""></a></span> -->
4974 </div>
4975 <div class="prop-value">
4976 <p>successful operation</p>
4977 </div>
4978 </div>
4979 <div class="prop-row prop-inner">
4980 <div class="prop-name">type</div>
4981 <div class="prop-value">
4982 <span class="json-property-type">
4983 <span class="json-schema-ref-array">
4984 <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
4985 </span>
4986 </span>
4987 <span class="json-property-range" title="Value limits"></span>
4988 </div>
4989 </div>
4990 </section>
4991 </div>
4992 <div class="doc-examples">
4993 <h5>Response Content-Types:
4994 <span>application/json</span>
4995 </h5>
4996 <section>
4997 <h5>Response Example
4998 <span>(200 OK)</span>
4999 </h5>
5000 <!-- <div class="hljs"> --><pre><code class="hljs lang-json">[
5001 {
5002 <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5003 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5004 <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
5005 <span class="hljs-attr">&quot;owner&quot;</span>: {
5006 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5007 <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5008 },
5009 <span class="hljs-attr">&quot;videos&quot;</span>: [
5010 {
5011 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5012 <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5013 <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5014 <span class="hljs-attr">&quot;publishedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5015 <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5016 <span class="hljs-attr">&quot;category&quot;</span>: {
4792 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 5017 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
4793 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 5018 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
4794 }, 5019 },
5020 <span class="hljs-attr">&quot;licence&quot;</span>: {
5021 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5022 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5023 },
5024 <span class="hljs-attr">&quot;language&quot;</span>: {
5025 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5026 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5027 },
4795 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 5028 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4796 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 5029 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
4797 <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 5030 <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
@@ -4825,7 +5058,7 @@
4825 </div> 5058 </div>
4826 </div> 5059 </div>
4827 </div> 5060 </div>
4828 <div id="operation--videos-channels-post" class="operation panel" data-traverse-target="operation--videos-channels-post"> 5061 <div id="operation--accounts--accountId--video-channels-post" class="operation panel" data-traverse-target="operation--accounts--accountId--video-channels-post">
4829 <!-- <section class="operation-tags row"> --> 5062 <!-- <section class="operation-tags row"> -->
4830 <!-- <div class="doc-copy"> --> 5063 <!-- <div class="doc-copy"> -->
4831 <div class="operation-tags"> 5064 <div class="operation-tags">
@@ -4837,7 +5070,7 @@
4837 <h2 class="operation-title"> 5070 <h2 class="operation-title">
4838 <span class="operation-name"> 5071 <span class="operation-name">
4839 <span class="operation-name">POST</span> 5072 <span class="operation-name">POST</span>
4840 <span class="operation-path">/videos/channels</span> 5073 <span class="operation-path">/accounts/{accountId}/video-channels</span>
4841 </span> 5074 </span>
4842 </h2> 5075 </h2>
4843 <div class="doc-row"> 5076 <div class="doc-row">
@@ -4858,6 +5091,22 @@
4858 </div> 5091 </div>
4859 </div> 5092 </div>
4860 </section> 5093 </section>
5094 <section class="swagger-request-params">
5095 <div class="prop-row prop-group">
5096 <div class="prop-name">
5097 <div class="prop-title">accountId</div>
5098 <span class="json-property-required"></span>
5099 <div class="prop-subtitle"> in path </div>
5100 <div class="prop-subtitle">
5101 <span class="json-property-type">string</span>
5102 <span class="json-property-range" title="Value limits"></span>
5103 </div>
5104 </div>
5105 <div class="prop-value">
5106 <p>The account id </p>
5107 </div>
5108 </div>
5109 </section>
4861 </div> 5110 </div>
4862 <div class="doc-examples"> 5111 <div class="doc-examples">
4863 <section> 5112 <section>
@@ -4916,7 +5165,7 @@
4916 </div> 5165 </div>
4917 </div> 5166 </div>
4918 </div> 5167 </div>
4919 <div id="operation--videos-channels--id--get" class="operation panel" data-traverse-target="operation--videos-channels--id--get"> 5168 <div id="operation--account--accountId--video-channels--id--get" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--get">
4920 <!-- <section class="operation-tags row"> --> 5169 <!-- <section class="operation-tags row"> -->
4921 <!-- <div class="doc-copy"> --> 5170 <!-- <div class="doc-copy"> -->
4922 <div class="operation-tags"> 5171 <div class="operation-tags">
@@ -4928,7 +5177,7 @@
4928 <h2 class="operation-title"> 5177 <h2 class="operation-title">
4929 <span class="operation-name"> 5178 <span class="operation-name">
4930 <span class="operation-name">GET</span> 5179 <span class="operation-name">GET</span>
4931 <span class="operation-path">/videos/channels/{id}</span> 5180 <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
4932 </span> 5181 </span>
4933 </h2> 5182 </h2>
4934 <div class="doc-row"> 5183 <div class="doc-row">
@@ -4936,6 +5185,20 @@
4936 <section class="swagger-request-params"> 5185 <section class="swagger-request-params">
4937 <div class="prop-row prop-group"> 5186 <div class="prop-row prop-group">
4938 <div class="prop-name"> 5187 <div class="prop-name">
5188 <div class="prop-title">accountId</div>
5189 <span class="json-property-required"></span>
5190 <div class="prop-subtitle"> in path </div>
5191 <div class="prop-subtitle">
5192 <span class="json-property-type">string</span>
5193 <span class="json-property-range" title="Value limits"></span>
5194 </div>
5195 </div>
5196 <div class="prop-value">
5197 <p>The account id </p>
5198 </div>
5199 </div>
5200 <div class="prop-row prop-group">
5201 <div class="prop-name">
4939 <div class="prop-title">id</div> 5202 <div class="prop-title">id</div>
4940 <span class="json-property-required"></span> 5203 <span class="json-property-required"></span>
4941 <div class="prop-subtitle"> in path </div> 5204 <div class="prop-subtitle"> in path </div>
@@ -4945,7 +5208,7 @@
4945 </div> 5208 </div>
4946 </div> 5209 </div>
4947 <div class="prop-value"> 5210 <div class="prop-value">
4948 <p>The video id </p> 5211 <p>The video channel id </p>
4949 </div> 5212 </div>
4950 </div> 5213 </div>
4951 </section> 5214 </section>
@@ -5003,7 +5266,7 @@
5003 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 5266 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5004 }, 5267 },
5005 <span class="hljs-attr">&quot;language&quot;</span>: { 5268 <span class="hljs-attr">&quot;language&quot;</span>: {
5006 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 5269 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5007 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 5270 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5008 }, 5271 },
5009 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 5272 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -5038,7 +5301,7 @@
5038 </div> 5301 </div>
5039 </div> 5302 </div>
5040 </div> 5303 </div>
5041 <div id="operation--videos-channels--id--put" class="operation panel" data-traverse-target="operation--videos-channels--id--put"> 5304 <div id="operation--account--accountId--video-channels--id--put" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--put">
5042 <!-- <section class="operation-tags row"> --> 5305 <!-- <section class="operation-tags row"> -->
5043 <!-- <div class="doc-copy"> --> 5306 <!-- <div class="doc-copy"> -->
5044 <div class="operation-tags"> 5307 <div class="operation-tags">
@@ -5050,7 +5313,7 @@
5050 <h2 class="operation-title"> 5313 <h2 class="operation-title">
5051 <span class="operation-name"> 5314 <span class="operation-name">
5052 <span class="operation-name">PUT</span> 5315 <span class="operation-name">PUT</span>
5053 <span class="operation-path">/videos/channels/{id}</span> 5316 <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
5054 </span> 5317 </span>
5055 </h2> 5318 </h2>
5056 <div class="doc-row"> 5319 <div class="doc-row">
@@ -5074,6 +5337,20 @@
5074 <section class="swagger-request-params"> 5337 <section class="swagger-request-params">
5075 <div class="prop-row prop-group"> 5338 <div class="prop-row prop-group">
5076 <div class="prop-name"> 5339 <div class="prop-name">
5340 <div class="prop-title">accountId</div>
5341 <span class="json-property-required"></span>
5342 <div class="prop-subtitle"> in path </div>
5343 <div class="prop-subtitle">
5344 <span class="json-property-type">string</span>
5345 <span class="json-property-range" title="Value limits"></span>
5346 </div>
5347 </div>
5348 <div class="prop-value">
5349 <p>The account id </p>
5350 </div>
5351 </div>
5352 <div class="prop-row prop-group">
5353 <div class="prop-name">
5077 <div class="prop-title">id</div> 5354 <div class="prop-title">id</div>
5078 <span class="json-property-required"></span> 5355 <span class="json-property-required"></span>
5079 <div class="prop-subtitle"> in path </div> 5356 <div class="prop-subtitle"> in path </div>
@@ -5083,7 +5360,7 @@
5083 </div> 5360 </div>
5084 </div> 5361 </div>
5085 <div class="prop-value"> 5362 <div class="prop-value">
5086 <p>The video id </p> 5363 <p>The video channel id </p>
5087 </div> 5364 </div>
5088 </div> 5365 </div>
5089 </section> 5366 </section>
@@ -5145,7 +5422,7 @@
5145 </div> 5422 </div>
5146 </div> 5423 </div>
5147 </div> 5424 </div>
5148 <div id="operation--videos-channels--id--delete" class="operation panel" data-traverse-target="operation--videos-channels--id--delete"> 5425 <div id="operation--account--accountId--video-channels--id--delete" class="operation panel" data-traverse-target="operation--account--accountId--video-channels--id--delete">
5149 <!-- <section class="operation-tags row"> --> 5426 <!-- <section class="operation-tags row"> -->
5150 <!-- <div class="doc-copy"> --> 5427 <!-- <div class="doc-copy"> -->
5151 <div class="operation-tags"> 5428 <div class="operation-tags">
@@ -5157,7 +5434,7 @@
5157 <h2 class="operation-title"> 5434 <h2 class="operation-title">
5158 <span class="operation-name"> 5435 <span class="operation-name">
5159 <span class="operation-name">DELETE</span> 5436 <span class="operation-name">DELETE</span>
5160 <span class="operation-path">/videos/channels/{id}</span> 5437 <span class="operation-path">/account/{accountId}/video-channels/{id}</span>
5161 </span> 5438 </span>
5162 </h2> 5439 </h2>
5163 <div class="doc-row"> 5440 <div class="doc-row">
@@ -5165,6 +5442,20 @@
5165 <section class="swagger-request-params"> 5442 <section class="swagger-request-params">
5166 <div class="prop-row prop-group"> 5443 <div class="prop-row prop-group">
5167 <div class="prop-name"> 5444 <div class="prop-name">
5445 <div class="prop-title">accountId</div>
5446 <span class="json-property-required"></span>
5447 <div class="prop-subtitle"> in path </div>
5448 <div class="prop-subtitle">
5449 <span class="json-property-type">string</span>
5450 <span class="json-property-range" title="Value limits"></span>
5451 </div>
5452 </div>
5453 <div class="prop-value">
5454 <p>The account id </p>
5455 </div>
5456 </div>
5457 <div class="prop-row prop-group">
5458 <div class="prop-name">
5168 <div class="prop-title">id</div> 5459 <div class="prop-title">id</div>
5169 <span class="json-property-required"></span> 5460 <span class="json-property-required"></span>
5170 <div class="prop-subtitle"> in path </div> 5461 <div class="prop-subtitle"> in path </div>
@@ -5174,7 +5465,7 @@
5174 </div> 5465 </div>
5175 </div> 5466 </div>
5176 <div class="prop-value"> 5467 <div class="prop-value">
5177 <p>The video id </p> 5468 <p>The video channel id </p>
5178 </div> 5469 </div>
5179 </div> 5470 </div>
5180 </section> 5471 </section>
@@ -5223,141 +5514,6 @@
5223 </div> 5514 </div>
5224 </div> 5515 </div>
5225 </div> 5516 </div>
5226 <div id="operation--videos-accounts--accountId--channels-get" class="operation panel" data-traverse-target="operation--videos-accounts--accountId--channels-get">
5227 <!-- <section class="operation-tags row"> -->
5228 <!-- <div class="doc-copy"> -->
5229 <div class="operation-tags">
5230 <a class="label" href="#tag-VideoChannel">VideoChannel</a>
5231 <!---->
5232 </div>
5233 <!-- </div> -->
5234 <!-- </section> -->
5235 <h2 class="operation-title">
5236 <span class="operation-name">
5237 <span class="operation-name">GET</span>
5238 <span class="operation-path">/videos/accounts/{accountId}/channels</span>
5239 </span>
5240 </h2>
5241 <div class="doc-row">
5242 <div class="doc-copy">
5243 <section class="swagger-request-params">
5244 <div class="prop-row prop-group">
5245 <div class="prop-name">
5246 <div class="prop-title">accountId</div>
5247 <span class="json-property-required"></span>
5248 <div class="prop-subtitle"> in path </div>
5249 <div class="prop-subtitle">
5250 <span class="json-property-type">string</span>
5251 <span class="json-property-range" title="Value limits"></span>
5252 </div>
5253 </div>
5254 <div class="prop-value">
5255 <p>The account id </p>
5256 </div>
5257 </div>
5258 </section>
5259 </div>
5260 <div class="doc-examples"></div>
5261 </div>
5262 <div class="doc-row">
5263 <div class="doc-copy">
5264 <section class="swagger-responses">
5265 <div class="prop-row prop-group">
5266 <div class="prop-name">
5267 <div class="prop-title">200 OK</div>
5268 <div class="prop-ref">
5269 <span class="json-schema-ref-array">
5270 <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
5271 </span>
5272 </div>
5273 <!-- <span class="swagger-global"></span> <span class="json-schema-reference"><a href=""></a></span> -->
5274 </div>
5275 <div class="prop-value">
5276 <p>successful operation</p>
5277 </div>
5278 </div>
5279 <div class="prop-row prop-inner">
5280 <div class="prop-name">type</div>
5281 <div class="prop-value">
5282 <span class="json-property-type">
5283 <span class="json-schema-ref-array">
5284 <a class="json-schema-ref" href="#/definitions/VideoChannel">VideoChannel</a>
5285 </span>
5286 </span>
5287 <span class="json-property-range" title="Value limits"></span>
5288 </div>
5289 </div>
5290 </section>
5291 </div>
5292 <div class="doc-examples">
5293 <h5>Response Content-Types:
5294 <span>application/json</span>
5295 </h5>
5296 <section>
5297 <h5>Response Example
5298 <span>(200 OK)</span>
5299 </h5>
5300 <!-- <div class="hljs"> --><pre><code class="hljs lang-json">[
5301 {
5302 <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5303 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5304 <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
5305 <span class="hljs-attr">&quot;owner&quot;</span>: {
5306 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5307 <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5308 },
5309 <span class="hljs-attr">&quot;videos&quot;</span>: [
5310 {
5311 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5312 <span class="hljs-attr">&quot;uuid&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5313 <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5314 <span class="hljs-attr">&quot;publishedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5315 <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5316 <span class="hljs-attr">&quot;category&quot;</span>: {
5317 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5318 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5319 },
5320 <span class="hljs-attr">&quot;licence&quot;</span>: {
5321 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5322 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5323 },
5324 <span class="hljs-attr">&quot;language&quot;</span>: {
5325 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5326 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5327 },
5328 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5329 <span class="hljs-attr">&quot;description&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5330 <span class="hljs-attr">&quot;duration&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5331 <span class="hljs-attr">&quot;isLocal&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
5332 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5333 <span class="hljs-attr">&quot;thumbnailPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5334 <span class="hljs-attr">&quot;previewPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5335 <span class="hljs-attr">&quot;embedPath&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5336 <span class="hljs-attr">&quot;views&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5337 <span class="hljs-attr">&quot;likes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5338 <span class="hljs-attr">&quot;dislikes&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>,
5339 <span class="hljs-attr">&quot;nsfw&quot;</span>: <span class="hljs-string">&quot;boolean&quot;</span>,
5340 <span class="hljs-attr">&quot;account&quot;</span>: {
5341 <span class="hljs-attr">&quot;name&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5342 <span class="hljs-attr">&quot;displayName&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5343 <span class="hljs-attr">&quot;url&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5344 <span class="hljs-attr">&quot;host&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5345 <span class="hljs-attr">&quot;avatar&quot;</span>: {
5346 <span class="hljs-attr">&quot;path&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5347 <span class="hljs-attr">&quot;createdAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
5348 <span class="hljs-attr">&quot;updatedAt&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
5349 }
5350 }
5351 }
5352 ]
5353 }
5354]
5355</code></pre>
5356 <!-- </div> -->
5357 </section>
5358 </div>
5359 </div>
5360 </div>
5361 <h1 id="tag-VideoComment" class="swagger-summary-tag" data-traverse-target="tag-VideoComment">VideoComment</h1> 5517 <h1 id="tag-VideoComment" class="swagger-summary-tag" data-traverse-target="tag-VideoComment">VideoComment</h1>
5362 <div id="operation--videos--videoId--comment-threads-get" class="operation panel" data-traverse-target="operation--videos--videoId--comment-threads-get"> 5518 <div id="operation--videos--videoId--comment-threads-get" class="operation panel" data-traverse-target="operation--videos--videoId--comment-threads-get">
5363 <!-- <section class="operation-tags row"> --> 5519 <!-- <section class="operation-tags row"> -->
@@ -6104,9 +6260,9 @@
6104 </div> 6260 </div>
6105 </div> 6261 </div>
6106 <h1>Schema Definitions</h1> 6262 <h1>Schema Definitions</h1>
6107 <div id="definition-VideoConstant" class="definition panel" data-traverse-target="definition-VideoConstant"> 6263 <div id="definition-VideoConstantNumber" class="definition panel" data-traverse-target="definition-VideoConstantNumber">
6108 <h2 class="panel-title"> 6264 <h2 class="panel-title">
6109 <a name="/definitions/VideoConstant"></a>VideoConstant: 6265 <a name="/definitions/VideoConstantNumber"></a>VideoConstantNumber:
6110 <!-- <span class="json-property-type"><span class="json-property-type">object</span> 6266 <!-- <span class="json-property-type"><span class="json-property-type">object</span>
6111 <span class="json-property-range" title="Value limits"></span> 6267 <span class="json-property-range" title="Value limits"></span>
6112 6268
@@ -6143,6 +6299,45 @@
6143 </div> 6299 </div>
6144 </div> 6300 </div>
6145 </div> 6301 </div>
6302 <div id="definition-VideoConstantString" class="definition panel" data-traverse-target="definition-VideoConstantString">
6303 <h2 class="panel-title">
6304 <a name="/definitions/VideoConstantString"></a>VideoConstantString:
6305 <!-- <span class="json-property-type"><span class="json-property-type">object</span>
6306 <span class="json-property-range" title="Value limits"></span>
6307
6308
6309 </span> -->
6310 </h2>
6311 <div class="doc-row">
6312 <div class="doc-copy">
6313 <section class="json-schema-properties">
6314 <dl>
6315 <dt data-property-name="id">
6316 <span class="json-property-name">id:</span>
6317 <span class="json-property-type">string</span>
6318 <span class="json-property-range" title="Value limits"></span>
6319 </dt>
6320 <dt data-property-name="label">
6321 <span class="json-property-name">label:</span>
6322 <span class="json-property-type">string</span>
6323 <span class="json-property-range" title="Value limits"></span>
6324 </dt>
6325 </dl>
6326 </section>
6327 </div>
6328 <div class="doc-examples">
6329 <section>
6330 <h5>Example</h5>
6331 <!-- <div class="hljs"> --><pre><code class="hljs lang-json">{
6332 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
6333 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
6334}
6335</code></pre>
6336 <!-- </div> -->
6337 </section>
6338 </div>
6339 </div>
6340 </div>
6146 <div id="definition-VideoPrivacy" class="definition panel" data-traverse-target="definition-VideoPrivacy"> 6341 <div id="definition-VideoPrivacy" class="definition panel" data-traverse-target="definition-VideoPrivacy">
6147 <h2 class="panel-title"> 6342 <h2 class="panel-title">
6148 <a name="/definitions/VideoPrivacy"></a>VideoPrivacy: string 6343 <a name="/definitions/VideoPrivacy"></a>VideoPrivacy: string
@@ -6210,7 +6405,7 @@
6210 <span class="json-property-name">category:</span> 6405 <span class="json-property-name">category:</span>
6211 <span class="json-property-type"> 6406 <span class="json-property-type">
6212 <span class=""> 6407 <span class="">
6213 <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a> 6408 <a class="json-schema-ref" href="#/definitions/VideoConstantNumber">VideoConstantNumber</a>
6214 </span> 6409 </span>
6215 </span> 6410 </span>
6216 <span class="json-property-range" title="Value limits"></span> 6411 <span class="json-property-range" title="Value limits"></span>
@@ -6219,7 +6414,7 @@
6219 <span class="json-property-name">licence:</span> 6414 <span class="json-property-name">licence:</span>
6220 <span class="json-property-type"> 6415 <span class="json-property-type">
6221 <span class=""> 6416 <span class="">
6222 <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a> 6417 <a class="json-schema-ref" href="#/definitions/VideoConstantNumber">VideoConstantNumber</a>
6223 </span> 6418 </span>
6224 </span> 6419 </span>
6225 <span class="json-property-range" title="Value limits"></span> 6420 <span class="json-property-range" title="Value limits"></span>
@@ -6228,7 +6423,7 @@
6228 <span class="json-property-name">language:</span> 6423 <span class="json-property-name">language:</span>
6229 <span class="json-property-type"> 6424 <span class="json-property-type">
6230 <span class=""> 6425 <span class="">
6231 <a class="json-schema-ref" href="#/definitions/VideoConstant">VideoConstant</a> 6426 <a class="json-schema-ref" href="#/definitions/VideoConstantString">VideoConstantString</a>
6232 </span> 6427 </span>
6233 </span> 6428 </span>
6234 <span class="json-property-range" title="Value limits"></span> 6429 <span class="json-property-range" title="Value limits"></span>
@@ -6323,7 +6518,7 @@
6323 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 6518 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
6324 }, 6519 },
6325 <span class="hljs-attr">&quot;language&quot;</span>: { 6520 <span class="hljs-attr">&quot;language&quot;</span>: {
6326 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 6521 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
6327 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 6522 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
6328 }, 6523 },
6329 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 6524 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -6609,7 +6804,7 @@
6609 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 6804 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
6610 }, 6805 },
6611 <span class="hljs-attr">&quot;language&quot;</span>: { 6806 <span class="hljs-attr">&quot;language&quot;</span>: {
6612 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 6807 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
6613 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 6808 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
6614 }, 6809 },
6615 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 6810 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
@@ -7219,7 +7414,7 @@
7219 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 7414 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
7220 }, 7415 },
7221 <span class="hljs-attr">&quot;language&quot;</span>: { 7416 <span class="hljs-attr">&quot;language&quot;</span>: {
7222 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;number&quot;</span>, 7417 <span class="hljs-attr">&quot;id&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
7223 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span> 7418 <span class="hljs-attr">&quot;label&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>
7224 }, 7419 },
7225 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>, 7420 <span class="hljs-attr">&quot;privacy&quot;</span>: <span class="hljs-string">&quot;string&quot;</span>,
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 10f60175d..4a1f06d00 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -91,7 +91,7 @@ paths:
91 in: path 91 in: path
92 required: true 92 required: true
93 type: string 93 type: string
94 enum: ['xml', 'atom' 'json'] 94 enum: [ 'xml', 'atom', 'json']
95 default: 'xml' 95 default: 'xml'
96 description: 'The format expected (xml defaults to RSS 2.0, atom to ATOM 1.0 and json to JSON FEED 1.0' 96 description: 'The format expected (xml defaults to RSS 2.0, atom to ATOM 1.0 and json to JSON FEED 1.0'
97 - name: accountId 97 - name: accountId
@@ -967,7 +967,7 @@ paths:
967 type: array 967 type: array
968 items: 968 items:
969 $ref: '#/definitions/VideoBlacklist' 969 $ref: '#/definitions/VideoBlacklist'
970 /videos/channels: 970 /video-channels:
971 get: 971 get:
972 tags: 972 tags:
973 - VideoChannel 973 - VideoChannel
@@ -998,6 +998,27 @@ paths:
998 type: array 998 type: array
999 items: 999 items:
1000 $ref: '#/definitions/VideoChannel' 1000 $ref: '#/definitions/VideoChannel'
1001 /accounts/{accountId}/video-channels:
1002 get:
1003 tags:
1004 - VideoChannel
1005 consumes:
1006 - application/json
1007 produces:
1008 - application/json
1009 parameters:
1010 - name: accountId
1011 in: path
1012 required: true
1013 type: string
1014 description: 'The account id '
1015 responses:
1016 '200':
1017 description: successful operation
1018 schema:
1019 type: array
1020 items:
1021 $ref: '#/definitions/VideoChannel'
1001 post: 1022 post:
1002 security: 1023 security:
1003 - OAuth2: [ ] 1024 - OAuth2: [ ]
@@ -1008,6 +1029,11 @@ paths:
1008 produces: 1029 produces:
1009 - application/json 1030 - application/json
1010 parameters: 1031 parameters:
1032 - name: accountId
1033 in: path
1034 required: true
1035 type: string
1036 description: 'The account id '
1011 - in: body 1037 - in: body
1012 name: body 1038 name: body
1013 schema: 1039 schema:
@@ -1015,7 +1041,7 @@ paths:
1015 responses: 1041 responses:
1016 '204': 1042 '204':
1017 description: successful operation 1043 description: successful operation
1018 "/videos/channels/{id}": 1044 "/account/{accountId}/video-channels/{id}":
1019 get: 1045 get:
1020 tags: 1046 tags:
1021 - VideoChannel 1047 - VideoChannel
@@ -1024,11 +1050,16 @@ paths:
1024 produces: 1050 produces:
1025 - application/json 1051 - application/json
1026 parameters: 1052 parameters:
1053 - name: accountId
1054 in: path
1055 required: true
1056 type: string
1057 description: 'The account id '
1027 - name: id 1058 - name: id
1028 in: path 1059 in: path
1029 required: true 1060 required: true
1030 type: string 1061 type: string
1031 description: 'The video id ' 1062 description: 'The video channel id '
1032 responses: 1063 responses:
1033 '200': 1064 '200':
1034 description: successful operation 1065 description: successful operation
@@ -1044,11 +1075,16 @@ paths:
1044 produces: 1075 produces:
1045 - application/json 1076 - application/json
1046 parameters: 1077 parameters:
1078 - name: accountId
1079 in: path
1080 required: true
1081 type: string
1082 description: 'The account id '
1047 - name: id 1083 - name: id
1048 in: path 1084 in: path
1049 required: true 1085 required: true
1050 type: string 1086 type: string
1051 description: 'The video id ' 1087 description: 'The video channel id '
1052 - in: body 1088 - in: body
1053 name: body 1089 name: body
1054 schema: 1090 schema:
@@ -1066,35 +1102,19 @@ paths:
1066 produces: 1102 produces:
1067 - application/json 1103 - application/json
1068 parameters: 1104 parameters:
1069 - name: id 1105 - name: accountId
1070 in: path 1106 in: path
1071 required: true 1107 required: true
1072 type: string 1108 type: string
1073 description: 'The video id ' 1109 description: 'The account id '
1074 responses: 1110 - name: id
1075 '204':
1076 description: successful operation
1077 /videos/accounts/{accountId}/channels:
1078 get:
1079 tags:
1080 - VideoChannel
1081 consumes:
1082 - application/json
1083 produces:
1084 - application/json
1085 parameters:
1086 - name: accountId
1087 in: path 1111 in: path
1088 required: true 1112 required: true
1089 type: string 1113 type: string
1090 description: 'The account id ' 1114 description: 'The video channel id '
1091 responses: 1115 responses:
1092 '200': 1116 '204':
1093 description: successful operation 1117 description: successful operation
1094 schema:
1095 type: array
1096 items:
1097 $ref: '#/definitions/VideoChannel'
1098 "/videos/{videoId}/comment-threads": 1118 "/videos/{videoId}/comment-threads":
1099 get: 1119 get:
1100 tags: 1120 tags: