diff options
-rw-r--r-- | server/controllers/api/remote/videos.ts | 4 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 21 | ||||
-rw-r--r-- | server/helpers/custom-validators/remote/videos.ts | 4 | ||||
-rw-r--r-- | server/helpers/custom-validators/videos.ts | 5 | ||||
-rw-r--r-- | server/initializers/constants.ts | 5 | ||||
-rw-r--r-- | server/initializers/database.ts | 9 | ||||
-rw-r--r-- | server/initializers/migrations/0090-videos-description.ts | 25 | ||||
-rw-r--r-- | server/lib/friends.ts | 19 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 4 | ||||
-rw-r--r-- | server/models/video/video.ts | 86 | ||||
-rw-r--r-- | server/tests/api/index.ts | 1 | ||||
-rw-r--r-- | server/tests/api/video-description.ts | 86 | ||||
-rw-r--r-- | server/tests/utils/videos.ts | 9 | ||||
-rw-r--r-- | shared/models/pods/remote-video/remote-video-create-request.model.ts | 2 | ||||
-rw-r--r-- | shared/models/pods/remote-video/remote-video-update-request.model.ts | 2 | ||||
-rw-r--r-- | shared/models/videos/video.model.ts | 1 |
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' |
21 | import { | 22 | import { |
22 | authenticate, | 23 | authenticate, |
@@ -102,6 +103,11 @@ videosRouter.post('/upload', | |||
102 | videosAddValidator, | 103 | videosAddValidator, |
103 | asyncMiddleware(addVideoRetryWrapper) | 104 | asyncMiddleware(addVideoRetryWrapper) |
104 | ) | 105 | ) |
106 | |||
107 | videosRouter.get('/:id/description', | ||
108 | videosGetValidator, | ||
109 | asyncMiddleware(getVideoDescription) | ||
110 | ) | ||
105 | videosRouter.get('/:id', | 111 | videosRouter.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 | ||
337 | async 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 | |||
331 | async function listVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 350 | async 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 | ||
57 | function isVideoTruncatedDescriptionValid (value: string) { | ||
58 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION) | ||
59 | } | ||
60 | |||
57 | function isVideoDescriptionValid (value: string) { | 61 | function 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 | ||
18 | const LAST_MIGRATION_VERSION = 85 | 18 | const 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const q = utils.queryInterface | ||
10 | |||
11 | const data = { | ||
12 | type: Sequelize.STRING(3000), | ||
13 | allowNull: false | ||
14 | } | ||
15 | await q.changeColumn('Videos', 'description', data) | ||
16 | } | ||
17 | |||
18 | function down (options) { | ||
19 | throw new Error('Not implemented.') | ||
20 | } | ||
21 | |||
22 | export { | ||
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 | ||
352 | function 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 | |||
352 | async function removeFriend (pod: PodInstance) { | 370 | async 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' | |||
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import * as Sequelize from 'sequelize' | 7 | import * as Sequelize from 'sequelize' |
8 | import * as Promise from 'bluebird' | 8 | import * as Promise from 'bluebird' |
9 | import { maxBy } from 'lodash' | 9 | import { maxBy, truncate } from 'lodash' |
10 | 10 | ||
11 | import { TagInstance } from './tag-interface' | 11 | import { TagInstance } from './tag-interface' |
12 | import { | 12 | import { |
@@ -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' |
40 | import { removeVideoToFriends } from '../../lib' | 43 | import { removeVideoToFriends } from '../../lib' |
41 | import { VideoResolution } from '../../../shared' | 44 | import { VideoResolution } from '../../../shared' |
@@ -48,7 +51,6 @@ import { | |||
48 | 51 | ||
49 | VideoMethods | 52 | VideoMethods |
50 | } from './video-interface' | 53 | } from './video-interface' |
51 | import { PREVIEWS_SIZE } from '../../initializers/constants' | ||
52 | 54 | ||
53 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> | 55 | let Video: Sequelize.Model<VideoInstance, VideoAttributes> |
54 | let getOriginalFile: VideoMethods.GetOriginalFile | 56 | let getOriginalFile: VideoMethods.GetOriginalFile |
@@ -71,6 +73,8 @@ let getVideoFilePath: VideoMethods.GetVideoFilePath | |||
71 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | 73 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash |
72 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | 74 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight |
73 | let getEmbedPath: VideoMethods.GetEmbedPath | 75 | let getEmbedPath: VideoMethods.GetEmbedPath |
76 | let getDescriptionPath: VideoMethods.GetDescriptionPath | ||
77 | let getTruncatedDescription: VideoMethods.GetTruncatedDescription | ||
74 | 78 | ||
75 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 79 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
76 | let list: VideoMethods.List | 80 | let 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 | ||
495 | toFormattedDetailsJSON = function (this: VideoInstance) { | 501 | toFormattedDetailsJSON = 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 | ||
572 | toAddRemoteJSON = function (this: VideoInstance) { | 536 | toAddRemoteJSON = 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 | ||
605 | getTruncatedDescription = function (this: VideoInstance) { | ||
606 | const options = { | ||
607 | length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max | ||
608 | } | ||
609 | |||
610 | return truncate(this.description, options) | ||
611 | } | ||
612 | |||
641 | optimizeOriginalVideofile = function (this: VideoInstance) { | 613 | optimizeOriginalVideofile = 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 | ||
705 | getDescriptionPath = function (this: VideoInstance) { | ||
706 | return `/api/${API_VERSION}/videos/${this.uuid}/description` | ||
707 | } | ||
708 | |||
733 | removeThumbnail = function (this: VideoInstance) { | 709 | removeThumbnail = 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' | |||
7 | import './video-abuse' | 7 | import './video-abuse' |
8 | import './video-blacklist' | 8 | import './video-blacklist' |
9 | import './video-blacklist-management' | 9 | import './video-blacklist-management' |
10 | import './video-description' | ||
10 | import './multiple-pods' | 11 | import './multiple-pods' |
11 | import './services' | 12 | import './services' |
12 | import './request-schedulers' | 13 | import './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 | |||
3 | import 'mocha' | ||
4 | import * as chai from 'chai' | ||
5 | |||
6 | import { | ||
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 | |||
20 | const expect = chai.expect | ||
21 | |||
22 | describe('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 | ||
64 | function 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 | |||
64 | function getVideosList (url: string) { | 72 | function 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 | ||
265 | export { | 273 | export { |
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 | ||
39 | export interface VideoDetails extends Video { | 39 | export interface VideoDetails extends Video { |
40 | descriptionPath: string, | ||
40 | channel: VideoChannel | 41 | channel: VideoChannel |
41 | files: VideoFile[] | 42 | files: VideoFile[] |
42 | } | 43 | } |