diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-10-31 11:52:52 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-10-31 11:53:13 +0100 |
commit | fd45e8f43c2638478599ca75632518054461da85 (patch) | |
tree | 01e1fb5ddad53bde8fb2c48f348fb8add51cfdb3 /server | |
parent | b7a485121d71c95fcf5e432e4cc745cf91af4f93 (diff) | |
download | PeerTube-fd45e8f43c2638478599ca75632518054461da85.tar.gz PeerTube-fd45e8f43c2638478599ca75632518054461da85.tar.zst PeerTube-fd45e8f43c2638478599ca75632518054461da85.zip |
Add video privacy setting
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/remote/videos.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/users.ts | 21 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 28 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 12 | ||||
-rw-r--r-- | server/initializers/constants.ts | 10 | ||||
-rw-r--r-- | server/initializers/migrations/0095-videos-privacy.ts | 35 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 19 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 3 | ||||
-rw-r--r-- | server/models/video/video.ts | 78 |
9 files changed, 181 insertions, 29 deletions
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index 3ecc62ada..cba47f0a1 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts | |||
@@ -267,7 +267,8 @@ async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod | |||
267 | views: videoToCreateData.views, | 267 | views: videoToCreateData.views, |
268 | likes: videoToCreateData.likes, | 268 | likes: videoToCreateData.likes, |
269 | dislikes: videoToCreateData.dislikes, | 269 | dislikes: videoToCreateData.dislikes, |
270 | remote: true | 270 | remote: true, |
271 | privacy: videoToCreateData.privacy | ||
271 | } | 272 | } |
272 | 273 | ||
273 | const video = db.Video.build(videoData) | 274 | const video = db.Video.build(videoData) |
@@ -334,6 +335,7 @@ async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData | |||
334 | videoInstance.set('views', videoAttributesToUpdate.views) | 335 | videoInstance.set('views', videoAttributesToUpdate.views) |
335 | videoInstance.set('likes', videoAttributesToUpdate.likes) | 336 | videoInstance.set('likes', videoAttributesToUpdate.likes) |
336 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | 337 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) |
338 | videoInstance.set('privacy', videoAttributesToUpdate.privacy) | ||
337 | 339 | ||
338 | await videoInstance.save(sequelizeOptions) | 340 | await videoInstance.save(sequelizeOptions) |
339 | 341 | ||
diff --git a/server/controllers/api/users.ts b/server/controllers/api/users.ts index fdc9b0c87..dcd407fdf 100644 --- a/server/controllers/api/users.ts +++ b/server/controllers/api/users.ts | |||
@@ -30,6 +30,8 @@ import { | |||
30 | } from '../../../shared' | 30 | } from '../../../shared' |
31 | import { createUserAuthorAndChannel } from '../../lib' | 31 | import { createUserAuthorAndChannel } from '../../lib' |
32 | import { UserInstance } from '../../models' | 32 | import { UserInstance } from '../../models' |
33 | import { videosSortValidator } from '../../middlewares/validators/sort' | ||
34 | import { setVideosSort } from '../../middlewares/sort' | ||
33 | 35 | ||
34 | const usersRouter = express.Router() | 36 | const usersRouter = express.Router() |
35 | 37 | ||
@@ -38,6 +40,15 @@ usersRouter.get('/me', | |||
38 | asyncMiddleware(getUserInformation) | 40 | asyncMiddleware(getUserInformation) |
39 | ) | 41 | ) |
40 | 42 | ||
43 | usersRouter.get('/me/videos', | ||
44 | authenticate, | ||
45 | paginationValidator, | ||
46 | videosSortValidator, | ||
47 | setVideosSort, | ||
48 | setPagination, | ||
49 | asyncMiddleware(getUserVideos) | ||
50 | ) | ||
51 | |||
41 | usersRouter.get('/me/videos/:videoId/rating', | 52 | usersRouter.get('/me/videos/:videoId/rating', |
42 | authenticate, | 53 | authenticate, |
43 | usersVideoRatingValidator, | 54 | usersVideoRatingValidator, |
@@ -101,6 +112,13 @@ export { | |||
101 | 112 | ||
102 | // --------------------------------------------------------------------------- | 113 | // --------------------------------------------------------------------------- |
103 | 114 | ||
115 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
116 | const user = res.locals.oauth.token.User | ||
117 | const resultList = await db.Video.listUserVideosForApi(user.id ,req.query.start, req.query.count, req.query.sort) | ||
118 | |||
119 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | ||
120 | } | ||
121 | |||
104 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 122 | async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
105 | const options = { | 123 | const options = { |
106 | arguments: [ req, res ], | 124 | arguments: [ req, res ], |
@@ -146,13 +164,14 @@ async function registerUser (req: express.Request, res: express.Response, next: | |||
146 | } | 164 | } |
147 | 165 | ||
148 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | 166 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { |
167 | // We did not load channels in res.locals.user | ||
149 | const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 168 | const user = await db.User.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
150 | 169 | ||
151 | return res.json(user.toFormattedJSON()) | 170 | return res.json(user.toFormattedJSON()) |
152 | } | 171 | } |
153 | 172 | ||
154 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 173 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { |
155 | return res.json(res.locals.user.toFormattedJSON()) | 174 | return res.json(res.locals.oauth.token.User.toFormattedJSON()) |
156 | } | 175 | } |
157 | 176 | ||
158 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | 177 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 49f0e4630..4dd09917b 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -9,7 +9,8 @@ import { | |||
9 | REQUEST_VIDEO_EVENT_TYPES, | 9 | REQUEST_VIDEO_EVENT_TYPES, |
10 | VIDEO_CATEGORIES, | 10 | VIDEO_CATEGORIES, |
11 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
12 | VIDEO_LANGUAGES | 12 | VIDEO_LANGUAGES, |
13 | VIDEO_PRIVACIES | ||
13 | } from '../../../initializers' | 14 | } from '../../../initializers' |
14 | import { | 15 | import { |
15 | addEventToRemoteVideo, | 16 | addEventToRemoteVideo, |
@@ -43,7 +44,7 @@ import { | |||
43 | resetSequelizeInstance | 44 | resetSequelizeInstance |
44 | } from '../../../helpers' | 45 | } from '../../../helpers' |
45 | import { VideoInstance } from '../../../models' | 46 | import { VideoInstance } from '../../../models' |
46 | import { VideoCreate, VideoUpdate } from '../../../../shared' | 47 | import { VideoCreate, VideoUpdate, VideoPrivacy } from '../../../../shared' |
47 | 48 | ||
48 | import { abuseVideoRouter } from './abuse' | 49 | import { abuseVideoRouter } from './abuse' |
49 | import { blacklistRouter } from './blacklist' | 50 | import { blacklistRouter } from './blacklist' |
@@ -84,6 +85,7 @@ videosRouter.use('/', videoChannelRouter) | |||
84 | videosRouter.get('/categories', listVideoCategories) | 85 | videosRouter.get('/categories', listVideoCategories) |
85 | videosRouter.get('/licences', listVideoLicences) | 86 | videosRouter.get('/licences', listVideoLicences) |
86 | videosRouter.get('/languages', listVideoLanguages) | 87 | videosRouter.get('/languages', listVideoLanguages) |
88 | videosRouter.get('/privacies', listVideoPrivacies) | ||
87 | 89 | ||
88 | videosRouter.get('/', | 90 | videosRouter.get('/', |
89 | paginationValidator, | 91 | paginationValidator, |
@@ -149,6 +151,10 @@ function listVideoLanguages (req: express.Request, res: express.Response) { | |||
149 | res.json(VIDEO_LANGUAGES) | 151 | res.json(VIDEO_LANGUAGES) |
150 | } | 152 | } |
151 | 153 | ||
154 | function listVideoPrivacies (req: express.Request, res: express.Response) { | ||
155 | res.json(VIDEO_PRIVACIES) | ||
156 | } | ||
157 | |||
152 | // Wrapper to video add that retry the function if there is a database error | 158 | // Wrapper to video add that retry the function if there is a database error |
153 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail | 159 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
154 | async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { | 160 | async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -179,6 +185,7 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
179 | language: videoInfo.language, | 185 | language: videoInfo.language, |
180 | nsfw: videoInfo.nsfw, | 186 | nsfw: videoInfo.nsfw, |
181 | description: videoInfo.description, | 187 | description: videoInfo.description, |
188 | privacy: videoInfo.privacy, | ||
182 | duration: videoPhysicalFile['duration'], // duration was added by a previous middleware | 189 | duration: videoPhysicalFile['duration'], // duration was added by a previous middleware |
183 | channelId: res.locals.videoChannel.id | 190 | channelId: res.locals.videoChannel.id |
184 | } | 191 | } |
@@ -240,6 +247,8 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi | |||
240 | 247 | ||
241 | // Let transcoding job send the video to friends because the video file extension might change | 248 | // Let transcoding job send the video to friends because the video file extension might change |
242 | if (CONFIG.TRANSCODING.ENABLED === true) return undefined | 249 | if (CONFIG.TRANSCODING.ENABLED === true) return undefined |
250 | // Don't send video to remote pods, it is private | ||
251 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | ||
243 | 252 | ||
244 | const remoteVideo = await video.toAddRemoteJSON() | 253 | const remoteVideo = await video.toAddRemoteJSON() |
245 | // Now we'll add the video's meta data to our friends | 254 | // Now we'll add the video's meta data to our friends |
@@ -264,6 +273,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
264 | const videoInstance = res.locals.video | 273 | const videoInstance = res.locals.video |
265 | const videoFieldsSave = videoInstance.toJSON() | 274 | const videoFieldsSave = videoInstance.toJSON() |
266 | const videoInfoToUpdate: VideoUpdate = req.body | 275 | const videoInfoToUpdate: VideoUpdate = req.body |
276 | const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE | ||
267 | 277 | ||
268 | try { | 278 | try { |
269 | await db.sequelize.transaction(async t => { | 279 | await db.sequelize.transaction(async t => { |
@@ -276,6 +286,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
276 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) | 286 | if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) |
277 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) | 287 | if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) |
278 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) | 288 | if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) |
289 | if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy) | ||
279 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) | 290 | if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) |
280 | 291 | ||
281 | await videoInstance.save(sequelizeOptions) | 292 | await videoInstance.save(sequelizeOptions) |
@@ -287,10 +298,17 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
287 | videoInstance.Tags = tagInstances | 298 | videoInstance.Tags = tagInstances |
288 | } | 299 | } |
289 | 300 | ||
290 | const json = videoInstance.toUpdateRemoteJSON() | ||
291 | |||
292 | // Now we'll update the video's meta data to our friends | 301 | // Now we'll update the video's meta data to our friends |
293 | return updateVideoToFriends(json, t) | 302 | if (wasPrivateVideo === false) { |
303 | const json = videoInstance.toUpdateRemoteJSON() | ||
304 | return updateVideoToFriends(json, t) | ||
305 | } | ||
306 | |||
307 | // Video is not private anymore, send a create action to remote pods | ||
308 | if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) { | ||
309 | const remoteVideo = await videoInstance.toAddRemoteJSON() | ||
310 | return addVideoToFriends(remoteVideo, t) | ||
311 | } | ||
294 | }) | 312 | }) |
295 | 313 | ||
296 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) | 314 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 5b9102275..f3fdcaf2d 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -11,6 +11,7 @@ import { | |||
11 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
12 | VIDEO_LANGUAGES, | 12 | VIDEO_LANGUAGES, |
13 | VIDEO_RATE_TYPES, | 13 | VIDEO_RATE_TYPES, |
14 | VIDEO_PRIVACIES, | ||
14 | database as db | 15 | database as db |
15 | } from '../../initializers' | 16 | } from '../../initializers' |
16 | import { isUserUsernameValid } from './users' | 17 | import { isUserUsernameValid } from './users' |
@@ -36,6 +37,15 @@ function isVideoLicenceValid (value: number) { | |||
36 | return VIDEO_LICENCES[value] !== undefined | 37 | return VIDEO_LICENCES[value] !== undefined |
37 | } | 38 | } |
38 | 39 | ||
40 | function isVideoPrivacyValid (value: string) { | ||
41 | return VIDEO_PRIVACIES[value] !== undefined | ||
42 | } | ||
43 | |||
44 | // Maybe we don't know the remote privacy setting, but that doesn't matter | ||
45 | function isRemoteVideoPrivacyValid (value: string) { | ||
46 | return validator.isInt('' + value) | ||
47 | } | ||
48 | |||
39 | // Maybe we don't know the remote licence, but that doesn't matter | 49 | // Maybe we don't know the remote licence, but that doesn't matter |
40 | function isRemoteVideoLicenceValid (value: string) { | 50 | function isRemoteVideoLicenceValid (value: string) { |
41 | return validator.isInt('' + value) | 51 | return validator.isInt('' + value) |
@@ -195,6 +205,8 @@ export { | |||
195 | isVideoDislikesValid, | 205 | isVideoDislikesValid, |
196 | isVideoEventCountValid, | 206 | isVideoEventCountValid, |
197 | isVideoFileSizeValid, | 207 | isVideoFileSizeValid, |
208 | isVideoPrivacyValid, | ||
209 | isRemoteVideoPrivacyValid, | ||
198 | isVideoFileResolutionValid, | 210 | isVideoFileResolutionValid, |
199 | checkVideoExists, | 211 | checkVideoExists, |
200 | isRemoteVideoCategoryValid, | 212 | isRemoteVideoCategoryValid, |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index adccb9f41..d349abaf0 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -12,10 +12,11 @@ import { | |||
12 | RemoteVideoRequestType, | 12 | RemoteVideoRequestType, |
13 | JobState | 13 | JobState |
14 | } from '../../shared/models' | 14 | } from '../../shared/models' |
15 | import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum' | ||
15 | 16 | ||
16 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
17 | 18 | ||
18 | const LAST_MIGRATION_VERSION = 90 | 19 | const LAST_MIGRATION_VERSION = 95 |
19 | 20 | ||
20 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
21 | 22 | ||
@@ -196,6 +197,12 @@ const VIDEO_LANGUAGES = { | |||
196 | 14: 'Italian' | 197 | 14: 'Italian' |
197 | } | 198 | } |
198 | 199 | ||
200 | const VIDEO_PRIVACIES = { | ||
201 | [VideoPrivacy.PUBLIC]: 'Public', | ||
202 | [VideoPrivacy.UNLISTED]: 'Unlisted', | ||
203 | [VideoPrivacy.PRIVATE]: 'Private' | ||
204 | } | ||
205 | |||
199 | // --------------------------------------------------------------------------- | 206 | // --------------------------------------------------------------------------- |
200 | 207 | ||
201 | // Score a pod has when we create it as a friend | 208 | // Score a pod has when we create it as a friend |
@@ -394,6 +401,7 @@ export { | |||
394 | THUMBNAILS_SIZE, | 401 | THUMBNAILS_SIZE, |
395 | VIDEO_CATEGORIES, | 402 | VIDEO_CATEGORIES, |
396 | VIDEO_LANGUAGES, | 403 | VIDEO_LANGUAGES, |
404 | VIDEO_PRIVACIES, | ||
397 | VIDEO_LICENCES, | 405 | VIDEO_LICENCES, |
398 | VIDEO_RATE_TYPES | 406 | VIDEO_RATE_TYPES |
399 | } | 407 | } |
diff --git a/server/initializers/migrations/0095-videos-privacy.ts b/server/initializers/migrations/0095-videos-privacy.ts new file mode 100644 index 000000000..4c2bf91d0 --- /dev/null +++ b/server/initializers/migrations/0095-videos-privacy.ts | |||
@@ -0,0 +1,35 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const q = utils.queryInterface | ||
10 | |||
11 | const data = { | ||
12 | type: Sequelize.INTEGER, | ||
13 | defaultValue: null, | ||
14 | allowNull: true | ||
15 | } | ||
16 | await q.addColumn('Videos', 'privacy', data) | ||
17 | |||
18 | const query = 'UPDATE "Videos" SET "privacy" = 1' | ||
19 | const options = { | ||
20 | type: Sequelize.QueryTypes.BULKUPDATE | ||
21 | } | ||
22 | await utils.sequelize.query(query, options) | ||
23 | |||
24 | data.allowNull = false | ||
25 | await q.changeColumn('Videos', 'privacy', data) | ||
26 | } | ||
27 | |||
28 | function down (options) { | ||
29 | throw new Error('Not implemented.') | ||
30 | } | ||
31 | |||
32 | export { | ||
33 | up, | ||
34 | down | ||
35 | } | ||
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 0c07404c5..e197d4606 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -20,9 +20,10 @@ import { | |||
20 | isVideoRatingTypeValid, | 20 | isVideoRatingTypeValid, |
21 | getDurationFromVideoFile, | 21 | getDurationFromVideoFile, |
22 | checkVideoExists, | 22 | checkVideoExists, |
23 | isIdValid | 23 | isIdValid, |
24 | isVideoPrivacyValid | ||
24 | } from '../../helpers' | 25 | } from '../../helpers' |
25 | import { UserRight } from '../../../shared' | 26 | import { UserRight, VideoPrivacy } from '../../../shared' |
26 | 27 | ||
27 | const videosAddValidator = [ | 28 | const videosAddValidator = [ |
28 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( | 29 | body('videofile').custom((value, { req }) => isVideoFile(req.files)).withMessage( |
@@ -36,6 +37,7 @@ const videosAddValidator = [ | |||
36 | body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), | 37 | body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), |
37 | body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 38 | body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
38 | body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), | 39 | body('channelId').custom(isIdValid).withMessage('Should have correct video channel id'), |
40 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | ||
39 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 41 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
40 | 42 | ||
41 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 43 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
@@ -110,6 +112,7 @@ const videosUpdateValidator = [ | |||
110 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), | 112 | body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'), |
111 | body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), | 113 | body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), |
112 | body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), | 114 | body('nsfw').optional().custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), |
115 | body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'), | ||
113 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), | 116 | body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'), |
114 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), | 117 | body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), |
115 | 118 | ||
@@ -118,19 +121,27 @@ const videosUpdateValidator = [ | |||
118 | 121 | ||
119 | checkErrors(req, res, () => { | 122 | checkErrors(req, res, () => { |
120 | checkVideoExists(req.params.id, res, () => { | 123 | checkVideoExists(req.params.id, res, () => { |
124 | const video = res.locals.video | ||
125 | |||
121 | // We need to make additional checks | 126 | // We need to make additional checks |
122 | if (res.locals.video.isOwned() === false) { | 127 | if (video.isOwned() === false) { |
123 | return res.status(403) | 128 | return res.status(403) |
124 | .json({ error: 'Cannot update video of another pod' }) | 129 | .json({ error: 'Cannot update video of another pod' }) |
125 | .end() | 130 | .end() |
126 | } | 131 | } |
127 | 132 | ||
128 | if (res.locals.video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) { | 133 | if (video.VideoChannel.Author.userId !== res.locals.oauth.token.User.id) { |
129 | return res.status(403) | 134 | return res.status(403) |
130 | .json({ error: 'Cannot update video of another user' }) | 135 | .json({ error: 'Cannot update video of another user' }) |
131 | .end() | 136 | .end() |
132 | } | 137 | } |
133 | 138 | ||
139 | if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) { | ||
140 | return res.status(409) | ||
141 | .json({ error: 'Cannot set "private" a video that was not private anymore.' }) | ||
142 | .end() | ||
143 | } | ||
144 | |||
134 | next() | 145 | next() |
135 | }) | 146 | }) |
136 | }) | 147 | }) |
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 587652f45..cfe65f9aa 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -49,6 +49,7 @@ export namespace VideoMethods { | |||
49 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> | 49 | export type ListOwnedByAuthor = (author: string) => Promise<VideoInstance[]> |
50 | 50 | ||
51 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | 51 | export type ListForApi = (start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > |
52 | export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Promise< ResultList<VideoInstance> > | ||
52 | export type SearchAndPopulateAuthorAndPodAndTags = ( | 53 | export type SearchAndPopulateAuthorAndPodAndTags = ( |
53 | value: string, | 54 | value: string, |
54 | field: string, | 55 | field: string, |
@@ -75,6 +76,7 @@ export interface VideoClass { | |||
75 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 76 | generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
76 | list: VideoMethods.List | 77 | list: VideoMethods.List |
77 | listForApi: VideoMethods.ListForApi | 78 | listForApi: VideoMethods.ListForApi |
79 | listUserVideosForApi: VideoMethods.ListUserVideosForApi | ||
78 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 80 | listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags |
79 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 81 | listOwnedByAuthor: VideoMethods.ListOwnedByAuthor |
80 | load: VideoMethods.Load | 82 | load: VideoMethods.Load |
@@ -97,6 +99,7 @@ export interface VideoAttributes { | |||
97 | nsfw: boolean | 99 | nsfw: boolean |
98 | description: string | 100 | description: string |
99 | duration: number | 101 | duration: number |
102 | privacy: number | ||
100 | views?: number | 103 | views?: number |
101 | likes?: number | 104 | likes?: number |
102 | dislikes?: number | 105 | dislikes?: number |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 1877c506a..2c1bd6b6e 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -18,6 +18,7 @@ import { | |||
18 | isVideoNSFWValid, | 18 | isVideoNSFWValid, |
19 | isVideoDescriptionValid, | 19 | isVideoDescriptionValid, |
20 | isVideoDurationValid, | 20 | isVideoDurationValid, |
21 | isVideoPrivacyValid, | ||
21 | readFileBufferPromise, | 22 | readFileBufferPromise, |
22 | unlinkPromise, | 23 | unlinkPromise, |
23 | renamePromise, | 24 | renamePromise, |
@@ -38,10 +39,11 @@ import { | |||
38 | THUMBNAILS_SIZE, | 39 | THUMBNAILS_SIZE, |
39 | PREVIEWS_SIZE, | 40 | PREVIEWS_SIZE, |
40 | CONSTRAINTS_FIELDS, | 41 | CONSTRAINTS_FIELDS, |
41 | API_VERSION | 42 | API_VERSION, |
43 | VIDEO_PRIVACIES | ||
42 | } from '../../initializers' | 44 | } from '../../initializers' |
43 | import { removeVideoToFriends } from '../../lib' | 45 | import { removeVideoToFriends } from '../../lib' |
44 | import { VideoResolution } from '../../../shared' | 46 | import { VideoResolution, VideoPrivacy } from '../../../shared' |
45 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' | 47 | import { VideoFileInstance, VideoFileModel } from './video-file-interface' |
46 | 48 | ||
47 | import { addMethodsToModel, getSort } from '../utils' | 49 | import { addMethodsToModel, getSort } from '../utils' |
@@ -79,6 +81,7 @@ let getTruncatedDescription: VideoMethods.GetTruncatedDescription | |||
79 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 81 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
80 | let list: VideoMethods.List | 82 | let list: VideoMethods.List |
81 | let listForApi: VideoMethods.ListForApi | 83 | let listForApi: VideoMethods.ListForApi |
84 | let listUserVideosForApi: VideoMethods.ListUserVideosForApi | ||
82 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID | 85 | let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID |
83 | let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags | 86 | let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags |
84 | let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor | 87 | let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor |
@@ -146,6 +149,16 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
146 | } | 149 | } |
147 | } | 150 | } |
148 | }, | 151 | }, |
152 | privacy: { | ||
153 | type: DataTypes.INTEGER, | ||
154 | allowNull: false, | ||
155 | validate: { | ||
156 | privacyValid: value => { | ||
157 | const res = isVideoPrivacyValid(value) | ||
158 | if (res === false) throw new Error('Video privacy is not valid.') | ||
159 | } | ||
160 | } | ||
161 | }, | ||
149 | nsfw: { | 162 | nsfw: { |
150 | type: DataTypes.BOOLEAN, | 163 | type: DataTypes.BOOLEAN, |
151 | allowNull: false, | 164 | allowNull: false, |
@@ -245,6 +258,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
245 | generateThumbnailFromData, | 258 | generateThumbnailFromData, |
246 | list, | 259 | list, |
247 | listForApi, | 260 | listForApi, |
261 | listUserVideosForApi, | ||
248 | listOwnedAndPopulateAuthorAndTags, | 262 | listOwnedAndPopulateAuthorAndTags, |
249 | listOwnedByAuthor, | 263 | listOwnedByAuthor, |
250 | load, | 264 | load, |
@@ -501,7 +515,13 @@ toFormattedJSON = function (this: VideoInstance) { | |||
501 | toFormattedDetailsJSON = function (this: VideoInstance) { | 515 | toFormattedDetailsJSON = function (this: VideoInstance) { |
502 | const formattedJson = this.toFormattedJSON() | 516 | const formattedJson = this.toFormattedJSON() |
503 | 517 | ||
518 | // Maybe our pod is not up to date and there are new privacy settings since our version | ||
519 | let privacyLabel = VIDEO_PRIVACIES[this.privacy] | ||
520 | if (!privacyLabel) privacyLabel = 'Unknown' | ||
521 | |||
504 | const detailsJson = { | 522 | const detailsJson = { |
523 | privacyLabel, | ||
524 | privacy: this.privacy, | ||
505 | descriptionPath: this.getDescriptionPath(), | 525 | descriptionPath: this.getDescriptionPath(), |
506 | channel: this.VideoChannel.toFormattedJSON(), | 526 | channel: this.VideoChannel.toFormattedJSON(), |
507 | files: [] | 527 | files: [] |
@@ -555,6 +575,7 @@ toAddRemoteJSON = function (this: VideoInstance) { | |||
555 | views: this.views, | 575 | views: this.views, |
556 | likes: this.likes, | 576 | likes: this.likes, |
557 | dislikes: this.dislikes, | 577 | dislikes: this.dislikes, |
578 | privacy: this.privacy, | ||
558 | files: [] | 579 | files: [] |
559 | } | 580 | } |
560 | 581 | ||
@@ -587,6 +608,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) { | |||
587 | views: this.views, | 608 | views: this.views, |
588 | likes: this.likes, | 609 | likes: this.likes, |
589 | dislikes: this.dislikes, | 610 | dislikes: this.dislikes, |
611 | privacy: this.privacy, | ||
590 | files: [] | 612 | files: [] |
591 | } | 613 | } |
592 | 614 | ||
@@ -746,8 +768,39 @@ list = function () { | |||
746 | return Video.findAll(query) | 768 | return Video.findAll(query) |
747 | } | 769 | } |
748 | 770 | ||
771 | listUserVideosForApi = function (userId: number, start: number, count: number, sort: string) { | ||
772 | const query = { | ||
773 | distinct: true, | ||
774 | offset: start, | ||
775 | limit: count, | ||
776 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ], | ||
777 | include: [ | ||
778 | { | ||
779 | model: Video['sequelize'].models.VideoChannel, | ||
780 | required: true, | ||
781 | include: [ | ||
782 | { | ||
783 | model: Video['sequelize'].models.Author, | ||
784 | where: { | ||
785 | userId | ||
786 | }, | ||
787 | required: true | ||
788 | } | ||
789 | ] | ||
790 | }, | ||
791 | Video['sequelize'].models.Tag | ||
792 | ] | ||
793 | } | ||
794 | |||
795 | return Video.findAndCountAll(query).then(({ rows, count }) => { | ||
796 | return { | ||
797 | data: rows, | ||
798 | total: count | ||
799 | } | ||
800 | }) | ||
801 | } | ||
802 | |||
749 | listForApi = function (start: number, count: number, sort: string) { | 803 | listForApi = function (start: number, count: number, sort: string) { |
750 | // Exclude blacklisted videos from the list | ||
751 | const query = { | 804 | const query = { |
752 | distinct: true, | 805 | distinct: true, |
753 | offset: start, | 806 | offset: start, |
@@ -768,8 +821,7 @@ listForApi = function (start: number, count: number, sort: string) { | |||
768 | } | 821 | } |
769 | ] | 822 | ] |
770 | }, | 823 | }, |
771 | Video['sequelize'].models.Tag, | 824 | Video['sequelize'].models.Tag |
772 | Video['sequelize'].models.VideoFile | ||
773 | ], | 825 | ], |
774 | where: createBaseVideosWhere() | 826 | where: createBaseVideosWhere() |
775 | } | 827 | } |
@@ -969,10 +1021,6 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
969 | model: Video['sequelize'].models.Tag | 1021 | model: Video['sequelize'].models.Tag |
970 | } | 1022 | } |
971 | 1023 | ||
972 | const videoFileInclude: Sequelize.IncludeOptions = { | ||
973 | model: Video['sequelize'].models.VideoFile | ||
974 | } | ||
975 | |||
976 | const query: Sequelize.FindOptions<VideoAttributes> = { | 1024 | const query: Sequelize.FindOptions<VideoAttributes> = { |
977 | distinct: true, | 1025 | distinct: true, |
978 | where: createBaseVideosWhere(), | 1026 | where: createBaseVideosWhere(), |
@@ -981,12 +1029,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
981 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] | 1029 | order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] |
982 | } | 1030 | } |
983 | 1031 | ||
984 | // Make an exact search with the magnet | 1032 | if (field === 'tags') { |
985 | if (field === 'magnetUri') { | ||
986 | videoFileInclude.where = { | ||
987 | infoHash: magnetUtil.decode(value).infoHash | ||
988 | } | ||
989 | } else if (field === 'tags') { | ||
990 | const escapedValue = Video['sequelize'].escape('%' + value + '%') | 1033 | const escapedValue = Video['sequelize'].escape('%' + value + '%') |
991 | query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( | 1034 | query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( |
992 | `(SELECT "VideoTags"."videoId" | 1035 | `(SELECT "VideoTags"."videoId" |
@@ -1016,7 +1059,7 @@ searchAndPopulateAuthorAndPodAndTags = function (value: string, field: string, s | |||
1016 | } | 1059 | } |
1017 | 1060 | ||
1018 | query.include = [ | 1061 | query.include = [ |
1019 | videoChannelInclude, tagInclude, videoFileInclude | 1062 | videoChannelInclude, tagInclude |
1020 | ] | 1063 | ] |
1021 | 1064 | ||
1022 | return Video.findAndCountAll(query).then(({ rows, count }) => { | 1065 | return Video.findAndCountAll(query).then(({ rows, count }) => { |
@@ -1035,7 +1078,8 @@ function createBaseVideosWhere () { | |||
1035 | [Sequelize.Op.notIn]: Video['sequelize'].literal( | 1078 | [Sequelize.Op.notIn]: Video['sequelize'].literal( |
1036 | '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")' | 1079 | '(SELECT "BlacklistedVideos"."videoId" FROM "BlacklistedVideos")' |
1037 | ) | 1080 | ) |
1038 | } | 1081 | }, |
1082 | privacy: VideoPrivacy.PUBLIC | ||
1039 | } | 1083 | } |
1040 | } | 1084 | } |
1041 | 1085 | ||