aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-31 11:52:52 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-10-31 11:53:13 +0100
commitfd45e8f43c2638478599ca75632518054461da85 (patch)
tree01e1fb5ddad53bde8fb2c48f348fb8add51cfdb3 /server
parentb7a485121d71c95fcf5e432e4cc745cf91af4f93 (diff)
downloadPeerTube-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.ts4
-rw-r--r--server/controllers/api/users.ts21
-rw-r--r--server/controllers/api/videos/index.ts28
-rw-r--r--server/helpers/custom-validators/videos.ts12
-rw-r--r--server/initializers/constants.ts10
-rw-r--r--server/initializers/migrations/0095-videos-privacy.ts35
-rw-r--r--server/middlewares/validators/videos.ts19
-rw-r--r--server/models/video/video-interface.ts3
-rw-r--r--server/models/video/video.ts78
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'
31import { createUserAuthorAndChannel } from '../../lib' 31import { createUserAuthorAndChannel } from '../../lib'
32import { UserInstance } from '../../models' 32import { UserInstance } from '../../models'
33import { videosSortValidator } from '../../middlewares/validators/sort'
34import { setVideosSort } from '../../middlewares/sort'
33 35
34const usersRouter = express.Router() 36const usersRouter = express.Router()
35 37
@@ -38,6 +40,15 @@ usersRouter.get('/me',
38 asyncMiddleware(getUserInformation) 40 asyncMiddleware(getUserInformation)
39) 41)
40 42
43usersRouter.get('/me/videos',
44 authenticate,
45 paginationValidator,
46 videosSortValidator,
47 setVideosSort,
48 setPagination,
49 asyncMiddleware(getUserVideos)
50)
51
41usersRouter.get('/me/videos/:videoId/rating', 52usersRouter.get('/me/videos/:videoId/rating',
42 authenticate, 53 authenticate,
43 usersVideoRatingValidator, 54 usersVideoRatingValidator,
@@ -101,6 +112,13 @@ export {
101 112
102// --------------------------------------------------------------------------- 113// ---------------------------------------------------------------------------
103 114
115async 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
104async function createUserRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 122async 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
148async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 166async 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
154function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { 173function 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
158async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 177async 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'
14import { 15import {
15 addEventToRemoteVideo, 16 addEventToRemoteVideo,
@@ -43,7 +44,7 @@ import {
43 resetSequelizeInstance 44 resetSequelizeInstance
44} from '../../../helpers' 45} from '../../../helpers'
45import { VideoInstance } from '../../../models' 46import { VideoInstance } from '../../../models'
46import { VideoCreate, VideoUpdate } from '../../../../shared' 47import { VideoCreate, VideoUpdate, VideoPrivacy } from '../../../../shared'
47 48
48import { abuseVideoRouter } from './abuse' 49import { abuseVideoRouter } from './abuse'
49import { blacklistRouter } from './blacklist' 50import { blacklistRouter } from './blacklist'
@@ -84,6 +85,7 @@ videosRouter.use('/', videoChannelRouter)
84videosRouter.get('/categories', listVideoCategories) 85videosRouter.get('/categories', listVideoCategories)
85videosRouter.get('/licences', listVideoLicences) 86videosRouter.get('/licences', listVideoLicences)
86videosRouter.get('/languages', listVideoLanguages) 87videosRouter.get('/languages', listVideoLanguages)
88videosRouter.get('/privacies', listVideoPrivacies)
87 89
88videosRouter.get('/', 90videosRouter.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
154function 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
154async function addVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 160async 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'
16import { isUserUsernameValid } from './users' 17import { 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
40function 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
45function 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
40function isRemoteVideoLicenceValid (value: string) { 50function 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'
15import { VideoPrivacy } from '../../shared/models/videos/video-privacy.enum'
15 16
16// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
17 18
18const LAST_MIGRATION_VERSION = 90 19const 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
200const 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 @@
1import * as Sequelize from 'sequelize'
2
3async 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
28function down (options) {
29 throw new Error('Not implemented.')
30}
31
32export {
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'
25import { UserRight } from '../../../shared' 26import { UserRight, VideoPrivacy } from '../../../shared'
26 27
27const videosAddValidator = [ 28const 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'
43import { removeVideoToFriends } from '../../lib' 45import { removeVideoToFriends } from '../../lib'
44import { VideoResolution } from '../../../shared' 46import { VideoResolution, VideoPrivacy } from '../../../shared'
45import { VideoFileInstance, VideoFileModel } from './video-file-interface' 47import { VideoFileInstance, VideoFileModel } from './video-file-interface'
46 48
47import { addMethodsToModel, getSort } from '../utils' 49import { addMethodsToModel, getSort } from '../utils'
@@ -79,6 +81,7 @@ let getTruncatedDescription: VideoMethods.GetTruncatedDescription
79let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData 81let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
80let list: VideoMethods.List 82let list: VideoMethods.List
81let listForApi: VideoMethods.ListForApi 83let listForApi: VideoMethods.ListForApi
84let listUserVideosForApi: VideoMethods.ListUserVideosForApi
82let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID 85let loadByHostAndUUID: VideoMethods.LoadByHostAndUUID
83let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags 86let listOwnedAndPopulateAuthorAndTags: VideoMethods.ListOwnedAndPopulateAuthorAndTags
84let listOwnedByAuthor: VideoMethods.ListOwnedByAuthor 87let 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) {
501toFormattedDetailsJSON = function (this: VideoInstance) { 515toFormattedDetailsJSON = 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
771listUserVideosForApi = 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
749listForApi = function (start: number, count: number, sort: string) { 803listForApi = 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