diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/client.ts | 32 | ||||
-rw-r--r-- | server/controllers/index.ts | 1 | ||||
-rw-r--r-- | server/controllers/services.ts | 62 | ||||
-rw-r--r-- | server/initializers/constants.ts | 14 | ||||
-rw-r--r-- | server/middlewares/validators/index.ts | 1 | ||||
-rw-r--r-- | server/middlewares/validators/oembed.ts | 63 | ||||
-rw-r--r-- | server/models/video/video-interface.ts | 6 | ||||
-rw-r--r-- | server/models/video/video.ts | 29 | ||||
-rw-r--r-- | server/tests/api/check-params/index.ts | 1 | ||||
-rw-r--r-- | server/tests/api/check-params/services.ts | 159 | ||||
-rw-r--r-- | server/tests/api/index.ts | 1 | ||||
-rw-r--r-- | server/tests/api/services.ts | 85 | ||||
-rw-r--r-- | server/tests/client.ts | 17 | ||||
-rw-r--r-- | server/tests/utils/index.ts | 1 | ||||
-rw-r--r-- | server/tests/utils/servers.ts | 2 | ||||
-rw-r--r-- | server/tests/utils/services.ts | 23 |
16 files changed, 477 insertions, 20 deletions
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index b23f7e1ae..e3c962058 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -8,7 +8,7 @@ import { | |||
8 | CONFIG, | 8 | CONFIG, |
9 | STATIC_PATHS, | 9 | STATIC_PATHS, |
10 | STATIC_MAX_AGE, | 10 | STATIC_MAX_AGE, |
11 | OPENGRAPH_COMMENT | 11 | OPENGRAPH_AND_OEMBED_COMMENT |
12 | } from '../initializers' | 12 | } from '../initializers' |
13 | import { root, readFileBufferPromise } from '../helpers' | 13 | import { root, readFileBufferPromise } from '../helpers' |
14 | import { VideoInstance } from '../models' | 14 | import { VideoInstance } from '../models' |
@@ -19,7 +19,7 @@ const distPath = join(root(), 'client', 'dist') | |||
19 | const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') | 19 | const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') |
20 | const indexPath = join(distPath, 'index.html') | 20 | const indexPath = join(distPath, 'index.html') |
21 | 21 | ||
22 | // Special route that add OpenGraph tags | 22 | // Special route that add OpenGraph and oEmbed tags |
23 | // Do not use a template engine for a so little thing | 23 | // Do not use a template engine for a so little thing |
24 | clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage) | 24 | clientsRouter.use('/videos/watch/:id', generateWatchHtmlPage) |
25 | 25 | ||
@@ -43,11 +43,11 @@ export { | |||
43 | 43 | ||
44 | // --------------------------------------------------------------------------- | 44 | // --------------------------------------------------------------------------- |
45 | 45 | ||
46 | function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) { | 46 | function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoInstance) { |
47 | const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() | 47 | const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() |
48 | const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id | 48 | const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid |
49 | 49 | ||
50 | const metaTags = { | 50 | const openGraphMetaTags = { |
51 | 'og:type': 'video', | 51 | 'og:type': 'video', |
52 | 'og:title': video.name, | 52 | 'og:title': video.name, |
53 | 'og:image': previewUrl, | 53 | 'og:image': previewUrl, |
@@ -65,14 +65,26 @@ function addOpenGraphTags (htmlStringPage: string, video: VideoInstance) { | |||
65 | 'twitter:image': previewUrl | 65 | 'twitter:image': previewUrl |
66 | } | 66 | } |
67 | 67 | ||
68 | const oembedLinkTags = [ | ||
69 | { | ||
70 | type: 'application/json+oembed', | ||
71 | href: CONFIG.WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl), | ||
72 | title: video.name | ||
73 | } | ||
74 | ] | ||
75 | |||
68 | let tagsString = '' | 76 | let tagsString = '' |
69 | Object.keys(metaTags).forEach(tagName => { | 77 | Object.keys(openGraphMetaTags).forEach(tagName => { |
70 | const tagValue = metaTags[tagName] | 78 | const tagValue = openGraphMetaTags[tagName] |
71 | 79 | ||
72 | tagsString += '<meta property="' + tagName + '" content="' + tagValue + '" />' | 80 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` |
73 | }) | 81 | }) |
74 | 82 | ||
75 | return htmlStringPage.replace(OPENGRAPH_COMMENT, tagsString) | 83 | for (const oembedLinkTag of oembedLinkTags) { |
84 | tagsString += `<link rel="alternate" type="${oembedLinkTag.type}" href="${oembedLinkTag.href}" title="${oembedLinkTag.title}" />` | ||
85 | } | ||
86 | |||
87 | return htmlStringPage.replace(OPENGRAPH_AND_OEMBED_COMMENT, tagsString) | ||
76 | } | 88 | } |
77 | 89 | ||
78 | function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { | 90 | function generateWatchHtmlPage (req: express.Request, res: express.Response, next: express.NextFunction) { |
@@ -101,7 +113,7 @@ function generateWatchHtmlPage (req: express.Request, res: express.Response, nex | |||
101 | // Let Angular application handle errors | 113 | // Let Angular application handle errors |
102 | if (!video) return res.sendFile(indexPath) | 114 | if (!video) return res.sendFile(indexPath) |
103 | 115 | ||
104 | const htmlStringPageWithTags = addOpenGraphTags(html, video) | 116 | const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) |
105 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) | 117 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) |
106 | }) | 118 | }) |
107 | .catch(err => next(err)) | 119 | .catch(err => next(err)) |
diff --git a/server/controllers/index.ts b/server/controllers/index.ts index 0223a98f1..51cb480a3 100644 --- a/server/controllers/index.ts +++ b/server/controllers/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './static' | 1 | export * from './static' |
2 | export * from './client' | 2 | export * from './client' |
3 | export * from './services' | ||
3 | export * from './api' | 4 | export * from './api' |
diff --git a/server/controllers/services.ts b/server/controllers/services.ts new file mode 100644 index 000000000..3ce6bd526 --- /dev/null +++ b/server/controllers/services.ts | |||
@@ -0,0 +1,62 @@ | |||
1 | import * as express from 'express' | ||
2 | |||
3 | import { CONFIG, THUMBNAILS_SIZE } from '../initializers' | ||
4 | import { oembedValidator } from '../middlewares' | ||
5 | import { VideoInstance } from '../models' | ||
6 | |||
7 | const servicesRouter = express.Router() | ||
8 | |||
9 | servicesRouter.use('/oembed', oembedValidator, generateOEmbed) | ||
10 | |||
11 | // --------------------------------------------------------------------------- | ||
12 | |||
13 | export { | ||
14 | servicesRouter | ||
15 | } | ||
16 | |||
17 | // --------------------------------------------------------------------------- | ||
18 | |||
19 | function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
20 | const video = res.locals.video as VideoInstance | ||
21 | const webserverUrl = CONFIG.WEBSERVER.URL | ||
22 | const maxHeight = parseInt(req.query.maxheight, 10) | ||
23 | const maxWidth = parseInt(req.query.maxwidth, 10) | ||
24 | |||
25 | const embedUrl = webserverUrl + video.getEmbedPath() | ||
26 | let thumbnailUrl = webserverUrl + video.getThumbnailPath() | ||
27 | let embedWidth = 560 | ||
28 | let embedHeight = 315 | ||
29 | |||
30 | if (maxHeight < embedHeight) embedHeight = maxHeight | ||
31 | if (maxWidth < embedWidth) embedWidth = maxWidth | ||
32 | |||
33 | // Our thumbnail is too big for the consumer | ||
34 | if ( | ||
35 | (maxHeight !== undefined && maxHeight < THUMBNAILS_SIZE.height) || | ||
36 | (maxWidth !== undefined && maxWidth < THUMBNAILS_SIZE.width) | ||
37 | ) { | ||
38 | thumbnailUrl = undefined | ||
39 | } | ||
40 | |||
41 | const html = `<iframe width="${embedWidth}" height="${embedHeight}" src="${embedUrl}" frameborder="0" allowfullscreen></iframe>` | ||
42 | |||
43 | const json: any = { | ||
44 | type: 'video', | ||
45 | version: '1.0', | ||
46 | html, | ||
47 | width: embedWidth, | ||
48 | height: embedHeight, | ||
49 | title: video.name, | ||
50 | author_name: video.Author.name, | ||
51 | provider_name: 'PeerTube', | ||
52 | provider_url: webserverUrl | ||
53 | } | ||
54 | |||
55 | if (thumbnailUrl !== undefined) { | ||
56 | json.thumbnail_url = thumbnailUrl | ||
57 | json.thumbnail_width = THUMBNAILS_SIZE.width | ||
58 | json.thumbnail_height = THUMBNAILS_SIZE.height | ||
59 | } | ||
60 | |||
61 | return res.json(json) | ||
62 | } | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index b11575b34..6218644cf 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -295,8 +295,14 @@ const STATIC_PATHS = { | |||
295 | let STATIC_MAX_AGE = '30d' | 295 | let STATIC_MAX_AGE = '30d' |
296 | 296 | ||
297 | // Videos thumbnail size | 297 | // Videos thumbnail size |
298 | const THUMBNAILS_SIZE = '200x110' | 298 | const THUMBNAILS_SIZE = { |
299 | const PREVIEWS_SIZE = '640x480' | 299 | width: 200, |
300 | height: 110 | ||
301 | } | ||
302 | const PREVIEWS_SIZE = { | ||
303 | width: 640, | ||
304 | height: 480 | ||
305 | } | ||
300 | 306 | ||
301 | // Sub folders of cache directory | 307 | // Sub folders of cache directory |
302 | const CACHE = { | 308 | const CACHE = { |
@@ -314,7 +320,7 @@ const USER_ROLES: { [ id: string ]: UserRole } = { | |||
314 | 320 | ||
315 | // --------------------------------------------------------------------------- | 321 | // --------------------------------------------------------------------------- |
316 | 322 | ||
317 | const OPENGRAPH_COMMENT = '<!-- open graph tags -->' | 323 | const OPENGRAPH_AND_OEMBED_COMMENT = '<!-- open graph and oembed tags -->' |
318 | 324 | ||
319 | // --------------------------------------------------------------------------- | 325 | // --------------------------------------------------------------------------- |
320 | 326 | ||
@@ -344,7 +350,7 @@ export { | |||
344 | JOBS_FETCHING_INTERVAL, | 350 | JOBS_FETCHING_INTERVAL, |
345 | LAST_MIGRATION_VERSION, | 351 | LAST_MIGRATION_VERSION, |
346 | OAUTH_LIFETIME, | 352 | OAUTH_LIFETIME, |
347 | OPENGRAPH_COMMENT, | 353 | OPENGRAPH_AND_OEMBED_COMMENT, |
348 | PAGINATION_COUNT_DEFAULT, | 354 | PAGINATION_COUNT_DEFAULT, |
349 | PODS_SCORE, | 355 | PODS_SCORE, |
350 | PREVIEWS_SIZE, | 356 | PREVIEWS_SIZE, |
diff --git a/server/middlewares/validators/index.ts b/server/middlewares/validators/index.ts index 418fa5f1d..068c41b24 100644 --- a/server/middlewares/validators/index.ts +++ b/server/middlewares/validators/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | export * from './oembed' | ||
1 | export * from './remote' | 2 | export * from './remote' |
2 | export * from './pagination' | 3 | export * from './pagination' |
3 | export * from './pods' | 4 | export * from './pods' |
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts new file mode 100644 index 000000000..4b8c03faf --- /dev/null +++ b/server/middlewares/validators/oembed.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { query } from 'express-validator/check' | ||
2 | import * as express from 'express' | ||
3 | import { join } from 'path' | ||
4 | |||
5 | import { checkErrors } from './utils' | ||
6 | import { CONFIG } from '../../initializers' | ||
7 | import { logger } from '../../helpers' | ||
8 | import { checkVideoExists, isVideoIdOrUUIDValid } from '../../helpers/custom-validators/videos' | ||
9 | import { isTestInstance } from '../../helpers/core-utils' | ||
10 | |||
11 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' | ||
12 | const videoWatchRegex = new RegExp('([^/]+)$') | ||
13 | const isURLOptions = { | ||
14 | require_host: true, | ||
15 | require_tld: true | ||
16 | } | ||
17 | |||
18 | // We validate 'localhost', so we don't have the top level domain | ||
19 | if (isTestInstance()) { | ||
20 | isURLOptions.require_tld = false | ||
21 | } | ||
22 | |||
23 | const oembedValidator = [ | ||
24 | query('url').isURL(isURLOptions).withMessage('Should have a valid url'), | ||
25 | query('maxwidth').optional().isInt().withMessage('Should have a valid max width'), | ||
26 | query('maxheight').optional().isInt().withMessage('Should have a valid max height'), | ||
27 | query('format').optional().isIn([ 'xml', 'json' ]).withMessage('Should have a valid format'), | ||
28 | |||
29 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
30 | logger.debug('Checking oembed parameters', { parameters: req.query }) | ||
31 | |||
32 | checkErrors(req, res, () => { | ||
33 | if (req.query.format !== undefined && req.query.format !== 'json') { | ||
34 | return res.status(501) | ||
35 | .json({ error: 'Requested format is not implemented on server.' }) | ||
36 | .end() | ||
37 | } | ||
38 | |||
39 | const startIsOk = req.query.url.startsWith(urlShouldStartWith) | ||
40 | const matches = videoWatchRegex.exec(req.query.url) | ||
41 | if (startIsOk === false || matches === null) { | ||
42 | return res.status(400) | ||
43 | .json({ error: 'Invalid url.' }) | ||
44 | .end() | ||
45 | } | ||
46 | |||
47 | const videoId = matches[1] | ||
48 | if (isVideoIdOrUUIDValid(videoId) === false) { | ||
49 | return res.status(400) | ||
50 | .json({ error: 'Invalid video id.' }) | ||
51 | .end() | ||
52 | } | ||
53 | |||
54 | checkVideoExists(videoId, res, next) | ||
55 | }) | ||
56 | } | ||
57 | ] | ||
58 | |||
59 | // --------------------------------------------------------------------------- | ||
60 | |||
61 | export { | ||
62 | oembedValidator | ||
63 | } | ||
diff --git a/server/models/video/video-interface.ts b/server/models/video/video-interface.ts index 6a3db4f3e..1402df26a 100644 --- a/server/models/video/video-interface.ts +++ b/server/models/video/video-interface.ts | |||
@@ -32,6 +32,9 @@ export namespace VideoMethods { | |||
32 | export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void> | 32 | export type OptimizeOriginalVideofile = (this: VideoInstance) => Promise<void> |
33 | export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void> | 33 | export type TranscodeOriginalVideofile = (this: VideoInstance, resolution: number) => Promise<void> |
34 | export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number> | 34 | export type GetOriginalFileHeight = (this: VideoInstance) => Promise<number> |
35 | export type GetEmbedPath = (this: VideoInstance) => string | ||
36 | export type GetThumbnailPath = (this: VideoInstance) => string | ||
37 | export type GetPreviewPath = (this: VideoInstance) => string | ||
35 | 38 | ||
36 | // Return thumbnail name | 39 | // Return thumbnail name |
37 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> | 40 | export type GenerateThumbnailFromData = (video: VideoInstance, thumbnailData: string) => Promise<string> |
@@ -107,7 +110,9 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
107 | getOriginalFile: VideoMethods.GetOriginalFile | 110 | getOriginalFile: VideoMethods.GetOriginalFile |
108 | generateMagnetUri: VideoMethods.GenerateMagnetUri | 111 | generateMagnetUri: VideoMethods.GenerateMagnetUri |
109 | getPreviewName: VideoMethods.GetPreviewName | 112 | getPreviewName: VideoMethods.GetPreviewName |
113 | getPreviewPath: VideoMethods.GetPreviewPath | ||
110 | getThumbnailName: VideoMethods.GetThumbnailName | 114 | getThumbnailName: VideoMethods.GetThumbnailName |
115 | getThumbnailPath: VideoMethods.GetThumbnailPath | ||
111 | getTorrentFileName: VideoMethods.GetTorrentFileName | 116 | getTorrentFileName: VideoMethods.GetTorrentFileName |
112 | getVideoFilename: VideoMethods.GetVideoFilename | 117 | getVideoFilename: VideoMethods.GetVideoFilename |
113 | getVideoFilePath: VideoMethods.GetVideoFilePath | 118 | getVideoFilePath: VideoMethods.GetVideoFilePath |
@@ -122,6 +127,7 @@ export interface VideoInstance extends VideoClass, VideoAttributes, Sequelize.In | |||
122 | optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile | 127 | optimizeOriginalVideofile: VideoMethods.OptimizeOriginalVideofile |
123 | transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile | 128 | transcodeOriginalVideofile: VideoMethods.TranscodeOriginalVideofile |
124 | getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | 129 | getOriginalFileHeight: VideoMethods.GetOriginalFileHeight |
130 | getEmbedPath: VideoMethods.GetEmbedPath | ||
125 | 131 | ||
126 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> | 132 | setTags: Sequelize.HasManySetAssociationsMixin<TagAttributes, string> |
127 | addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> | 133 | addVideoFile: Sequelize.HasManyAddAssociationMixin<VideoFileAttributes, string> |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 2ba6cf25f..0d0048b4a 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -54,7 +54,9 @@ let getOriginalFile: VideoMethods.GetOriginalFile | |||
54 | let generateMagnetUri: VideoMethods.GenerateMagnetUri | 54 | let generateMagnetUri: VideoMethods.GenerateMagnetUri |
55 | let getVideoFilename: VideoMethods.GetVideoFilename | 55 | let getVideoFilename: VideoMethods.GetVideoFilename |
56 | let getThumbnailName: VideoMethods.GetThumbnailName | 56 | let getThumbnailName: VideoMethods.GetThumbnailName |
57 | let getThumbnailPath: VideoMethods.GetThumbnailPath | ||
57 | let getPreviewName: VideoMethods.GetPreviewName | 58 | let getPreviewName: VideoMethods.GetPreviewName |
59 | let getPreviewPath: VideoMethods.GetPreviewPath | ||
58 | let getTorrentFileName: VideoMethods.GetTorrentFileName | 60 | let getTorrentFileName: VideoMethods.GetTorrentFileName |
59 | let isOwned: VideoMethods.IsOwned | 61 | let isOwned: VideoMethods.IsOwned |
60 | let toFormattedJSON: VideoMethods.ToFormattedJSON | 62 | let toFormattedJSON: VideoMethods.ToFormattedJSON |
@@ -67,6 +69,7 @@ let createThumbnail: VideoMethods.CreateThumbnail | |||
67 | let getVideoFilePath: VideoMethods.GetVideoFilePath | 69 | let getVideoFilePath: VideoMethods.GetVideoFilePath |
68 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash | 70 | let createTorrentAndSetInfoHash: VideoMethods.CreateTorrentAndSetInfoHash |
69 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight | 71 | let getOriginalFileHeight: VideoMethods.GetOriginalFileHeight |
72 | let getEmbedPath: VideoMethods.GetEmbedPath | ||
70 | 73 | ||
71 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData | 74 | let generateThumbnailFromData: VideoMethods.GenerateThumbnailFromData |
72 | let list: VideoMethods.List | 75 | let list: VideoMethods.List |
@@ -252,7 +255,9 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
252 | createTorrentAndSetInfoHash, | 255 | createTorrentAndSetInfoHash, |
253 | generateMagnetUri, | 256 | generateMagnetUri, |
254 | getPreviewName, | 257 | getPreviewName, |
258 | getPreviewPath, | ||
255 | getThumbnailName, | 259 | getThumbnailName, |
260 | getThumbnailPath, | ||
256 | getTorrentFileName, | 261 | getTorrentFileName, |
257 | getVideoFilename, | 262 | getVideoFilename, |
258 | getVideoFilePath, | 263 | getVideoFilePath, |
@@ -267,7 +272,8 @@ export default function (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.Da | |||
267 | toUpdateRemoteJSON, | 272 | toUpdateRemoteJSON, |
268 | optimizeOriginalVideofile, | 273 | optimizeOriginalVideofile, |
269 | transcodeOriginalVideofile, | 274 | transcodeOriginalVideofile, |
270 | getOriginalFileHeight | 275 | getOriginalFileHeight, |
276 | getEmbedPath | ||
271 | ] | 277 | ] |
272 | addMethodsToModel(Video, classMethods, instanceMethods) | 278 | addMethodsToModel(Video, classMethods, instanceMethods) |
273 | 279 | ||
@@ -375,11 +381,13 @@ createPreview = function (this: VideoInstance, videoFile: VideoFileInstance) { | |||
375 | } | 381 | } |
376 | 382 | ||
377 | createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { | 383 | createThumbnail = function (this: VideoInstance, videoFile: VideoFileInstance) { |
384 | const imageSize = THUMBNAILS_SIZE.width + 'x' + THUMBNAILS_SIZE.height | ||
385 | |||
378 | return generateImageFromVideoFile( | 386 | return generateImageFromVideoFile( |
379 | this.getVideoFilePath(videoFile), | 387 | this.getVideoFilePath(videoFile), |
380 | CONFIG.STORAGE.THUMBNAILS_DIR, | 388 | CONFIG.STORAGE.THUMBNAILS_DIR, |
381 | this.getThumbnailName(), | 389 | this.getThumbnailName(), |
382 | THUMBNAILS_SIZE | 390 | imageSize |
383 | ) | 391 | ) |
384 | } | 392 | } |
385 | 393 | ||
@@ -438,6 +446,18 @@ generateMagnetUri = function (this: VideoInstance, videoFile: VideoFileInstance) | |||
438 | return magnetUtil.encode(magnetHash) | 446 | return magnetUtil.encode(magnetHash) |
439 | } | 447 | } |
440 | 448 | ||
449 | getEmbedPath = function (this: VideoInstance) { | ||
450 | return '/videos/embed/' + this.uuid | ||
451 | } | ||
452 | |||
453 | getThumbnailPath = function (this: VideoInstance) { | ||
454 | return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) | ||
455 | } | ||
456 | |||
457 | getPreviewPath = function (this: VideoInstance) { | ||
458 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) | ||
459 | } | ||
460 | |||
441 | toFormattedJSON = function (this: VideoInstance) { | 461 | toFormattedJSON = function (this: VideoInstance) { |
442 | let podHost | 462 | let podHost |
443 | 463 | ||
@@ -480,8 +500,9 @@ toFormattedJSON = function (this: VideoInstance) { | |||
480 | likes: this.likes, | 500 | likes: this.likes, |
481 | dislikes: this.dislikes, | 501 | dislikes: this.dislikes, |
482 | tags: map<TagInstance, string>(this.Tags, 'name'), | 502 | tags: map<TagInstance, string>(this.Tags, 'name'), |
483 | thumbnailPath: join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), | 503 | thumbnailPath: this.getThumbnailPath(), |
484 | previewPath: join(STATIC_PATHS.PREVIEWS, this.getPreviewName()), | 504 | previewPath: this.getPreviewPath(), |
505 | embedPath: this.getEmbedPath(), | ||
485 | createdAt: this.createdAt, | 506 | createdAt: this.createdAt, |
486 | updatedAt: this.updatedAt, | 507 | updatedAt: this.updatedAt, |
487 | files: [] | 508 | files: [] |
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 399a05bc3..954b206e9 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -3,6 +3,7 @@ import './pods' | |||
3 | import './remotes' | 3 | import './remotes' |
4 | import './users' | 4 | import './users' |
5 | import './request-schedulers' | 5 | import './request-schedulers' |
6 | import './services' | ||
6 | import './videos' | 7 | import './videos' |
7 | import './video-abuses' | 8 | import './video-abuses' |
8 | import './video-blacklist' | 9 | import './video-blacklist' |
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts new file mode 100644 index 000000000..780254df5 --- /dev/null +++ b/server/tests/api/check-params/services.ts | |||
@@ -0,0 +1,159 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as request from 'supertest' | ||
4 | import 'mocha' | ||
5 | |||
6 | import { | ||
7 | flushTests, | ||
8 | runServer, | ||
9 | setAccessTokensToServers, | ||
10 | killallServers | ||
11 | } from '../../utils' | ||
12 | import { getVideosList, uploadVideo } from '../../utils/videos' | ||
13 | |||
14 | describe('Test services API validators', function () { | ||
15 | let server | ||
16 | |||
17 | // --------------------------------------------------------------- | ||
18 | |||
19 | before(async function () { | ||
20 | this.timeout(60000) | ||
21 | |||
22 | await flushTests() | ||
23 | |||
24 | server = await runServer(1) | ||
25 | await setAccessTokensToServers([ server ]) | ||
26 | |||
27 | const videoAttributes = { | ||
28 | name: 'my super name' | ||
29 | } | ||
30 | await uploadVideo(server.url, server.accessToken, videoAttributes) | ||
31 | |||
32 | const res = await getVideosList(server.url) | ||
33 | server.video = res.body.data[0] | ||
34 | }) | ||
35 | |||
36 | describe('Test oEmbed API validators', function () { | ||
37 | const path = '/services/oembed' | ||
38 | |||
39 | it('Should fail with an invalid url', async function () { | ||
40 | const embedUrl = 'hello.com' | ||
41 | |||
42 | await request(server.url) | ||
43 | .get(path) | ||
44 | .query({ url: embedUrl }) | ||
45 | .set('Accept', 'application/json') | ||
46 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
47 | .expect(400) | ||
48 | }) | ||
49 | |||
50 | it('Should fail with an invalid host', async function () { | ||
51 | const embedUrl = 'http://hello.com/videos/watch/' + server.video.uuid | ||
52 | |||
53 | await request(server.url) | ||
54 | .get(path) | ||
55 | .query({ url: embedUrl }) | ||
56 | .set('Accept', 'application/json') | ||
57 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
58 | .expect(400) | ||
59 | }) | ||
60 | |||
61 | it('Should fail with an invalid video id', async function () { | ||
62 | const embedUrl = 'http://localhost:9001/videos/watch/blabla' | ||
63 | |||
64 | await request(server.url) | ||
65 | .get(path) | ||
66 | .query({ url: embedUrl }) | ||
67 | .set('Accept', 'application/json') | ||
68 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
69 | .expect(400) | ||
70 | }) | ||
71 | |||
72 | it('Should fail with an unknown video', async function () { | ||
73 | const embedUrl = 'http://localhost:9001/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c' | ||
74 | |||
75 | await request(server.url) | ||
76 | .get(path) | ||
77 | .query({ url: embedUrl }) | ||
78 | .set('Accept', 'application/json') | ||
79 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
80 | .expect(404) | ||
81 | }) | ||
82 | |||
83 | it('Should fail with an invalid path', async function () { | ||
84 | const embedUrl = 'http://localhost:9001/videos/watchs/' + server.video.uuid | ||
85 | |||
86 | await request(server.url) | ||
87 | .get(path) | ||
88 | .query({ url: embedUrl }) | ||
89 | .set('Accept', 'application/json') | ||
90 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
91 | .expect(400) | ||
92 | }) | ||
93 | |||
94 | it('Should fail with an invalid max height', async function () { | ||
95 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
96 | |||
97 | await request(server.url) | ||
98 | .get(path) | ||
99 | .query({ | ||
100 | url: embedUrl, | ||
101 | maxheight: 'hello' | ||
102 | }) | ||
103 | .set('Accept', 'application/json') | ||
104 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
105 | .expect(400) | ||
106 | }) | ||
107 | |||
108 | it('Should fail with an invalid max width', async function () { | ||
109 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
110 | |||
111 | await request(server.url) | ||
112 | .get(path) | ||
113 | .query({ | ||
114 | url: embedUrl, | ||
115 | maxwidth: 'hello' | ||
116 | }) | ||
117 | .set('Accept', 'application/json') | ||
118 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
119 | .expect(400) | ||
120 | }) | ||
121 | |||
122 | it('Should fail with an invalid format', async function () { | ||
123 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
124 | |||
125 | await request(server.url) | ||
126 | .get(path) | ||
127 | .query({ | ||
128 | url: embedUrl, | ||
129 | format: 'blabla' | ||
130 | }) | ||
131 | .set('Accept', 'application/json') | ||
132 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
133 | .expect(400) | ||
134 | }) | ||
135 | |||
136 | it('Should fail with a non supported format', async function () { | ||
137 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
138 | |||
139 | await request(server.url) | ||
140 | .get(path) | ||
141 | .query({ | ||
142 | url: embedUrl, | ||
143 | format: 'xml' | ||
144 | }) | ||
145 | .set('Accept', 'application/json') | ||
146 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
147 | .expect(501) | ||
148 | }) | ||
149 | }) | ||
150 | |||
151 | after(async function () { | ||
152 | killallServers([ server ]) | ||
153 | |||
154 | // Keep the logs if the test failed | ||
155 | if (this['ok']) { | ||
156 | await flushTests() | ||
157 | } | ||
158 | }) | ||
159 | }) | ||
diff --git a/server/tests/api/index.ts b/server/tests/api/index.ts index 03711e68a..e50e65049 100644 --- a/server/tests/api/index.ts +++ b/server/tests/api/index.ts | |||
@@ -8,6 +8,7 @@ import './video-abuse' | |||
8 | import './video-blacklist' | 8 | import './video-blacklist' |
9 | import './video-blacklist-management' | 9 | import './video-blacklist-management' |
10 | import './multiple-pods' | 10 | import './multiple-pods' |
11 | import './services' | ||
11 | import './request-schedulers' | 12 | import './request-schedulers' |
12 | import './friends-advanced' | 13 | import './friends-advanced' |
13 | import './video-transcoder' | 14 | import './video-transcoder' |
diff --git a/server/tests/api/services.ts b/server/tests/api/services.ts new file mode 100644 index 000000000..b396ea582 --- /dev/null +++ b/server/tests/api/services.ts | |||
@@ -0,0 +1,85 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import * as chai from 'chai' | ||
5 | const expect = chai.expect | ||
6 | |||
7 | import { | ||
8 | ServerInfo, | ||
9 | flushTests, | ||
10 | uploadVideo, | ||
11 | getVideosList, | ||
12 | setAccessTokensToServers, | ||
13 | killallServers, | ||
14 | getOEmbed | ||
15 | } from '../utils' | ||
16 | import { runServer } from '../utils/servers' | ||
17 | |||
18 | describe('Test services', function () { | ||
19 | let server: ServerInfo = null | ||
20 | |||
21 | before(async function () { | ||
22 | this.timeout(120000) | ||
23 | |||
24 | await flushTests() | ||
25 | |||
26 | server = await runServer(1) | ||
27 | |||
28 | await setAccessTokensToServers([ server ]) | ||
29 | |||
30 | const videoAttributes = { | ||
31 | name: 'my super name' | ||
32 | } | ||
33 | await uploadVideo(server.url, server.accessToken, videoAttributes) | ||
34 | |||
35 | const res = await getVideosList(server.url) | ||
36 | server.video = res.body.data[0] | ||
37 | }) | ||
38 | |||
39 | it('Should have a valid oEmbed response', async function () { | ||
40 | const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
41 | |||
42 | const res = await getOEmbed(server.url, oembedUrl) | ||
43 | const expectedHtml = `<iframe width="560" height="315" src="http://localhost:9001/videos/embed/${server.video.uuid}" ` + | ||
44 | 'frameborder="0" allowfullscreen></iframe>' | ||
45 | const expectedThumbnailUrl = 'http://localhost:9001/static/thumbnails/' + server.video.uuid + '.jpg' | ||
46 | |||
47 | expect(res.body.html).to.equal(expectedHtml) | ||
48 | expect(res.body.title).to.equal(server.video.name) | ||
49 | expect(res.body.author_name).to.equal(server.video.author) | ||
50 | expect(res.body.height).to.equal(315) | ||
51 | expect(res.body.width).to.equal(560) | ||
52 | expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) | ||
53 | expect(res.body.thumbnail_width).to.equal(200) | ||
54 | expect(res.body.thumbnail_height).to.equal(110) | ||
55 | }) | ||
56 | |||
57 | it('Should have a valid oEmbed response with small max height query', async function () { | ||
58 | const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | ||
59 | const format = 'json' | ||
60 | const maxHeight = 50 | ||
61 | const maxWidth = 50 | ||
62 | |||
63 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) | ||
64 | const expectedHtml = `<iframe width="50" height="50" src="http://localhost:9001/videos/embed/${server.video.uuid}" ` + | ||
65 | 'frameborder="0" allowfullscreen></iframe>' | ||
66 | |||
67 | expect(res.body.html).to.equal(expectedHtml) | ||
68 | expect(res.body.title).to.equal(server.video.name) | ||
69 | expect(res.body.author_name).to.equal(server.video.author) | ||
70 | expect(res.body.height).to.equal(50) | ||
71 | expect(res.body.width).to.equal(50) | ||
72 | expect(res.body).to.not.have.property('thumbnail_url') | ||
73 | expect(res.body).to.not.have.property('thumbnail_width') | ||
74 | expect(res.body).to.not.have.property('thumbnail_height') | ||
75 | }) | ||
76 | |||
77 | after(async function () { | ||
78 | killallServers([ server ]) | ||
79 | |||
80 | // Keep the logs if the test failed | ||
81 | if (this['ok']) { | ||
82 | await flushTests() | ||
83 | } | ||
84 | }) | ||
85 | }) | ||
diff --git a/server/tests/client.ts b/server/tests/client.ts index 5e5abba5a..5f947ed2b 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts | |||
@@ -39,7 +39,7 @@ describe('Test a client controllers', function () { | |||
39 | server.video = videos[0] | 39 | server.video = videos[0] |
40 | }) | 40 | }) |
41 | 41 | ||
42 | it('It should have valid Open Graph tags on the watch page with video id', async function () { | 42 | it('Should have valid Open Graph tags on the watch page with video id', async function () { |
43 | const res = await request(server.url) | 43 | const res = await request(server.url) |
44 | .get('/videos/watch/' + server.video.id) | 44 | .get('/videos/watch/' + server.video.id) |
45 | .expect(200) | 45 | .expect(200) |
@@ -48,7 +48,7 @@ describe('Test a client controllers', function () { | |||
48 | expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />') | 48 | expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />') |
49 | }) | 49 | }) |
50 | 50 | ||
51 | it('It should have valid Open Graph tags on the watch page with video uuid', async function () { | 51 | it('Should have valid Open Graph tags on the watch page with video uuid', async function () { |
52 | const res = await request(server.url) | 52 | const res = await request(server.url) |
53 | .get('/videos/watch/' + server.video.uuid) | 53 | .get('/videos/watch/' + server.video.uuid) |
54 | .expect(200) | 54 | .expect(200) |
@@ -57,6 +57,19 @@ describe('Test a client controllers', function () { | |||
57 | expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />') | 57 | expect(res.text).to.contain('<meta property="og:description" content="my super description for pod 1" />') |
58 | }) | 58 | }) |
59 | 59 | ||
60 | it('Should have valid oEmbed discovery tags', async function () { | ||
61 | const path = '/videos/watch/' + server.video.uuid | ||
62 | const res = await request(server.url) | ||
63 | .get(path) | ||
64 | .expect(200) | ||
65 | |||
66 | const expectedLink = '<link rel="alternate" type="application/json+oembed" href="http://localhost:9001/services/oembed?' + | ||
67 | `url=http%3A%2F%2Flocalhost%3A9001%2Fvideos%2Fwatch%2F${server.video.uuid}" ` + | ||
68 | `title="${server.video.name}" />` | ||
69 | |||
70 | expect(res.text).to.contain(expectedLink) | ||
71 | }) | ||
72 | |||
60 | after(async function () { | 73 | after(async function () { |
61 | process.kill(-server.app.pid) | 74 | process.kill(-server.app.pid) |
62 | 75 | ||
diff --git a/server/tests/utils/index.ts b/server/tests/utils/index.ts index 99c445887..90ee2d515 100644 --- a/server/tests/utils/index.ts +++ b/server/tests/utils/index.ts | |||
@@ -7,6 +7,7 @@ export * from './pods' | |||
7 | export * from './request-schedulers' | 7 | export * from './request-schedulers' |
8 | export * from './requests' | 8 | export * from './requests' |
9 | export * from './servers' | 9 | export * from './servers' |
10 | export * from './services' | ||
10 | export * from './users' | 11 | export * from './users' |
11 | export * from './video-abuses' | 12 | export * from './video-abuses' |
12 | export * from './video-blacklist' | 13 | export * from './video-blacklist' |
diff --git a/server/tests/utils/servers.ts b/server/tests/utils/servers.ts index 88027f74e..3526ffa51 100644 --- a/server/tests/utils/servers.ts +++ b/server/tests/utils/servers.ts | |||
@@ -23,6 +23,8 @@ interface ServerInfo { | |||
23 | video?: { | 23 | video?: { |
24 | id: number | 24 | id: number |
25 | uuid: string | 25 | uuid: string |
26 | name: string | ||
27 | author: string | ||
26 | } | 28 | } |
27 | 29 | ||
28 | remoteVideo?: { | 30 | remoteVideo?: { |
diff --git a/server/tests/utils/services.ts b/server/tests/utils/services.ts new file mode 100644 index 000000000..1a53dd4cf --- /dev/null +++ b/server/tests/utils/services.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import * as request from 'supertest' | ||
2 | |||
3 | function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) { | ||
4 | const path = '/services/oembed' | ||
5 | const query = { | ||
6 | url: oembedUrl, | ||
7 | format, | ||
8 | maxheight: maxHeight, | ||
9 | maxwidth: maxWidth | ||
10 | } | ||
11 | |||
12 | return request(url) | ||
13 | .get(path) | ||
14 | .query(query) | ||
15 | .set('Accept', 'application/json') | ||
16 | .expect(200) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | getOEmbed | ||
23 | } | ||