diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/accounts.ts | 209 | ||||
-rw-r--r-- | server/controllers/api/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 34 | ||||
-rw-r--r-- | server/controllers/api/videos/channel.ts | 177 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 17 | ||||
-rw-r--r-- | server/controllers/feeds.ts | 14 | ||||
-rw-r--r-- | server/middlewares/validators/follows.ts | 2 | ||||
-rw-r--r-- | server/middlewares/validators/video-channels.ts | 8 | ||||
-rw-r--r-- | server/models/video/video.ts | 92 | ||||
-rw-r--r-- | server/tests/api/check-params/video-channels.ts | 101 | ||||
-rw-r--r-- | server/tests/api/videos/multiple-servers.ts | 21 | ||||
-rw-r--r-- | server/tests/api/videos/video-channels.ts | 15 | ||||
-rw-r--r-- | server/tests/utils/videos/video-channels.ts | 35 |
13 files changed, 441 insertions, 286 deletions
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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { getFormattedObjects, resetSequelizeInstance } from '../../helpers/utils' |
3 | import { asyncMiddleware, optionalAuthenticate, paginationValidator, setDefaultPagination, setDefaultSort } from '../../middlewares' | 3 | import { |
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' | ||
4 | import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' | 16 | import { accountsGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' |
5 | import { AccountModel } from '../../models/account/account' | 17 | import { AccountModel } from '../../models/account/account' |
6 | import { VideoModel } from '../../models/video/video' | 18 | import { VideoModel } from '../../models/video/video' |
7 | import { VideoSortField } from '../../../client/src/app/shared/video/sort-field.type' | ||
8 | import { isNSFWHidden } from '../../helpers/express-utils' | 19 | import { isNSFWHidden } from '../../helpers/express-utils' |
20 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | ||
22 | import { sendUpdateActor } from '../../lib/activitypub/send' | ||
23 | import { createVideoChannel } from '../../lib/video-channel' | ||
24 | import { setAsyncActorKeys } from '../../lib/activitypub' | ||
25 | import { sequelizeTypescript } from '../../initializers' | ||
26 | import { logger } from '../../helpers/logger' | ||
27 | import { retryTransactionWrapper } from '../../helpers/database-utils' | ||
9 | 28 | ||
10 | const accountsRouter = express.Router() | 29 | const 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 | |||
54 | accountsRouter.get('/:accountId/video-channels', | ||
55 | asyncMiddleware(listVideoAccountChannelsValidator), | ||
56 | asyncMiddleware(listVideoAccountChannels) | ||
57 | ) | ||
58 | |||
59 | accountsRouter.post('/:accountId/video-channels', | ||
60 | authenticate, | ||
61 | videoChannelsAddValidator, | ||
62 | asyncMiddleware(addVideoChannelRetryWrapper) | ||
63 | ) | ||
64 | |||
65 | accountsRouter.put('/:accountId/video-channels/:id', | ||
66 | authenticate, | ||
67 | asyncMiddleware(videoChannelsUpdateValidator), | ||
68 | updateVideoChannelRetryWrapper | ||
69 | ) | ||
70 | |||
71 | accountsRouter.delete('/:accountId/video-channels/:id', | ||
72 | authenticate, | ||
73 | asyncMiddleware(videoChannelsRemoveValidator), | ||
74 | asyncMiddleware(removeVideoChannelRetryWrapper) | ||
75 | ) | ||
76 | |||
77 | accountsRouter.get('/:accountId/video-channels/:id', | ||
78 | asyncMiddleware(videoChannelsGetValidator), | ||
79 | asyncMiddleware(getVideoChannel) | ||
80 | ) | ||
81 | |||
82 | accountsRouter.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 | ||
55 | async function getAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 112 | async 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 | ||
120 | async 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 | |||
134 | async 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 | |||
150 | async 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 | |||
161 | async 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 | |||
193 | async 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 | |||
204 | async 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 | |||
215 | async 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 | |||
221 | async 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 | |||
237 | async 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' | |||
7 | import { accountsRouter } from './accounts' | 7 | import { accountsRouter } from './accounts' |
8 | import { videosRouter } from './videos' | 8 | import { videosRouter } from './videos' |
9 | import { badRequest } from '../../helpers/express-utils' | 9 | import { badRequest } from '../../helpers/express-utils' |
10 | import { videoChannelRouter } from './video-channel' | ||
10 | 11 | ||
11 | const apiRouter = express.Router() | 12 | const apiRouter = express.Router() |
12 | 13 | ||
@@ -15,6 +16,7 @@ apiRouter.use('/oauth-clients', oauthClientsRouter) | |||
15 | apiRouter.use('/config', configRouter) | 16 | apiRouter.use('/config', configRouter) |
16 | apiRouter.use('/users', usersRouter) | 17 | apiRouter.use('/users', usersRouter) |
17 | apiRouter.use('/accounts', accountsRouter) | 18 | apiRouter.use('/accounts', accountsRouter) |
19 | apiRouter.use('/video-channels', videoChannelRouter) | ||
18 | apiRouter.use('/videos', videosRouter) | 20 | apiRouter.use('/videos', videosRouter) |
19 | apiRouter.use('/jobs', jobsRouter) | 21 | apiRouter.use('/jobs', jobsRouter) |
20 | apiRouter.use('/ping', pong) | 22 | apiRouter.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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { getFormattedObjects } from '../../helpers/utils' | ||
3 | import { | ||
4 | asyncMiddleware, | ||
5 | paginationValidator, | ||
6 | setDefaultPagination, | ||
7 | setDefaultSort, | ||
8 | videoChannelsSortValidator | ||
9 | } from '../../middlewares' | ||
10 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
11 | |||
12 | const videoChannelRouter = express.Router() | ||
13 | |||
14 | videoChannelRouter.get('/', | ||
15 | paginationValidator, | ||
16 | videoChannelsSortValidator, | ||
17 | setDefaultSort, | ||
18 | setDefaultPagination, | ||
19 | asyncMiddleware(listVideoChannels) | ||
20 | ) | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | videoChannelRouter | ||
26 | } | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | async 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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared' | ||
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | ||
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { getFormattedObjects, resetSequelizeInstance } from '../../../helpers/utils' | ||
6 | import { sequelizeTypescript } from '../../../initializers' | ||
7 | import { setAsyncActorKeys } from '../../../lib/activitypub' | ||
8 | import { sendUpdateActor } from '../../../lib/activitypub/send' | ||
9 | import { createVideoChannel } from '../../../lib/video-channel' | ||
10 | import { | ||
11 | asyncMiddleware, authenticate, listVideoAccountChannelsValidator, paginationValidator, setDefaultSort, setDefaultPagination, | ||
12 | videoChannelsAddValidator, videoChannelsGetValidator, videoChannelsRemoveValidator, videoChannelsSortValidator, | ||
13 | videoChannelsUpdateValidator | ||
14 | } from '../../../middlewares' | ||
15 | import { AccountModel } from '../../../models/account/account' | ||
16 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
17 | |||
18 | const videoChannelRouter = express.Router() | ||
19 | |||
20 | videoChannelRouter.get('/channels', | ||
21 | paginationValidator, | ||
22 | videoChannelsSortValidator, | ||
23 | setDefaultSort, | ||
24 | setDefaultPagination, | ||
25 | asyncMiddleware(listVideoChannels) | ||
26 | ) | ||
27 | |||
28 | videoChannelRouter.get('/accounts/:accountId/channels', | ||
29 | asyncMiddleware(listVideoAccountChannelsValidator), | ||
30 | asyncMiddleware(listVideoAccountChannels) | ||
31 | ) | ||
32 | |||
33 | videoChannelRouter.post('/channels', | ||
34 | authenticate, | ||
35 | videoChannelsAddValidator, | ||
36 | asyncMiddleware(addVideoChannelRetryWrapper) | ||
37 | ) | ||
38 | |||
39 | videoChannelRouter.put('/channels/:id', | ||
40 | authenticate, | ||
41 | asyncMiddleware(videoChannelsUpdateValidator), | ||
42 | updateVideoChannelRetryWrapper | ||
43 | ) | ||
44 | |||
45 | videoChannelRouter.delete('/channels/:id', | ||
46 | authenticate, | ||
47 | asyncMiddleware(videoChannelsRemoveValidator), | ||
48 | asyncMiddleware(removeVideoChannelRetryWrapper) | ||
49 | ) | ||
50 | |||
51 | videoChannelRouter.get('/channels/:id', | ||
52 | asyncMiddleware(videoChannelsGetValidator), | ||
53 | asyncMiddleware(getVideoChannel) | ||
54 | ) | ||
55 | |||
56 | // --------------------------------------------------------------------------- | ||
57 | |||
58 | export { | ||
59 | videoChannelRouter | ||
60 | } | ||
61 | |||
62 | // --------------------------------------------------------------------------- | ||
63 | |||
64 | async 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 | |||
70 | async 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 | ||
78 | async 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 | |||
92 | async 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 | |||
108 | async 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 | |||
119 | async 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 | |||
151 | async 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 | |||
162 | async 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 | |||
173 | async 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' | |||
42 | import { VideoFileModel } from '../../../models/video/video-file' | 42 | import { VideoFileModel } from '../../../models/video/video-file' |
43 | import { abuseVideoRouter } from './abuse' | 43 | import { abuseVideoRouter } from './abuse' |
44 | import { blacklistRouter } from './blacklist' | 44 | import { blacklistRouter } from './blacklist' |
45 | import { videoChannelRouter } from './channel' | ||
46 | import { videoCommentRouter } from './comment' | 45 | import { videoCommentRouter } from './comment' |
47 | import { rateVideoRouter } from './rate' | 46 | import { rateVideoRouter } from './rate' |
48 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 47 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
@@ -72,7 +71,6 @@ const reqVideoFileUpdate = createReqFiles( | |||
72 | videosRouter.use('/', abuseVideoRouter) | 71 | videosRouter.use('/', abuseVideoRouter) |
73 | videosRouter.use('/', blacklistRouter) | 72 | videosRouter.use('/', blacklistRouter) |
74 | videosRouter.use('/', rateVideoRouter) | 73 | videosRouter.use('/', rateVideoRouter) |
75 | videosRouter.use('/', videoChannelRouter) | ||
76 | videosRouter.use('/', videoCommentRouter) | 74 | videosRouter.use('/', videoCommentRouter) |
77 | 75 | ||
78 | videosRouter.get('/categories', listVideoCategories) | 76 | videosRouter.get('/categories', listVideoCategories) |
@@ -397,13 +395,14 @@ async function getVideoDescription (req: express.Request, res: express.Response) | |||
397 | } | 395 | } |
398 | 396 | ||
399 | async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 397 | async 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 | ||
28 | const videoChannelsAddValidator = [ | 28 | const 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 | ||
42 | const videoChannelsUpdateValidator = [ | 43 | const 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 | ||
71 | const videoChannelsRemoveValidator = [ | 74 | const 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 | ||
88 | const videoChannelsGetValidator = [ | 93 | const 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' | |||
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import 'mocha' | 5 | import 'mocha' |
6 | import { | 6 | import { |
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' |
10 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' | 22 | import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params' |
23 | import { getAccountsList } from '../../utils/users/accounts' | ||
11 | 24 | ||
12 | const expect = chai.expect | 25 | const expect = chai.expect |
13 | 26 | ||
14 | describe('Test videos API validator', function () { | 27 | describe('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' |
42 | import { getAccountsList } from '../../utils/users/accounts' | ||
42 | 43 | ||
43 | const expect = chai.expect | 44 | const 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' |
20 | import { getAccountsList } from '../../utils/users/accounts' | ||
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
23 | describe('Test video channels', function () { | 24 | describe('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 | ||
9 | function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { | 9 | function 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 | ||
24 | function getAccountVideoChannelsList (url: string, accountId: number | string, specialStatus = 200) { | 24 | function 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 | ||
34 | function addVideoChannel (url: string, token: string, videoChannelAttributesArg: VideoChannelAttributes, expectedStatus = 200) { | 34 | function 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 | ||
53 | function updateVideoChannel (url: string, token: string, channelId: number, attributes: VideoChannelAttributes, expectedStatus = 204) { | 59 | function 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 | ||
69 | function deleteVideoChannel (url: string, token: string, channelId: number, expectedStatus = 204) { | 82 | function 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 | ||
79 | function getVideoChannel (url: string, channelId: number) { | 92 | function 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) |