diff options
Diffstat (limited to 'server/controllers/api')
-rw-r--r-- | server/controllers/api/accounts.ts | 57 | ||||
-rw-r--r-- | server/controllers/api/index.ts | 2 | ||||
-rw-r--r-- | server/controllers/api/video-channel.ts | 27 | ||||
-rw-r--r-- | server/controllers/api/video-playlist.ts | 415 | ||||
-rw-r--r-- | server/controllers/api/videos/blacklist.ts | 4 |
5 files changed, 490 insertions, 15 deletions
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 8c0237203..03c831092 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,21 +1,23 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | commonVideosFiltersValidator, | 5 | commonVideosFiltersValidator, |
6 | listVideoAccountChannelsValidator, | ||
7 | optionalAuthenticate, | 6 | optionalAuthenticate, |
8 | paginationValidator, | 7 | paginationValidator, |
9 | setDefaultPagination, | 8 | setDefaultPagination, |
10 | setDefaultSort | 9 | setDefaultSort, |
10 | videoPlaylistsSortValidator | ||
11 | } from '../../middlewares' | 11 | } from '../../middlewares' |
12 | import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' | 12 | import { accountNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' |
13 | import { AccountModel } from '../../models/account/account' | 13 | import { AccountModel } from '../../models/account/account' |
14 | import { VideoModel } from '../../models/video/video' | 14 | import { VideoModel } from '../../models/video/video' |
15 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 15 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
16 | import { VideoChannelModel } from '../../models/video/video-channel' | 16 | import { VideoChannelModel } from '../../models/video/video-channel' |
17 | import { JobQueue } from '../../lib/job-queue' | 17 | import { JobQueue } from '../../lib/job-queue' |
18 | import { logger } from '../../helpers/logger' | 18 | import { logger } from '../../helpers/logger' |
19 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
20 | import { UserModel } from '../../models/account/user' | ||
19 | 21 | ||
20 | const accountsRouter = express.Router() | 22 | const accountsRouter = express.Router() |
21 | 23 | ||
@@ -28,12 +30,12 @@ accountsRouter.get('/', | |||
28 | ) | 30 | ) |
29 | 31 | ||
30 | accountsRouter.get('/:accountName', | 32 | accountsRouter.get('/:accountName', |
31 | asyncMiddleware(accountsNameWithHostGetValidator), | 33 | asyncMiddleware(accountNameWithHostGetValidator), |
32 | getAccount | 34 | getAccount |
33 | ) | 35 | ) |
34 | 36 | ||
35 | accountsRouter.get('/:accountName/videos', | 37 | accountsRouter.get('/:accountName/videos', |
36 | asyncMiddleware(accountsNameWithHostGetValidator), | 38 | asyncMiddleware(accountNameWithHostGetValidator), |
37 | paginationValidator, | 39 | paginationValidator, |
38 | videosSortValidator, | 40 | videosSortValidator, |
39 | setDefaultSort, | 41 | setDefaultSort, |
@@ -44,8 +46,18 @@ accountsRouter.get('/:accountName/videos', | |||
44 | ) | 46 | ) |
45 | 47 | ||
46 | accountsRouter.get('/:accountName/video-channels', | 48 | accountsRouter.get('/:accountName/video-channels', |
47 | asyncMiddleware(listVideoAccountChannelsValidator), | 49 | asyncMiddleware(accountNameWithHostGetValidator), |
48 | asyncMiddleware(listVideoAccountChannels) | 50 | asyncMiddleware(listAccountChannels) |
51 | ) | ||
52 | |||
53 | accountsRouter.get('/:accountName/video-playlists', | ||
54 | optionalAuthenticate, | ||
55 | asyncMiddleware(accountNameWithHostGetValidator), | ||
56 | paginationValidator, | ||
57 | videoPlaylistsSortValidator, | ||
58 | setDefaultSort, | ||
59 | setDefaultPagination, | ||
60 | asyncMiddleware(listAccountPlaylists) | ||
49 | ) | 61 | ) |
50 | 62 | ||
51 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
@@ -56,7 +68,7 @@ export { | |||
56 | 68 | ||
57 | // --------------------------------------------------------------------------- | 69 | // --------------------------------------------------------------------------- |
58 | 70 | ||
59 | function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { | 71 | function getAccount (req: express.Request, res: express.Response) { |
60 | const account: AccountModel = res.locals.account | 72 | const account: AccountModel = res.locals.account |
61 | 73 | ||
62 | if (account.isOutdated()) { | 74 | if (account.isOutdated()) { |
@@ -67,19 +79,40 @@ function getAccount (req: express.Request, res: express.Response, next: express. | |||
67 | return res.json(account.toFormattedJSON()) | 79 | return res.json(account.toFormattedJSON()) |
68 | } | 80 | } |
69 | 81 | ||
70 | async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { | 82 | async function listAccounts (req: express.Request, res: express.Response) { |
71 | const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort) | 83 | const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort) |
72 | 84 | ||
73 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 85 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
74 | } | 86 | } |
75 | 87 | ||
76 | async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { | 88 | async function listAccountChannels (req: express.Request, res: express.Response) { |
77 | const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) | 89 | const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) |
78 | 90 | ||
79 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 91 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
80 | } | 92 | } |
81 | 93 | ||
82 | async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 94 | async function listAccountPlaylists (req: express.Request, res: express.Response) { |
95 | const serverActor = await getServerActor() | ||
96 | |||
97 | // Allow users to see their private/unlisted video playlists | ||
98 | let privateAndUnlisted = false | ||
99 | if (res.locals.oauth && (res.locals.oauth.token.User as UserModel).Account.id === res.locals.account.id) { | ||
100 | privateAndUnlisted = true | ||
101 | } | ||
102 | |||
103 | const resultList = await VideoPlaylistModel.listForApi({ | ||
104 | followerActorId: serverActor.id, | ||
105 | start: req.query.start, | ||
106 | count: req.query.count, | ||
107 | sort: req.query.sort, | ||
108 | accountId: res.locals.account.id, | ||
109 | privateAndUnlisted | ||
110 | }) | ||
111 | |||
112 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
113 | } | ||
114 | |||
115 | async function listAccountVideos (req: express.Request, res: express.Response) { | ||
83 | const account: AccountModel = res.locals.account | 116 | const account: AccountModel = res.locals.account |
84 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined | 117 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined |
85 | 118 | ||
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 8a58b5466..ed4b33dea 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -11,6 +11,7 @@ import { videoChannelRouter } from './video-channel' | |||
11 | import * as cors from 'cors' | 11 | import * as cors from 'cors' |
12 | import { searchRouter } from './search' | 12 | import { searchRouter } from './search' |
13 | import { overviewsRouter } from './overviews' | 13 | import { overviewsRouter } from './overviews' |
14 | import { videoPlaylistRouter } from './video-playlist' | ||
14 | 15 | ||
15 | const apiRouter = express.Router() | 16 | const apiRouter = express.Router() |
16 | 17 | ||
@@ -26,6 +27,7 @@ apiRouter.use('/config', configRouter) | |||
26 | apiRouter.use('/users', usersRouter) | 27 | apiRouter.use('/users', usersRouter) |
27 | apiRouter.use('/accounts', accountsRouter) | 28 | apiRouter.use('/accounts', accountsRouter) |
28 | apiRouter.use('/video-channels', videoChannelRouter) | 29 | apiRouter.use('/video-channels', videoChannelRouter) |
30 | apiRouter.use('/video-playlists', videoPlaylistRouter) | ||
29 | apiRouter.use('/videos', videosRouter) | 31 | apiRouter.use('/videos', videosRouter) |
30 | apiRouter.use('/jobs', jobsRouter) | 32 | apiRouter.use('/jobs', jobsRouter) |
31 | apiRouter.use('/search', searchRouter) | 33 | apiRouter.use('/search', searchRouter) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index db7602139..534cc8d7b 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -12,7 +12,8 @@ import { | |||
12 | videoChannelsAddValidator, | 12 | videoChannelsAddValidator, |
13 | videoChannelsRemoveValidator, | 13 | videoChannelsRemoveValidator, |
14 | videoChannelsSortValidator, | 14 | videoChannelsSortValidator, |
15 | videoChannelsUpdateValidator | 15 | videoChannelsUpdateValidator, |
16 | videoPlaylistsSortValidator | ||
16 | } from '../../middlewares' | 17 | } from '../../middlewares' |
17 | import { VideoChannelModel } from '../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../models/video/video-channel' |
18 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' | 19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' |
@@ -31,6 +32,7 @@ import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '.. | |||
31 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 32 | import { resetSequelizeInstance } from '../../helpers/database-utils' |
32 | import { UserModel } from '../../models/account/user' | 33 | import { UserModel } from '../../models/account/user' |
33 | import { JobQueue } from '../../lib/job-queue' | 34 | import { JobQueue } from '../../lib/job-queue' |
35 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
34 | 36 | ||
35 | const auditLogger = auditLoggerFactory('channels') | 37 | const auditLogger = auditLoggerFactory('channels') |
36 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 38 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
@@ -77,6 +79,15 @@ videoChannelRouter.get('/:nameWithHost', | |||
77 | asyncMiddleware(getVideoChannel) | 79 | asyncMiddleware(getVideoChannel) |
78 | ) | 80 | ) |
79 | 81 | ||
82 | videoChannelRouter.get('/:nameWithHost/video-playlists', | ||
83 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
84 | paginationValidator, | ||
85 | videoPlaylistsSortValidator, | ||
86 | setDefaultSort, | ||
87 | setDefaultPagination, | ||
88 | asyncMiddleware(listVideoChannelPlaylists) | ||
89 | ) | ||
90 | |||
80 | videoChannelRouter.get('/:nameWithHost/videos', | 91 | videoChannelRouter.get('/:nameWithHost/videos', |
81 | asyncMiddleware(videoChannelsNameWithHostValidator), | 92 | asyncMiddleware(videoChannelsNameWithHostValidator), |
82 | paginationValidator, | 93 | paginationValidator, |
@@ -206,6 +217,20 @@ async function getVideoChannel (req: express.Request, res: express.Response, nex | |||
206 | return res.json(videoChannelWithVideos.toFormattedJSON()) | 217 | return res.json(videoChannelWithVideos.toFormattedJSON()) |
207 | } | 218 | } |
208 | 219 | ||
220 | async function listVideoChannelPlaylists (req: express.Request, res: express.Response) { | ||
221 | const serverActor = await getServerActor() | ||
222 | |||
223 | const resultList = await VideoPlaylistModel.listForApi({ | ||
224 | followerActorId: serverActor.id, | ||
225 | start: req.query.start, | ||
226 | count: req.query.count, | ||
227 | sort: req.query.sort, | ||
228 | videoChannelId: res.locals.videoChannel.id | ||
229 | }) | ||
230 | |||
231 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
232 | } | ||
233 | |||
209 | async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 234 | async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
210 | const videoChannelInstance: VideoChannelModel = res.locals.videoChannel | 235 | const videoChannelInstance: VideoChannelModel = res.locals.videoChannel |
211 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined | 236 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts new file mode 100644 index 000000000..709c58beb --- /dev/null +++ b/server/controllers/api/video-playlist.ts | |||
@@ -0,0 +1,415 @@ | |||
1 | import * as express from 'express' | ||
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | ||
3 | import { | ||
4 | asyncMiddleware, | ||
5 | asyncRetryTransactionMiddleware, | ||
6 | authenticate, | ||
7 | commonVideosFiltersValidator, | ||
8 | paginationValidator, | ||
9 | setDefaultPagination, | ||
10 | setDefaultSort | ||
11 | } from '../../middlewares' | ||
12 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
13 | import { videoPlaylistsSortValidator } from '../../middlewares/validators' | ||
14 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | ||
15 | import { CONFIG, MIMETYPES, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers' | ||
16 | import { logger } from '../../helpers/logger' | ||
17 | import { resetSequelizeInstance } from '../../helpers/database-utils' | ||
18 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
19 | import { | ||
20 | videoPlaylistsAddValidator, | ||
21 | videoPlaylistsAddVideoValidator, | ||
22 | videoPlaylistsDeleteValidator, | ||
23 | videoPlaylistsGetValidator, | ||
24 | videoPlaylistsReorderVideosValidator, | ||
25 | videoPlaylistsUpdateOrRemoveVideoValidator, | ||
26 | videoPlaylistsUpdateValidator | ||
27 | } from '../../middlewares/validators/videos/video-playlists' | ||
28 | import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model' | ||
29 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
30 | import { processImage } from '../../helpers/image-utils' | ||
31 | import { join } from 'path' | ||
32 | import { UserModel } from '../../models/account/user' | ||
33 | import { | ||
34 | getVideoPlaylistActivityPubUrl, | ||
35 | getVideoPlaylistElementActivityPubUrl, | ||
36 | sendCreateVideoPlaylist, | ||
37 | sendDeleteVideoPlaylist, | ||
38 | sendUpdateVideoPlaylist | ||
39 | } from '../../lib/activitypub' | ||
40 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | ||
41 | import { VideoModel } from '../../models/video/video' | ||
42 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | ||
43 | import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model' | ||
44 | import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model' | ||
45 | import { copy, pathExists } from 'fs-extra' | ||
46 | |||
47 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | ||
48 | |||
49 | const videoPlaylistRouter = express.Router() | ||
50 | |||
51 | videoPlaylistRouter.get('/', | ||
52 | paginationValidator, | ||
53 | videoPlaylistsSortValidator, | ||
54 | setDefaultSort, | ||
55 | setDefaultPagination, | ||
56 | asyncMiddleware(listVideoPlaylists) | ||
57 | ) | ||
58 | |||
59 | videoPlaylistRouter.get('/:playlistId', | ||
60 | asyncMiddleware(videoPlaylistsGetValidator), | ||
61 | getVideoPlaylist | ||
62 | ) | ||
63 | |||
64 | videoPlaylistRouter.post('/', | ||
65 | authenticate, | ||
66 | reqThumbnailFile, | ||
67 | asyncMiddleware(videoPlaylistsAddValidator), | ||
68 | asyncRetryTransactionMiddleware(addVideoPlaylist) | ||
69 | ) | ||
70 | |||
71 | videoPlaylistRouter.put('/:playlistId', | ||
72 | authenticate, | ||
73 | reqThumbnailFile, | ||
74 | asyncMiddleware(videoPlaylistsUpdateValidator), | ||
75 | asyncRetryTransactionMiddleware(updateVideoPlaylist) | ||
76 | ) | ||
77 | |||
78 | videoPlaylistRouter.delete('/:playlistId', | ||
79 | authenticate, | ||
80 | asyncMiddleware(videoPlaylistsDeleteValidator), | ||
81 | asyncRetryTransactionMiddleware(removeVideoPlaylist) | ||
82 | ) | ||
83 | |||
84 | videoPlaylistRouter.get('/:playlistId/videos', | ||
85 | asyncMiddleware(videoPlaylistsGetValidator), | ||
86 | paginationValidator, | ||
87 | setDefaultPagination, | ||
88 | commonVideosFiltersValidator, | ||
89 | asyncMiddleware(getVideoPlaylistVideos) | ||
90 | ) | ||
91 | |||
92 | videoPlaylistRouter.post('/:playlistId/videos', | ||
93 | authenticate, | ||
94 | asyncMiddleware(videoPlaylistsAddVideoValidator), | ||
95 | asyncRetryTransactionMiddleware(addVideoInPlaylist) | ||
96 | ) | ||
97 | |||
98 | videoPlaylistRouter.put('/:playlistId/videos', | ||
99 | authenticate, | ||
100 | asyncMiddleware(videoPlaylistsReorderVideosValidator), | ||
101 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) | ||
102 | ) | ||
103 | |||
104 | videoPlaylistRouter.put('/:playlistId/videos/:videoId', | ||
105 | authenticate, | ||
106 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
107 | asyncRetryTransactionMiddleware(updateVideoPlaylistElement) | ||
108 | ) | ||
109 | |||
110 | videoPlaylistRouter.delete('/:playlistId/videos/:videoId', | ||
111 | authenticate, | ||
112 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
113 | asyncRetryTransactionMiddleware(removeVideoFromPlaylist) | ||
114 | ) | ||
115 | |||
116 | // --------------------------------------------------------------------------- | ||
117 | |||
118 | export { | ||
119 | videoPlaylistRouter | ||
120 | } | ||
121 | |||
122 | // --------------------------------------------------------------------------- | ||
123 | |||
124 | async function listVideoPlaylists (req: express.Request, res: express.Response) { | ||
125 | const serverActor = await getServerActor() | ||
126 | const resultList = await VideoPlaylistModel.listForApi({ | ||
127 | followerActorId: serverActor.id, | ||
128 | start: req.query.start, | ||
129 | count: req.query.count, | ||
130 | sort: req.query.sort | ||
131 | }) | ||
132 | |||
133 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
134 | } | ||
135 | |||
136 | function getVideoPlaylist (req: express.Request, res: express.Response) { | ||
137 | const videoPlaylist = res.locals.videoPlaylist as VideoPlaylistModel | ||
138 | |||
139 | return res.json(videoPlaylist.toFormattedJSON()) | ||
140 | } | ||
141 | |||
142 | async function addVideoPlaylist (req: express.Request, res: express.Response) { | ||
143 | const videoPlaylistInfo: VideoPlaylistCreate = req.body | ||
144 | const user: UserModel = res.locals.oauth.token.User | ||
145 | |||
146 | const videoPlaylist = new VideoPlaylistModel({ | ||
147 | name: videoPlaylistInfo.displayName, | ||
148 | description: videoPlaylistInfo.description, | ||
149 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, | ||
150 | ownerAccountId: user.Account.id | ||
151 | }) | ||
152 | |||
153 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object | ||
154 | |||
155 | if (videoPlaylistInfo.videoChannelId !== undefined) { | ||
156 | const videoChannel = res.locals.videoChannel as VideoChannelModel | ||
157 | |||
158 | videoPlaylist.videoChannelId = videoChannel.id | ||
159 | videoPlaylist.VideoChannel = videoChannel | ||
160 | } | ||
161 | |||
162 | const thumbnailField = req.files['thumbnailfile'] | ||
163 | if (thumbnailField) { | ||
164 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | ||
165 | await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylist.getThumbnailName()), THUMBNAILS_SIZE) | ||
166 | } | ||
167 | |||
168 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { | ||
169 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) | ||
170 | |||
171 | await sendCreateVideoPlaylist(videoPlaylistCreated, t) | ||
172 | |||
173 | return videoPlaylistCreated | ||
174 | }) | ||
175 | |||
176 | logger.info('Video playlist with uuid %s created.', videoPlaylist.uuid) | ||
177 | |||
178 | return res.json({ | ||
179 | videoPlaylist: { | ||
180 | id: videoPlaylistCreated.id, | ||
181 | uuid: videoPlaylistCreated.uuid | ||
182 | } | ||
183 | }).end() | ||
184 | } | ||
185 | |||
186 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { | ||
187 | const videoPlaylistInstance = res.locals.videoPlaylist as VideoPlaylistModel | ||
188 | const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() | ||
189 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate | ||
190 | const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE | ||
191 | |||
192 | const thumbnailField = req.files['thumbnailfile'] | ||
193 | if (thumbnailField) { | ||
194 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | ||
195 | await processImage( | ||
196 | thumbnailPhysicalFile, | ||
197 | join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylistInstance.getThumbnailName()), | ||
198 | THUMBNAILS_SIZE | ||
199 | ) | ||
200 | } | ||
201 | |||
202 | try { | ||
203 | await sequelizeTypescript.transaction(async t => { | ||
204 | const sequelizeOptions = { | ||
205 | transaction: t | ||
206 | } | ||
207 | |||
208 | if (videoPlaylistInfoToUpdate.videoChannelId !== undefined) { | ||
209 | if (videoPlaylistInfoToUpdate.videoChannelId === null) { | ||
210 | videoPlaylistInstance.videoChannelId = null | ||
211 | } else { | ||
212 | const videoChannel = res.locals.videoChannel as VideoChannelModel | ||
213 | |||
214 | videoPlaylistInstance.videoChannelId = videoChannel.id | ||
215 | } | ||
216 | } | ||
217 | |||
218 | if (videoPlaylistInfoToUpdate.displayName !== undefined) videoPlaylistInstance.name = videoPlaylistInfoToUpdate.displayName | ||
219 | if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description | ||
220 | |||
221 | if (videoPlaylistInfoToUpdate.privacy !== undefined) { | ||
222 | videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10) | ||
223 | } | ||
224 | |||
225 | const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions) | ||
226 | |||
227 | const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE | ||
228 | |||
229 | if (isNewPlaylist) { | ||
230 | await sendCreateVideoPlaylist(playlistUpdated, t) | ||
231 | } else { | ||
232 | await sendUpdateVideoPlaylist(playlistUpdated, t) | ||
233 | } | ||
234 | |||
235 | logger.info('Video playlist %s updated.', videoPlaylistInstance.uuid) | ||
236 | |||
237 | return playlistUpdated | ||
238 | }) | ||
239 | } catch (err) { | ||
240 | logger.debug('Cannot update the video playlist.', { err }) | ||
241 | |||
242 | // Force fields we want to update | ||
243 | // If the transaction is retried, sequelize will think the object has not changed | ||
244 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
245 | resetSequelizeInstance(videoPlaylistInstance, videoPlaylistFieldsSave) | ||
246 | |||
247 | throw err | ||
248 | } | ||
249 | |||
250 | return res.type('json').status(204).end() | ||
251 | } | ||
252 | |||
253 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { | ||
254 | const videoPlaylistInstance: VideoPlaylistModel = res.locals.videoPlaylist | ||
255 | |||
256 | await sequelizeTypescript.transaction(async t => { | ||
257 | await videoPlaylistInstance.destroy({ transaction: t }) | ||
258 | |||
259 | await sendDeleteVideoPlaylist(videoPlaylistInstance, t) | ||
260 | |||
261 | logger.info('Video playlist %s deleted.', videoPlaylistInstance.uuid) | ||
262 | }) | ||
263 | |||
264 | return res.type('json').status(204).end() | ||
265 | } | ||
266 | |||
267 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { | ||
268 | const body: VideoPlaylistElementCreate = req.body | ||
269 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | ||
270 | const video: VideoModel = res.locals.video | ||
271 | |||
272 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | ||
273 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) | ||
274 | |||
275 | const playlistElement = await VideoPlaylistElementModel.create({ | ||
276 | url: getVideoPlaylistElementActivityPubUrl(videoPlaylist, video), | ||
277 | position, | ||
278 | startTimestamp: body.startTimestamp || null, | ||
279 | stopTimestamp: body.stopTimestamp || null, | ||
280 | videoPlaylistId: videoPlaylist.id, | ||
281 | videoId: video.id | ||
282 | }, { transaction: t }) | ||
283 | |||
284 | // If the user did not set a thumbnail, automatically take the video thumbnail | ||
285 | if (playlistElement.position === 1) { | ||
286 | const playlistThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoPlaylist.getThumbnailName()) | ||
287 | |||
288 | if (await pathExists(playlistThumbnailPath) === false) { | ||
289 | const videoThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()) | ||
290 | await copy(videoThumbnailPath, playlistThumbnailPath) | ||
291 | } | ||
292 | } | ||
293 | |||
294 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
295 | |||
296 | return playlistElement | ||
297 | }) | ||
298 | |||
299 | logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position) | ||
300 | |||
301 | return res.json({ | ||
302 | videoPlaylistElement: { | ||
303 | id: playlistElement.id | ||
304 | } | ||
305 | }).end() | ||
306 | } | ||
307 | |||
308 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { | ||
309 | const body: VideoPlaylistElementUpdate = req.body | ||
310 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | ||
311 | const videoPlaylistElement: VideoPlaylistElementModel = res.locals.videoPlaylistElement | ||
312 | |||
313 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | ||
314 | if (body.startTimestamp !== undefined) videoPlaylistElement.startTimestamp = body.startTimestamp | ||
315 | if (body.stopTimestamp !== undefined) videoPlaylistElement.stopTimestamp = body.stopTimestamp | ||
316 | |||
317 | const element = await videoPlaylistElement.save({ transaction: t }) | ||
318 | |||
319 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
320 | |||
321 | return element | ||
322 | }) | ||
323 | |||
324 | logger.info('Element of position %d of playlist %s updated.', playlistElement.position, videoPlaylist.uuid) | ||
325 | |||
326 | return res.type('json').status(204).end() | ||
327 | } | ||
328 | |||
329 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { | ||
330 | const videoPlaylistElement: VideoPlaylistElementModel = res.locals.videoPlaylistElement | ||
331 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | ||
332 | const positionToDelete = videoPlaylistElement.position | ||
333 | |||
334 | await sequelizeTypescript.transaction(async t => { | ||
335 | await videoPlaylistElement.destroy({ transaction: t }) | ||
336 | |||
337 | // Decrease position of the next elements | ||
338 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t) | ||
339 | |||
340 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
341 | |||
342 | logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid) | ||
343 | }) | ||
344 | |||
345 | return res.type('json').status(204).end() | ||
346 | } | ||
347 | |||
348 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { | ||
349 | const videoPlaylist: VideoPlaylistModel = res.locals.videoPlaylist | ||
350 | |||
351 | const start: number = req.body.startPosition | ||
352 | const insertAfter: number = req.body.insertAfter | ||
353 | const reorderLength: number = req.body.reorderLength || 1 | ||
354 | |||
355 | if (start === insertAfter) { | ||
356 | return res.status(204).end() | ||
357 | } | ||
358 | |||
359 | // Example: if we reorder position 2 and insert after position 5 (so at position 6): # 1 2 3 4 5 6 7 8 9 | ||
360 | // * increase position when position > 5 # 1 2 3 4 5 7 8 9 10 | ||
361 | // * update position 2 -> position 6 # 1 3 4 5 6 7 8 9 10 | ||
362 | // * decrease position when position position > 2 # 1 2 3 4 5 6 7 8 9 | ||
363 | await sequelizeTypescript.transaction(async t => { | ||
364 | const newPosition = insertAfter + 1 | ||
365 | |||
366 | // Add space after the position when we want to insert our reordered elements (increase) | ||
367 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, newPosition, null, reorderLength, t) | ||
368 | |||
369 | let oldPosition = start | ||
370 | |||
371 | // We incremented the position of the elements we want to reorder | ||
372 | if (start >= newPosition) oldPosition += reorderLength | ||
373 | |||
374 | const endOldPosition = oldPosition + reorderLength - 1 | ||
375 | // Insert our reordered elements in their place (update) | ||
376 | await VideoPlaylistElementModel.reassignPositionOf(videoPlaylist.id, oldPosition, endOldPosition, newPosition, t) | ||
377 | |||
378 | // Decrease positions of elements after the old position of our ordered elements (decrease) | ||
379 | await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t) | ||
380 | |||
381 | await sendUpdateVideoPlaylist(videoPlaylist, t) | ||
382 | }) | ||
383 | |||
384 | logger.info( | ||
385 | 'Reordered playlist %s (inserted after %d elements %d - %d).', | ||
386 | videoPlaylist.uuid, insertAfter, start, start + reorderLength - 1 | ||
387 | ) | ||
388 | |||
389 | return res.type('json').status(204).end() | ||
390 | } | ||
391 | |||
392 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { | ||
393 | const videoPlaylistInstance: VideoPlaylistModel = res.locals.videoPlaylist | ||
394 | const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined | ||
395 | |||
396 | const resultList = await VideoModel.listForApi({ | ||
397 | followerActorId, | ||
398 | start: req.query.start, | ||
399 | count: req.query.count, | ||
400 | sort: 'VideoPlaylistElements.position', | ||
401 | includeLocalVideos: true, | ||
402 | categoryOneOf: req.query.categoryOneOf, | ||
403 | licenceOneOf: req.query.licenceOneOf, | ||
404 | languageOneOf: req.query.languageOneOf, | ||
405 | tagsOneOf: req.query.tagsOneOf, | ||
406 | tagsAllOf: req.query.tagsAllOf, | ||
407 | filter: req.query.filter, | ||
408 | nsfw: buildNSFWFilter(res, req.query.nsfw), | ||
409 | withFiles: false, | ||
410 | videoPlaylistId: videoPlaylistInstance.id, | ||
411 | user: res.locals.oauth ? res.locals.oauth.token.User : undefined | ||
412 | }) | ||
413 | |||
414 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
415 | } | ||
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 43b0516e7..b01296200 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared' | 2 | import { UserRight, VideoBlacklist, VideoBlacklistCreate } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { | 5 | import { |
@@ -18,7 +18,7 @@ import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | |||
18 | import { sequelizeTypescript } from '../../../initializers' | 18 | import { sequelizeTypescript } from '../../../initializers' |
19 | import { Notifier } from '../../../lib/notifier' | 19 | import { Notifier } from '../../../lib/notifier' |
20 | import { VideoModel } from '../../../models/video/video' | 20 | import { VideoModel } from '../../../models/video/video' |
21 | import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send' | 21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' |
22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
23 | 23 | ||
24 | const blacklistRouter = express.Router() | 24 | const blacklistRouter = express.Router() |