aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-10-30 10:16:27 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-10-30 10:16:27 +0100
commit9567011bf01f36c7f796ac1e0f1fb12c71635e53 (patch)
treebec3ed173767cff031ed0a84231d6dd50e792569
parent757f0da370a992cf07afd20d3829b2748c76cc15 (diff)
downloadPeerTube-9567011bf01f36c7f796ac1e0f1fb12c71635e53.tar.gz
PeerTube-9567011bf01f36c7f796ac1e0f1fb12c71635e53.tar.zst
PeerTube-9567011bf01f36c7f796ac1e0f1fb12c71635e53.zip
Add lazy description on server
-rw-r--r--server/controllers/api/remote/videos.ts4
-rw-r--r--server/controllers/api/videos/index.ts21
-rw-r--r--server/helpers/custom-validators/remote/videos.ts4
-rw-r--r--server/helpers/custom-validators/videos.ts5
-rw-r--r--server/initializers/constants.ts5
-rw-r--r--server/initializers/database.ts9
-rw-r--r--server/initializers/migrations/0090-videos-description.ts25
-rw-r--r--server/lib/friends.ts19
-rw-r--r--server/models/video/video-interface.ts4
-rw-r--r--server/models/video/video.ts86
-rw-r--r--server/tests/api/index.ts1
-rw-r--r--server/tests/api/video-description.ts86
-rw-r--r--server/tests/utils/videos.ts9
-rw-r--r--shared/models/pods/remote-video/remote-video-create-request.model.ts2
-rw-r--r--shared/models/pods/remote-video/remote-video-update-request.model.ts2
-rw-r--r--shared/models/videos/video.model.ts1
16 files changed, 217 insertions, 66 deletions
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts
index d0febdd4b..3ecc62ada 100644
--- a/server/controllers/api/remote/videos.ts
+++ b/server/controllers/api/remote/videos.ts
@@ -258,7 +258,7 @@ async function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod
258 licence: videoToCreateData.licence, 258 licence: videoToCreateData.licence,
259 language: videoToCreateData.language, 259 language: videoToCreateData.language,
260 nsfw: videoToCreateData.nsfw, 260 nsfw: videoToCreateData.nsfw,
261 description: videoToCreateData.description, 261 description: videoToCreateData.truncatedDescription,
262 channelId: videoChannel.id, 262 channelId: videoChannel.id,
263 duration: videoToCreateData.duration, 263 duration: videoToCreateData.duration,
264 createdAt: videoToCreateData.createdAt, 264 createdAt: videoToCreateData.createdAt,
@@ -327,7 +327,7 @@ async function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData
327 videoInstance.set('licence', videoAttributesToUpdate.licence) 327 videoInstance.set('licence', videoAttributesToUpdate.licence)
328 videoInstance.set('language', videoAttributesToUpdate.language) 328 videoInstance.set('language', videoAttributesToUpdate.language)
329 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) 329 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
330 videoInstance.set('description', videoAttributesToUpdate.description) 330 videoInstance.set('description', videoAttributesToUpdate.truncatedDescription)
331 videoInstance.set('duration', videoAttributesToUpdate.duration) 331 videoInstance.set('duration', videoAttributesToUpdate.duration)
332 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) 332 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
333 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) 333 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 9e233a8cc..49f0e4630 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -16,7 +16,8 @@ import {
16 quickAndDirtyUpdateVideoToFriends, 16 quickAndDirtyUpdateVideoToFriends,
17 addVideoToFriends, 17 addVideoToFriends,
18 updateVideoToFriends, 18 updateVideoToFriends,
19 JobScheduler 19 JobScheduler,
20 fetchRemoteDescription
20} from '../../../lib' 21} from '../../../lib'
21import { 22import {
22 authenticate, 23 authenticate,
@@ -102,6 +103,11 @@ videosRouter.post('/upload',
102 videosAddValidator, 103 videosAddValidator,
103 asyncMiddleware(addVideoRetryWrapper) 104 asyncMiddleware(addVideoRetryWrapper)
104) 105)
106
107videosRouter.get('/:id/description',
108 videosGetValidator,
109 asyncMiddleware(getVideoDescription)
110)
105videosRouter.get('/:id', 111videosRouter.get('/:id',
106 videosGetValidator, 112 videosGetValidator,
107 getVideo 113 getVideo
@@ -328,6 +334,19 @@ function getVideo (req: express.Request, res: express.Response) {
328 return res.json(videoInstance.toFormattedDetailsJSON()) 334 return res.json(videoInstance.toFormattedDetailsJSON())
329} 335}
330 336
337async function getVideoDescription (req: express.Request, res: express.Response) {
338 const videoInstance = res.locals.video
339 let description = ''
340
341 if (videoInstance.isOwned()) {
342 description = videoInstance.description
343 } else {
344 description = await fetchRemoteDescription(videoInstance)
345 }
346
347 return res.json({ description })
348}
349
331async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 350async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
332 const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort) 351 const resultList = await db.Video.listForApi(req.query.start, req.query.count, req.query.sort)
333 352
diff --git a/server/helpers/custom-validators/remote/videos.ts b/server/helpers/custom-validators/remote/videos.ts
index a9ca36fe8..e0ffba679 100644
--- a/server/helpers/custom-validators/remote/videos.ts
+++ b/server/helpers/custom-validators/remote/videos.ts
@@ -19,7 +19,7 @@ import {
19 isRemoteVideoLicenceValid, 19 isRemoteVideoLicenceValid,
20 isRemoteVideoLanguageValid, 20 isRemoteVideoLanguageValid,
21 isVideoNSFWValid, 21 isVideoNSFWValid,
22 isVideoDescriptionValid, 22 isVideoTruncatedDescriptionValid,
23 isVideoDurationValid, 23 isVideoDurationValid,
24 isVideoFileInfoHashValid, 24 isVideoFileInfoHashValid,
25 isVideoNameValid, 25 isVideoNameValid,
@@ -112,7 +112,7 @@ function isCommonVideoAttributesValid (video: any) {
112 isRemoteVideoLicenceValid(video.licence) && 112 isRemoteVideoLicenceValid(video.licence) &&
113 isRemoteVideoLanguageValid(video.language) && 113 isRemoteVideoLanguageValid(video.language) &&
114 isVideoNSFWValid(video.nsfw) && 114 isVideoNSFWValid(video.nsfw) &&
115 isVideoDescriptionValid(video.description) && 115 isVideoTruncatedDescriptionValid(video.truncatedDescription) &&
116 isVideoDurationValid(video.duration) && 116 isVideoDurationValid(video.duration) &&
117 isVideoNameValid(video.name) && 117 isVideoNameValid(video.name) &&
118 isVideoTagsValid(video.tags) && 118 isVideoTagsValid(video.tags) &&
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 11b085b78..5b9102275 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -54,6 +54,10 @@ function isVideoNSFWValid (value: any) {
54 return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value)) 54 return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
55} 55}
56 56
57function isVideoTruncatedDescriptionValid (value: string) {
58 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION)
59}
60
57function isVideoDescriptionValid (value: string) { 61function isVideoDescriptionValid (value: string) {
58 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION) 62 return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)
59} 63}
@@ -173,6 +177,7 @@ export {
173 isVideoLicenceValid, 177 isVideoLicenceValid,
174 isVideoLanguageValid, 178 isVideoLanguageValid,
175 isVideoNSFWValid, 179 isVideoNSFWValid,
180 isVideoTruncatedDescriptionValid,
176 isVideoDescriptionValid, 181 isVideoDescriptionValid,
177 isVideoDurationValid, 182 isVideoDurationValid,
178 isVideoFileInfoHashValid, 183 isVideoFileInfoHashValid,
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 6dc9737d2..adccb9f41 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -15,7 +15,7 @@ import {
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
17 17
18const LAST_MIGRATION_VERSION = 85 18const LAST_MIGRATION_VERSION = 90
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
@@ -122,7 +122,8 @@ const CONSTRAINTS_FIELDS = {
122 }, 122 },
123 VIDEOS: { 123 VIDEOS: {
124 NAME: { min: 3, max: 120 }, // Length 124 NAME: { min: 3, max: 120 }, // Length
125 DESCRIPTION: { min: 3, max: 250 }, // Length 125 TRUNCATED_DESCRIPTION: { min: 3, max: 250 }, // Length
126 DESCRIPTION: { min: 3, max: 3000 }, // Length
126 EXTNAME: [ '.mp4', '.ogv', '.webm' ], 127 EXTNAME: [ '.mp4', '.ogv', '.webm' ],
127 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 128 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
128 DURATION: { min: 1, max: 7200 }, // Number 129 DURATION: { min: 1, max: 7200 }, // Number
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index dfad01581..141566c3a 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -84,9 +84,14 @@ database.init = async (silent: boolean) => {
84 const filePaths = await getModelFiles(modelDirectory) 84 const filePaths = await getModelFiles(modelDirectory)
85 85
86 for (const filePath of filePaths) { 86 for (const filePath of filePaths) {
87 const model = sequelize.import(filePath) 87 try {
88 const model = sequelize.import(filePath)
88 89
89 database[model['name']] = model 90 database[model['name']] = model
91 } catch (err) {
92 logger.error('Cannot import database model %s.', filePath, err)
93 process.exit(0)
94 }
90 } 95 }
91 96
92 for (const modelName of Object.keys(database)) { 97 for (const modelName of Object.keys(database)) {
diff --git a/server/initializers/migrations/0090-videos-description.ts b/server/initializers/migrations/0090-videos-description.ts
new file mode 100644
index 000000000..6f98dcade
--- /dev/null
+++ b/server/initializers/migrations/0090-videos-description.ts
@@ -0,0 +1,25 @@
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.STRING(3000),
13 allowNull: false
14 }
15 await q.changeColumn('Videos', 'description', data)
16}
17
18function down (options) {
19 throw new Error('Not implemented.')
20}
21
22export {
23 up,
24 down
25}
diff --git a/server/lib/friends.ts b/server/lib/friends.ts
index 55cbb55b9..5c9baef47 100644
--- a/server/lib/friends.ts
+++ b/server/lib/friends.ts
@@ -349,6 +349,24 @@ function fetchRemotePreview (video: VideoInstance) {
349 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path) 349 return request.get(REMOTE_SCHEME.HTTP + '://' + host + path)
350} 350}
351 351
352function fetchRemoteDescription (video: VideoInstance) {
353 const host = video.VideoChannel.Author.Pod.host
354 const path = video.getDescriptionPath()
355
356 const requestOptions = {
357 url: REMOTE_SCHEME.HTTP + '://' + host + path,
358 json: true
359 }
360
361 return new Promise<string>((res, rej) => {
362 request.get(requestOptions, (err, response, body) => {
363 if (err) return rej(err)
364
365 return res(body.description ? body.description : '')
366 })
367 })
368}
369
352async function removeFriend (pod: PodInstance) { 370async function removeFriend (pod: PodInstance) {
353 const requestParams = { 371 const requestParams = {
354 method: 'POST' as 'POST', 372 method: 'POST' as 'POST',
@@ -407,6 +425,7 @@ export {
407 getRequestVideoEventScheduler, 425 getRequestVideoEventScheduler,
408 fetchRemotePreview, 426 fetchRemotePreview,
409 addVideoChannelToFriends, 427 addVideoChannelToFriends,
428 fetchRemoteDescription,
410 updateVideoChannelToFriends, 429 updateVideoChannelToFriends,
411 removeVideoChannelToFriends 430 removeVideoChannelToFriends
412} 431}
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts
index 2afbaf09e..3a7bc82a4 100644
--- a/server/models/video/video-interface.ts
+++ b/server/models/video/video-interface.ts
@@ -38,6 +38,8 @@ export namespace VideoMethods {
38 export type GetEmbedPath = (this: VideoInstance) => string 38 export type GetEmbedPath = (this: VideoInstance) => string
39 export type GetThumbnailPath = (this: VideoInstance) => string 39 export type GetThumbnailPath = (this: VideoInstance) => string
40 export type GetPreviewPath = (this: VideoInstance) => string 40 export type GetPreviewPath = (this: VideoInstance) => string
41 export type GetDescriptionPath = (this: VideoInstance) => string
42 export type GetTruncatedDescription = (this: VideoInstance) => string
41 43
42 // Return thumbnail name 44 // Return thumbnail name
43 export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> 45 export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string>
@@ -135,6 +137,8 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In
135 transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile 137 transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile
136 getOriginalFileHeight: VideoMethods.GetOriginalFileHeight 138 getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
137 getEmbedPath: VideoMethods.GetEmbedPath 139 getEmbedPath: VideoMethods.GetEmbedPath
140 getDescriptionPath: VideoMethods.GetDescriptionPath
141 getTruncatedDescription : VideoMethods.GetTruncatedDescription
138 142
139 setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> 143 setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string>
140 addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> 144 addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string>
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 27f59f3a9..1877c506a 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -6,7 +6,7 @@ import * as parseTorrent from 'parse-torrent'
6import { join } from 'path' 6import { join } from 'path'
7import * as Sequelize from 'sequelize' 7import * as Sequelize from 'sequelize'
8import * as Promise from 'bluebird' 8import * as Promise from 'bluebird'
9import { maxBy } from 'lodash' 9import { maxBy, truncate } from 'lodash'
10 10
11import { TagInstance } from './tag-interface' 11import { TagInstance } from './tag-interface'
12import { 12import {
@@ -35,7 +35,10 @@ import {
35 VIDEO_CATEGORIES, 35 VIDEO_CATEGORIES,
36 VIDEO_LICENCES, 36 VIDEO_LICENCES,
37 VIDEO_LANGUAGES, 37 VIDEO_LANGUAGES,
38 THUMBNAILS_SIZE 38 THUMBNAILS_SIZE,
39 PREVIEWS_SIZE,
40 CONSTRAINTS_FIELDS,
41 API_VERSION
39} from '../../initializers' 42} from '../../initializers'
40import { removeVideoToFriends } from '../../lib' 43import { removeVideoToFriends } from '../../lib'
41import { VideoResolution } from '../../../shared' 44import { VideoResolution } from '../../../shared'
@@ -48,7 +51,6 @@ import {
48 51
49 VideoMethods 52 VideoMethods
50} from './video-interface' 53} from './video-interface'
51import { PREVIEWS_SIZE } from '../../initializers/constants'
52 54
53let Video: Sequelize.Model<VideoInstance, VideoAttributes> 55let Video: Sequelize.Model<VideoInstance, VideoAttributes>
54let getOriginalFile: VideoMethods.GetOriginalFile 56let getOriginalFile: VideoMethods.GetOriginalFile
@@ -71,6 +73,8 @@ let getVideoFilePath: VideoMethods.GetVideoFilePath
71let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash 73let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash
72let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight 74let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight
73let getEmbedPath: VideoMethods.GetEmbedPath 75let getEmbedPath: VideoMethods.GetEmbedPath
76let getDescriptionPath: VideoMethods.GetDescriptionPath
77let getTruncatedDescription: VideoMethods.GetTruncatedDescription
74 78
75let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData 79let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData
76let list: VideoMethods.List 80let list: VideoMethods.List
@@ -153,7 +157,7 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
153 } 157 }
154 }, 158 },
155 description: { 159 description: {
156 type: DataTypes.STRING, 160 type: DataTypes.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max),
157 allowNull: false, 161 allowNull: false,
158 validate: { 162 validate: {
159 descriptionValid: value => { 163 descriptionValid: value => {
@@ -276,7 +280,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da
276 optimizeOriginalVideofile, 280 optimizeOriginalVideofile,
277 transcodeOriginalVideofile, 281 transcodeOriginalVideofile,
278 getOriginalFileHeight, 282 getOriginalFileHeight,
279 getEmbedPath 283 getEmbedPath,
284 getTruncatedDescription,
285 getDescriptionPath
280 ] 286 ]
281 addMethodsToModel(Video, classMethods, instanceMethods) 287 addMethodsToModel(Video, classMethods, instanceMethods)
282 288
@@ -473,7 +479,7 @@ toFormattedJSON = function (this: VideoInstance) {
473 language: this.language, 479 language: this.language,
474 languageLabel, 480 languageLabel,
475 nsfw: this.nsfw, 481 nsfw: this.nsfw,
476 description: this.description, 482 description: this.getTruncatedDescription(),
477 podHost, 483 podHost,
478 isLocal: this.isOwned(), 484 isLocal: this.isOwned(),
479 author: this.VideoChannel.Author.name, 485 author: this.VideoChannel.Author.name,
@@ -493,59 +499,17 @@ toFormattedJSON = function (this: VideoInstance) {
493} 499}
494 500
495toFormattedDetailsJSON = function (this: VideoInstance) { 501toFormattedDetailsJSON = function (this: VideoInstance) {
496 let podHost 502 const formattedJson = this.toFormattedJSON()
497
498 if (this.VideoChannel.Author.Pod) {
499 podHost = this.VideoChannel.Author.Pod.host
500 } else {
501 // It means it's our video
502 podHost = CONFIG.WEBSERVER.HOST
503 }
504 503
505 // Maybe our pod is not up to date and there are new categories since our version 504 const detailsJson = {
506 let categoryLabel = VIDEO_CATEGORIES[this.category] 505 descriptionPath: this.getDescriptionPath(),
507 if (!categoryLabel) categoryLabel = 'Misc'
508
509 // Maybe our pod is not up to date and there are new licences since our version
510 let licenceLabel = VIDEO_LICENCES[this.licence]
511 if (!licenceLabel) licenceLabel = 'Unknown'
512
513 // Language is an optional attribute
514 let languageLabel = VIDEO_LANGUAGES[this.language]
515 if (!languageLabel) languageLabel = 'Unknown'
516
517 const json = {
518 id: this.id,
519 uuid: this.uuid,
520 name: this.name,
521 category: this.category,
522 categoryLabel,
523 licence: this.licence,
524 licenceLabel,
525 language: this.language,
526 languageLabel,
527 nsfw: this.nsfw,
528 description: this.description,
529 podHost,
530 isLocal: this.isOwned(),
531 author: this.VideoChannel.Author.name,
532 duration: this.duration,
533 views: this.views,
534 likes: this.likes,
535 dislikes: this.dislikes,
536 tags: map<TagInstance, string>(this.Tags, 'name'),
537 thumbnailPath: this.getThumbnailPath(),
538 previewPath: this.getPreviewPath(),
539 embedPath: this.getEmbedPath(),
540 createdAt: this.createdAt,
541 updatedAt: this.updatedAt,
542 channel: this.VideoChannel.toFormattedJSON(), 506 channel: this.VideoChannel.toFormattedJSON(),
543 files: [] 507 files: []
544 } 508 }
545 509
546 // Format and sort video files 510 // Format and sort video files
547 const { baseUrlHttp, baseUrlWs } = getBaseUrls(this) 511 const { baseUrlHttp, baseUrlWs } = getBaseUrls(this)
548 json.files = this.VideoFiles 512 detailsJson.files = this.VideoFiles
549 .map(videoFile => { 513 .map(videoFile => {
550 let resolutionLabel = videoFile.resolution + 'p' 514 let resolutionLabel = videoFile.resolution + 'p'
551 515
@@ -566,7 +530,7 @@ toFormattedDetailsJSON = function (this: VideoInstance) {
566 return -1 530 return -1
567 }) 531 })
568 532
569 return json 533 return Object.assign(formattedJson, detailsJson)
570} 534}
571 535
572toAddRemoteJSON = function (this: VideoInstance) { 536toAddRemoteJSON = function (this: VideoInstance) {
@@ -581,7 +545,7 @@ toAddRemoteJSON = function (this: VideoInstance) {
581 licence: this.licence, 545 licence: this.licence,
582 language: this.language, 546 language: this.language,
583 nsfw: this.nsfw, 547 nsfw: this.nsfw,
584 description: this.description, 548 truncatedDescription: this.getTruncatedDescription(),
585 channelUUID: this.VideoChannel.uuid, 549 channelUUID: this.VideoChannel.uuid,
586 duration: this.duration, 550 duration: this.duration,
587 thumbnailData: thumbnailData.toString('binary'), 551 thumbnailData: thumbnailData.toString('binary'),
@@ -615,7 +579,7 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
615 licence: this.licence, 579 licence: this.licence,
616 language: this.language, 580 language: this.language,
617 nsfw: this.nsfw, 581 nsfw: this.nsfw,
618 description: this.description, 582 truncatedDescription: this.getTruncatedDescription(),
619 duration: this.duration, 583 duration: this.duration,
620 tags: map<TagInstance, string>(this.Tags, 'name'), 584 tags: map<TagInstance, string>(this.Tags, 'name'),
621 createdAt: this.createdAt, 585 createdAt: this.createdAt,
@@ -638,6 +602,14 @@ toUpdateRemoteJSON = function (this: VideoInstance) {
638 return json 602 return json
639} 603}
640 604
605getTruncatedDescription = function (this: VideoInstance) {
606 const options = {
607 length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max
608 }
609
610 return truncate(this.description, options)
611}
612
641optimizeOriginalVideofile = function (this: VideoInstance) { 613optimizeOriginalVideofile = function (this: VideoInstance) {
642 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 614 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
643 const newExtname = '.mp4' 615 const newExtname = '.mp4'
@@ -730,6 +702,10 @@ getOriginalFileHeight = function (this: VideoInstance) {
730 return getVideoFileHeight(originalFilePath) 702 return getVideoFileHeight(originalFilePath)
731} 703}
732 704
705getDescriptionPath = function (this: VideoInstance) {
706 return `/api/${API_VERSION}/videos/${this.uuid}/description`
707}
708
733removeThumbnail = function (this: VideoInstance) { 709removeThumbnail = function (this: VideoInstance) {
734 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) 710 const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
735 return unlinkPromise(thumbnailPath) 711 return unlinkPromise(thumbnailPath)
diff --git a/server/tests/api/index.ts b/server/tests/api/index.ts
index e50e65049..2ff0ecf24 100644
--- a/server/tests/api/index.ts
+++ b/server/tests/api/index.ts
@@ -7,6 +7,7 @@ import './single-pod'
7import './video-abuse' 7import './video-abuse'
8import './video-blacklist' 8import './video-blacklist'
9import './video-blacklist-management' 9import './video-blacklist-management'
10import './video-description'
10import './multiple-pods' 11import './multiple-pods'
11import './services' 12import './services'
12import './request-schedulers' 13import './request-schedulers'
diff --git a/server/tests/api/video-description.ts b/server/tests/api/video-description.ts
new file mode 100644
index 000000000..f04c5f1f6
--- /dev/null
+++ b/server/tests/api/video-description.ts
@@ -0,0 +1,86 @@
1/* tslint:disable:no-unused-expression */
2
3import 'mocha'
4import * as chai from 'chai'
5
6import {
7 flushAndRunMultipleServers,
8 flushTests,
9 getVideo,
10 getVideosList,
11 killallServers,
12 makeFriends,
13 ServerInfo,
14 setAccessTokensToServers,
15 uploadVideo,
16 wait,
17 getVideoDescription
18} from '../utils'
19
20const expect = chai.expect
21
22describe('Test video description', function () {
23 let servers: ServerInfo[] = []
24 let videoUUID = ''
25 let longDescription = 'my super description for pod 1'.repeat(50)
26
27 before(async function () {
28 this.timeout(10000)
29
30 // Run servers
31 servers = await flushAndRunMultipleServers(2)
32
33 // Get the access tokens
34 await setAccessTokensToServers(servers)
35
36 // Pod 1 makes friend with pod 2
37 await makeFriends(servers[0].url, servers[0].accessToken)
38 })
39
40 it('Should upload video with long description', async function () {
41 this.timeout(15000)
42
43 const attributes = {
44 description: longDescription
45 }
46 await uploadVideo(servers[0].url, servers[0].accessToken, attributes)
47
48 await wait(11000)
49
50 const res = await getVideosList(servers[0].url)
51
52 videoUUID = res.body.data[0].uuid
53 })
54
55 it('Should have a truncated description on each pod', async function () {
56 for (const server of servers) {
57 const res = await getVideo(server.url, videoUUID)
58 const video = res.body
59
60 // 30 characters * 6 -> 240 characters
61 const truncatedDescription = 'my super description for pod 1'.repeat(8) +
62 'my supe...'
63
64 expect(video.description).to.equal(truncatedDescription)
65 }
66 })
67
68 it('Should fetch long description on each pod', async function () {
69 for (const server of servers) {
70 const res = await getVideo(server.url, videoUUID)
71 const video = res.body
72
73 const res2 = await getVideoDescription(server.url, video.descriptionPath)
74 expect(res2.body.description).to.equal(longDescription)
75 }
76 })
77
78 after(async function () {
79 killallServers(servers)
80
81 // Keep the logs if the test failed
82 if (this['ok']) {
83 await flushTests()
84 }
85 })
86})
diff --git a/server/tests/utils/videos.ts b/server/tests/utils/videos.ts
index 08fa48da6..2a5d00255 100644
--- a/server/tests/utils/videos.ts
+++ b/server/tests/utils/videos.ts
@@ -61,6 +61,14 @@ function getVideo (url: string, id: number | string) {
61 .expect('Content-Type', /json/) 61 .expect('Content-Type', /json/)
62} 62}
63 63
64function getVideoDescription (url: string, descriptionPath: string) {
65 return request(url)
66 .get(descriptionPath)
67 .set('Accept', 'application/json')
68 .expect(200)
69 .expect('Content-Type', /json/)
70}
71
64function getVideosList (url: string) { 72function getVideosList (url: string) {
65 const path = '/api/v1/videos' 73 const path = '/api/v1/videos'
66 74
@@ -263,6 +271,7 @@ function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: n
263// --------------------------------------------------------------------------- 271// ---------------------------------------------------------------------------
264 272
265export { 273export {
274 getVideoDescription,
266 getVideoCategories, 275 getVideoCategories,
267 getVideoLicences, 276 getVideoLicences,
268 getVideoLanguages, 277 getVideoLanguages,
diff --git a/shared/models/pods/remote-video/remote-video-create-request.model.ts b/shared/models/pods/remote-video/remote-video-create-request.model.ts
index e00e81214..cb20dfa03 100644
--- a/shared/models/pods/remote-video/remote-video-create-request.model.ts
+++ b/shared/models/pods/remote-video/remote-video-create-request.model.ts
@@ -9,7 +9,7 @@ export interface RemoteVideoCreateData {
9 licence: number 9 licence: number
10 language: number 10 language: number
11 nsfw: boolean 11 nsfw: boolean
12 description: string 12 truncatedDescription: string
13 duration: number 13 duration: number
14 createdAt: Date 14 createdAt: Date
15 updatedAt: Date 15 updatedAt: Date
diff --git a/shared/models/pods/remote-video/remote-video-update-request.model.ts b/shared/models/pods/remote-video/remote-video-update-request.model.ts
index 90c42fc28..8439cfa24 100644
--- a/shared/models/pods/remote-video/remote-video-update-request.model.ts
+++ b/shared/models/pods/remote-video/remote-video-update-request.model.ts
@@ -8,7 +8,7 @@ export interface RemoteVideoUpdateData {
8 licence: number 8 licence: number
9 language: number 9 language: number
10 nsfw: boolean 10 nsfw: boolean
11 description: string 11 truncatedDescription: string
12 duration: number 12 duration: number
13 createdAt: Date 13 createdAt: Date
14 updatedAt: Date 14 updatedAt: Date
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 32463933d..1490d345c 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -37,6 +37,7 @@ export interface Video {
37} 37}
38 38
39export interface VideoDetails extends Video { 39export interface VideoDetails extends Video {
40 descriptionPath: string,
40 channel: VideoChannel 41 channel: VideoChannel
41 files: VideoFile[] 42 files: VideoFile[]
42} 43}