aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-12-11 11:06:32 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-12-11 11:06:32 +0100
commitfada8d75550dc7365f7e18ee1569b9406251d660 (patch)
treedb9dc01c18693824f83fce5020f4c1f3ae7c0865 /server
parent492fd28167f770d79a430fc57451b5a9e075d8e7 (diff)
parentc2830fa8f84f61462098bf36add824f89436dfa9 (diff)
downloadPeerTube-fada8d75550dc7365f7e18ee1569b9406251d660.tar.gz
PeerTube-fada8d75550dc7365f7e18ee1569b9406251d660.tar.zst
PeerTube-fada8d75550dc7365f7e18ee1569b9406251d660.zip
Merge branch 'feature/design' into develop
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/index.ts139
-rw-r--r--server/controllers/client.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts6
-rw-r--r--server/helpers/custom-validators/videos.ts8
-rw-r--r--server/initializers/constants.ts13
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/initializers/migrations/0115-account-avatar.ts31
-rw-r--r--server/initializers/migrations/0120-video-null.ts47
-rw-r--r--server/lib/activitypub/process/misc.ts21
-rw-r--r--server/middlewares/index.ts1
-rw-r--r--server/middlewares/search.ts14
-rw-r--r--server/middlewares/validators/videos.ts11
-rw-r--r--server/models/account/account-interface.ts3
-rw-r--r--server/models/account/account.ts26
-rw-r--r--server/models/account/user.ts5
-rw-r--r--server/models/avatar/avatar-interface.ts16
-rw-r--r--server/models/avatar/avatar.ts24
-rw-r--r--server/models/avatar/index.ts1
-rw-r--r--server/models/index.ts1
-rw-r--r--server/models/video/video-interface.ts1
-rw-r--r--server/models/video/video.ts92
-rw-r--r--server/tests/api/check-params/videos.ts30
-rw-r--r--server/tests/api/follows.ts2
-rw-r--r--server/tests/api/multiple-servers.ts57
-rw-r--r--server/tests/api/services.ts4
-rw-r--r--server/tests/api/single-server.ts185
-rw-r--r--server/tests/api/users.ts8
-rw-r--r--server/tests/utils/servers.ts2
-rw-r--r--server/tests/utils/videos.ts22
29 files changed, 474 insertions, 300 deletions
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index e2798830e..63de662a7 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -15,6 +15,7 @@ import { getServerAccount } from '../../../helpers/utils'
15import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers' 15import { CONFIG, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_MIMETYPE_EXT, VIDEO_PRIVACIES } from '../../../initializers'
16import { database as db } from '../../../initializers/database' 16import { database as db } from '../../../initializers/database'
17import { sendAddVideo } from '../../../lib/activitypub/send/send-add' 17import { sendAddVideo } from '../../../lib/activitypub/send/send-add'
18import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create'
18import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update' 19import { sendUpdateVideo } from '../../../lib/activitypub/send/send-update'
19import { shareVideoByServer } from '../../../lib/activitypub/share' 20import { shareVideoByServer } from '../../../lib/activitypub/share'
20import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' 21import { getVideoActivityPubUrl } from '../../../lib/activitypub/url'
@@ -26,7 +27,6 @@ import {
26 authenticate, 27 authenticate,
27 paginationValidator, 28 paginationValidator,
28 setPagination, 29 setPagination,
29 setVideosSearch,
30 setVideosSort, 30 setVideosSort,
31 videosAddValidator, 31 videosAddValidator,
32 videosGetValidator, 32 videosGetValidator,
@@ -40,7 +40,6 @@ import { abuseVideoRouter } from './abuse'
40import { blacklistRouter } from './blacklist' 40import { blacklistRouter } from './blacklist'
41import { videoChannelRouter } from './channel' 41import { videoChannelRouter } from './channel'
42import { rateVideoRouter } from './rate' 42import { rateVideoRouter } from './rate'
43import { sendCreateViewToOrigin } from '../../../lib/activitypub/send/send-create'
44 43
45const videosRouter = express.Router() 44const videosRouter = express.Router()
46 45
@@ -84,6 +83,14 @@ videosRouter.get('/',
84 setPagination, 83 setPagination,
85 asyncMiddleware(listVideos) 84 asyncMiddleware(listVideos)
86) 85)
86videosRouter.get('/search',
87 videosSearchValidator,
88 paginationValidator,
89 videosSortValidator,
90 setVideosSort,
91 setPagination,
92 asyncMiddleware(searchVideos)
93)
87videosRouter.put('/:id', 94videosRouter.put('/:id',
88 authenticate, 95 authenticate,
89 asyncMiddleware(videosUpdateValidator), 96 asyncMiddleware(videosUpdateValidator),
@@ -115,16 +122,6 @@ videosRouter.delete('/:id',
115 asyncMiddleware(removeVideoRetryWrapper) 122 asyncMiddleware(removeVideoRetryWrapper)
116) 123)
117 124
118videosRouter.get('/search/:value',
119 videosSearchValidator,
120 paginationValidator,
121 videosSortValidator,
122 setVideosSort,
123 setPagination,
124 setVideosSearch,
125 asyncMiddleware(searchVideos)
126)
127
128// --------------------------------------------------------------------------- 125// ---------------------------------------------------------------------------
129 126
130export { 127export {
@@ -157,59 +154,64 @@ async function addVideoRetryWrapper (req: express.Request, res: express.Response
157 errorMessage: 'Cannot insert the video with many retries.' 154 errorMessage: 'Cannot insert the video with many retries.'
158 } 155 }
159 156
160 await retryTransactionWrapper(addVideo, options) 157 const video = await retryTransactionWrapper(addVideo, options)
161 158
162 // TODO : include Location of the new video -> 201 159 res.json({
163 res.type('json').status(204).end() 160 video: {
161 id: video.id,
162 uuid: video.uuid
163 }
164 }).end()
164} 165}
165 166
166async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { 167async function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) {
167 const videoInfo: VideoCreate = req.body 168 const videoInfo: VideoCreate = req.body
168 let videoUUID = ''
169 169
170 await db.sequelize.transaction(async t => { 170 // Prepare data so we don't block the transaction
171 const sequelizeOptions = { transaction: t } 171 const videoData = {
172 name: videoInfo.name,
173 remote: false,
174 extname: extname(videoPhysicalFile.filename),
175 category: videoInfo.category,
176 licence: videoInfo.licence,
177 language: videoInfo.language,
178 nsfw: videoInfo.nsfw,
179 description: videoInfo.description,
180 privacy: videoInfo.privacy,
181 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
182 channelId: res.locals.videoChannel.id
183 }
184 const video = db.Video.build(videoData)
185 video.url = getVideoActivityPubUrl(video)
172 186
173 const videoData = { 187 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename)
174 name: videoInfo.name, 188 const videoFileHeight = await getVideoFileHeight(videoFilePath)
175 remote: false,
176 extname: extname(videoPhysicalFile.filename),
177 category: videoInfo.category,
178 licence: videoInfo.licence,
179 language: videoInfo.language,
180 nsfw: videoInfo.nsfw,
181 description: videoInfo.description,
182 privacy: videoInfo.privacy,
183 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
184 channelId: res.locals.videoChannel.id
185 }
186 const video = db.Video.build(videoData)
187 video.url = getVideoActivityPubUrl(video)
188 189
189 const videoFilePath = join(CONFIG.STORAGE.VIDEOS_DIR, videoPhysicalFile.filename) 190 const videoFileData = {
190 const videoFileHeight = await getVideoFileHeight(videoFilePath) 191 extname: extname(videoPhysicalFile.filename),
192 resolution: videoFileHeight,
193 size: videoPhysicalFile.size
194 }
195 const videoFile = db.VideoFile.build(videoFileData)
196 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
197 const source = join(videoDir, videoPhysicalFile.filename)
198 const destination = join(videoDir, video.getVideoFilename(videoFile))
191 199
192 const videoFileData = { 200 await renamePromise(source, destination)
193 extname: extname(videoPhysicalFile.filename), 201 // This is important in case if there is another attempt in the retry process
194 resolution: videoFileHeight, 202 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
195 size: videoPhysicalFile.size
196 }
197 const videoFile = db.VideoFile.build(videoFileData)
198 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
199 const source = join(videoDir, videoPhysicalFile.filename)
200 const destination = join(videoDir, video.getVideoFilename(videoFile))
201 203
202 await renamePromise(source, destination) 204 const tasks = []
203 // This is important in case if there is another attempt in the retry process
204 videoPhysicalFile.filename = video.getVideoFilename(videoFile)
205 205
206 const tasks = [] 206 tasks.push(
207 video.createTorrentAndSetInfoHash(videoFile),
208 video.createThumbnail(videoFile),
209 video.createPreview(videoFile)
210 )
211 await Promise.all(tasks)
207 212
208 tasks.push( 213 return db.sequelize.transaction(async t => {
209 video.createTorrentAndSetInfoHash(videoFile), 214 const sequelizeOptions = { transaction: t }
210 video.createThumbnail(videoFile),
211 video.createPreview(videoFile)
212 )
213 215
214 if (CONFIG.TRANSCODING.ENABLED === true) { 216 if (CONFIG.TRANSCODING.ENABLED === true) {
215 // Put uuid because we don't have id auto incremented for now 217 // Put uuid because we don't have id auto incremented for now
@@ -217,21 +219,17 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
217 videoUUID: video.uuid 219 videoUUID: video.uuid
218 } 220 }
219 221
220 tasks.push( 222 await transcodingJobScheduler.createJob(t, 'videoFileOptimizer', dataInput)
221 transcodingJobScheduler.createJob(t, 'videoFileOptimizer', dataInput)
222 )
223 } 223 }
224 await Promise.all(tasks)
225 224
226 const videoCreated = await video.save(sequelizeOptions) 225 const videoCreated = await video.save(sequelizeOptions)
227 // Do not forget to add video channel information to the created video 226 // Do not forget to add video channel information to the created video
228 videoCreated.VideoChannel = res.locals.videoChannel 227 videoCreated.VideoChannel = res.locals.videoChannel
229 videoUUID = videoCreated.uuid
230 228
231 videoFile.videoId = video.id 229 videoFile.videoId = video.id
232
233 await videoFile.save(sequelizeOptions) 230 await videoFile.save(sequelizeOptions)
234 video.VideoFiles = [videoFile] 231
232 video.VideoFiles = [ videoFile ]
235 233
236 if (videoInfo.tags) { 234 if (videoInfo.tags) {
237 const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t) 235 const tagInstances = await db.Tag.findOrCreateTags(videoInfo.tags, t)
@@ -241,15 +239,17 @@ async function addVideo (req: express.Request, res: express.Response, videoPhysi
241 } 239 }
242 240
243 // Let transcoding job send the video to friends because the video file extension might change 241 // Let transcoding job send the video to friends because the video file extension might change
244 if (CONFIG.TRANSCODING.ENABLED === true) return undefined 242 if (CONFIG.TRANSCODING.ENABLED === true) return videoCreated
245 // Don't send video to remote servers, it is private 243 // Don't send video to remote servers, it is private
246 if (video.privacy === VideoPrivacy.PRIVATE) return undefined 244 if (video.privacy === VideoPrivacy.PRIVATE) return videoCreated
247 245
248 await sendAddVideo(video, t) 246 await sendAddVideo(video, t)
249 await shareVideoByServer(video, t) 247 await shareVideoByServer(video, t)
250 })
251 248
252 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID) 249 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
250
251 return videoCreated
252 })
253} 253}
254 254
255async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) { 255async function updateVideoRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -280,7 +280,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
280 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence) 280 if (videoInfoToUpdate.licence !== undefined) videoInstance.set('licence', videoInfoToUpdate.licence)
281 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language) 281 if (videoInfoToUpdate.language !== undefined) videoInstance.set('language', videoInfoToUpdate.language)
282 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw) 282 if (videoInfoToUpdate.nsfw !== undefined) videoInstance.set('nsfw', videoInfoToUpdate.nsfw)
283 if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', videoInfoToUpdate.privacy) 283 if (videoInfoToUpdate.privacy !== undefined) videoInstance.set('privacy', parseInt(videoInfoToUpdate.privacy.toString(), 10))
284 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) 284 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
285 285
286 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 286 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
@@ -298,9 +298,9 @@ async function updateVideo (req: express.Request, res: express.Response) {
298 } 298 }
299 299
300 // Video is not private anymore, send a create action to remote servers 300 // Video is not private anymore, send a create action to remote servers
301 if (wasPrivateVideo === true && videoInstance.privacy !== VideoPrivacy.PRIVATE) { 301 if (wasPrivateVideo === true && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE) {
302 await sendAddVideo(videoInstance, t) 302 await sendAddVideo(videoInstanceUpdated, t)
303 await shareVideoByServer(videoInstance, t) 303 await shareVideoByServer(videoInstanceUpdated, t)
304 } 304 }
305 }) 305 })
306 306
@@ -378,8 +378,7 @@ async function removeVideo (req: express.Request, res: express.Response) {
378 378
379async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 379async function searchVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
380 const resultList = await db.Video.searchAndPopulateAccountAndServerAndTags( 380 const resultList = await db.Video.searchAndPopulateAccountAndServerAndTags(
381 req.params.value, 381 req.query.search,
382 req.query.field,
383 req.query.start, 382 req.query.start,
384 req.query.count, 383 req.query.count,
385 req.query.sort 384 req.query.sort
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 64e5829ca..f474c4282 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -18,6 +18,7 @@ import { VideoInstance } from '../models'
18const clientsRouter = express.Router() 18const clientsRouter = express.Router()
19 19
20const distPath = join(root(), 'client', 'dist') 20const distPath = join(root(), 'client', 'dist')
21const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
21const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') 22const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
22const indexPath = join(distPath, 'index.html') 23const indexPath = join(distPath, 'index.html')
23 24
@@ -33,6 +34,7 @@ clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response,
33 34
34// Static HTML/CSS/JS client files 35// Static HTML/CSS/JS client files
35clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE })) 36clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE }))
37clientsRouter.use('/client/assets/images', express.static(assetsImagesPath, { maxAge: STATIC_MAX_AGE }))
36 38
37// 404 for static files not found 39// 404 for static files not found
38clientsRouter.use('/client/*', (req: express.Request, res: express.Response, next: express.NextFunction) => { 40clientsRouter.use('/client/*', (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index 12c672fd2..2ed2988f5 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -49,14 +49,14 @@ function isVideoTorrentObjectValid (video: any) {
49 isActivityPubVideoDurationValid(video.duration) && 49 isActivityPubVideoDurationValid(video.duration) &&
50 isUUIDValid(video.uuid) && 50 isUUIDValid(video.uuid) &&
51 setValidRemoteTags(video) && 51 setValidRemoteTags(video) &&
52 isRemoteIdentifierValid(video.category) && 52 (!video.category || isRemoteIdentifierValid(video.category)) &&
53 isRemoteIdentifierValid(video.licence) && 53 (!video.licence || isRemoteIdentifierValid(video.licence)) &&
54 (!video.language || isRemoteIdentifierValid(video.language)) && 54 (!video.language || isRemoteIdentifierValid(video.language)) &&
55 isVideoViewsValid(video.views) && 55 isVideoViewsValid(video.views) &&
56 isVideoNSFWValid(video.nsfw) && 56 isVideoNSFWValid(video.nsfw) &&
57 isDateValid(video.published) && 57 isDateValid(video.published) &&
58 isDateValid(video.updated) && 58 isDateValid(video.updated) &&
59 isRemoteVideoContentValid(video.mediaType, video.content) && 59 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
60 isRemoteVideoIconValid(video.icon) && 60 isRemoteVideoIconValid(video.icon) &&
61 setValidRemoteVideoUrls(video) && 61 setValidRemoteVideoUrls(video) &&
62 video.url.length !== 0 62 video.url.length !== 0
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index f13178c54..37fa8b08a 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -14,11 +14,11 @@ const VIDEOS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEOS
14const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES 14const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
15 15
16function isVideoCategoryValid (value: number) { 16function isVideoCategoryValid (value: number) {
17 return VIDEO_CATEGORIES[value] !== undefined 17 return value === null || VIDEO_CATEGORIES[value] !== undefined
18} 18}
19 19
20function isVideoLicenceValid (value: number) { 20function isVideoLicenceValid (value: number) {
21 return VIDEO_LICENCES[value] !== undefined 21 return value === null || VIDEO_LICENCES[value] !== undefined
22} 22}
23 23
24function isVideoLanguageValid (value: number) { 24function isVideoLanguageValid (value: number) {
@@ -38,7 +38,7 @@ function isVideoTruncatedDescriptionValid (value: string) {
38} 38}
39 39
40function isVideoDescriptionValid (value: string) { 40function isVideoDescriptionValid (value: string) {
41 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION) 41 return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION))
42} 42}
43 43
44function isVideoNameValid (value: string) { 44function isVideoNameValid (value: string) {
@@ -84,7 +84,7 @@ function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } |
84} 84}
85 85
86function isVideoPrivacyValid (value: string) { 86function isVideoPrivacyValid (value: string) {
87 return VIDEO_PRIVACIES[value] !== undefined 87 return validator.isInt(value + '') && VIDEO_PRIVACIES[value] !== undefined
88} 88}
89 89
90function isVideoFileInfoHashValid (value: string) { 90function isVideoFileInfoHashValid (value: string) {
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index e3d779456..7be7a5f95 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -14,7 +14,7 @@ import { FollowState } from '../../shared/models/accounts/follow.model'
14 14
15// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
16 16
17const LAST_MIGRATION_VERSION = 110 17const LAST_MIGRATION_VERSION = 120
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
@@ -25,11 +25,6 @@ const API_VERSION = 'v1'
25const PAGINATION_COUNT_DEFAULT = 15 25const PAGINATION_COUNT_DEFAULT = 15
26 26
27// Sortable columns per schema 27// Sortable columns per schema
28const SEARCHABLE_COLUMNS = {
29 VIDEOS: [ 'name', 'magnetUri', 'host', 'account', 'tags' ]
30}
31
32// Sortable columns per schema
33const SORTABLE_COLUMNS = { 28const SORTABLE_COLUMNS = {
34 USERS: [ 'id', 'username', 'createdAt' ], 29 USERS: [ 'id', 'username', 'createdAt' ],
35 JOBS: [ 'id', 'createdAt' ], 30 JOBS: [ 'id', 'createdAt' ],
@@ -60,6 +55,7 @@ const CONFIG = {
60 PASSWORD: config.get<string>('database.password') 55 PASSWORD: config.get<string>('database.password')
61 }, 56 },
62 STORAGE: { 57 STORAGE: {
58 AVATARS_DIR: join(root(), config.get<string>('storage.avatars')),
63 LOG_DIR: join(root(), config.get<string>('storage.logs')), 59 LOG_DIR: join(root(), config.get<string>('storage.logs')),
64 VIDEOS_DIR: join(root(), config.get<string>('storage.videos')), 60 VIDEOS_DIR: join(root(), config.get<string>('storage.videos')),
65 THUMBNAILS_DIR: join(root(), config.get<string>('storage.thumbnails')), 61 THUMBNAILS_DIR: join(root(), config.get<string>('storage.thumbnails')),
@@ -105,6 +101,9 @@ const CONFIG = {
105CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 101CONFIG.WEBSERVER.URL = CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
106CONFIG.WEBSERVER.HOST = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 102CONFIG.WEBSERVER.HOST = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
107 103
104const AVATARS_DIR = {
105 ACCOUNT: join(CONFIG.STORAGE.AVATARS_DIR, 'account')
106}
108// --------------------------------------------------------------------------- 107// ---------------------------------------------------------------------------
109 108
110const CONSTRAINTS_FIELDS = { 109const CONSTRAINTS_FIELDS = {
@@ -356,7 +355,7 @@ export {
356 PREVIEWS_SIZE, 355 PREVIEWS_SIZE,
357 REMOTE_SCHEME, 356 REMOTE_SCHEME,
358 FOLLOW_STATES, 357 FOLLOW_STATES,
359 SEARCHABLE_COLUMNS, 358 AVATARS_DIR,
360 SERVER_ACCOUNT_NAME, 359 SERVER_ACCOUNT_NAME,
361 PRIVATE_RSA_KEY_SIZE, 360 PRIVATE_RSA_KEY_SIZE,
362 SORTABLE_COLUMNS, 361 SORTABLE_COLUMNS,
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 90dbba5b9..bb95992e1 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -2,6 +2,7 @@ import { join } from 'path'
2import { flattenDepth } from 'lodash' 2import { flattenDepth } from 'lodash'
3require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string 3require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
4import * as Sequelize from 'sequelize' 4import * as Sequelize from 'sequelize'
5import { AvatarModel } from '../models/avatar'
5 6
6import { CONFIG } from './constants' 7import { CONFIG } from './constants'
7// Do not use barrel, we need to load database first 8// Do not use barrel, we need to load database first
@@ -36,6 +37,7 @@ export type PeerTubeDatabase = {
36 init?: (silent: boolean) => Promise<void>, 37 init?: (silent: boolean) => Promise<void>,
37 38
38 Application?: ApplicationModel, 39 Application?: ApplicationModel,
40 Avatar?: AvatarModel,
39 Account?: AccountModel, 41 Account?: AccountModel,
40 Job?: JobModel, 42 Job?: JobModel,
41 OAuthClient?: OAuthClientModel, 43 OAuthClient?: OAuthClientModel,
diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts
new file mode 100644
index 000000000..2b947ceda
--- /dev/null
+++ b/server/initializers/migrations/0115-account-avatar.ts
@@ -0,0 +1,31 @@
1import * as Sequelize from 'sequelize'
2import { PeerTubeDatabase } from '../database'
3
4async function up (utils: {
5 transaction: Sequelize.Transaction,
6 queryInterface: Sequelize.QueryInterface,
7 sequelize: Sequelize.Sequelize,
8 db: PeerTubeDatabase
9}): Promise<void> {
10 await utils.db.Avatar.sync()
11
12 const data = {
13 type: Sequelize.INTEGER,
14 allowNull: true,
15 references: {
16 model: 'Avatars',
17 key: 'id'
18 },
19 onDelete: 'CASCADE'
20 }
21 await utils.queryInterface.addColumn('Accounts', 'avatarId', data)
22}
23
24function down (options) {
25 throw new Error('Not implemented.')
26}
27
28export {
29 up,
30 down
31}
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts
new file mode 100644
index 000000000..9130d10ee
--- /dev/null
+++ b/server/initializers/migrations/0120-video-null.ts
@@ -0,0 +1,47 @@
1import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants'
3import { PeerTubeDatabase } from '../database'
4
5async function up (utils: {
6 transaction: Sequelize.Transaction,
7 queryInterface: Sequelize.QueryInterface,
8 sequelize: Sequelize.Sequelize,
9 db: PeerTubeDatabase
10}): Promise<void> {
11
12 {
13 const data = {
14 type: Sequelize.INTEGER,
15 allowNull: true,
16 defaultValue: null
17 }
18 await utils.queryInterface.changeColumn('Videos', 'licence', data)
19 }
20
21 {
22 const data = {
23 type: Sequelize.INTEGER,
24 allowNull: true,
25 defaultValue: null
26 }
27 await utils.queryInterface.changeColumn('Videos', 'category', data)
28 }
29
30 {
31 const data = {
32 type: Sequelize.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max),
33 allowNull: true,
34 defaultValue: null
35 }
36 await utils.queryInterface.changeColumn('Videos', 'description', data)
37 }
38}
39
40function down (options) {
41 throw new Error('Not implemented.')
42}
43
44export {
45 up,
46 down
47}
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index f20e588ab..0baa22c26 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -41,15 +41,30 @@ async function videoActivityObjectToDBAttributes (
41 language = parseInt(videoObject.language.identifier, 10) 41 language = parseInt(videoObject.language.identifier, 10)
42 } 42 }
43 43
44 let category = null
45 if (videoObject.category) {
46 category = parseInt(videoObject.category.identifier, 10)
47 }
48
49 let licence = null
50 if (videoObject.licence) {
51 licence = parseInt(videoObject.licence.identifier, 10)
52 }
53
54 let description = null
55 if (videoObject.content) {
56 description = videoObject.content
57 }
58
44 const videoData: VideoAttributes = { 59 const videoData: VideoAttributes = {
45 name: videoObject.name, 60 name: videoObject.name,
46 uuid: videoObject.uuid, 61 uuid: videoObject.uuid,
47 url: videoObject.id, 62 url: videoObject.id,
48 category: parseInt(videoObject.category.identifier, 10), 63 category,
49 licence: parseInt(videoObject.licence.identifier, 10), 64 licence,
50 language, 65 language,
66 description,
51 nsfw: videoObject.nsfw, 67 nsfw: videoObject.nsfw,
52 description: videoObject.content,
53 channelId: videoChannel.id, 68 channelId: videoChannel.id,
54 duration: parseInt(duration, 10), 69 duration: parseInt(duration, 10),
55 createdAt: new Date(videoObject.published), 70 createdAt: new Date(videoObject.published),
diff --git a/server/middlewares/index.ts b/server/middlewares/index.ts
index aafcad2d9..0cef26953 100644
--- a/server/middlewares/index.ts
+++ b/server/middlewares/index.ts
@@ -4,6 +4,5 @@ export * from './async'
4export * from './oauth' 4export * from './oauth'
5export * from './pagination' 5export * from './pagination'
6export * from './servers' 6export * from './servers'
7export * from './search'
8export * from './sort' 7export * from './sort'
9export * from './user-right' 8export * from './user-right'
diff --git a/server/middlewares/search.ts b/server/middlewares/search.ts
deleted file mode 100644
index 6fe83d25b..000000000
--- a/server/middlewares/search.ts
+++ /dev/null
@@ -1,14 +0,0 @@
1import 'express-validator'
2import * as express from 'express'
3
4function setVideosSearch (req: express.Request, res: express.Response, next: express.NextFunction) {
5 if (!req.query.field) req.query.field = 'name'
6
7 return next()
8}
9
10// ---------------------------------------------------------------------------
11
12export {
13 setVideosSearch
14}
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts
index f21680aa0..10625e41d 100644
--- a/server/middlewares/validators/videos.ts
+++ b/server/middlewares/validators/videos.ts
@@ -18,7 +18,7 @@ import {
18} from '../../helpers/custom-validators/videos' 18} from '../../helpers/custom-validators/videos'
19import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils' 19import { getDurationFromVideoFile } from '../../helpers/ffmpeg-utils'
20import { logger } from '../../helpers/logger' 20import { logger } from '../../helpers/logger'
21import { CONSTRAINTS_FIELDS, SEARCHABLE_COLUMNS } from '../../initializers' 21import { CONSTRAINTS_FIELDS } from '../../initializers'
22import { database as db } from '../../initializers/database' 22import { database as db } from '../../initializers/database'
23import { UserInstance } from '../../models/account/user-interface' 23import { UserInstance } from '../../models/account/user-interface'
24import { VideoInstance } from '../../models/video/video-interface' 24import { VideoInstance } from '../../models/video/video-interface'
@@ -31,11 +31,11 @@ const videosAddValidator = [
31 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 31 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
32 ), 32 ),
33 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), 33 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
34 body('category').custom(isVideoCategoryValid).withMessage('Should have a valid category'), 34 body('category').optional().custom(isVideoCategoryValid).withMessage('Should have a valid category'),
35 body('licence').custom(isVideoLicenceValid).withMessage('Should have a valid licence'), 35 body('licence').optional().custom(isVideoLicenceValid).withMessage('Should have a valid licence'),
36 body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'), 36 body('language').optional().custom(isVideoLanguageValid).withMessage('Should have a valid language'),
37 body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'), 37 body('nsfw').custom(isVideoNSFWValid).withMessage('Should have a valid NSFW attribute'),
38 body('description').custom(isVideoDescriptionValid).withMessage('Should have a valid description'), 38 body('description').optional().custom(isVideoDescriptionValid).withMessage('Should have a valid description'),
39 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'), 40 body('privacy').custom(isVideoPrivacyValid).withMessage('Should have correct video privacy'),
41 body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'), 41 body('tags').optional().custom(isVideoTagsValid).withMessage('Should have correct tags'),
@@ -172,8 +172,7 @@ const videosRemoveValidator = [
172] 172]
173 173
174const videosSearchValidator = [ 174const videosSearchValidator = [
175 param('value').not().isEmpty().withMessage('Should have a valid search'), 175 query('search').not().isEmpty().withMessage('Should have a valid search'),
176 query('field').optional().isIn(SEARCHABLE_COLUMNS.VIDEOS).withMessage('Should have correct searchable column'),
177 176
178 (req: express.Request, res: express.Response, next: express.NextFunction) => { 177 (req: express.Request, res: express.Response, next: express.NextFunction) => {
179 logger.debug('Checking videosSearch parameters', { parameters: req.params }) 178 logger.debug('Checking videosSearch parameters', { parameters: req.params })
diff --git a/server/models/account/account-interface.ts b/server/models/account/account-interface.ts
index b369766dc..46fe068e3 100644
--- a/server/models/account/account-interface.ts
+++ b/server/models/account/account-interface.ts
@@ -1,6 +1,7 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3import { Account as FormattedAccount, ActivityPubActor } from '../../../shared' 3import { Account as FormattedAccount, ActivityPubActor } from '../../../shared'
4import { AvatarInstance } from '../avatar'
4import { ServerInstance } from '../server/server-interface' 5import { ServerInstance } from '../server/server-interface'
5import { VideoChannelInstance } from '../video/video-channel-interface' 6import { VideoChannelInstance } from '../video/video-channel-interface'
6 7
@@ -51,6 +52,7 @@ export interface AccountAttributes {
51 serverId?: number 52 serverId?: number
52 userId?: number 53 userId?: number
53 applicationId?: number 54 applicationId?: number
55 avatarId?: number
54} 56}
55 57
56export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance<AccountAttributes> { 58export interface AccountInstance extends AccountClass, AccountAttributes, Sequelize.Instance<AccountAttributes> {
@@ -68,6 +70,7 @@ export interface AccountInstance extends AccountClass, AccountAttributes, Sequel
68 70
69 Server: ServerInstance 71 Server: ServerInstance
70 VideoChannels: VideoChannelInstance[] 72 VideoChannels: VideoChannelInstance[]
73 Avatar: AvatarInstance
71} 74}
72 75
73export interface AccountModel extends AccountClass, Sequelize.Model<AccountInstance, AccountAttributes> {} 76export interface AccountModel extends AccountClass, Sequelize.Model<AccountInstance, AccountAttributes> {}
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 61a88524c..8b0819f39 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -1,4 +1,6 @@
1import { join } from 'path'
1import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3import { Avatar } from '../../../shared/models/avatars/avatar.model'
2import { 4import {
3 activityPubContextify, 5 activityPubContextify,
4 isAccountFollowersCountValid, 6 isAccountFollowersCountValid,
@@ -8,6 +10,7 @@ import {
8 isUserUsernameValid 10 isUserUsernameValid
9} from '../../helpers' 11} from '../../helpers'
10import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 12import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
13import { AVATARS_DIR } from '../../initializers'
11import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' 14import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
12import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' 15import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete'
13import { addMethodsToModel } from '../utils' 16import { addMethodsToModel } from '../utils'
@@ -252,6 +255,14 @@ function associate (models) {
252 as: 'followers', 255 as: 'followers',
253 onDelete: 'cascade' 256 onDelete: 'cascade'
254 }) 257 })
258
259 Account.hasOne(models.Avatar, {
260 foreignKey: {
261 name: 'avatarId',
262 allowNull: true
263 },
264 onDelete: 'cascade'
265 })
255} 266}
256 267
257function afterDestroy (account: AccountInstance) { 268function afterDestroy (account: AccountInstance) {
@@ -265,6 +276,15 @@ function afterDestroy (account: AccountInstance) {
265toFormattedJSON = function (this: AccountInstance) { 276toFormattedJSON = function (this: AccountInstance) {
266 let host = CONFIG.WEBSERVER.HOST 277 let host = CONFIG.WEBSERVER.HOST
267 let score: number 278 let score: number
279 let avatar: Avatar = null
280
281 if (this.Avatar) {
282 avatar = {
283 path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
284 createdAt: this.Avatar.createdAt,
285 updatedAt: this.Avatar.updatedAt
286 }
287 }
268 288
269 if (this.Server) { 289 if (this.Server) {
270 host = this.Server.host 290 host = this.Server.host
@@ -273,11 +293,15 @@ toFormattedJSON = function (this: AccountInstance) {
273 293
274 const json = { 294 const json = {
275 id: this.id, 295 id: this.id,
296 uuid: this.uuid,
276 host, 297 host,
277 score, 298 score,
278 name: this.name, 299 name: this.name,
300 followingCount: this.followingCount,
301 followersCount: this.followersCount,
279 createdAt: this.createdAt, 302 createdAt: this.createdAt,
280 updatedAt: this.updatedAt 303 updatedAt: this.updatedAt,
304 avatar
281 } 305 }
282 306
283 return json 307 return json
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 8f7c9b013..3705947c0 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -157,10 +157,7 @@ toFormattedJSON = function (this: UserInstance) {
157 roleLabel: USER_ROLE_LABELS[this.role], 157 roleLabel: USER_ROLE_LABELS[this.role],
158 videoQuota: this.videoQuota, 158 videoQuota: this.videoQuota,
159 createdAt: this.createdAt, 159 createdAt: this.createdAt,
160 account: { 160 account: this.Account.toFormattedJSON()
161 id: this.Account.id,
162 uuid: this.Account.uuid
163 }
164 } 161 }
165 162
166 if (Array.isArray(this.Account.VideoChannels) === true) { 163 if (Array.isArray(this.Account.VideoChannels) === true) {
diff --git a/server/models/avatar/avatar-interface.ts b/server/models/avatar/avatar-interface.ts
new file mode 100644
index 000000000..4af2b87b7
--- /dev/null
+++ b/server/models/avatar/avatar-interface.ts
@@ -0,0 +1,16 @@
1import * as Sequelize from 'sequelize'
2
3export namespace AvatarMethods {}
4
5export interface AvatarClass {}
6
7export interface AvatarAttributes {
8 filename: string
9}
10
11export interface AvatarInstance extends AvatarClass, AvatarAttributes, Sequelize.Instance<AvatarAttributes> {
12 createdAt: Date
13 updatedAt: Date
14}
15
16export interface AvatarModel extends AvatarClass, Sequelize.Model<AvatarInstance, AvatarAttributes> {}
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts
new file mode 100644
index 000000000..96308fd5f
--- /dev/null
+++ b/server/models/avatar/avatar.ts
@@ -0,0 +1,24 @@
1import * as Sequelize from 'sequelize'
2import { addMethodsToModel } from '../utils'
3import { AvatarAttributes, AvatarInstance } from './avatar-interface'
4
5let Avatar: Sequelize.Model<AvatarInstance, AvatarAttributes>
6
7export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) {
8 Avatar = sequelize.define<AvatarInstance, AvatarAttributes>('Avatar',
9 {
10 filename: {
11 type: DataTypes.STRING,
12 allowNull: false
13 }
14 },
15 {}
16 )
17
18 const classMethods = []
19 addMethodsToModel(Avatar, classMethods)
20
21 return Avatar
22}
23
24// ------------------------------ Statics ------------------------------
diff --git a/server/models/avatar/index.ts b/server/models/avatar/index.ts
new file mode 100644
index 000000000..877aed1ce
--- /dev/null
+++ b/server/models/avatar/index.ts
@@ -0,0 +1 @@
export * from './avatar-interface'
diff --git a/server/models/index.ts b/server/models/index.ts
index 65faa5294..fedd97dd1 100644
--- a/server/models/index.ts
+++ b/server/models/index.ts
@@ -1,4 +1,5 @@
1export * from './application' 1export * from './application'
2export * from './avatar'
2export * from './job' 3export * from './job'
3export * from './oauth' 4export * from './oauth'
4export * from './server' 5export * from './server'
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index be140de86..2a63350af 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -50,7 +50,6 @@ export namespace VideoMethods {
50 export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> > 50 export type ListUserVideosForApi = (userId: number, start: number, count: number, sort: string) => Bluebird< ResultList<VideoInstance> >
51 export type SearchAndPopulateAccountAndServerAndTags = ( 51 export type SearchAndPopulateAccountAndServerAndTags = (
52 value: string, 52 value: string,
53 field: string,
54 start: number, 53 start: number,
55 count: number, 54 count: number,
56 sort: string 55 sort: string
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index f3469c1de..d46fdeebe 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -104,7 +104,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
104 }, 104 },
105 category: { 105 category: {
106 type: DataTypes.INTEGER, 106 type: DataTypes.INTEGER,
107 allowNull: false, 107 allowNull: true,
108 defaultValue: null,
108 validate: { 109 validate: {
109 categoryValid: value => { 110 categoryValid: value => {
110 const res = isVideoCategoryValid(value) 111 const res = isVideoCategoryValid(value)
@@ -114,7 +115,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
114 }, 115 },
115 licence: { 116 licence: {
116 type: DataTypes.INTEGER, 117 type: DataTypes.INTEGER,
117 allowNull: false, 118 allowNull: true,
118 defaultValue: null, 119 defaultValue: null,
119 validate: { 120 validate: {
120 licenceValid: value => { 121 licenceValid: value => {
@@ -126,6 +127,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
126 language: { 127 language: {
127 type: DataTypes.INTEGER, 128 type: DataTypes.INTEGER,
128 allowNull: true, 129 allowNull: true,
130 defaultValue: null,
129 validate: { 131 validate: {
130 languageValid: value => { 132 languageValid: value => {
131 const res = isVideoLanguageValid(value) 133 const res = isVideoLanguageValid(value)
@@ -155,7 +157,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
155 }, 157 },
156 description: { 158 description: {
157 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max), 159 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max),
158 allowNull: false, 160 allowNull: true,
161 defaultValue: null,
159 validate: { 162 validate: {
160 descriptionValid: value => { 163 descriptionValid: value => {
161 const res = isVideoDescriptionValid(value) 164 const res = isVideoDescriptionValid(value)
@@ -486,7 +489,7 @@ toFormattedJSON = function (this: VideoInstance) {
486 description: this.getTruncatedDescription(), 489 description: this.getTruncatedDescription(),
487 serverHost, 490 serverHost,
488 isLocal: this.isOwned(), 491 isLocal: this.isOwned(),
489 account: this.VideoChannel.Account.name, 492 accountName: this.VideoChannel.Account.name,
490 duration: this.duration, 493 duration: this.duration,
491 views: this.views, 494 views: this.views,
492 likes: this.likes, 495 likes: this.likes,
@@ -514,6 +517,7 @@ toFormattedDetailsJSON = function (this: VideoInstance) {
514 privacy: this.privacy, 517 privacy: this.privacy,
515 descriptionPath: this.getDescriptionPath(), 518 descriptionPath: this.getDescriptionPath(),
516 channel: this.VideoChannel.toFormattedJSON(), 519 channel: this.VideoChannel.toFormattedJSON(),
520 account: this.VideoChannel.Account.toFormattedJSON(),
517 files: [] 521 files: []
518 } 522 }
519 523
@@ -560,6 +564,22 @@ toActivityPubObject = function (this: VideoInstance) {
560 } 564 }
561 } 565 }
562 566
567 let category
568 if (this.category) {
569 category = {
570 identifier: this.category + '',
571 name: this.getCategoryLabel()
572 }
573 }
574
575 let licence
576 if (this.licence) {
577 licence = {
578 identifier: this.licence + '',
579 name: this.getLicenceLabel()
580 }
581 }
582
563 let likesObject 583 let likesObject
564 let dislikesObject 584 let dislikesObject
565 585
@@ -631,14 +651,8 @@ toActivityPubObject = function (this: VideoInstance) {
631 duration: 'PT' + this.duration + 'S', 651 duration: 'PT' + this.duration + 'S',
632 uuid: this.uuid, 652 uuid: this.uuid,
633 tag, 653 tag,
634 category: { 654 category,
635 identifier: this.category + '', 655 licence,
636 name: this.getCategoryLabel()
637 },
638 licence: {
639 identifier: this.licence + '',
640 name: this.getLicenceLabel()
641 },
642 language, 656 language,
643 views: this.views, 657 views: this.views,
644 nsfw: this.nsfw, 658 nsfw: this.nsfw,
@@ -663,6 +677,8 @@ toActivityPubObject = function (this: VideoInstance) {
663} 677}
664 678
665getTruncatedDescription = function (this: VideoInstance) { 679getTruncatedDescription = function (this: VideoInstance) {
680 if (!this.description) return null
681
666 const options = { 682 const options = {
667 length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max 683 length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max
668 } 684 }
@@ -753,8 +769,6 @@ getDescriptionPath = function (this: VideoInstance) {
753 769
754getCategoryLabel = function (this: VideoInstance) { 770getCategoryLabel = function (this: VideoInstance) {
755 let categoryLabel = VIDEO_CATEGORIES[this.category] 771 let categoryLabel = VIDEO_CATEGORIES[this.category]
756
757 // Maybe our server is not up to date and there are new categories since our version
758 if (!categoryLabel) categoryLabel = 'Misc' 772 if (!categoryLabel) categoryLabel = 'Misc'
759 773
760 return categoryLabel 774 return categoryLabel
@@ -762,15 +776,12 @@ getCategoryLabel = function (this: VideoInstance) {
762 776
763getLicenceLabel = function (this: VideoInstance) { 777getLicenceLabel = function (this: VideoInstance) {
764 let licenceLabel = VIDEO_LICENCES[this.licence] 778 let licenceLabel = VIDEO_LICENCES[this.licence]
765
766 // Maybe our server is not up to date and there are new licences since our version
767 if (!licenceLabel) licenceLabel = 'Unknown' 779 if (!licenceLabel) licenceLabel = 'Unknown'
768 780
769 return licenceLabel 781 return licenceLabel
770} 782}
771 783
772getLanguageLabel = function (this: VideoInstance) { 784getLanguageLabel = function (this: VideoInstance) {
773 // Language is an optional attribute
774 let languageLabel = VIDEO_LANGUAGES[this.language] 785 let languageLabel = VIDEO_LANGUAGES[this.language]
775 if (!languageLabel) languageLabel = 'Unknown' 786 if (!languageLabel) languageLabel = 'Unknown'
776 787
@@ -1070,7 +1081,7 @@ loadByUUIDAndPopulateAccountAndServerAndTags = function (uuid: string) {
1070 return Video.findOne(options) 1081 return Video.findOne(options)
1071} 1082}
1072 1083
1073searchAndPopulateAccountAndServerAndTags = function (value: string, field: string, start: number, count: number, sort: string) { 1084searchAndPopulateAccountAndServerAndTags = function (value: string, start: number, count: number, sort: string) {
1074 const serverInclude: Sequelize.IncludeOptions = { 1085 const serverInclude: Sequelize.IncludeOptions = {
1075 model: Video['sequelize'].models.Server, 1086 model: Video['sequelize'].models.Server,
1076 required: false 1087 required: false
@@ -1099,33 +1110,24 @@ searchAndPopulateAccountAndServerAndTags = function (value: string, field: strin
1099 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ] 1110 order: [ getSort(sort), [ Video['sequelize'].models.Tag, 'name', 'ASC' ] ]
1100 } 1111 }
1101 1112
1102 if (field === 'tags') { 1113 // TODO: search on tags too
1103 const escapedValue = Video['sequelize'].escape('%' + value + '%') 1114 // const escapedValue = Video['sequelize'].escape('%' + value + '%')
1104 query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal( 1115 // query.where['id'][Sequelize.Op.in] = Video['sequelize'].literal(
1105 `(SELECT "VideoTags"."videoId" 1116 // `(SELECT "VideoTags"."videoId"
1106 FROM "Tags" 1117 // FROM "Tags"
1107 INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" 1118 // INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId"
1108 WHERE name ILIKE ${escapedValue} 1119 // WHERE name ILIKE ${escapedValue}
1109 )` 1120 // )`
1110 ) 1121 // )
1111 } else if (field === 'host') { 1122
1112 // FIXME: Include our server? (not stored in the database) 1123 // TODO: search on account too
1113 serverInclude.where = { 1124 // accountInclude.where = {
1114 host: { 1125 // name: {
1115 [Sequelize.Op.iLike]: '%' + value + '%' 1126 // [Sequelize.Op.iLike]: '%' + value + '%'
1116 } 1127 // }
1117 } 1128 // }
1118 serverInclude.required = true 1129 query.where['name'] = {
1119 } else if (field === 'account') { 1130 [Sequelize.Op.iLike]: '%' + value + '%'
1120 accountInclude.where = {
1121 name: {
1122 [Sequelize.Op.iLike]: '%' + value + '%'
1123 }
1124 }
1125 } else {
1126 query.where[field] = {
1127 [Sequelize.Op.iLike]: '%' + value + '%'
1128 }
1129 } 1131 }
1130 1132
1131 query.include = [ 1133 query.include = [
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 2962f5640..0aaa6e7c9 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -189,14 +189,6 @@ describe('Test videos API validator', function () {
189 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 189 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
190 }) 190 })
191 191
192 it('Should fail without a category', async function () {
193 const fields = getCompleteVideoUploadAttributes()
194 delete fields.category
195
196 const attaches = getVideoUploadAttaches
197 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
198 })
199
200 it('Should fail with a bad category', async function () { 192 it('Should fail with a bad category', async function () {
201 const fields = getCompleteVideoUploadAttributes() 193 const fields = getCompleteVideoUploadAttributes()
202 fields.category = 125 194 fields.category = 125
@@ -205,14 +197,6 @@ describe('Test videos API validator', function () {
205 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 197 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
206 }) 198 })
207 199
208 it('Should fail without a licence', async function () {
209 const fields = getCompleteVideoUploadAttributes()
210 delete fields.licence
211
212 const attaches = getVideoUploadAttaches()
213 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
214 })
215
216 it('Should fail with a bad licence', async function () { 200 it('Should fail with a bad licence', async function () {
217 const fields = getCompleteVideoUploadAttributes() 201 const fields = getCompleteVideoUploadAttributes()
218 fields.licence = 125 202 fields.licence = 125
@@ -245,14 +229,6 @@ describe('Test videos API validator', function () {
245 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 229 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
246 }) 230 })
247 231
248 it('Should fail without description', async function () {
249 const fields = getCompleteVideoUploadAttributes()
250 delete fields.description
251
252 const attaches = getVideoUploadAttaches()
253 await makePostUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
254 })
255
256 it('Should fail with a long description', async function () { 232 it('Should fail with a long description', async function () {
257 const fields = getCompleteVideoUploadAttributes() 233 const fields = getCompleteVideoUploadAttributes()
258 fields.description = 'my super description which is very very very very very very very very very very very very long'.repeat(35) 234 fields.description = 'my super description which is very very very very very very very very very very very very long'.repeat(35)
@@ -345,7 +321,7 @@ describe('Test videos API validator', function () {
345 token: server.accessToken, 321 token: server.accessToken,
346 fields, 322 fields,
347 attaches, 323 attaches,
348 statusCodeExpected: 204 324 statusCodeExpected: 200
349 }) 325 })
350 326
351 attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.mp4') 327 attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.mp4')
@@ -355,7 +331,7 @@ describe('Test videos API validator', function () {
355 token: server.accessToken, 331 token: server.accessToken,
356 fields, 332 fields,
357 attaches, 333 attaches,
358 statusCodeExpected: 204 334 statusCodeExpected: 200
359 }) 335 })
360 336
361 attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.ogv') 337 attaches.videofile = join(__dirname, '..', 'fixtures', 'video_short.ogv')
@@ -365,7 +341,7 @@ describe('Test videos API validator', function () {
365 token: server.accessToken, 341 token: server.accessToken,
366 fields, 342 fields,
367 attaches, 343 attaches,
368 statusCodeExpected: 204 344 statusCodeExpected: 200
369 }) 345 })
370 }) 346 })
371 }) 347 })
diff --git a/server/tests/api/follows.ts b/server/tests/api/follows.ts
index aadae3cce..dcb4c8bd9 100644
--- a/server/tests/api/follows.ts
+++ b/server/tests/api/follows.ts
@@ -227,7 +227,7 @@ describe('Test follows', function () {
227 expect(videoDetails.nsfw).to.be.ok 227 expect(videoDetails.nsfw).to.be.ok
228 expect(videoDetails.description).to.equal('my super description') 228 expect(videoDetails.description).to.equal('my super description')
229 expect(videoDetails.serverHost).to.equal('localhost:9003') 229 expect(videoDetails.serverHost).to.equal('localhost:9003')
230 expect(videoDetails.account).to.equal('root') 230 expect(videoDetails.accountName).to.equal('root')
231 expect(videoDetails.likes).to.equal(1) 231 expect(videoDetails.likes).to.equal(1)
232 expect(videoDetails.dislikes).to.equal(1) 232 expect(videoDetails.dislikes).to.equal(1)
233 expect(videoDetails.isLocal).to.be.false 233 expect(videoDetails.isLocal).to.be.false
diff --git a/server/tests/api/multiple-servers.ts b/server/tests/api/multiple-servers.ts
index c80ded862..2f17f017a 100644
--- a/server/tests/api/multiple-servers.ts
+++ b/server/tests/api/multiple-servers.ts
@@ -2,6 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { join } from 'path'
6import * as request from 'supertest'
5 7
6import { 8import {
7 dateIsValid, 9 dateIsValid,
@@ -111,13 +113,14 @@ describe('Test multiple servers', function () {
111 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) 113 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
112 expect(dateIsValid(video.createdAt)).to.be.true 114 expect(dateIsValid(video.createdAt)).to.be.true
113 expect(dateIsValid(video.updatedAt)).to.be.true 115 expect(dateIsValid(video.updatedAt)).to.be.true
114 expect(video.account).to.equal('root') 116 expect(video.accountName).to.equal('root')
115 117
116 const res2 = await getVideo(server.url, video.uuid) 118 const res2 = await getVideo(server.url, video.uuid)
117 const videoDetails = res2.body 119 const videoDetails = res2.body
118 120
119 expect(videoDetails.channel.name).to.equal('my channel') 121 expect(videoDetails.channel.name).to.equal('my channel')
120 expect(videoDetails.channel.description).to.equal('super channel') 122 expect(videoDetails.channel.description).to.equal('super channel')
123 expect(videoDetails.account.name).to.equal('root')
121 expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true 124 expect(dateIsValid(videoDetails.channel.createdAt)).to.be.true
122 expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true 125 expect(dateIsValid(videoDetails.channel.updatedAt)).to.be.true
123 expect(videoDetails.files).to.have.lengthOf(1) 126 expect(videoDetails.files).to.have.lengthOf(1)
@@ -201,7 +204,7 @@ describe('Test multiple servers', function () {
201 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) 204 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
202 expect(dateIsValid(video.createdAt)).to.be.true 205 expect(dateIsValid(video.createdAt)).to.be.true
203 expect(dateIsValid(video.updatedAt)).to.be.true 206 expect(dateIsValid(video.updatedAt)).to.be.true
204 expect(video.account).to.equal('user1') 207 expect(video.accountName).to.equal('user1')
205 208
206 if (server.url !== 'http://localhost:9002') { 209 if (server.url !== 'http://localhost:9002') {
207 expect(video.isLocal).to.be.false 210 expect(video.isLocal).to.be.false
@@ -316,7 +319,7 @@ describe('Test multiple servers', function () {
316 expect(video1.serverHost).to.equal('localhost:9003') 319 expect(video1.serverHost).to.equal('localhost:9003')
317 expect(video1.duration).to.equal(5) 320 expect(video1.duration).to.equal(5)
318 expect(video1.tags).to.deep.equal([ 'tag1p3' ]) 321 expect(video1.tags).to.deep.equal([ 'tag1p3' ])
319 expect(video1.account).to.equal('root') 322 expect(video1.accountName).to.equal('root')
320 expect(dateIsValid(video1.createdAt)).to.be.true 323 expect(dateIsValid(video1.createdAt)).to.be.true
321 expect(dateIsValid(video1.updatedAt)).to.be.true 324 expect(dateIsValid(video1.updatedAt)).to.be.true
322 325
@@ -342,7 +345,7 @@ describe('Test multiple servers', function () {
342 expect(video2.serverHost).to.equal('localhost:9003') 345 expect(video2.serverHost).to.equal('localhost:9003')
343 expect(video2.duration).to.equal(5) 346 expect(video2.duration).to.equal(5)
344 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) 347 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
345 expect(video2.account).to.equal('root') 348 expect(video2.accountName).to.equal('root')
346 expect(dateIsValid(video2.createdAt)).to.be.true 349 expect(dateIsValid(video2.createdAt)).to.be.true
347 expect(dateIsValid(video2.updatedAt)).to.be.true 350 expect(dateIsValid(video2.updatedAt)).to.be.true
348 351
@@ -690,7 +693,7 @@ describe('Test multiple servers', function () {
690 expect(baseVideo.licence).to.equal(video.licence) 693 expect(baseVideo.licence).to.equal(video.licence)
691 expect(baseVideo.category).to.equal(video.category) 694 expect(baseVideo.category).to.equal(video.category)
692 expect(baseVideo.nsfw).to.equal(video.nsfw) 695 expect(baseVideo.nsfw).to.equal(video.nsfw)
693 expect(baseVideo.account).to.equal(video.account) 696 expect(baseVideo.accountName).to.equal(video.accountName)
694 expect(baseVideo.tags).to.deep.equal(video.tags) 697 expect(baseVideo.tags).to.deep.equal(video.tags)
695 } 698 }
696 }) 699 })
@@ -706,6 +709,50 @@ describe('Test multiple servers', function () {
706 }) 709 })
707 }) 710 })
708 711
712 describe('With minimum parameters', function () {
713 it('Should upload and propagate the video', async function () {
714 this.timeout(50000)
715
716 const path = '/api/v1/videos/upload'
717
718 const req = request(servers[1].url)
719 .post(path)
720 .set('Accept', 'application/json')
721 .set('Authorization', 'Bearer ' + servers[1].accessToken)
722 .field('name', 'minimum parameters')
723 .field('privacy', '1')
724 .field('nsfw', 'false')
725 .field('channelId', '1')
726
727 const filePath = join(__dirname, '..', 'api', 'fixtures', 'video_short.webm')
728
729 await req.attach('videofile', filePath)
730 .expect(200)
731
732 await wait(25000)
733
734 for (const server of servers) {
735 const res = await getVideosList(server.url)
736 const video = res.body.data.find(v => v.name === 'minimum parameters')
737
738 expect(video.name).to.equal('minimum parameters')
739 expect(video.category).to.equal(null)
740 expect(video.categoryLabel).to.equal('Misc')
741 expect(video.licence).to.equal(null)
742 expect(video.licenceLabel).to.equal('Unknown')
743 expect(video.language).to.equal(null)
744 expect(video.languageLabel).to.equal('Unknown')
745 expect(video.nsfw).to.not.be.ok
746 expect(video.description).to.equal(null)
747 expect(video.serverHost).to.equal('localhost:9002')
748 expect(video.accountName).to.equal('root')
749 expect(video.tags).to.deep.equal([ ])
750 expect(dateIsValid(video.createdAt)).to.be.true
751 expect(dateIsValid(video.updatedAt)).to.be.true
752 }
753 })
754 })
755
709 after(async function () { 756 after(async function () {
710 killallServers(servers) 757 killallServers(servers)
711 758
diff --git a/server/tests/api/services.ts b/server/tests/api/services.ts
index 8d96ccc5e..4d480c305 100644
--- a/server/tests/api/services.ts
+++ b/server/tests/api/services.ts
@@ -46,7 +46,7 @@ describe('Test services', function () {
46 46
47 expect(res.body.html).to.equal(expectedHtml) 47 expect(res.body.html).to.equal(expectedHtml)
48 expect(res.body.title).to.equal(server.video.name) 48 expect(res.body.title).to.equal(server.video.name)
49 expect(res.body.author_name).to.equal(server.video.account) 49 expect(res.body.author_name).to.equal(server.video.accountName)
50 expect(res.body.width).to.equal(560) 50 expect(res.body.width).to.equal(560)
51 expect(res.body.height).to.equal(315) 51 expect(res.body.height).to.equal(315)
52 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) 52 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
@@ -66,7 +66,7 @@ describe('Test services', function () {
66 66
67 expect(res.body.html).to.equal(expectedHtml) 67 expect(res.body.html).to.equal(expectedHtml)
68 expect(res.body.title).to.equal(server.video.name) 68 expect(res.body.title).to.equal(server.video.name)
69 expect(res.body.author_name).to.equal(server.video.account) 69 expect(res.body.author_name).to.equal(server.video.accountName)
70 expect(res.body.height).to.equal(50) 70 expect(res.body.height).to.equal(50)
71 expect(res.body.width).to.equal(50) 71 expect(res.body.width).to.equal(50)
72 expect(res.body).to.not.have.property('thumbnail_url') 72 expect(res.body).to.not.have.property('thumbnail_url')
diff --git a/server/tests/api/single-server.ts b/server/tests/api/single-server.ts
index 041d13225..174fb480d 100644
--- a/server/tests/api/single-server.ts
+++ b/server/tests/api/single-server.ts
@@ -1,40 +1,40 @@
1/* tslint:disable:no-unused-expression */ 1/* tslint:disable:no-unused-expression */
2 2
3import * as chai from 'chai'
3import { keyBy } from 'lodash' 4import { keyBy } from 'lodash'
4import { join } from 'path'
5import 'mocha' 5import 'mocha'
6import * as chai from 'chai' 6import { join } from 'path'
7const expect = chai.expect
8
9import { 7import {
10 ServerInfo,
11 flushTests,
12 runServer,
13 uploadVideo,
14 getVideosList,
15 rateVideo,
16 removeVideo,
17 wait,
18 setAccessTokensToServers,
19 searchVideo,
20 killallServers,
21 dateIsValid, 8 dateIsValid,
9 flushTests,
10 getVideo,
22 getVideoCategories, 11 getVideoCategories,
23 getVideoLicences,
24 getVideoLanguages, 12 getVideoLanguages,
13 getVideoLicences,
25 getVideoPrivacies, 14 getVideoPrivacies,
26 testVideoImage, 15 getVideosList,
27 webtorrentAdd,
28 getVideo,
29 readdirPromise,
30 getVideosListPagination, 16 getVideosListPagination,
31 searchVideoWithPagination,
32 getVideosListSort, 17 getVideosListSort,
18 killallServers,
19 rateVideo,
20 readdirPromise,
21 removeVideo,
22 runServer,
23 searchVideo,
24 searchVideoWithPagination,
33 searchVideoWithSort, 25 searchVideoWithSort,
34 updateVideo 26 ServerInfo,
27 setAccessTokensToServers,
28 testVideoImage,
29 updateVideo,
30 uploadVideo,
31 wait,
32 webtorrentAdd
35} from '../utils' 33} from '../utils'
36import { viewVideo } from '../utils/videos' 34import { viewVideo } from '../utils/videos'
37 35
36const expect = chai.expect
37
38describe('Test a single server', function () { 38describe('Test a single server', function () {
39 let server: ServerInfo = null 39 let server: ServerInfo = null
40 let videoId = -1 40 let videoId = -1
@@ -103,7 +103,10 @@ describe('Test a single server', function () {
103 licence: 6, 103 licence: 6,
104 tags: [ 'tag1', 'tag2', 'tag3' ] 104 tags: [ 'tag1', 'tag2', 'tag3' ]
105 } 105 }
106 await uploadVideo(server.url, server.accessToken, videoAttributes) 106 const res = await uploadVideo(server.url, server.accessToken, videoAttributes)
107 expect(res.body.video).to.not.be.undefined
108 expect(res.body.video.id).to.equal(1)
109 expect(res.body.video.uuid).to.have.length.above(5)
107 }) 110 })
108 111
109 it('Should seed the uploaded video', async function () { 112 it('Should seed the uploaded video', async function () {
@@ -127,7 +130,7 @@ describe('Test a single server', function () {
127 expect(video.nsfw).to.be.ok 130 expect(video.nsfw).to.be.ok
128 expect(video.description).to.equal('my super description') 131 expect(video.description).to.equal('my super description')
129 expect(video.serverHost).to.equal('localhost:9001') 132 expect(video.serverHost).to.equal('localhost:9001')
130 expect(video.account).to.equal('root') 133 expect(video.accountName).to.equal('root')
131 expect(video.isLocal).to.be.true 134 expect(video.isLocal).to.be.true
132 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 135 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
133 expect(dateIsValid(video.createdAt)).to.be.true 136 expect(dateIsValid(video.createdAt)).to.be.true
@@ -176,7 +179,7 @@ describe('Test a single server', function () {
176 expect(video.nsfw).to.be.ok 179 expect(video.nsfw).to.be.ok
177 expect(video.description).to.equal('my super description') 180 expect(video.description).to.equal('my super description')
178 expect(video.serverHost).to.equal('localhost:9001') 181 expect(video.serverHost).to.equal('localhost:9001')
179 expect(video.account).to.equal('root') 182 expect(video.accountName).to.equal('root')
180 expect(video.isLocal).to.be.true 183 expect(video.isLocal).to.be.true
181 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 184 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
182 expect(dateIsValid(video.createdAt)).to.be.true 185 expect(dateIsValid(video.createdAt)).to.be.true
@@ -225,7 +228,7 @@ describe('Test a single server', function () {
225 expect(video.views).to.equal(3) 228 expect(video.views).to.equal(3)
226 }) 229 })
227 230
228 it('Should search the video by name by default', async function () { 231 it('Should search the video by name', async function () {
229 const res = await searchVideo(server.url, 'my') 232 const res = await searchVideo(server.url, 'my')
230 233
231 expect(res.body.total).to.equal(1) 234 expect(res.body.total).to.equal(1)
@@ -243,7 +246,7 @@ describe('Test a single server', function () {
243 expect(video.nsfw).to.be.ok 246 expect(video.nsfw).to.be.ok
244 expect(video.description).to.equal('my super description') 247 expect(video.description).to.equal('my super description')
245 expect(video.serverHost).to.equal('localhost:9001') 248 expect(video.serverHost).to.equal('localhost:9001')
246 expect(video.account).to.equal('root') 249 expect(video.accountName).to.equal('root')
247 expect(video.isLocal).to.be.true 250 expect(video.isLocal).to.be.true
248 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 251 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
249 expect(dateIsValid(video.createdAt)).to.be.true 252 expect(dateIsValid(video.createdAt)).to.be.true
@@ -279,35 +282,36 @@ describe('Test a single server', function () {
279 // }) 282 // })
280 // }) 283 // })
281 284
282 it('Should search the video by tag', async function () { 285 // Not implemented yet
283 const res = await searchVideo(server.url, 'tag1', 'tags') 286 // it('Should search the video by tag', async function () {
284 287 // const res = await searchVideo(server.url, 'tag1')
285 expect(res.body.total).to.equal(1) 288 //
286 expect(res.body.data).to.be.an('array') 289 // expect(res.body.total).to.equal(1)
287 expect(res.body.data.length).to.equal(1) 290 // expect(res.body.data).to.be.an('array')
288 291 // expect(res.body.data.length).to.equal(1)
289 const video = res.body.data[0] 292 //
290 expect(video.name).to.equal('my super name') 293 // const video = res.body.data[0]
291 expect(video.category).to.equal(2) 294 // expect(video.name).to.equal('my super name')
292 expect(video.categoryLabel).to.equal('Films') 295 // expect(video.category).to.equal(2)
293 expect(video.licence).to.equal(6) 296 // expect(video.categoryLabel).to.equal('Films')
294 expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives') 297 // expect(video.licence).to.equal(6)
295 expect(video.language).to.equal(3) 298 // expect(video.licenceLabel).to.equal('Attribution - Non Commercial - No Derivatives')
296 expect(video.languageLabel).to.equal('Mandarin') 299 // expect(video.language).to.equal(3)
297 expect(video.nsfw).to.be.ok 300 // expect(video.languageLabel).to.equal('Mandarin')
298 expect(video.description).to.equal('my super description') 301 // expect(video.nsfw).to.be.ok
299 expect(video.serverHost).to.equal('localhost:9001') 302 // expect(video.description).to.equal('my super description')
300 expect(video.account).to.equal('root') 303 // expect(video.serverHost).to.equal('localhost:9001')
301 expect(video.isLocal).to.be.true 304 // expect(video.accountName).to.equal('root')
302 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 305 // expect(video.isLocal).to.be.true
303 expect(dateIsValid(video.createdAt)).to.be.true 306 // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
304 expect(dateIsValid(video.updatedAt)).to.be.true 307 // expect(dateIsValid(video.createdAt)).to.be.true
305 308 // expect(dateIsValid(video.updatedAt)).to.be.true
306 const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath) 309 //
307 expect(test).to.equal(true) 310 // const test = await testVideoImage(server.url, 'video_short.webm', video.thumbnailPath)
308 }) 311 // expect(test).to.equal(true)
312 // })
309 313
310 it('Should not find a search by name by default', async function () { 314 it('Should not find a search by name', async function () {
311 const res = await searchVideo(server.url, 'hello') 315 const res = await searchVideo(server.url, 'hello')
312 316
313 expect(res.body.total).to.equal(0) 317 expect(res.body.total).to.equal(0)
@@ -315,21 +319,23 @@ describe('Test a single server', function () {
315 expect(res.body.data.length).to.equal(0) 319 expect(res.body.data.length).to.equal(0)
316 }) 320 })
317 321
318 it('Should not find a search by author', async function () { 322 // Not implemented yet
319 const res = await searchVideo(server.url, 'hello', 'account') 323 // it('Should not find a search by author', async function () {
320 324 // const res = await searchVideo(server.url, 'hello')
321 expect(res.body.total).to.equal(0) 325 //
322 expect(res.body.data).to.be.an('array') 326 // expect(res.body.total).to.equal(0)
323 expect(res.body.data.length).to.equal(0) 327 // expect(res.body.data).to.be.an('array')
324 }) 328 // expect(res.body.data.length).to.equal(0)
325 329 // })
326 it('Should not find a search by tag', async function () { 330 //
327 const res = await searchVideo(server.url, 'hello', 'tags') 331 // Not implemented yet
328 332 // it('Should not find a search by tag', async function () {
329 expect(res.body.total).to.equal(0) 333 // const res = await searchVideo(server.url, 'hello')
330 expect(res.body.data).to.be.an('array') 334 //
331 expect(res.body.data.length).to.equal(0) 335 // expect(res.body.total).to.equal(0)
332 }) 336 // expect(res.body.data).to.be.an('array')
337 // expect(res.body.data.length).to.equal(0)
338 // })
333 339
334 it('Should remove the video', async function () { 340 it('Should remove the video', async function () {
335 await removeVideo(server.url, server.accessToken, videoId) 341 await removeVideo(server.url, server.accessToken, videoId)
@@ -357,7 +363,7 @@ describe('Test a single server', function () {
357 'video_short1.webm', 'video_short2.webm', 'video_short3.webm' 363 'video_short1.webm', 'video_short2.webm', 'video_short3.webm'
358 ] 364 ]
359 365
360 // const tasks: Promise<any>[] = [] 366 const tasks: Promise<any>[] = []
361 for (const video of videos) { 367 for (const video of videos) {
362 const videoAttributes = { 368 const videoAttributes = {
363 name: video + ' name', 369 name: video + ' name',
@@ -371,13 +377,10 @@ describe('Test a single server', function () {
371 } 377 }
372 378
373 const p = uploadVideo(server.url, server.accessToken, videoAttributes) 379 const p = uploadVideo(server.url, server.accessToken, videoAttributes)
374 await p 380 tasks.push(p)
375 } 381 }
376 // FIXME: concurrent uploads does not work :( 382
377 // tasks.push(p) 383 await Promise.all(tasks)
378 // }
379 //
380 // await Promise.all(tasks)
381 }) 384 })
382 385
383 it('Should have the correct durations', async function () { 386 it('Should have the correct durations', async function () {
@@ -443,7 +446,7 @@ describe('Test a single server', function () {
443 }) 446 })
444 447
445 it('Should search the first video', async function () { 448 it('Should search the first video', async function () {
446 const res = await searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, 'name') 449 const res = await searchVideoWithPagination(server.url, 'webm', 0, 1, 'name')
447 450
448 const videos = res.body.data 451 const videos = res.body.data
449 expect(res.body.total).to.equal(4) 452 expect(res.body.total).to.equal(4)
@@ -452,7 +455,7 @@ describe('Test a single server', function () {
452 }) 455 })
453 456
454 it('Should search the last two videos', async function () { 457 it('Should search the last two videos', async function () {
455 const res = await searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, 'name') 458 const res = await searchVideoWithPagination(server.url, 'webm', 2, 2, 'name')
456 459
457 const videos = res.body.data 460 const videos = res.body.data
458 expect(res.body.total).to.equal(4) 461 expect(res.body.total).to.equal(4)
@@ -462,20 +465,21 @@ describe('Test a single server', function () {
462 }) 465 })
463 466
464 it('Should search all the webm videos', async function () { 467 it('Should search all the webm videos', async function () {
465 const res = await searchVideoWithPagination(server.url, 'webm', 'name', 0, 15) 468 const res = await searchVideoWithPagination(server.url, 'webm', 0, 15)
466 469
467 const videos = res.body.data 470 const videos = res.body.data
468 expect(res.body.total).to.equal(4) 471 expect(res.body.total).to.equal(4)
469 expect(videos.length).to.equal(4) 472 expect(videos.length).to.equal(4)
470 }) 473 })
471 474
472 it('Should search all the root author videos', async function () { 475 // Not implemented yet
473 const res = await searchVideoWithPagination(server.url, 'root', 'account', 0, 15) 476 // it('Should search all the root author videos', async function () {
474 477 // const res = await searchVideoWithPagination(server.url, 'root', 0, 15)
475 const videos = res.body.data 478 //
476 expect(res.body.total).to.equal(6) 479 // const videos = res.body.data
477 expect(videos.length).to.equal(6) 480 // expect(res.body.total).to.equal(6)
478 }) 481 // expect(videos.length).to.equal(6)
482 // })
479 483
480 // Not implemented yet 484 // Not implemented yet
481 // it('Should search all the 9001 port videos', async function () { 485 // it('Should search all the 9001 port videos', async function () {
@@ -559,7 +563,8 @@ describe('Test a single server', function () {
559 expect(video.nsfw).to.be.ok 563 expect(video.nsfw).to.be.ok
560 expect(video.description).to.equal('my super description updated') 564 expect(video.description).to.equal('my super description updated')
561 expect(video.serverHost).to.equal('localhost:9001') 565 expect(video.serverHost).to.equal('localhost:9001')
562 expect(video.account).to.equal('root') 566 expect(video.accountName).to.equal('root')
567 expect(video.account.name).to.equal('root')
563 expect(video.isLocal).to.be.true 568 expect(video.isLocal).to.be.true
564 expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ]) 569 expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ])
565 expect(dateIsValid(video.createdAt)).to.be.true 570 expect(dateIsValid(video.createdAt)).to.be.true
@@ -608,7 +613,7 @@ describe('Test a single server', function () {
608 expect(video.nsfw).to.be.ok 613 expect(video.nsfw).to.be.ok
609 expect(video.description).to.equal('my super description updated') 614 expect(video.description).to.equal('my super description updated')
610 expect(video.serverHost).to.equal('localhost:9001') 615 expect(video.serverHost).to.equal('localhost:9001')
611 expect(video.account).to.equal('root') 616 expect(video.accountName).to.equal('root')
612 expect(video.isLocal).to.be.true 617 expect(video.isLocal).to.be.true
613 expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) 618 expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ])
614 expect(dateIsValid(video.createdAt)).to.be.true 619 expect(dateIsValid(video.createdAt)).to.be.true
@@ -648,7 +653,7 @@ describe('Test a single server', function () {
648 expect(video.nsfw).to.be.ok 653 expect(video.nsfw).to.be.ok
649 expect(video.description).to.equal('hello everybody') 654 expect(video.description).to.equal('hello everybody')
650 expect(video.serverHost).to.equal('localhost:9001') 655 expect(video.serverHost).to.equal('localhost:9001')
651 expect(video.account).to.equal('root') 656 expect(video.accountName).to.equal('root')
652 expect(video.isLocal).to.be.true 657 expect(video.isLocal).to.be.true
653 expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ]) 658 expect(video.tags).to.deep.equal([ 'supertag', 'tag1', 'tag2' ])
654 expect(dateIsValid(video.createdAt)).to.be.true 659 expect(dateIsValid(video.createdAt)).to.be.true
diff --git a/server/tests/api/users.ts b/server/tests/api/users.ts
index 33646e84f..b3163b1e1 100644
--- a/server/tests/api/users.ts
+++ b/server/tests/api/users.ts
@@ -113,11 +113,11 @@ describe('Test users', function () {
113 113
114 it('Should upload the video with the correct token', async function () { 114 it('Should upload the video with the correct token', async function () {
115 const videoAttributes = {} 115 const videoAttributes = {}
116 await uploadVideo(server.url, accessToken, videoAttributes, 204) 116 await uploadVideo(server.url, accessToken, videoAttributes)
117 const res = await getVideosList(server.url) 117 const res = await getVideosList(server.url)
118 const video = res.body.data[ 0 ] 118 const video = res.body.data[ 0 ]
119 119
120 expect(video.account) 120 expect(video.accountName)
121 .to 121 .to
122 .equal('root') 122 .equal('root')
123 videoId = video.id 123 videoId = video.id
@@ -125,7 +125,7 @@ describe('Test users', function () {
125 125
126 it('Should upload the video again with the correct token', async function () { 126 it('Should upload the video again with the correct token', async function () {
127 const videoAttributes = {} 127 const videoAttributes = {}
128 await uploadVideo(server.url, accessToken, videoAttributes, 204) 128 await uploadVideo(server.url, accessToken, videoAttributes)
129 }) 129 })
130 130
131 it('Should retrieve a video rating', async function () { 131 it('Should retrieve a video rating', async function () {
@@ -487,7 +487,7 @@ describe('Test users', function () {
487 .equal(1) 487 .equal(1)
488 488
489 const video = res.body.data[ 0 ] 489 const video = res.body.data[ 0 ]
490 expect(video.account) 490 expect(video.accountName)
491 .to 491 .to
492 .equal('root') 492 .equal('root')
493 }) 493 })
diff --git a/server/tests/utils/servers.ts b/server/tests/utils/servers.ts
index faa2f19ff..8340fbc18 100644
--- a/server/tests/utils/servers.ts
+++ b/server/tests/utils/servers.ts
@@ -24,7 +24,7 @@ interface ServerInfo {
24 id: number 24 id: number
25 uuid: string 25 uuid: string
26 name: string 26 name: string
27 account: string 27 accountName: string
28 } 28 }
29 29
30 remoteVideo?: { 30 remoteVideo?: {
diff --git a/server/tests/utils/videos.ts b/server/tests/utils/videos.ts
index 73a9f1a0a..fb758cf29 100644
--- a/server/tests/utils/videos.ts
+++ b/server/tests/utils/videos.ts
@@ -145,26 +145,25 @@ function removeVideo (url: string, token: string, id: number, expectedStatus = 2
145 .expect(expectedStatus) 145 .expect(expectedStatus)
146} 146}
147 147
148function searchVideo (url: string, search: string, field?: string) { 148function searchVideo (url: string, search: string) {
149 const path = '/api/v1/videos' 149 const path = '/api/v1/videos'
150 const req = request(url) 150 const req = request(url)
151 .get(path + '/search/' + search) 151 .get(path + '/search')
152 .set('Accept', 'application/json') 152 .query({ search })
153 153 .set('Accept', 'application/json')
154 if (field) req.query({ field })
155 154
156 return req.expect(200) 155 return req.expect(200)
157 .expect('Content-Type', /json/) 156 .expect('Content-Type', /json/)
158} 157}
159 158
160function searchVideoWithPagination (url: string, search: string, field: string, start: number, count: number, sort?: string) { 159function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
161 const path = '/api/v1/videos' 160 const path = '/api/v1/videos'
162 161
163 const req = request(url) 162 const req = request(url)
164 .get(path + '/search/' + search) 163 .get(path + '/search')
165 .query({ start }) 164 .query({ start })
165 .query({ search })
166 .query({ count }) 166 .query({ count })
167 .query({ field })
168 167
169 if (sort) req.query({ sort }) 168 if (sort) req.query({ sort })
170 169
@@ -177,7 +176,8 @@ function searchVideoWithSort (url: string, search: string, sort: string) {
177 const path = '/api/v1/videos' 176 const path = '/api/v1/videos'
178 177
179 return request(url) 178 return request(url)
180 .get(path + '/search/' + search) 179 .get(path + '/search')
180 .query({ search })
181 .query({ sort }) 181 .query({ sort })
182 .set('Accept', 'application/json') 182 .set('Accept', 'application/json')
183 .expect(200) 183 .expect(200)
@@ -201,7 +201,7 @@ async function testVideoImage (url: string, imageName: string, imagePath: string
201 } 201 }
202} 202}
203 203
204async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 204) { 204async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
205 const path = '/api/v1/videos/upload' 205 const path = '/api/v1/videos/upload'
206 let defaultChannelId = '1' 206 let defaultChannelId = '1'
207 207