aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/logs/logs.ts25
-rw-r--r--shared/core-utils/miscs/miscs.ts7
-rw-r--r--shared/extra-utils/cli/cli.ts (renamed from shared/utils/cli/cli.ts)0
-rw-r--r--shared/extra-utils/feeds/feeds.ts (renamed from shared/utils/feeds/feeds.ts)0
-rw-r--r--shared/extra-utils/index.ts (renamed from shared/utils/index.ts)5
-rw-r--r--shared/extra-utils/logs/logs.ts18
-rw-r--r--shared/extra-utils/miscs/email-child-process.js (renamed from shared/utils/miscs/email-child-process.js)2
-rw-r--r--shared/extra-utils/miscs/email.ts (renamed from shared/utils/miscs/email.ts)12
-rw-r--r--shared/extra-utils/miscs/miscs.ts (renamed from shared/utils/miscs/miscs.ts)2
-rw-r--r--shared/extra-utils/miscs/sql.ts80
-rw-r--r--shared/extra-utils/miscs/stubs.ts (renamed from shared/utils/miscs/stubs.ts)0
-rw-r--r--shared/extra-utils/overviews/overviews.ts (renamed from shared/utils/overviews/overviews.ts)0
-rw-r--r--shared/extra-utils/requests/activitypub.ts (renamed from shared/utils/requests/activitypub.ts)2
-rw-r--r--shared/extra-utils/requests/check-api-params.ts (renamed from shared/utils/requests/check-api-params.ts)0
-rw-r--r--shared/extra-utils/requests/requests.ts (renamed from shared/utils/requests/requests.ts)24
-rw-r--r--shared/extra-utils/search/video-channels.ts (renamed from shared/utils/search/video-channels.ts)0
-rw-r--r--shared/extra-utils/search/videos.ts (renamed from shared/utils/search/videos.ts)0
-rw-r--r--shared/extra-utils/server/activitypub.ts (renamed from shared/utils/server/activitypub.ts)0
-rw-r--r--shared/extra-utils/server/clients.ts (renamed from shared/utils/server/clients.ts)0
-rw-r--r--shared/extra-utils/server/config.ts (renamed from shared/utils/server/config.ts)17
-rw-r--r--shared/extra-utils/server/contact-form.ts (renamed from shared/utils/server/contact-form.ts)0
-rw-r--r--shared/extra-utils/server/follows.ts (renamed from shared/utils/server/follows.ts)46
-rw-r--r--shared/extra-utils/server/jobs.ts (renamed from shared/utils/server/jobs.ts)0
-rw-r--r--shared/extra-utils/server/redundancy.ts (renamed from shared/utils/server/redundancy.ts)0
-rw-r--r--shared/extra-utils/server/servers.ts313
-rw-r--r--shared/extra-utils/server/stats.ts (renamed from shared/utils/server/stats.ts)0
-rw-r--r--shared/extra-utils/socket/socket-io.ts (renamed from shared/utils/socket/socket-io.ts)0
-rw-r--r--shared/extra-utils/users/accounts.ts (renamed from shared/utils/users/accounts.ts)19
-rw-r--r--shared/extra-utils/users/blocklist.ts (renamed from shared/utils/users/blocklist.ts)0
-rw-r--r--shared/extra-utils/users/login.ts (renamed from shared/utils/users/login.ts)0
-rw-r--r--shared/extra-utils/users/user-notifications.ts (renamed from shared/utils/users/user-notifications.ts)67
-rw-r--r--shared/extra-utils/users/user-subscriptions.ts (renamed from shared/utils/users/user-subscriptions.ts)0
-rw-r--r--shared/extra-utils/users/users.ts (renamed from shared/utils/users/users.ts)42
-rw-r--r--shared/extra-utils/videos/services.ts (renamed from shared/utils/videos/services.ts)0
-rw-r--r--shared/extra-utils/videos/video-abuses.ts (renamed from shared/utils/videos/video-abuses.ts)0
-rw-r--r--shared/extra-utils/videos/video-blacklist.ts (renamed from shared/utils/videos/video-blacklist.ts)38
-rw-r--r--shared/extra-utils/videos/video-captions.ts (renamed from shared/utils/videos/video-captions.ts)0
-rw-r--r--shared/extra-utils/videos/video-change-ownership.ts (renamed from shared/utils/videos/video-change-ownership.ts)4
-rw-r--r--shared/extra-utils/videos/video-channels.ts (renamed from shared/utils/videos/video-channels.ts)18
-rw-r--r--shared/extra-utils/videos/video-comments.ts (renamed from shared/utils/videos/video-comments.ts)0
-rw-r--r--shared/extra-utils/videos/video-history.ts (renamed from shared/utils/videos/video-history.ts)0
-rw-r--r--shared/extra-utils/videos/video-imports.ts (renamed from shared/utils/videos/video-imports.ts)0
-rw-r--r--shared/extra-utils/videos/video-playlists.ts318
-rw-r--r--shared/extra-utils/videos/video-streaming-playlists.ts51
-rw-r--r--shared/extra-utils/videos/videos.ts (renamed from shared/utils/videos/videos.ts)85
-rw-r--r--shared/models/activitypub/activity.ts5
-rw-r--r--shared/models/activitypub/activitypub-actor.ts1
-rw-r--r--shared/models/activitypub/activitypub-ordered-collection.ts5
-rw-r--r--shared/models/activitypub/objects/cache-file-object.ts4
-rw-r--r--shared/models/activitypub/objects/common-objects.ts60
-rw-r--r--shared/models/activitypub/objects/playlist-element-object.ts10
-rw-r--r--shared/models/activitypub/objects/playlist-object.ts26
-rw-r--r--shared/models/activitypub/objects/video-torrent-object.ts4
-rw-r--r--shared/models/actors/account.model.ts11
-rw-r--r--shared/models/i18n/i18n.ts9
-rw-r--r--shared/models/overviews/videos-overview.ts4
-rw-r--r--shared/models/search/videos-search-query.model.ts3
-rw-r--r--shared/models/server/custom-config.model.ts20
-rw-r--r--shared/models/server/debug.model.ts3
-rw-r--r--shared/models/server/index.ts1
-rw-r--r--shared/models/server/job.model.ts2
-rw-r--r--shared/models/server/log-level.type.ts1
-rw-r--r--shared/models/server/server-config.model.ts21
-rw-r--r--shared/models/users/user-create.model.ts2
-rw-r--r--shared/models/users/user-flag.model.ts4
-rw-r--r--shared/models/users/user-notification-setting.model.ts2
-rw-r--r--shared/models/users/user-notification.model.ts9
-rw-r--r--shared/models/users/user-right.enum.ts8
-rw-r--r--shared/models/users/user-role.ts1
-rw-r--r--shared/models/users/user-update.model.ts3
-rw-r--r--shared/models/users/user.model.ts5
-rw-r--r--shared/models/videos/blacklist/video-blacklist.model.ts20
-rw-r--r--shared/models/videos/channel/video-channel.model.ts12
-rw-r--r--shared/models/videos/import/video-import.model.ts1
-rw-r--r--shared/models/videos/index.ts10
-rw-r--r--shared/models/videos/playlist/video-exist-in-playlist.model.ts7
-rw-r--r--shared/models/videos/playlist/video-playlist-create.model.ts11
-rw-r--r--shared/models/videos/playlist/video-playlist-element-create.model.ts6
-rw-r--r--shared/models/videos/playlist/video-playlist-element-update.model.ts4
-rw-r--r--shared/models/videos/playlist/video-playlist-privacy.model.ts5
-rw-r--r--shared/models/videos/playlist/video-playlist-reorder.model.ts5
-rw-r--r--shared/models/videos/playlist/video-playlist-type.model.ts4
-rw-r--r--shared/models/videos/playlist/video-playlist-update.model.ts10
-rw-r--r--shared/models/videos/playlist/video-playlist.model.ts26
-rw-r--r--shared/models/videos/rate/account-video-rate.model.ts7
-rw-r--r--shared/models/videos/thumbnail.type.ts4
-rw-r--r--shared/models/videos/video-create.model.ts2
-rw-r--r--shared/models/videos/video-streaming-playlist.model.ts12
-rw-r--r--shared/models/videos/video-streaming-playlist.type.ts3
-rw-r--r--shared/models/videos/video-update.model.ts2
-rw-r--r--shared/models/videos/video.model.ts37
-rw-r--r--shared/utils/miscs/sql.ts38
-rw-r--r--shared/utils/server/servers.ts210
93 files changed, 1479 insertions, 376 deletions
diff --git a/shared/core-utils/logs/logs.ts b/shared/core-utils/logs/logs.ts
new file mode 100644
index 000000000..d0996cf55
--- /dev/null
+++ b/shared/core-utils/logs/logs.ts
@@ -0,0 +1,25 @@
1import { stat } from 'fs-extra'
2
3async function mtimeSortFilesDesc (files: string[], basePath: string) {
4 const promises = []
5 const out: { file: string, mtime: number }[] = []
6
7 for (const file of files) {
8 const p = stat(basePath + '/' + file)
9 .then(stats => {
10 if (stats.isFile()) out.push({ file, mtime: stats.mtime.getTime() })
11 })
12
13 promises.push(p)
14 }
15
16 await Promise.all(promises)
17
18 out.sort((a, b) => b.mtime - a.mtime)
19
20 return out
21}
22
23export {
24 mtimeSortFilesDesc
25}
diff --git a/shared/core-utils/miscs/miscs.ts b/shared/core-utils/miscs/miscs.ts
new file mode 100644
index 000000000..c668e44c1
--- /dev/null
+++ b/shared/core-utils/miscs/miscs.ts
@@ -0,0 +1,7 @@
1function randomInt (low: number, high: number) {
2 return Math.floor(Math.random() * (high - low) + low)
3}
4
5export {
6 randomInt
7}
diff --git a/shared/utils/cli/cli.ts b/shared/extra-utils/cli/cli.ts
index 54d05e9c6..54d05e9c6 100644
--- a/shared/utils/cli/cli.ts
+++ b/shared/extra-utils/cli/cli.ts
diff --git a/shared/utils/feeds/feeds.ts b/shared/extra-utils/feeds/feeds.ts
index af6df2b20..af6df2b20 100644
--- a/shared/utils/feeds/feeds.ts
+++ b/shared/extra-utils/feeds/feeds.ts
diff --git a/shared/utils/index.ts b/shared/extra-utils/index.ts
index e08bbfd2a..9d0bbaa38 100644
--- a/shared/utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -8,15 +8,18 @@ export * from './miscs/miscs'
8export * from './miscs/stubs' 8export * from './miscs/stubs'
9export * from './miscs/sql' 9export * from './miscs/sql'
10export * from './server/follows' 10export * from './server/follows'
11export * from './requests/activitypub'
12export * from './requests/requests' 11export * from './requests/requests'
13export * from './requests/check-api-params' 12export * from './requests/check-api-params'
14export * from './server/servers' 13export * from './server/servers'
15export * from './videos/services' 14export * from './videos/services'
15export * from './videos/video-playlists'
16export * from './users/users' 16export * from './users/users'
17export * from './users/accounts'
17export * from './videos/video-abuses' 18export * from './videos/video-abuses'
18export * from './videos/video-blacklist' 19export * from './videos/video-blacklist'
19export * from './videos/video-channels' 20export * from './videos/video-channels'
21export * from './videos/video-comments'
22export * from './videos/video-streaming-playlists'
20export * from './videos/videos' 23export * from './videos/videos'
21export * from './videos/video-change-ownership' 24export * from './videos/video-change-ownership'
22export * from './feeds/feeds' 25export * from './feeds/feeds'
diff --git a/shared/extra-utils/logs/logs.ts b/shared/extra-utils/logs/logs.ts
new file mode 100644
index 000000000..cbb1afb93
--- /dev/null
+++ b/shared/extra-utils/logs/logs.ts
@@ -0,0 +1,18 @@
1import { makeGetRequest } from '../requests/requests'
2import { LogLevel } from '../../models/server/log-level.type'
3
4function getLogs (url: string, accessToken: string, startDate: Date, endDate?: Date, level?: LogLevel) {
5 const path = '/api/v1/server/logs'
6
7 return makeGetRequest({
8 url,
9 path,
10 token: accessToken,
11 query: { startDate, endDate, level },
12 statusCodeExpected: 200
13 })
14}
15
16export {
17 getLogs
18}
diff --git a/shared/utils/miscs/email-child-process.js b/shared/extra-utils/miscs/email-child-process.js
index 40ae37d70..088a5a08c 100644
--- a/shared/utils/miscs/email-child-process.js
+++ b/shared/extra-utils/miscs/email-child-process.js
@@ -6,7 +6,7 @@ process.on('message', (msg) => {
6 if (msg.start) { 6 if (msg.start) {
7 const maildev = new MailDev({ 7 const maildev = new MailDev({
8 ip: '127.0.0.1', 8 ip: '127.0.0.1',
9 smtp: 1025, 9 smtp: msg.port,
10 disableWeb: true, 10 disableWeb: true,
11 silent: true 11 silent: true
12 }) 12 })
diff --git a/shared/utils/miscs/email.ts b/shared/extra-utils/miscs/email.ts
index f9f1bd95b..b2a1093da 100644
--- a/shared/utils/miscs/email.ts
+++ b/shared/extra-utils/miscs/email.ts
@@ -1,4 +1,6 @@
1import { fork, ChildProcess } from 'child_process' 1import { ChildProcess, fork } from 'child_process'
2import { randomInt } from '../../core-utils/miscs/miscs'
3import { parallelTests } from '../server/servers'
2 4
3class MockSmtpServer { 5class MockSmtpServer {
4 6
@@ -20,7 +22,9 @@ class MockSmtpServer {
20 } 22 }
21 23
22 collectEmails (emailsCollection: object[]) { 24 collectEmails (emailsCollection: object[]) {
23 return new Promise((res, rej) => { 25 return new Promise<number>((res, rej) => {
26 const port = parallelTests() ? randomInt(1000, 2000) : 1025
27
24 if (this.started) { 28 if (this.started) {
25 this.emails = emailsCollection 29 this.emails = emailsCollection
26 return res() 30 return res()
@@ -28,7 +32,7 @@ class MockSmtpServer {
28 32
29 // ensure maildev isn't started until 33 // ensure maildev isn't started until
30 // unexpected exit can be reported to test runner 34 // unexpected exit can be reported to test runner
31 this.emailChildProcess.send({ start: true }) 35 this.emailChildProcess.send({ start: true, port })
32 this.emailChildProcess.on('exit', () => { 36 this.emailChildProcess.on('exit', () => {
33 return rej(new Error('maildev exited unexpectedly, confirm port not in use')) 37 return rej(new Error('maildev exited unexpectedly, confirm port not in use'))
34 }) 38 })
@@ -38,7 +42,7 @@ class MockSmtpServer {
38 } 42 }
39 this.started = true 43 this.started = true
40 this.emails = emailsCollection 44 this.emails = emailsCollection
41 return res() 45 return res(port)
42 }) 46 })
43 }) 47 })
44 } 48 }
diff --git a/shared/utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts
index 91a93b631..d1ffb7be4 100644
--- a/shared/utils/miscs/miscs.ts
+++ b/shared/extra-utils/miscs/miscs.ts
@@ -33,7 +33,7 @@ function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
33} 33}
34 34
35function root () { 35function root () {
36 // We are in /shared/utils/miscs 36 // We are in /miscs
37 return join(__dirname, '..', '..', '..') 37 return join(__dirname, '..', '..', '..')
38} 38}
39 39
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
new file mode 100644
index 000000000..3cfae5c23
--- /dev/null
+++ b/shared/extra-utils/miscs/sql.ts
@@ -0,0 +1,80 @@
1import { QueryTypes, Sequelize } from 'sequelize'
2
3let sequelizes: { [ id: number ]: Sequelize } = {}
4
5function getSequelize (serverNumber: number) {
6 if (sequelizes[serverNumber]) return sequelizes[serverNumber]
7
8 const dbname = 'peertube_test' + serverNumber
9 const username = 'peertube'
10 const password = 'peertube'
11 const host = 'localhost'
12 const port = 5432
13
14 const seq = new Sequelize(dbname, username, password, {
15 dialect: 'postgres',
16 host,
17 port,
18 logging: false
19 })
20
21 sequelizes[serverNumber] = seq
22
23 return seq
24}
25
26function setActorField (serverNumber: number, to: string, field: string, value: string) {
27 const seq = getSequelize(serverNumber)
28
29 const options = { type: QueryTypes.UPDATE }
30
31 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
32}
33
34function setVideoField (serverNumber: number, uuid: string, field: string, value: string) {
35 const seq = getSequelize(serverNumber)
36
37 const options = { type: QueryTypes.UPDATE }
38
39 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
40}
41
42function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) {
43 const seq = getSequelize(serverNumber)
44
45 const options = { type: QueryTypes.UPDATE }
46
47 return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
48}
49
50async function countVideoViewsOf (serverNumber: number, uuid: string) {
51 const seq = getSequelize(serverNumber)
52
53 // tslint:disable
54 const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
55
56 const options = { type: QueryTypes.SELECT as QueryTypes.SELECT }
57 const [ { total } ] = await seq.query<{ total: number }>(query, options)
58
59 if (!total) return 0
60
61 // FIXME: check if we really need parseInt
62 return parseInt(total + '', 10)
63}
64
65async function closeAllSequelize (servers: any[]) {
66 for (let i = 1; i <= servers.length; i++) {
67 if (sequelizes[ i ]) {
68 await sequelizes[ i ].close()
69 delete sequelizes[ i ]
70 }
71 }
72}
73
74export {
75 setVideoField,
76 setPlaylistField,
77 setActorField,
78 countVideoViewsOf,
79 closeAllSequelize
80}
diff --git a/shared/utils/miscs/stubs.ts b/shared/extra-utils/miscs/stubs.ts
index d1eb0e3b2..d1eb0e3b2 100644
--- a/shared/utils/miscs/stubs.ts
+++ b/shared/extra-utils/miscs/stubs.ts
diff --git a/shared/utils/overviews/overviews.ts b/shared/extra-utils/overviews/overviews.ts
index 23e3ceb1e..23e3ceb1e 100644
--- a/shared/utils/overviews/overviews.ts
+++ b/shared/extra-utils/overviews/overviews.ts
diff --git a/shared/utils/requests/activitypub.ts b/shared/extra-utils/requests/activitypub.ts
index e2348ace0..4762a8665 100644
--- a/shared/utils/requests/activitypub.ts
+++ b/shared/extra-utils/requests/activitypub.ts
@@ -1,5 +1,5 @@
1import { doRequest } from '../../../server/helpers/requests' 1import { doRequest } from '../../../server/helpers/requests'
2import { HTTP_SIGNATURE } from '../../../server/initializers' 2import { HTTP_SIGNATURE } from '../../../server/initializers/constants'
3import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' 3import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils'
4import { activityPubContextify } from '../../../server/helpers/activitypub' 4import { activityPubContextify } from '../../../server/helpers/activitypub'
5 5
diff --git a/shared/utils/requests/check-api-params.ts b/shared/extra-utils/requests/check-api-params.ts
index a2a549682..a2a549682 100644
--- a/shared/utils/requests/check-api-params.ts
+++ b/shared/extra-utils/requests/check-api-params.ts
diff --git a/shared/utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts
index 77e9f6164..3532fb429 100644
--- a/shared/utils/requests/requests.ts
+++ b/shared/extra-utils/requests/requests.ts
@@ -1,24 +1,36 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { buildAbsoluteFixturePath, root } from '../miscs/miscs' 2import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
3import { isAbsolute, join } from 'path' 3import { isAbsolute, join } from 'path'
4import { parse } from 'url'
5
6function get4KFileUrl () {
7 return 'https://download.cpy.re/peertube/4k_file.txt'
8}
9
10function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
11 const { host, protocol, pathname } = parse(url)
12
13 return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range })
14}
4 15
5function makeGetRequest (options: { 16function makeGetRequest (options: {
6 url: string, 17 url: string,
7 path: string, 18 path?: string,
8 query?: any, 19 query?: any,
9 token?: string, 20 token?: string,
10 statusCodeExpected?: number, 21 statusCodeExpected?: number,
11 contentType?: string 22 contentType?: string,
23 range?: string
12}) { 24}) {
13 if (!options.statusCodeExpected) options.statusCodeExpected = 400 25 if (!options.statusCodeExpected) options.statusCodeExpected = 400
14 if (options.contentType === undefined) options.contentType = 'application/json' 26 if (options.contentType === undefined) options.contentType = 'application/json'
15 27
16 const req = request(options.url) 28 const req = request(options.url).get(options.path)
17 .get(options.path)
18 29
19 if (options.contentType) req.set('Accept', options.contentType) 30 if (options.contentType) req.set('Accept', options.contentType)
20 if (options.token) req.set('Authorization', 'Bearer ' + options.token) 31 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
21 if (options.query) req.query(options.query) 32 if (options.query) req.query(options.query)
33 if (options.range) req.set('Range', options.range)
22 34
23 return req.expect(options.statusCodeExpected) 35 return req.expect(options.statusCodeExpected)
24} 36}
@@ -65,6 +77,8 @@ function makeUploadRequest (options: {
65 Object.keys(options.fields).forEach(field => { 77 Object.keys(options.fields).forEach(field => {
66 const value = options.fields[field] 78 const value = options.fields[field]
67 79
80 if (value === undefined) return
81
68 if (Array.isArray(value)) { 82 if (Array.isArray(value)) {
69 for (let i = 0; i < value.length; i++) { 83 for (let i = 0; i < value.length; i++) {
70 req.field(field + '[' + i + ']', value[i]) 84 req.field(field + '[' + i + ']', value[i])
@@ -158,11 +172,13 @@ function updateAvatarRequest (options: {
158// --------------------------------------------------------------------------- 172// ---------------------------------------------------------------------------
159 173
160export { 174export {
175 get4KFileUrl,
161 makeHTMLRequest, 176 makeHTMLRequest,
162 makeGetRequest, 177 makeGetRequest,
163 makeUploadRequest, 178 makeUploadRequest,
164 makePostBodyRequest, 179 makePostBodyRequest,
165 makePutBodyRequest, 180 makePutBodyRequest,
166 makeDeleteRequest, 181 makeDeleteRequest,
182 makeRawRequest,
167 updateAvatarRequest 183 updateAvatarRequest
168} 184}
diff --git a/shared/utils/search/video-channels.ts b/shared/extra-utils/search/video-channels.ts
index 0532134ae..0532134ae 100644
--- a/shared/utils/search/video-channels.ts
+++ b/shared/extra-utils/search/video-channels.ts
diff --git a/shared/utils/search/videos.ts b/shared/extra-utils/search/videos.ts
index ba4627017..ba4627017 100644
--- a/shared/utils/search/videos.ts
+++ b/shared/extra-utils/search/videos.ts
diff --git a/shared/utils/server/activitypub.ts b/shared/extra-utils/server/activitypub.ts
index eccb198ca..eccb198ca 100644
--- a/shared/utils/server/activitypub.ts
+++ b/shared/extra-utils/server/activitypub.ts
diff --git a/shared/utils/server/clients.ts b/shared/extra-utils/server/clients.ts
index 273aac747..273aac747 100644
--- a/shared/utils/server/clients.ts
+++ b/shared/extra-utils/server/clients.ts
diff --git a/shared/utils/server/config.ts b/shared/extra-utils/server/config.ts
index 0c5512bab..deb77e9c0 100644
--- a/shared/utils/server/config.ts
+++ b/shared/extra-utils/server/config.ts
@@ -52,6 +52,7 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
52 description: 'my super description', 52 description: 'my super description',
53 terms: 'my super terms', 53 terms: 'my super terms',
54 defaultClientRoute: '/videos/recently-added', 54 defaultClientRoute: '/videos/recently-added',
55 isNSFW: true,
55 defaultNSFWPolicy: 'blur', 56 defaultNSFWPolicy: 'blur',
56 customizations: { 57 customizations: {
57 javascript: 'alert("coucou")', 58 javascript: 'alert("coucou")',
@@ -97,6 +98,9 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
97 '480p': true, 98 '480p': true,
98 '720p': false, 99 '720p': false,
99 '1080p': false 100 '1080p': false
101 },
102 hls: {
103 enabled: false
100 } 104 }
101 }, 105 },
102 import: { 106 import: {
@@ -108,6 +112,19 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
108 enabled: false 112 enabled: false
109 } 113 }
110 } 114 }
115 },
116 autoBlacklist: {
117 videos: {
118 ofUsers: {
119 enabled: false
120 }
121 }
122 },
123 followers: {
124 instance: {
125 enabled: true,
126 manualApproval: false
127 }
111 } 128 }
112 } 129 }
113 130
diff --git a/shared/utils/server/contact-form.ts b/shared/extra-utils/server/contact-form.ts
index 80394cf99..80394cf99 100644
--- a/shared/utils/server/contact-form.ts
+++ b/shared/extra-utils/server/contact-form.ts
diff --git a/shared/utils/server/follows.ts b/shared/extra-utils/server/follows.ts
index 7741757a6..1505804de 100644
--- a/shared/utils/server/follows.ts
+++ b/shared/extra-utils/server/follows.ts
@@ -1,6 +1,7 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { ServerInfo } from './servers' 2import { ServerInfo } from './servers'
3import { waitJobs } from './jobs' 3import { waitJobs } from './jobs'
4import { makeGetRequest, makePostBodyRequest } from '..'
4 5
5function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 6function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
6 const path = '/api/v1/server/followers' 7 const path = '/api/v1/server/followers'
@@ -16,6 +17,28 @@ function getFollowersListPaginationAndSort (url: string, start: number, count: n
16 .expect('Content-Type', /json/) 17 .expect('Content-Type', /json/)
17} 18}
18 19
20function acceptFollower (url: string, token: string, follower: string, statusCodeExpected = 204) {
21 const path = '/api/v1/server/followers/' + follower + '/accept'
22
23 return makePostBodyRequest({
24 url,
25 token,
26 path,
27 statusCodeExpected
28 })
29}
30
31function rejectFollower (url: string, token: string, follower: string, statusCodeExpected = 204) {
32 const path = '/api/v1/server/followers/' + follower + '/reject'
33
34 return makePostBodyRequest({
35 url,
36 token,
37 path,
38 statusCodeExpected
39 })
40}
41
19function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 42function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
20 const path = '/api/v1/server/following' 43 const path = '/api/v1/server/following'
21 44
@@ -30,30 +53,36 @@ function getFollowingListPaginationAndSort (url: string, start: number, count: n
30 .expect('Content-Type', /json/) 53 .expect('Content-Type', /json/)
31} 54}
32 55
33async function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { 56function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) {
34 const path = '/api/v1/server/following' 57 const path = '/api/v1/server/following'
35 58
36 const followingHosts = following.map(f => f.replace(/^http:\/\//, '')) 59 const followingHosts = following.map(f => f.replace(/^http:\/\//, ''))
37 const res = await request(follower) 60 return request(follower)
38 .post(path) 61 .post(path)
39 .set('Accept', 'application/json') 62 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + accessToken) 63 .set('Authorization', 'Bearer ' + accessToken)
41 .send({ 'hosts': followingHosts }) 64 .send({ 'hosts': followingHosts })
42 .expect(expectedStatus) 65 .expect(expectedStatus)
43
44 return res
45} 66}
46 67
47async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { 68async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) {
48 const path = '/api/v1/server/following/' + target.host 69 const path = '/api/v1/server/following/' + target.host
49 70
50 const res = await request(url) 71 return request(url)
51 .delete(path) 72 .delete(path)
52 .set('Accept', 'application/json') 73 .set('Accept', 'application/json')
53 .set('Authorization', 'Bearer ' + accessToken) 74 .set('Authorization', 'Bearer ' + accessToken)
54 .expect(expectedStatus) 75 .expect(expectedStatus)
76}
77
78function removeFollower (url: string, accessToken: string, follower: ServerInfo, expectedStatus = 204) {
79 const path = '/api/v1/server/followers/peertube@' + follower.host
55 80
56 return res 81 return request(url)
82 .delete(path)
83 .set('Accept', 'application/json')
84 .set('Authorization', 'Bearer ' + accessToken)
85 .expect(expectedStatus)
57} 86}
58 87
59async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { 88async function doubleFollow (server1: ServerInfo, server2: ServerInfo) {
@@ -74,6 +103,9 @@ export {
74 getFollowersListPaginationAndSort, 103 getFollowersListPaginationAndSort,
75 getFollowingListPaginationAndSort, 104 getFollowingListPaginationAndSort,
76 unfollow, 105 unfollow,
106 removeFollower,
77 follow, 107 follow,
78 doubleFollow 108 doubleFollow,
109 acceptFollower,
110 rejectFollower
79} 111}
diff --git a/shared/utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts
index 692b5e24d..692b5e24d 100644
--- a/shared/utils/server/jobs.ts
+++ b/shared/extra-utils/server/jobs.ts
diff --git a/shared/utils/server/redundancy.ts b/shared/extra-utils/server/redundancy.ts
index c39ff2c8b..c39ff2c8b 100644
--- a/shared/utils/server/redundancy.ts
+++ b/shared/extra-utils/server/redundancy.ts
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
new file mode 100644
index 000000000..ed41bfa48
--- /dev/null
+++ b/shared/extra-utils/server/servers.ts
@@ -0,0 +1,313 @@
1/* tslint:disable:no-unused-expression */
2
3import { ChildProcess, exec, fork } from 'child_process'
4import { join } from 'path'
5import { root, wait } from '../miscs/miscs'
6import { copy, readdir, readFile, remove } from 'fs-extra'
7import { existsSync } from 'fs'
8import { expect } from 'chai'
9import { VideoChannel } from '../../models/videos'
10import { randomInt } from '../../core-utils/miscs/miscs'
11
12interface ServerInfo {
13 app: ChildProcess,
14 url: string
15 host: string
16
17 port: number
18 parallel: boolean
19 internalServerNumber: number
20 serverNumber: number
21
22 client: {
23 id: string,
24 secret: string
25 }
26
27 user: {
28 username: string,
29 password: string,
30 email?: string
31 }
32
33 customConfigFile?: string
34
35 accessToken?: string
36 videoChannel?: VideoChannel
37
38 video?: {
39 id: number
40 uuid: string
41 name: string
42 account: {
43 name: string
44 }
45 }
46
47 remoteVideo?: {
48 id: number
49 uuid: string
50 }
51
52 videos?: { id: number, uuid: string }[]
53}
54
55function parallelTests () {
56 return process.env.MOCHA_PARALLEL === 'true'
57}
58
59function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
60 let apps = []
61 let i = 0
62
63 return new Promise<ServerInfo[]>(res => {
64 function anotherServerDone (serverNumber, app) {
65 apps[serverNumber - 1] = app
66 i++
67 if (i === totalServers) {
68 return res(apps)
69 }
70 }
71
72 for (let j = 1; j <= totalServers; j++) {
73 flushAndRunServer(j, configOverride).then(app => anotherServerDone(j, app))
74 }
75 })
76}
77
78function flushTests (serverNumber?: number) {
79 return new Promise<void>((res, rej) => {
80 const suffix = serverNumber ? ` -- ${serverNumber}` : ''
81
82 return exec('npm run clean:server:test' + suffix, err => {
83 if (err) return rej(err)
84
85 return res()
86 })
87 })
88}
89
90function randomServer () {
91 const low = 10
92 const high = 10000
93
94 return randomInt(low, high)
95}
96
97async function flushAndRunServer (serverNumber: number, configOverride?: Object, args = []) {
98 const parallel = parallelTests()
99
100 const internalServerNumber = parallel ? randomServer() : serverNumber
101 const port = 9000 + internalServerNumber
102
103 await flushTests(internalServerNumber)
104
105 const server: ServerInfo = {
106 app: null,
107 port,
108 internalServerNumber,
109 parallel,
110 serverNumber,
111 url: `http://localhost:${port}`,
112 host: `localhost:${port}`,
113 client: {
114 id: null,
115 secret: null
116 },
117 user: {
118 username: null,
119 password: null
120 }
121 }
122
123 return runServer(server, configOverride, args)
124}
125
126async function runServer (server: ServerInfo, configOverrideArg?: any, args = []) {
127 // These actions are async so we need to be sure that they have both been done
128 const serverRunString = {
129 'Server listening': false
130 }
131 const key = 'Database peertube_test' + server.internalServerNumber + ' is ready'
132 serverRunString[key] = false
133
134 const regexps = {
135 client_id: 'Client id: (.+)',
136 client_secret: 'Client secret: (.+)',
137 user_username: 'Username: (.+)',
138 user_password: 'User password: (.+)'
139 }
140
141 if (server.internalServerNumber !== server.serverNumber) {
142 const basePath = join(root(), 'config')
143
144 const tmpConfigFile = join(basePath, `test-${server.internalServerNumber}.yaml`)
145 await copy(join(basePath, `test-${server.serverNumber}.yaml`), tmpConfigFile)
146
147 server.customConfigFile = tmpConfigFile
148 }
149
150 const configOverride: any = {}
151
152 if (server.parallel) {
153 Object.assign(configOverride, {
154 listen: {
155 port: server.port
156 },
157 webserver: {
158 port: server.port
159 },
160 database: {
161 suffix: '_test' + server.internalServerNumber
162 },
163 storage: {
164 tmp: `test${server.internalServerNumber}/tmp/`,
165 avatars: `test${server.internalServerNumber}/avatars/`,
166 videos: `test${server.internalServerNumber}/videos/`,
167 streaming_playlists: `test${server.internalServerNumber}/streaming-playlists/`,
168 redundancy: `test${server.internalServerNumber}/redundancy/`,
169 logs: `test${server.internalServerNumber}/logs/`,
170 previews: `test${server.internalServerNumber}/previews/`,
171 thumbnails: `test${server.internalServerNumber}/thumbnails/`,
172 torrents: `test${server.internalServerNumber}/torrents/`,
173 captions: `test${server.internalServerNumber}/captions/`,
174 cache: `test${server.internalServerNumber}/cache/`
175 },
176 admin: {
177 email: `admin${server.internalServerNumber}@example.com`
178 }
179 })
180 }
181
182 if (configOverrideArg !== undefined) {
183 Object.assign(configOverride, configOverrideArg)
184 }
185
186 // Share the environment
187 const env = Object.create(process.env)
188 env['NODE_ENV'] = 'test'
189 env['NODE_APP_INSTANCE'] = server.internalServerNumber.toString()
190 env['NODE_CONFIG'] = JSON.stringify(configOverride)
191
192 const options = {
193 silent: true,
194 env,
195 detached: true
196 }
197
198 return new Promise<ServerInfo>(res => {
199 server.app = fork(join(root(), 'dist', 'server.js'), args, options)
200 server.app.stdout.on('data', function onStdout (data) {
201 let dontContinue = false
202
203 // Capture things if we want to
204 for (const key of Object.keys(regexps)) {
205 const regexp = regexps[ key ]
206 const matches = data.toString().match(regexp)
207 if (matches !== null) {
208 if (key === 'client_id') server.client.id = matches[ 1 ]
209 else if (key === 'client_secret') server.client.secret = matches[ 1 ]
210 else if (key === 'user_username') server.user.username = matches[ 1 ]
211 else if (key === 'user_password') server.user.password = matches[ 1 ]
212 }
213 }
214
215 // Check if all required sentences are here
216 for (const key of Object.keys(serverRunString)) {
217 if (data.toString().indexOf(key) !== -1) serverRunString[ key ] = true
218 if (serverRunString[ key ] === false) dontContinue = true
219 }
220
221 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
222 if (dontContinue === true) return
223
224 server.app.stdout.removeListener('data', onStdout)
225
226 process.on('exit', () => {
227 try {
228 process.kill(server.app.pid)
229 } catch { /* empty */ }
230 })
231
232 res(server)
233 })
234 })
235}
236
237async function reRunServer (server: ServerInfo, configOverride?: any) {
238 const newServer = await runServer(server, configOverride)
239 server.app = newServer.app
240
241 return server
242}
243
244async function checkTmpIsEmpty (server: ServerInfo) {
245 return checkDirectoryIsEmpty(server, 'tmp')
246}
247
248async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
249 const testDirectory = 'test' + server.serverNumber
250
251 const directoryPath = join(root(), testDirectory, directory)
252
253 const directoryExists = existsSync(directoryPath)
254 expect(directoryExists).to.be.true
255
256 const files = await readdir(directoryPath)
257 expect(files).to.have.lengthOf(0)
258}
259
260function killallServers (servers: ServerInfo[]) {
261 for (const server of servers) {
262 if (!server.app) continue
263
264 process.kill(-server.app.pid)
265 server.app = null
266 }
267}
268
269function cleanupTests (servers: ServerInfo[]) {
270 killallServers(servers)
271
272 const p: Promise<any>[] = []
273 for (const server of servers) {
274 if (server.parallel) {
275 p.push(flushTests(server.internalServerNumber))
276 }
277
278 if (server.customConfigFile) {
279 p.push(remove(server.customConfigFile))
280 }
281 }
282
283 return Promise.all(p)
284}
285
286async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
287 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log')
288
289 while (true) {
290 const buf = await readFile(logfile)
291
292 const matches = buf.toString().match(new RegExp(str, 'g'))
293 if (matches && matches.length === count) return
294
295 await wait(1000)
296 }
297}
298
299// ---------------------------------------------------------------------------
300
301export {
302 checkDirectoryIsEmpty,
303 checkTmpIsEmpty,
304 ServerInfo,
305 parallelTests,
306 cleanupTests,
307 flushAndRunMultipleServers,
308 flushTests,
309 flushAndRunServer,
310 killallServers,
311 reRunServer,
312 waitUntilLog
313}
diff --git a/shared/utils/server/stats.ts b/shared/extra-utils/server/stats.ts
index 6f079ad18..6f079ad18 100644
--- a/shared/utils/server/stats.ts
+++ b/shared/extra-utils/server/stats.ts
diff --git a/shared/utils/socket/socket-io.ts b/shared/extra-utils/socket/socket-io.ts
index 854ab71af..854ab71af 100644
--- a/shared/utils/socket/socket-io.ts
+++ b/shared/extra-utils/socket/socket-io.ts
diff --git a/shared/utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
index 388eb6973..f64a2dbad 100644
--- a/shared/utils/users/accounts.ts
+++ b/shared/extra-utils/users/accounts.ts
@@ -1,11 +1,13 @@
1/* tslint:disable:no-unused-expression */ 1/* tslint:disable:no-unused-expression */
2 2
3import * as request from 'supertest'
3import { expect } from 'chai' 4import { expect } from 'chai'
4import { existsSync, readdir } from 'fs-extra' 5import { existsSync, readdir } from 'fs-extra'
5import { join } from 'path' 6import { join } from 'path'
6import { Account } from '../../models/actors' 7import { Account } from '../../models/actors'
7import { root } from '../miscs/miscs' 8import { root } from '../miscs/miscs'
8import { makeGetRequest } from '../requests/requests' 9import { makeGetRequest } from '../requests/requests'
10import { VideoRateType } from '../../models/videos'
9 11
10function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) { 12function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) {
11 const path = '/api/v1/accounts' 13 const path = '/api/v1/accounts'
@@ -53,11 +55,26 @@ async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: numb
53 } 55 }
54} 56}
55 57
58function getAccountRatings (url: string, accountName: string, accessToken: string, rating?: VideoRateType, statusCodeExpected = 200) {
59 const path = '/api/v1/accounts/' + accountName + '/ratings'
60
61 const query = rating ? { rating } : {}
62
63 return request(url)
64 .get(path)
65 .query(query)
66 .set('Accept', 'application/json')
67 .set('Authorization', 'Bearer ' + accessToken)
68 .expect(statusCodeExpected)
69 .expect('Content-Type', /json/)
70}
71
56// --------------------------------------------------------------------------- 72// ---------------------------------------------------------------------------
57 73
58export { 74export {
59 getAccount, 75 getAccount,
60 expectAccountFollows, 76 expectAccountFollows,
61 getAccountsList, 77 getAccountsList,
62 checkActorFilesWereRemoved 78 checkActorFilesWereRemoved,
79 getAccountRatings
63} 80}
diff --git a/shared/utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts
index 5feb84179..5feb84179 100644
--- a/shared/utils/users/blocklist.ts
+++ b/shared/extra-utils/users/blocklist.ts
diff --git a/shared/utils/users/login.ts b/shared/extra-utils/users/login.ts
index ddeb9df2a..ddeb9df2a 100644
--- a/shared/utils/users/login.ts
+++ b/shared/extra-utils/users/login.ts
diff --git a/shared/utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts
index c8ed7df30..495ff80d9 100644
--- a/shared/utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/user-notifications.ts
@@ -18,7 +18,7 @@ function updateMyNotificationSettings (url: string, token: string, settings: Use
18 }) 18 })
19} 19}
20 20
21function getUserNotifications ( 21async function getUserNotifications (
22 url: string, 22 url: string,
23 token: string, 23 token: string,
24 start: number, 24 start: number,
@@ -165,12 +165,15 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName
165 checkVideo(notification.video, videoName, videoUUID) 165 checkVideo(notification.video, videoName, videoUUID)
166 checkActor(notification.video.channel) 166 checkActor(notification.video.channel)
167 } else { 167 } else {
168 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName) 168 expect(notification).to.satisfy((n: UserNotification) => {
169 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
170 })
169 } 171 }
170 } 172 }
171 173
172 function emailFinder (email: object) { 174 function emailFinder (email: object) {
173 return email[ 'text' ].indexOf(videoUUID) !== -1 175 const text = email[ 'text' ]
176 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
174 } 177 }
175 178
176 await checkNotification(base, notificationChecker, emailFinder, type) 179 await checkNotification(base, notificationChecker, emailFinder, type)
@@ -295,6 +298,35 @@ async function checkNewActorFollow (
295 await checkNotification(base, notificationChecker, emailFinder, type) 298 await checkNotification(base, notificationChecker, emailFinder, type)
296} 299}
297 300
301async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
302 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
303
304 function notificationChecker (notification: UserNotification, type: CheckerType) {
305 if (type === 'presence') {
306 expect(notification).to.not.be.undefined
307 expect(notification.type).to.equal(notificationType)
308
309 checkActor(notification.actorFollow.follower)
310 expect(notification.actorFollow.follower.name).to.equal('peertube')
311 expect(notification.actorFollow.follower.host).to.equal(followerHost)
312
313 expect(notification.actorFollow.following.name).to.equal('peertube')
314 } else {
315 expect(notification).to.satisfy(n => {
316 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
317 })
318 }
319 }
320
321 function emailFinder (email: object) {
322 const text: string = email[ 'text' ]
323
324 return text.includes('instance has a new follower') && text.includes(followerHost)
325 }
326
327 await checkNotification(base, notificationChecker, emailFinder, type)
328}
329
298async function checkCommentMention ( 330async function checkCommentMention (
299 base: CheckerBaseParams, 331 base: CheckerBaseParams,
300 uuid: string, 332 uuid: string,
@@ -387,6 +419,31 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
387 await checkNotification(base, notificationChecker, emailFinder, type) 419 await checkNotification(base, notificationChecker, emailFinder, type)
388} 420}
389 421
422async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
423 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
424
425 function notificationChecker (notification: UserNotification, type: CheckerType) {
426 if (type === 'presence') {
427 expect(notification).to.not.be.undefined
428 expect(notification.type).to.equal(notificationType)
429
430 expect(notification.video.id).to.be.a('number')
431 checkVideo(notification.video, videoName, videoUUID)
432 } else {
433 expect(notification).to.satisfy((n: UserNotification) => {
434 return n === undefined || n.video === undefined || n.video.uuid !== videoUUID
435 })
436 }
437 }
438
439 function emailFinder (email: object) {
440 const text = email[ 'text' ]
441 return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1
442 }
443
444 await checkNotification(base, notificationChecker, emailFinder, type)
445}
446
390async function checkNewBlacklistOnMyVideo ( 447async function checkNewBlacklistOnMyVideo (
391 base: CheckerBaseParams, 448 base: CheckerBaseParams,
392 videoUUID: string, 449 videoUUID: string,
@@ -431,7 +488,9 @@ export {
431 checkCommentMention, 488 checkCommentMention,
432 updateMyNotificationSettings, 489 updateMyNotificationSettings,
433 checkNewVideoAbuseForModerators, 490 checkNewVideoAbuseForModerators,
491 checkVideoAutoBlacklistForModerators,
434 getUserNotifications, 492 getUserNotifications,
435 markAsReadNotifications, 493 markAsReadNotifications,
436 getLastNotification 494 getLastNotification,
495 checkNewInstanceFollower
437} 496}
diff --git a/shared/utils/users/user-subscriptions.ts b/shared/extra-utils/users/user-subscriptions.ts
index 7148fbfca..7148fbfca 100644
--- a/shared/utils/users/user-subscriptions.ts
+++ b/shared/extra-utils/users/user-subscriptions.ts
diff --git a/shared/utils/users/users.ts b/shared/extra-utils/users/users.ts
index 61a7e3757..2bd37b8be 100644
--- a/shared/utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -3,22 +3,38 @@ import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '..
3 3
4import { UserRole } from '../../index' 4import { UserRole } from '../../index'
5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' 5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
6import { ServerInfo, userLogin } from '..'
7import { UserAdminFlag } from '../../models/users/user-flag.model'
6 8
7function createUser ( 9type CreateUserArgs = { url: string,
8 url: string,
9 accessToken: string, 10 accessToken: string,
10 username: string, 11 username: string,
11 password: string, 12 password: string,
12 videoQuota = 1000000, 13 videoQuota?: number,
13 videoQuotaDaily = -1, 14 videoQuotaDaily?: number,
14 role: UserRole = UserRole.USER, 15 role?: UserRole,
15 specialStatus = 200 16 adminFlags?: UserAdminFlag,
16) { 17 specialStatus?: number
18}
19function createUser (parameters: CreateUserArgs) {
20 const {
21 url,
22 accessToken,
23 username,
24 adminFlags,
25 password = 'password',
26 videoQuota = 1000000,
27 videoQuotaDaily = -1,
28 role = UserRole.USER,
29 specialStatus = 200
30 } = parameters
31
17 const path = '/api/v1/users' 32 const path = '/api/v1/users'
18 const body = { 33 const body = {
19 username, 34 username,
20 password, 35 password,
21 role, 36 role,
37 adminFlags,
22 email: username + '@example.com', 38 email: username + '@example.com',
23 videoQuota, 39 videoQuota,
24 videoQuotaDaily 40 videoQuotaDaily
@@ -32,6 +48,13 @@ function createUser (
32 .expect(specialStatus) 48 .expect(specialStatus)
33} 49}
34 50
51async function generateUserAccessToken (server: ServerInfo, username: string) {
52 const password = 'my super password'
53 await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password })
54
55 return userLogin(server, { username, password })
56}
57
35function registerUser (url: string, username: string, password: string, specialStatus = 204) { 58function registerUser (url: string, username: string, password: string, specialStatus = 204) {
36 const path = '/api/v1/users/register' 59 const path = '/api/v1/users/register'
37 const body = { 60 const body = {
@@ -213,16 +236,20 @@ function updateUser (options: {
213 emailVerified?: boolean, 236 emailVerified?: boolean,
214 videoQuota?: number, 237 videoQuota?: number,
215 videoQuotaDaily?: number, 238 videoQuotaDaily?: number,
239 password?: string,
240 adminFlags?: UserAdminFlag,
216 role?: UserRole 241 role?: UserRole
217}) { 242}) {
218 const path = '/api/v1/users/' + options.userId 243 const path = '/api/v1/users/' + options.userId
219 244
220 const toSend = {} 245 const toSend = {}
246 if (options.password !== undefined && options.password !== null) toSend['password'] = options.password
221 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email 247 if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
222 if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified 248 if (options.emailVerified !== undefined && options.emailVerified !== null) toSend['emailVerified'] = options.emailVerified
223 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota 249 if (options.videoQuota !== undefined && options.videoQuota !== null) toSend['videoQuota'] = options.videoQuota
224 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily 250 if (options.videoQuotaDaily !== undefined && options.videoQuotaDaily !== null) toSend['videoQuotaDaily'] = options.videoQuotaDaily
225 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role 251 if (options.role !== undefined && options.role !== null) toSend['role'] = options.role
252 if (options.adminFlags !== undefined && options.adminFlags !== null) toSend['adminFlags'] = options.adminFlags
226 253
227 return makePutBodyRequest({ 254 return makePutBodyRequest({
228 url: options.url, 255 url: options.url,
@@ -298,5 +325,6 @@ export {
298 resetPassword, 325 resetPassword,
299 updateMyAvatar, 326 updateMyAvatar,
300 askSendVerifyEmail, 327 askSendVerifyEmail,
328 generateUserAccessToken,
301 verifyEmail 329 verifyEmail
302} 330}
diff --git a/shared/utils/videos/services.ts b/shared/extra-utils/videos/services.ts
index 1a53dd4cf..1a53dd4cf 100644
--- a/shared/utils/videos/services.ts
+++ b/shared/extra-utils/videos/services.ts
diff --git a/shared/utils/videos/video-abuses.ts b/shared/extra-utils/videos/video-abuses.ts
index 7f011ec0f..7f011ec0f 100644
--- a/shared/utils/videos/video-abuses.ts
+++ b/shared/extra-utils/videos/video-abuses.ts
diff --git a/shared/utils/videos/video-blacklist.ts b/shared/extra-utils/videos/video-blacklist.ts
index f2ae0ed26..e25a292fc 100644
--- a/shared/utils/videos/video-blacklist.ts
+++ b/shared/extra-utils/videos/video-blacklist.ts
@@ -1,4 +1,6 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { VideoBlacklistType } from '../../models/videos'
3import { makeGetRequest } from '..'
2 4
3function addVideoToBlacklist ( 5function addVideoToBlacklist (
4 url: string, 6 url: string,
@@ -39,28 +41,25 @@ function removeVideoFromBlacklist (url: string, token: string, videoId: number |
39 .expect(specialStatus) 41 .expect(specialStatus)
40} 42}
41 43
42function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) { 44function getBlacklistedVideosList (parameters: {
45 url: string,
46 token: string,
47 sort?: string,
48 type?: VideoBlacklistType,
49 specialStatus?: number
50}) {
51 let { url, token, sort, type, specialStatus = 200 } = parameters
43 const path = '/api/v1/videos/blacklist/' 52 const path = '/api/v1/videos/blacklist/'
44 53
45 return request(url) 54 const query = { sort, type }
46 .get(path)
47 .query({ sort: 'createdAt' })
48 .set('Accept', 'application/json')
49 .set('Authorization', 'Bearer ' + token)
50 .expect(specialStatus)
51 .expect('Content-Type', /json/)
52}
53 55
54function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) { 56 return makeGetRequest({
55 const path = '/api/v1/videos/blacklist/' 57 url,
56 58 path,
57 return request(url) 59 query,
58 .get(path) 60 token,
59 .query({ sort: sort }) 61 statusCodeExpected: specialStatus
60 .set('Accept', 'application/json') 62 })
61 .set('Authorization', 'Bearer ' + token)
62 .expect(specialStatus)
63 .expect('Content-Type', /json/)
64} 63}
65 64
66// --------------------------------------------------------------------------- 65// ---------------------------------------------------------------------------
@@ -69,6 +68,5 @@ export {
69 addVideoToBlacklist, 68 addVideoToBlacklist,
70 removeVideoFromBlacklist, 69 removeVideoFromBlacklist,
71 getBlacklistedVideosList, 70 getBlacklistedVideosList,
72 getSortedBlacklistedVideosList,
73 updateVideoBlacklist 71 updateVideoBlacklist
74} 72}
diff --git a/shared/utils/videos/video-captions.ts b/shared/extra-utils/videos/video-captions.ts
index 8d67f617b..8d67f617b 100644
--- a/shared/utils/videos/video-captions.ts
+++ b/shared/extra-utils/videos/video-captions.ts
diff --git a/shared/utils/videos/video-change-ownership.ts b/shared/extra-utils/videos/video-change-ownership.ts
index f288692ea..371d02000 100644
--- a/shared/utils/videos/video-change-ownership.ts
+++ b/shared/extra-utils/videos/video-change-ownership.ts
@@ -1,6 +1,6 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2 2
3function changeVideoOwnership (url: string, token: string, videoId: number | string, username) { 3function changeVideoOwnership (url: string, token: string, videoId: number | string, username, expectedStatus = 204) {
4 const path = '/api/v1/videos/' + videoId + '/give-ownership' 4 const path = '/api/v1/videos/' + videoId + '/give-ownership'
5 5
6 return request(url) 6 return request(url)
@@ -8,7 +8,7 @@ function changeVideoOwnership (url: string, token: string, videoId: number | str
8 .set('Accept', 'application/json') 8 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + token) 9 .set('Authorization', 'Bearer ' + token)
10 .send({ username }) 10 .send({ username })
11 .expect(204) 11 .expect(expectedStatus)
12} 12}
13 13
14function getVideoChangeOwnershipList (url: string, token: string) { 14function getVideoChangeOwnershipList (url: string, token: string) {
diff --git a/shared/utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 3935c261e..93a257bf9 100644
--- a/shared/utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -1,6 +1,8 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' 2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
3import { updateAvatarRequest } from '../requests/requests' 3import { updateAvatarRequest } from '../requests/requests'
4import { getMyUserInformation, ServerInfo } from '..'
5import { User } from '../..'
4 6
5function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { 7function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
6 const path = '/api/v1/video-channels' 8 const path = '/api/v1/video-channels'
@@ -105,6 +107,19 @@ function updateVideoChannelAvatar (options: {
105 return updateAvatarRequest(Object.assign(options, { path })) 107 return updateAvatarRequest(Object.assign(options, { path }))
106} 108}
107 109
110function setDefaultVideoChannel (servers: ServerInfo[]) {
111 const tasks: Promise<any>[] = []
112
113 for (const server of servers) {
114 const p = getMyUserInformation(server.url, server.accessToken)
115 .then(res => server.videoChannel = (res.body as User).videoChannels[0])
116
117 tasks.push(p)
118 }
119
120 return Promise.all(tasks)
121}
122
108// --------------------------------------------------------------------------- 123// ---------------------------------------------------------------------------
109 124
110export { 125export {
@@ -114,5 +129,6 @@ export {
114 addVideoChannel, 129 addVideoChannel,
115 updateVideoChannel, 130 updateVideoChannel,
116 deleteVideoChannel, 131 deleteVideoChannel,
117 getVideoChannel 132 getVideoChannel,
133 setDefaultVideoChannel
118} 134}
diff --git a/shared/utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts
index 0ebf69ced..0ebf69ced 100644
--- a/shared/utils/videos/video-comments.ts
+++ b/shared/extra-utils/videos/video-comments.ts
diff --git a/shared/utils/videos/video-history.ts b/shared/extra-utils/videos/video-history.ts
index dc7095b4d..dc7095b4d 100644
--- a/shared/utils/videos/video-history.ts
+++ b/shared/extra-utils/videos/video-history.ts
diff --git a/shared/utils/videos/video-imports.ts b/shared/extra-utils/videos/video-imports.ts
index ec77cdcda..ec77cdcda 100644
--- a/shared/utils/videos/video-imports.ts
+++ b/shared/extra-utils/videos/video-imports.ts
diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts
new file mode 100644
index 000000000..4d110a131
--- /dev/null
+++ b/shared/extra-utils/videos/video-playlists.ts
@@ -0,0 +1,318 @@
1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests'
2import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model'
3import { omit } from 'lodash'
4import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model'
5import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model'
6import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model'
7import { videoUUIDToId } from './videos'
8import { join } from 'path'
9import { root } from '..'
10import { readdir } from 'fs-extra'
11import { expect } from 'chai'
12import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model'
13
14function getVideoPlaylistsList (url: string, start: number, count: number, sort?: string) {
15 const path = '/api/v1/video-playlists'
16
17 const query = {
18 start,
19 count,
20 sort
21 }
22
23 return makeGetRequest({
24 url,
25 path,
26 query,
27 statusCodeExpected: 200
28 })
29}
30
31function getVideoChannelPlaylistsList (url: string, videoChannelName: string, start: number, count: number, sort?: string) {
32 const path = '/api/v1/video-channels/' + videoChannelName + '/video-playlists'
33
34 const query = {
35 start,
36 count,
37 sort
38 }
39
40 return makeGetRequest({
41 url,
42 path,
43 query,
44 statusCodeExpected: 200
45 })
46}
47
48function getAccountPlaylistsList (url: string, accountName: string, start: number, count: number, sort?: string) {
49 const path = '/api/v1/accounts/' + accountName + '/video-playlists'
50
51 const query = {
52 start,
53 count,
54 sort
55 }
56
57 return makeGetRequest({
58 url,
59 path,
60 query,
61 statusCodeExpected: 200
62 })
63}
64
65function getAccountPlaylistsListWithToken (
66 url: string,
67 token: string,
68 accountName: string,
69 start: number,
70 count: number,
71 playlistType?: VideoPlaylistType,
72 sort?: string
73) {
74 const path = '/api/v1/accounts/' + accountName + '/video-playlists'
75
76 const query = {
77 start,
78 count,
79 playlistType,
80 sort
81 }
82
83 return makeGetRequest({
84 url,
85 token,
86 path,
87 query,
88 statusCodeExpected: 200
89 })
90}
91
92function getVideoPlaylist (url: string, playlistId: number | string, statusCodeExpected = 200) {
93 const path = '/api/v1/video-playlists/' + playlistId
94
95 return makeGetRequest({
96 url,
97 path,
98 statusCodeExpected
99 })
100}
101
102function getVideoPlaylistWithToken (url: string, token: string, playlistId: number | string, statusCodeExpected = 200) {
103 const path = '/api/v1/video-playlists/' + playlistId
104
105 return makeGetRequest({
106 url,
107 token,
108 path,
109 statusCodeExpected
110 })
111}
112
113function deleteVideoPlaylist (url: string, token: string, playlistId: number | string, statusCodeExpected = 204) {
114 const path = '/api/v1/video-playlists/' + playlistId
115
116 return makeDeleteRequest({
117 url,
118 path,
119 token,
120 statusCodeExpected
121 })
122}
123
124function createVideoPlaylist (options: {
125 url: string,
126 token: string,
127 playlistAttrs: VideoPlaylistCreate,
128 expectedStatus?: number
129}) {
130 const path = '/api/v1/video-playlists'
131
132 const fields = omit(options.playlistAttrs, 'thumbnailfile')
133
134 const attaches = options.playlistAttrs.thumbnailfile
135 ? { thumbnailfile: options.playlistAttrs.thumbnailfile }
136 : {}
137
138 return makeUploadRequest({
139 method: 'POST',
140 url: options.url,
141 path,
142 token: options.token,
143 fields,
144 attaches,
145 statusCodeExpected: options.expectedStatus || 200
146 })
147}
148
149function updateVideoPlaylist (options: {
150 url: string,
151 token: string,
152 playlistAttrs: VideoPlaylistUpdate,
153 playlistId: number | string,
154 expectedStatus?: number
155}) {
156 const path = '/api/v1/video-playlists/' + options.playlistId
157
158 const fields = omit(options.playlistAttrs, 'thumbnailfile')
159
160 const attaches = options.playlistAttrs.thumbnailfile
161 ? { thumbnailfile: options.playlistAttrs.thumbnailfile }
162 : {}
163
164 return makeUploadRequest({
165 method: 'PUT',
166 url: options.url,
167 path,
168 token: options.token,
169 fields,
170 attaches,
171 statusCodeExpected: options.expectedStatus || 204
172 })
173}
174
175async function addVideoInPlaylist (options: {
176 url: string,
177 token: string,
178 playlistId: number | string,
179 elementAttrs: VideoPlaylistElementCreate | { videoId: string }
180 expectedStatus?: number
181}) {
182 options.elementAttrs.videoId = await videoUUIDToId(options.url, options.elementAttrs.videoId)
183
184 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos'
185
186 return makePostBodyRequest({
187 url: options.url,
188 path,
189 token: options.token,
190 fields: options.elementAttrs,
191 statusCodeExpected: options.expectedStatus || 200
192 })
193}
194
195function updateVideoPlaylistElement (options: {
196 url: string,
197 token: string,
198 playlistId: number | string,
199 videoId: number | string,
200 elementAttrs: VideoPlaylistElementUpdate,
201 expectedStatus?: number
202}) {
203 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId
204
205 return makePutBodyRequest({
206 url: options.url,
207 path,
208 token: options.token,
209 fields: options.elementAttrs,
210 statusCodeExpected: options.expectedStatus || 204
211 })
212}
213
214function removeVideoFromPlaylist (options: {
215 url: string,
216 token: string,
217 playlistId: number | string,
218 videoId: number | string,
219 expectedStatus?: number
220}) {
221 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.videoId
222
223 return makeDeleteRequest({
224 url: options.url,
225 path,
226 token: options.token,
227 statusCodeExpected: options.expectedStatus || 204
228 })
229}
230
231function reorderVideosPlaylist (options: {
232 url: string,
233 token: string,
234 playlistId: number | string,
235 elementAttrs: {
236 startPosition: number,
237 insertAfterPosition: number,
238 reorderLength?: number
239 },
240 expectedStatus?: number
241}) {
242 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
243
244 return makePostBodyRequest({
245 url: options.url,
246 path,
247 token: options.token,
248 fields: options.elementAttrs,
249 statusCodeExpected: options.expectedStatus || 204
250 })
251}
252
253async function checkPlaylistFilesWereRemoved (
254 playlistUUID: string,
255 serverNumber: number,
256 directories = [ 'thumbnails' ]
257) {
258 const testDirectory = 'test' + serverNumber
259
260 for (const directory of directories) {
261 const directoryPath = join(root(), testDirectory, directory)
262
263 const files = await readdir(directoryPath)
264 for (const file of files) {
265 expect(file).to.not.contain(playlistUUID)
266 }
267 }
268}
269
270function getVideoPlaylistPrivacies (url: string) {
271 const path = '/api/v1/video-playlists/privacies'
272
273 return makeGetRequest({
274 url,
275 path,
276 statusCodeExpected: 200
277 })
278}
279
280function doVideosExistInMyPlaylist (url: string, token: string, videoIds: number[]) {
281 const path = '/api/v1/users/me/video-playlists/videos-exist'
282
283 return makeGetRequest({
284 url,
285 token,
286 path,
287 query: { videoIds },
288 statusCodeExpected: 200
289 })
290}
291
292// ---------------------------------------------------------------------------
293
294export {
295 getVideoPlaylistPrivacies,
296
297 getVideoPlaylistsList,
298 getVideoChannelPlaylistsList,
299 getAccountPlaylistsList,
300 getAccountPlaylistsListWithToken,
301
302 getVideoPlaylist,
303 getVideoPlaylistWithToken,
304
305 createVideoPlaylist,
306 updateVideoPlaylist,
307 deleteVideoPlaylist,
308
309 addVideoInPlaylist,
310 updateVideoPlaylistElement,
311 removeVideoFromPlaylist,
312
313 reorderVideosPlaylist,
314
315 checkPlaylistFilesWereRemoved,
316
317 doVideosExistInMyPlaylist
318}
diff --git a/shared/extra-utils/videos/video-streaming-playlists.ts b/shared/extra-utils/videos/video-streaming-playlists.ts
new file mode 100644
index 000000000..eb25011cb
--- /dev/null
+++ b/shared/extra-utils/videos/video-streaming-playlists.ts
@@ -0,0 +1,51 @@
1import { makeRawRequest } from '../requests/requests'
2import { sha256 } from '../../../server/helpers/core-utils'
3import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model'
4import { expect } from 'chai'
5
6function getPlaylist (url: string, statusCodeExpected = 200) {
7 return makeRawRequest(url, statusCodeExpected)
8}
9
10function getSegment (url: string, statusCodeExpected = 200, range?: string) {
11 return makeRawRequest(url, statusCodeExpected, range)
12}
13
14function getSegmentSha256 (url: string, statusCodeExpected = 200) {
15 return makeRawRequest(url, statusCodeExpected)
16}
17
18async function checkSegmentHash (
19 baseUrlPlaylist: string,
20 baseUrlSegment: string,
21 videoUUID: string,
22 resolution: number,
23 hlsPlaylist: VideoStreamingPlaylist
24) {
25 const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`)
26 const playlist = res.text
27
28 const videoName = `${videoUUID}-${resolution}-fragmented.mp4`
29
30 const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
31
32 const length = parseInt(matches[1], 10)
33 const offset = parseInt(matches[2], 10)
34 const range = `${offset}-${offset + length - 1}`
35
36 const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`)
37
38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
39
40 const sha256Server = resSha.body[ videoName ][range]
41 expect(sha256(res2.body)).to.equal(sha256Server)
42}
43
44// ---------------------------------------------------------------------------
45
46export {
47 getPlaylist,
48 getSegment,
49 getSegmentSha256,
50 checkSegmentHash
51}
diff --git a/shared/utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index 0cf6e7c4f..b5a07b792 100644
--- a/shared/utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -1,7 +1,7 @@
1/* tslint:disable:no-unused-expression */ 1/* tslint:disable:no-unused-expression */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { existsSync, readdir, readFile } from 'fs-extra' 4import { pathExists, readdir, readFile } from 'fs-extra'
5import * as parseTorrent from 'parse-torrent' 5import * as parseTorrent from 'parse-torrent'
6import { extname, join } from 'path' 6import { extname, join } from 'path'
7import * as request from 'supertest' 7import * as request from 'supertest'
@@ -16,11 +16,13 @@ import {
16 ServerInfo, 16 ServerInfo,
17 testImage 17 testImage
18} from '../' 18} from '../'
19 19import * as validator from 'validator'
20import { VideoDetails, VideoPrivacy } from '../../models/videos' 20import { VideoDetails, VideoPrivacy } from '../../models/videos'
21import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' 21import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, loadLanguages, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
22import { dateIsValid, webtorrentAdd } from '../miscs/miscs' 22import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
23 23
24loadLanguages()
25
24type VideoAttributes = { 26type VideoAttributes = {
25 name?: string 27 name?: string
26 category?: number 28 category?: number
@@ -28,8 +30,10 @@ type VideoAttributes = {
28 language?: string 30 language?: string
29 nsfw?: boolean 31 nsfw?: boolean
30 commentsEnabled?: boolean 32 commentsEnabled?: boolean
33 downloadEnabled?: boolean
31 waitTranscoding?: boolean 34 waitTranscoding?: boolean
32 description?: string 35 description?: string
36 originallyPublishedAt?: string
33 tags?: string[] 37 tags?: string[]
34 channelId?: number 38 channelId?: number
35 privacy?: VideoPrivacy 39 privacy?: VideoPrivacy
@@ -221,6 +225,28 @@ function getVideoChannelVideos (
221 }) 225 })
222} 226}
223 227
228function getPlaylistVideos (
229 url: string,
230 accessToken: string,
231 playlistId: number | string,
232 start: number,
233 count: number,
234 query: { nsfw?: boolean } = {}
235) {
236 const path = '/api/v1/video-playlists/' + playlistId + '/videos'
237
238 return makeGetRequest({
239 url,
240 path,
241 query: immutableAssign(query, {
242 start,
243 count
244 }),
245 token: accessToken,
246 statusCodeExpected: 200
247 })
248}
249
224function getVideosListPagination (url: string, start: number, count: number, sort?: string) { 250function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
225 const path = '/api/v1/videos' 251 const path = '/api/v1/videos'
226 252
@@ -271,15 +297,24 @@ function removeVideo (url: string, token: string, id: number | string, expectedS
271async function checkVideoFilesWereRemoved ( 297async function checkVideoFilesWereRemoved (
272 videoUUID: string, 298 videoUUID: string,
273 serverNumber: number, 299 serverNumber: number,
274 directories = [ 'redundancy', 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ] 300 directories = [
301 'redundancy',
302 'videos',
303 'thumbnails',
304 'torrents',
305 'previews',
306 'captions',
307 join('playlists', 'hls'),
308 join('redundancy', 'hls')
309 ]
275) { 310) {
276 const testDirectory = 'test' + serverNumber 311 const testDirectory = 'test' + serverNumber
277 312
278 for (const directory of directories) { 313 for (const directory of directories) {
279 const directoryPath = join(root(), testDirectory, directory) 314 const directoryPath = join(root(), testDirectory, directory)
280 315
281 const directoryExists = existsSync(directoryPath) 316 const directoryExists = await pathExists(directoryPath)
282 expect(directoryExists).to.be.true 317 if (directoryExists === false) continue
283 318
284 const files = await readdir(directoryPath) 319 const files = await readdir(directoryPath)
285 for (const file of files) { 320 for (const file of files) {
@@ -311,6 +346,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
311 tags: [ 'tag' ], 346 tags: [ 'tag' ],
312 privacy: VideoPrivacy.PUBLIC, 347 privacy: VideoPrivacy.PUBLIC,
313 commentsEnabled: true, 348 commentsEnabled: true,
349 downloadEnabled: true,
314 fixture: 'video_short.webm' 350 fixture: 'video_short.webm'
315 }, videoAttributesArg) 351 }, videoAttributesArg)
316 352
@@ -321,6 +357,7 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
321 .field('name', attributes.name) 357 .field('name', attributes.name)
322 .field('nsfw', JSON.stringify(attributes.nsfw)) 358 .field('nsfw', JSON.stringify(attributes.nsfw))
323 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled)) 359 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
360 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
324 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding)) 361 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
325 .field('privacy', attributes.privacy.toString()) 362 .field('privacy', attributes.privacy.toString())
326 .field('channelId', attributes.channelId) 363 .field('channelId', attributes.channelId)
@@ -357,6 +394,10 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
357 } 394 }
358 } 395 }
359 396
397 if (attributes.originallyPublishedAt !== undefined) {
398 req.field('originallyPublishedAt', attributes.originallyPublishedAt)
399 }
400
360 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture)) 401 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
361 .expect(specialStatus) 402 .expect(specialStatus)
362} 403}
@@ -371,6 +412,8 @@ function updateVideo (url: string, accessToken: string, id: number | string, att
371 if (attributes.language) body['language'] = attributes.language 412 if (attributes.language) body['language'] = attributes.language
372 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw) 413 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
373 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled) 414 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
415 if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled)
416 if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt
374 if (attributes.description) body['description'] = attributes.description 417 if (attributes.description) body['description'] = attributes.description
375 if (attributes.tags) body['tags'] = attributes.tags 418 if (attributes.tags) body['tags'] = attributes.tags
376 if (attributes.privacy) body['privacy'] = attributes.privacy 419 if (attributes.privacy) body['privacy'] = attributes.privacy
@@ -436,9 +479,11 @@ async function completeVideoCheck (
436 language: string 479 language: string
437 nsfw: boolean 480 nsfw: boolean
438 commentsEnabled: boolean 481 commentsEnabled: boolean
482 downloadEnabled: boolean
439 description: string 483 description: string
440 publishedAt?: string 484 publishedAt?: string
441 support: string 485 support: string
486 originallyPublishedAt?: string,
442 account: { 487 account: {
443 name: string 488 name: string
444 host: string 489 host: string
@@ -496,6 +541,12 @@ async function completeVideoCheck (
496 expect(video.publishedAt).to.equal(attributes.publishedAt) 541 expect(video.publishedAt).to.equal(attributes.publishedAt)
497 } 542 }
498 543
544 if (attributes.originallyPublishedAt) {
545 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
546 } else {
547 expect(video.originallyPublishedAt).to.be.null
548 }
549
499 const res = await getVideo(url, video.uuid) 550 const res = await getVideo(url, video.uuid)
500 const videoDetails: VideoDetails = res.body 551 const videoDetails: VideoDetails = res.body
501 552
@@ -510,6 +561,7 @@ async function completeVideoCheck (
510 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true 561 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
511 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true 562 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
512 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled) 563 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
564 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
513 565
514 for (const attributeFile of attributes.files) { 566 for (const attributeFile of attributes.files) {
515 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution) 567 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
@@ -547,12 +599,29 @@ async function completeVideoCheck (
547 } 599 }
548} 600}
549 601
602async function videoUUIDToId (url: string, id: number | string) {
603 if (validator.isUUID('' + id) === false) return id
604
605 const res = await getVideo(url, id)
606 return res.body.id
607}
608
609async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) {
610 const videoAttrs: any = { name: options.videoName }
611 if (options.nsfw) videoAttrs.nsfw = options.nsfw
612
613 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
614
615 return { id: res.body.video.id, uuid: res.body.video.uuid }
616}
617
550// --------------------------------------------------------------------------- 618// ---------------------------------------------------------------------------
551 619
552export { 620export {
553 getVideoDescription, 621 getVideoDescription,
554 getVideoCategories, 622 getVideoCategories,
555 getVideoLicences, 623 getVideoLicences,
624 videoUUIDToId,
556 getVideoPrivacies, 625 getVideoPrivacies,
557 getVideoLanguages, 626 getVideoLanguages,
558 getMyVideos, 627 getMyVideos,
@@ -573,5 +642,7 @@ export {
573 parseTorrentVideo, 642 parseTorrentVideo,
574 getLocalVideos, 643 getLocalVideos,
575 completeVideoCheck, 644 completeVideoCheck,
576 checkVideoFilesWereRemoved 645 checkVideoFilesWereRemoved,
646 getPlaylistVideos,
647 uploadVideoAndGetId
577} 648}
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index 89994f665..95801190d 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -6,6 +6,7 @@ import { VideoAbuseObject } from './objects/video-abuse-object'
6import { VideoCommentObject } from './objects/video-comment-object' 6import { VideoCommentObject } from './objects/video-comment-object'
7import { ViewObject } from './objects/view-object' 7import { ViewObject } from './objects/view-object'
8import { APObject } from './objects/object.model' 8import { APObject } from './objects/object.model'
9import { PlaylistObject } from './objects/playlist-object'
9 10
10export type Activity = ActivityCreate | ActivityUpdate | 11export type Activity = ActivityCreate | ActivityUpdate |
11 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | 12 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
@@ -31,12 +32,12 @@ export interface BaseActivity {
31 32
32export interface ActivityCreate extends BaseActivity { 33export interface ActivityCreate extends BaseActivity {
33 type: 'Create' 34 type: 'Create'
34 object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject 35 object: VideoTorrentObject | VideoAbuseObject | ViewObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject
35} 36}
36 37
37export interface ActivityUpdate extends BaseActivity { 38export interface ActivityUpdate extends BaseActivity {
38 type: 'Update' 39 type: 'Update'
39 object: VideoTorrentObject | ActivityPubActor | CacheFileObject 40 object: VideoTorrentObject | ActivityPubActor | CacheFileObject | PlaylistObject
40} 41}
41 42
42export interface ActivityDelete extends BaseActivity { 43export interface ActivityDelete extends BaseActivity {
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index 119bc22d4..5e30bf783 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -8,6 +8,7 @@ export interface ActivityPubActor {
8 id: string 8 id: string
9 following: string 9 following: string
10 followers: string 10 followers: string
11 playlists?: string
11 inbox: string 12 inbox: string
12 outbox: string 13 outbox: string
13 preferredUsername: string 14 preferredUsername: string
diff --git a/shared/models/activitypub/activitypub-ordered-collection.ts b/shared/models/activitypub/activitypub-ordered-collection.ts
index dfec0bb76..3de0890bb 100644
--- a/shared/models/activitypub/activitypub-ordered-collection.ts
+++ b/shared/models/activitypub/activitypub-ordered-collection.ts
@@ -2,6 +2,9 @@ export interface ActivityPubOrderedCollection<T> {
2 '@context': string[] 2 '@context': string[]
3 type: 'OrderedCollection' | 'OrderedCollectionPage' 3 type: 'OrderedCollection' | 'OrderedCollectionPage'
4 totalItems: number 4 totalItems: number
5 partOf?: string
6 orderedItems: T[] 5 orderedItems: T[]
6
7 partOf?: string
8 next?: string
9 first?: string
7} 10}
diff --git a/shared/models/activitypub/objects/cache-file-object.ts b/shared/models/activitypub/objects/cache-file-object.ts
index 0a5125f5b..4b0a3a724 100644
--- a/shared/models/activitypub/objects/cache-file-object.ts
+++ b/shared/models/activitypub/objects/cache-file-object.ts
@@ -1,9 +1,9 @@
1import { ActivityVideoUrlObject } from './common-objects' 1import { ActivityVideoUrlObject, ActivityPlaylistUrlObject } from './common-objects'
2 2
3export interface CacheFileObject { 3export interface CacheFileObject {
4 id: string 4 id: string
5 type: 'CacheFile', 5 type: 'CacheFile',
6 object: string 6 object: string
7 expires: string 7 expires: string
8 url: ActivityVideoUrlObject 8 url: ActivityVideoUrlObject | ActivityPlaylistUrlObject
9} 9}
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index 118a4f43d..8c89810d6 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -28,25 +28,47 @@ export type ActivityVideoUrlObject = {
28 fps: number 28 fps: number
29} 29}
30 30
31export type ActivityUrlObject = 31export type ActivityPlaylistSegmentHashesObject = {
32 ActivityVideoUrlObject 32 type: 'Link'
33 | 33 name: 'sha256'
34 { 34 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0)
35 type: 'Link' 35 mimeType?: 'application/json'
36 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0) 36 mediaType: 'application/json'
37 mimeType?: 'application/x-bittorrent' | 'application/x-bittorrent;x-scheme-handler/magnet' 37 href: string
38 mediaType: 'application/x-bittorrent' | 'application/x-bittorrent;x-scheme-handler/magnet' 38}
39 href: string 39
40 height: number 40export type ActivityPlaylistInfohashesObject = {
41 } 41 type: 'Infohash'
42 | 42 name: string
43 { 43}
44 type: 'Link' 44
45 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0) 45export type ActivityPlaylistUrlObject = {
46 mimeType?: 'text/html' 46 type: 'Link'
47 mediaType: 'text/html' 47 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0)
48 href: string 48 mimeType?: 'application/x-mpegURL'
49 } 49 mediaType: 'application/x-mpegURL'
50 href: string
51 tag?: (ActivityPlaylistSegmentHashesObject | ActivityPlaylistInfohashesObject)[]
52}
53
54export type ActivityBitTorrentUrlObject = {
55 type: 'Link'
56 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0)
57 mimeType?: 'application/x-bittorrent' | 'application/x-bittorrent;x-scheme-handler/magnet'
58 mediaType: 'application/x-bittorrent' | 'application/x-bittorrent;x-scheme-handler/magnet'
59 href: string
60 height: number
61}
62
63export type ActivityHtmlUrlObject = {
64 type: 'Link'
65 // TODO: remove mimeType (backward compatibility, introduced in v1.1.0)
66 mimeType?: 'text/html'
67 mediaType: 'text/html'
68 href: string
69}
70
71export type ActivityUrlObject = ActivityVideoUrlObject | ActivityPlaylistUrlObject | ActivityBitTorrentUrlObject | ActivityHtmlUrlObject
50 72
51export interface ActivityPubAttributedTo { 73export interface ActivityPubAttributedTo {
52 type: 'Group' | 'Person' 74 type: 'Group' | 'Person'
diff --git a/shared/models/activitypub/objects/playlist-element-object.ts b/shared/models/activitypub/objects/playlist-element-object.ts
new file mode 100644
index 000000000..b85e4fe19
--- /dev/null
+++ b/shared/models/activitypub/objects/playlist-element-object.ts
@@ -0,0 +1,10 @@
1export interface PlaylistElementObject {
2 id: string
3 type: 'PlaylistElement'
4
5 url: string
6 position: number
7
8 startTimestamp?: number
9 stopTimestamp?: number
10}
diff --git a/shared/models/activitypub/objects/playlist-object.ts b/shared/models/activitypub/objects/playlist-object.ts
new file mode 100644
index 000000000..b561d8efd
--- /dev/null
+++ b/shared/models/activitypub/objects/playlist-object.ts
@@ -0,0 +1,26 @@
1import { ActivityIconObject } from './common-objects'
2
3export interface PlaylistObject {
4 id: string
5 type: 'Playlist'
6
7 name: string
8 content: string
9 uuid: string
10
11 totalItems: number
12 attributedTo: string[]
13
14 icon?: ActivityIconObject
15
16 published: string
17 updated: string
18
19 orderedItems?: string[]
20
21 partOf?: string
22 next?: string
23 first?: string
24
25 to?: string[]
26}
diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts
index 8504c178f..239822bc4 100644
--- a/shared/models/activitypub/objects/video-torrent-object.ts
+++ b/shared/models/activitypub/objects/video-torrent-object.ts
@@ -20,10 +20,12 @@ export interface VideoTorrentObject {
20 subtitleLanguage: ActivityIdentifierObject[] 20 subtitleLanguage: ActivityIdentifierObject[]
21 views: number 21 views: number
22 sensitive: boolean 22 sensitive: boolean
23 commentsEnabled: boolean 23 commentsEnabled: boolean,
24 downloadEnabled: boolean,
24 waitTranscoding: boolean 25 waitTranscoding: boolean
25 state: VideoState 26 state: VideoState
26 published: string 27 published: string
28 originallyPublishedAt: string
27 updated: string 29 updated: string
28 mediaType: 'text/markdown' 30 mediaType: 'text/markdown'
29 content: string 31 content: string
diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts
index 7f1dbbc37..043a2507e 100644
--- a/shared/models/actors/account.model.ts
+++ b/shared/models/actors/account.model.ts
@@ -1,4 +1,5 @@
1import { Actor } from './actor.model' 1import { Actor } from './actor.model'
2import { Avatar } from '../avatars'
2 3
3export interface Account extends Actor { 4export interface Account extends Actor {
4 displayName: string 5 displayName: string
@@ -6,3 +7,13 @@ export interface Account extends Actor {
6 7
7 userId?: number 8 userId?: number
8} 9}
10
11export interface AccountSummary {
12 id: number
13 uuid: string
14 name: string
15 displayName: string
16 url: string
17 host: string
18 avatar?: Avatar
19}
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts
index d7164b73f..5d1d3f2ab 100644
--- a/shared/models/i18n/i18n.ts
+++ b/shared/models/i18n/i18n.ts
@@ -3,16 +3,19 @@ export const LOCALE_FILES = [ 'player', 'server' ]
3export const I18N_LOCALES = { 3export const I18N_LOCALES = {
4 'en-US': 'English', 4 'en-US': 'English',
5 'fr-FR': 'Français', 5 'fr-FR': 'Français',
6 'ja-JP': '日本語',
6 'eu-ES': 'Euskara', 7 'eu-ES': 'Euskara',
7 'ca-ES': 'Català', 8 'ca-ES': 'Català',
8 'cs-CZ': 'Čeština', 9 'cs-CZ': 'Čeština',
9 'eo': 'Esperanto', 10 'eo': 'Esperanto',
10 'de-DE': 'Deutsch', 11 'de-DE': 'Deutsch',
11 'it-IT': 'Italiano', 12 'it-IT': 'Italiano',
13 'nl-NL': 'Nederlands',
12 'es-ES': 'Español', 14 'es-ES': 'Español',
13 'oc': 'Occitan', 15 'oc': 'Occitan',
14 'zh-Hant-TW': '繁體中文(台灣)', 16 'zh-Hant-TW': '繁體中文(台灣)',
15 'pt-BR': 'Português (Brasil)', 17 'pt-BR': 'Português (Brasil)',
18 'pt-PT': 'Português (Portugal)',
16 'sv-SE': 'svenska', 19 'sv-SE': 'svenska',
17 'pl-PL': 'Polski', 20 'pl-PL': 'Polski',
18 'ru-RU': 'русский', 21 'ru-RU': 'русский',
@@ -27,10 +30,12 @@ const I18N_LOCALE_ALIAS = {
27 'cs': 'cs-CZ', 30 'cs': 'cs-CZ',
28 'de': 'de-DE', 31 'de': 'de-DE',
29 'es': 'es-ES', 32 'es': 'es-ES',
30 'pt': 'pt-BR', 33 'pt': 'pt-PT',
31 'sv': 'sv-SE', 34 'sv': 'sv-SE',
32 'pl': 'pl-PL', 35 'pl': 'pl-PL',
33 'ru': 'ru-RU' 36 'ru': 'ru-RU',
37 'nl': 'nl-NL',
38 'zh': 'zh-Hans-CN'
34} 39}
35 40
36export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) 41export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES)
diff --git a/shared/models/overviews/videos-overview.ts b/shared/models/overviews/videos-overview.ts
index ee009d94c..e725f166b 100644
--- a/shared/models/overviews/videos-overview.ts
+++ b/shared/models/overviews/videos-overview.ts
@@ -1,8 +1,8 @@
1import { Video, VideoChannelAttribute, VideoConstant } from '../videos' 1import { Video, VideoChannelSummary, VideoConstant } from '../videos'
2 2
3export interface VideosOverview { 3export interface VideosOverview {
4 channels: { 4 channels: {
5 channel: VideoChannelAttribute 5 channel: VideoChannelSummary
6 videos: Video[] 6 videos: Video[]
7 }[] 7 }[]
8 8
diff --git a/shared/models/search/videos-search-query.model.ts b/shared/models/search/videos-search-query.model.ts
index 0db220758..838063095 100644
--- a/shared/models/search/videos-search-query.model.ts
+++ b/shared/models/search/videos-search-query.model.ts
@@ -11,6 +11,9 @@ export interface VideosSearchQuery {
11 startDate?: string // ISO 8601 11 startDate?: string // ISO 8601
12 endDate?: string // ISO 8601 12 endDate?: string // ISO 8601
13 13
14 originallyPublishedStartDate?: string // ISO 8601
15 originallyPublishedEndDate?: string // ISO 8601
16
14 nsfw?: NSFWQuery 17 nsfw?: NSFWQuery
15 18
16 categoryOneOf?: number[] 19 categoryOneOf?: number[]
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 7a3eaa33f..ca52eff4b 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -6,6 +6,7 @@ export interface CustomConfig {
6 shortDescription: string 6 shortDescription: string
7 description: string 7 description: string
8 terms: string 8 terms: string
9 isNSFW: boolean
9 defaultClientRoute: string 10 defaultClientRoute: string
10 defaultNSFWPolicy: NSFWPolicyType 11 defaultNSFWPolicy: NSFWPolicyType
11 customizations: { 12 customizations: {
@@ -61,6 +62,9 @@ export interface CustomConfig {
61 '720p': boolean 62 '720p': boolean
62 '1080p': boolean 63 '1080p': boolean
63 } 64 }
65 hls: {
66 enabled: boolean
67 }
64 } 68 }
65 69
66 import: { 70 import: {
@@ -73,4 +77,20 @@ export interface CustomConfig {
73 } 77 }
74 } 78 }
75 } 79 }
80
81 autoBlacklist: {
82 videos: {
83 ofUsers: {
84 enabled: boolean
85 }
86 }
87 }
88
89 followers: {
90 instance: {
91 enabled: boolean,
92 manualApproval: boolean
93 }
94 }
95
76} 96}
diff --git a/shared/models/server/debug.model.ts b/shared/models/server/debug.model.ts
new file mode 100644
index 000000000..61cba6518
--- /dev/null
+++ b/shared/models/server/debug.model.ts
@@ -0,0 +1,3 @@
1export interface Debug {
2 ip: string
3}
diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts
index c42f6f67f..bf61ab270 100644
--- a/shared/models/server/index.ts
+++ b/shared/models/server/index.ts
@@ -1,6 +1,7 @@
1export * from './about.model' 1export * from './about.model'
2export * from './contact-form.model' 2export * from './contact-form.model'
3export * from './custom-config.model' 3export * from './custom-config.model'
4export * from './debug.model'
4export * from './job.model' 5export * from './job.model'
5export * from './server-config.model' 6export * from './server-config.model'
6export * from './server-stats.model' 7export * from './server-stats.model'
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index 85bc9541b..1b9aa8a07 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -5,7 +5,7 @@ export type JobType = 'activitypub-http-unicast' |
5 'activitypub-http-fetcher' | 5 'activitypub-http-fetcher' |
6 'activitypub-follow' | 6 'activitypub-follow' |
7 'video-file-import' | 7 'video-file-import' |
8 'video-file' | 8 'video-transcoding' |
9 'email' | 9 'email' |
10 'video-import' | 10 'video-import' |
11 'videos-views' | 11 'videos-views' |
diff --git a/shared/models/server/log-level.type.ts b/shared/models/server/log-level.type.ts
new file mode 100644
index 000000000..ce91559e3
--- /dev/null
+++ b/shared/models/server/log-level.type.ts
@@ -0,0 +1 @@
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index f4245ed4d..d937e9c05 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -8,6 +8,7 @@ export interface ServerConfig {
8 name: string 8 name: string
9 shortDescription: string 9 shortDescription: string
10 defaultClientRoute: string 10 defaultClientRoute: string
11 isNSFW: boolean
11 defaultNSFWPolicy: NSFWPolicyType 12 defaultNSFWPolicy: NSFWPolicyType
12 customizations: { 13 customizations: {
13 javascript: string 14 javascript: string
@@ -25,11 +26,15 @@ export interface ServerConfig {
25 26
26 signup: { 27 signup: {
27 allowed: boolean, 28 allowed: boolean,
28 allowedForCurrentIP: boolean, 29 allowedForCurrentIP: boolean
29 requiresEmailVerification: boolean 30 requiresEmailVerification: boolean
30 } 31 }
31 32
32 transcoding: { 33 transcoding: {
34 hls: {
35 enabled: boolean
36 }
37
33 enabledResolutions: number[] 38 enabledResolutions: number[]
34 } 39 }
35 40
@@ -44,11 +49,19 @@ export interface ServerConfig {
44 } 49 }
45 } 50 }
46 51
52 autoBlacklist: {
53 videos: {
54 ofUsers: {
55 enabled: boolean
56 }
57 }
58 }
59
47 avatar: { 60 avatar: {
48 file: { 61 file: {
49 size: { 62 size: {
50 max: number 63 max: number
51 }, 64 }
52 extensions: string[] 65 extensions: string[]
53 } 66 }
54 } 67 }
@@ -84,4 +97,8 @@ export interface ServerConfig {
84 intervalDays: number 97 intervalDays: number
85 } 98 }
86 } 99 }
100
101 tracker: {
102 enabled: boolean
103 }
87} 104}
diff --git a/shared/models/users/user-create.model.ts b/shared/models/users/user-create.model.ts
index 08be4db05..6677b42aa 100644
--- a/shared/models/users/user-create.model.ts
+++ b/shared/models/users/user-create.model.ts
@@ -1,4 +1,5 @@
1import { UserRole } from './user-role' 1import { UserRole } from './user-role'
2import { UserAdminFlag } from './user-flag.model'
2 3
3export interface UserCreate { 4export interface UserCreate {
4 username: string 5 username: string
@@ -7,4 +8,5 @@ export interface UserCreate {
7 videoQuota: number 8 videoQuota: number
8 videoQuotaDaily: number 9 videoQuotaDaily: number
9 role: UserRole 10 role: UserRole
11 adminFlags?: UserAdminFlag
10} 12}
diff --git a/shared/models/users/user-flag.model.ts b/shared/models/users/user-flag.model.ts
new file mode 100644
index 000000000..f5759f18f
--- /dev/null
+++ b/shared/models/users/user-flag.model.ts
@@ -0,0 +1,4 @@
1export enum UserAdminFlag {
2 NONE = 0,
3 BY_PASS_VIDEO_AUTO_BLACKLIST = 1 << 0
4}
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts
index 531e12bba..e2a882b69 100644
--- a/shared/models/users/user-notification-setting.model.ts
+++ b/shared/models/users/user-notification-setting.model.ts
@@ -8,10 +8,12 @@ export interface UserNotificationSetting {
8 newVideoFromSubscription: UserNotificationSettingValue 8 newVideoFromSubscription: UserNotificationSettingValue
9 newCommentOnMyVideo: UserNotificationSettingValue 9 newCommentOnMyVideo: UserNotificationSettingValue
10 videoAbuseAsModerator: UserNotificationSettingValue 10 videoAbuseAsModerator: UserNotificationSettingValue
11 videoAutoBlacklistAsModerator: UserNotificationSettingValue
11 blacklistOnMyVideo: UserNotificationSettingValue 12 blacklistOnMyVideo: UserNotificationSettingValue
12 myVideoPublished: UserNotificationSettingValue 13 myVideoPublished: UserNotificationSettingValue
13 myVideoImportFinished: UserNotificationSettingValue 14 myVideoImportFinished: UserNotificationSettingValue
14 newUserRegistration: UserNotificationSettingValue 15 newUserRegistration: UserNotificationSettingValue
15 newFollow: UserNotificationSettingValue 16 newFollow: UserNotificationSettingValue
16 commentMention: UserNotificationSettingValue 17 commentMention: UserNotificationSettingValue
18 newInstanceFollower: UserNotificationSettingValue
17} 19}
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
index 186b62612..fafc2b7d7 100644
--- a/shared/models/users/user-notification.model.ts
+++ b/shared/models/users/user-notification.model.ts
@@ -1,3 +1,5 @@
1import { FollowState } from '../actors'
2
1export enum UserNotificationType { 3export enum UserNotificationType {
2 NEW_VIDEO_FROM_SUBSCRIPTION = 1, 4 NEW_VIDEO_FROM_SUBSCRIPTION = 1,
3 NEW_COMMENT_ON_MY_VIDEO = 2, 5 NEW_COMMENT_ON_MY_VIDEO = 2,
@@ -13,7 +15,11 @@ export enum UserNotificationType {
13 15
14 NEW_USER_REGISTRATION = 9, 16 NEW_USER_REGISTRATION = 9,
15 NEW_FOLLOW = 10, 17 NEW_FOLLOW = 10,
16 COMMENT_MENTION = 11 18 COMMENT_MENTION = 11,
19
20 VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12,
21
22 NEW_INSTANCE_FOLLOWER = 13
17} 23}
18 24
19export interface VideoInfo { 25export interface VideoInfo {
@@ -71,6 +77,7 @@ export interface UserNotification {
71 actorFollow?: { 77 actorFollow?: {
72 id: number 78 id: number
73 follower: ActorInfo 79 follower: ActorInfo
80 state: FollowState
74 following: { 81 following: {
75 type: 'account' | 'channel' 82 type: 'account' | 'channel'
76 name: string 83 name: string
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 090256bca..71701bdb4 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -5,6 +5,10 @@ export enum UserRight {
5 5
6 MANAGE_SERVER_FOLLOW, 6 MANAGE_SERVER_FOLLOW,
7 7
8 MANAGE_LOGS,
9
10 MANAGE_DEBUG,
11
8 MANAGE_SERVER_REDUNDANCY, 12 MANAGE_SERVER_REDUNDANCY,
9 13
10 MANAGE_VIDEO_ABUSES, 14 MANAGE_VIDEO_ABUSES,
@@ -20,8 +24,12 @@ export enum UserRight {
20 24
21 REMOVE_ANY_VIDEO, 25 REMOVE_ANY_VIDEO,
22 REMOVE_ANY_VIDEO_CHANNEL, 26 REMOVE_ANY_VIDEO_CHANNEL,
27 REMOVE_ANY_VIDEO_PLAYLIST,
23 REMOVE_ANY_VIDEO_COMMENT, 28 REMOVE_ANY_VIDEO_COMMENT,
29
24 UPDATE_ANY_VIDEO, 30 UPDATE_ANY_VIDEO,
31 UPDATE_ANY_VIDEO_PLAYLIST,
32
25 SEE_ALL_VIDEOS, 33 SEE_ALL_VIDEOS,
26 CHANGE_VIDEO_OWNERSHIP 34 CHANGE_VIDEO_OWNERSHIP
27} 35}
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts
index 59c2ba106..0b6554e51 100644
--- a/shared/models/users/user-role.ts
+++ b/shared/models/users/user-role.ts
@@ -25,6 +25,7 @@ const userRoleRights: { [ id: number ]: UserRight[] } = {
25 UserRight.MANAGE_VIDEO_ABUSES, 25 UserRight.MANAGE_VIDEO_ABUSES,
26 UserRight.REMOVE_ANY_VIDEO, 26 UserRight.REMOVE_ANY_VIDEO,
27 UserRight.REMOVE_ANY_VIDEO_CHANNEL, 27 UserRight.REMOVE_ANY_VIDEO_CHANNEL,
28 UserRight.REMOVE_ANY_VIDEO_PLAYLIST,
28 UserRight.REMOVE_ANY_VIDEO_COMMENT, 29 UserRight.REMOVE_ANY_VIDEO_COMMENT,
29 UserRight.UPDATE_ANY_VIDEO, 30 UserRight.UPDATE_ANY_VIDEO,
30 UserRight.SEE_ALL_VIDEOS, 31 UserRight.SEE_ALL_VIDEOS,
diff --git a/shared/models/users/user-update.model.ts b/shared/models/users/user-update.model.ts
index abde51321..fa43487ac 100644
--- a/shared/models/users/user-update.model.ts
+++ b/shared/models/users/user-update.model.ts
@@ -1,9 +1,12 @@
1import { UserRole } from './user-role' 1import { UserRole } from './user-role'
2import { UserAdminFlag } from './user-flag.model'
2 3
3export interface UserUpdate { 4export interface UserUpdate {
5 password?: string
4 email?: string 6 email?: string
5 emailVerified?: boolean 7 emailVerified?: boolean
6 videoQuota?: number 8 videoQuota?: number
7 videoQuotaDaily?: number 9 videoQuotaDaily?: number
8 role?: UserRole 10 role?: UserRole
11 adminFlags?: UserAdminFlag
9} 12}
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index af783d389..2f6a3c719 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -3,6 +3,7 @@ import { VideoChannel } from '../videos/channel/video-channel.model'
3import { UserRole } from './user-role' 3import { UserRole } from './user-role'
4import { NSFWPolicyType } from '../videos/nsfw-policy.type' 4import { NSFWPolicyType } from '../videos/nsfw-policy.type'
5import { UserNotificationSetting } from './user-notification-setting.model' 5import { UserNotificationSetting } from './user-notification-setting.model'
6import { UserAdminFlag } from './user-flag.model'
6 7
7export interface User { 8export interface User {
8 id: number 9 id: number
@@ -11,11 +12,15 @@ export interface User {
11 emailVerified: boolean 12 emailVerified: boolean
12 nsfwPolicy: NSFWPolicyType 13 nsfwPolicy: NSFWPolicyType
13 14
15 adminFlags?: UserAdminFlag
16
14 autoPlayVideo: boolean 17 autoPlayVideo: boolean
15 webTorrentEnabled: boolean 18 webTorrentEnabled: boolean
16 videosHistoryEnabled: boolean 19 videosHistoryEnabled: boolean
17 20
18 role: UserRole 21 role: UserRole
22 roleLabel: string
23
19 videoQuota: number 24 videoQuota: number
20 videoQuotaDaily: number 25 videoQuotaDaily: number
21 createdAt: Date 26 createdAt: Date
diff --git a/shared/models/videos/blacklist/video-blacklist.model.ts b/shared/models/videos/blacklist/video-blacklist.model.ts
index 4bd976190..68d59e489 100644
--- a/shared/models/videos/blacklist/video-blacklist.model.ts
+++ b/shared/models/videos/blacklist/video-blacklist.model.ts
@@ -1,19 +1,17 @@
1import { Video } from '../video.model'
2
3export enum VideoBlacklistType {
4 MANUAL = 1,
5 AUTO_BEFORE_PUBLISHED = 2
6}
7
1export interface VideoBlacklist { 8export interface VideoBlacklist {
2 id: number 9 id: number
3 createdAt: Date 10 createdAt: Date
4 updatedAt: Date 11 updatedAt: Date
5 unfederated: boolean 12 unfederated: boolean
6 reason?: string 13 reason?: string
14 type: VideoBlacklistType
7 15
8 video: { 16 video: Video
9 id: number
10 name: string
11 uuid: string
12 description: string
13 duration: number
14 views: number
15 likes: number
16 dislikes: number
17 nsfw: boolean
18 }
19} 17}
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index 92918f66c..14a813f8f 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -1,6 +1,6 @@
1import { Actor } from '../../actors/actor.model' 1import { Actor } from '../../actors/actor.model'
2import { Video } from '../video.model'
3import { Account } from '../../actors/index' 2import { Account } from '../../actors/index'
3import { Avatar } from '../../avatars'
4 4
5export interface VideoChannel extends Actor { 5export interface VideoChannel extends Actor {
6 displayName: string 6 displayName: string
@@ -9,3 +9,13 @@ export interface VideoChannel extends Actor {
9 isLocal: boolean 9 isLocal: boolean
10 ownerAccount?: Account 10 ownerAccount?: Account
11} 11}
12
13export interface VideoChannelSummary {
14 id: number
15 uuid: string
16 name: string
17 displayName: string
18 url: string
19 host: string
20 avatar?: Avatar
21}
diff --git a/shared/models/videos/import/video-import.model.ts b/shared/models/videos/import/video-import.model.ts
index e2a56617d..92856c70f 100644
--- a/shared/models/videos/import/video-import.model.ts
+++ b/shared/models/videos/import/video-import.model.ts
@@ -11,6 +11,7 @@ export interface VideoImport {
11 11
12 createdAt: string 12 createdAt: string
13 updatedAt: string 13 updatedAt: string
14 originallyPublishedAt?: string
14 state: VideoConstant<VideoImportState> 15 state: VideoConstant<VideoImportState>
15 error?: string 16 error?: string
16 17
diff --git a/shared/models/videos/index.ts b/shared/models/videos/index.ts
index 056ae06da..e3d78220e 100644
--- a/shared/models/videos/index.ts
+++ b/shared/models/videos/index.ts
@@ -1,5 +1,6 @@
1export * from './rate/user-video-rate-update.model' 1export * from './rate/user-video-rate-update.model'
2export * from './rate/user-video-rate.model' 2export * from './rate/user-video-rate.model'
3export * from './rate/account-video-rate.model'
3export * from './rate/user-video-rate.type' 4export * from './rate/user-video-rate.type'
4export * from './abuse/video-abuse-state.model' 5export * from './abuse/video-abuse-state.model'
5export * from './abuse/video-abuse-create.model' 6export * from './abuse/video-abuse-create.model'
@@ -11,6 +12,13 @@ export * from './blacklist/video-blacklist-update.model'
11export * from './channel/video-channel-create.model' 12export * from './channel/video-channel-create.model'
12export * from './channel/video-channel-update.model' 13export * from './channel/video-channel-update.model'
13export * from './channel/video-channel.model' 14export * from './channel/video-channel.model'
15export * from './playlist/video-playlist-create.model'
16export * from './playlist/video-playlist-element-create.model'
17export * from './playlist/video-playlist-element-update.model'
18export * from './playlist/video-playlist-privacy.model'
19export * from './playlist/video-playlist-type.model'
20export * from './playlist/video-playlist-update.model'
21export * from './playlist/video-playlist.model'
14export * from './video-change-ownership.model' 22export * from './video-change-ownership.model'
15export * from './video-change-ownership-create.model' 23export * from './video-change-ownership-create.model'
16export * from './video-create.model' 24export * from './video-create.model'
@@ -27,4 +35,4 @@ export * from './caption/video-caption-update.model'
27export * from './import/video-import-create.model' 35export * from './import/video-import-create.model'
28export * from './import/video-import-state.enum' 36export * from './import/video-import-state.enum'
29export * from './import/video-import.model' 37export * from './import/video-import.model'
30export { VideoConstant } from './video-constant.model' 38export * from './video-constant.model'
diff --git a/shared/models/videos/playlist/video-exist-in-playlist.model.ts b/shared/models/videos/playlist/video-exist-in-playlist.model.ts
new file mode 100644
index 000000000..71240f51d
--- /dev/null
+++ b/shared/models/videos/playlist/video-exist-in-playlist.model.ts
@@ -0,0 +1,7 @@
1export type VideoExistInPlaylist = {
2 [videoId: number ]: {
3 playlistId: number
4 startTimestamp?: number
5 stopTimestamp?: number
6 }[]
7}
diff --git a/shared/models/videos/playlist/video-playlist-create.model.ts b/shared/models/videos/playlist/video-playlist-create.model.ts
new file mode 100644
index 000000000..67a33fa35
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-create.model.ts
@@ -0,0 +1,11 @@
1import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
2
3export interface VideoPlaylistCreate {
4 displayName: string
5 privacy: VideoPlaylistPrivacy
6
7 description?: string
8 videoChannelId?: number
9
10 thumbnailfile?: any
11}
diff --git a/shared/models/videos/playlist/video-playlist-element-create.model.ts b/shared/models/videos/playlist/video-playlist-element-create.model.ts
new file mode 100644
index 000000000..c31702892
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-element-create.model.ts
@@ -0,0 +1,6 @@
1export interface VideoPlaylistElementCreate {
2 videoId: number
3
4 startTimestamp?: number
5 stopTimestamp?: number
6}
diff --git a/shared/models/videos/playlist/video-playlist-element-update.model.ts b/shared/models/videos/playlist/video-playlist-element-update.model.ts
new file mode 100644
index 000000000..15a30fbdc
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-element-update.model.ts
@@ -0,0 +1,4 @@
1export interface VideoPlaylistElementUpdate {
2 startTimestamp?: number
3 stopTimestamp?: number
4}
diff --git a/shared/models/videos/playlist/video-playlist-privacy.model.ts b/shared/models/videos/playlist/video-playlist-privacy.model.ts
new file mode 100644
index 000000000..96e5e2211
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-privacy.model.ts
@@ -0,0 +1,5 @@
1export enum VideoPlaylistPrivacy {
2 PUBLIC = 1,
3 UNLISTED = 2,
4 PRIVATE = 3
5}
diff --git a/shared/models/videos/playlist/video-playlist-reorder.model.ts b/shared/models/videos/playlist/video-playlist-reorder.model.ts
new file mode 100644
index 000000000..63ec714c5
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-reorder.model.ts
@@ -0,0 +1,5 @@
1export interface VideoPlaylistReorder {
2 startPosition: number
3 insertAfterPosition: number
4 reorderLength?: number
5}
diff --git a/shared/models/videos/playlist/video-playlist-type.model.ts b/shared/models/videos/playlist/video-playlist-type.model.ts
new file mode 100644
index 000000000..49233b743
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-type.model.ts
@@ -0,0 +1,4 @@
1export enum VideoPlaylistType {
2 REGULAR = 1,
3 WATCH_LATER = 2
4}
diff --git a/shared/models/videos/playlist/video-playlist-update.model.ts b/shared/models/videos/playlist/video-playlist-update.model.ts
new file mode 100644
index 000000000..0ff5bcb0f
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist-update.model.ts
@@ -0,0 +1,10 @@
1import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
2
3export interface VideoPlaylistUpdate {
4 displayName: string
5 privacy: VideoPlaylistPrivacy
6
7 description?: string
8 videoChannelId?: number
9 thumbnailfile?: any
10}
diff --git a/shared/models/videos/playlist/video-playlist.model.ts b/shared/models/videos/playlist/video-playlist.model.ts
new file mode 100644
index 000000000..c0941727a
--- /dev/null
+++ b/shared/models/videos/playlist/video-playlist.model.ts
@@ -0,0 +1,26 @@
1import { AccountSummary } from '../../actors/index'
2import { VideoChannelSummary, VideoConstant } from '..'
3import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
4import { VideoPlaylistType } from './video-playlist-type.model'
5
6export interface VideoPlaylist {
7 id: number
8 uuid: string
9 isLocal: boolean
10
11 displayName: string
12 description: string
13 privacy: VideoConstant<VideoPlaylistPrivacy>
14
15 thumbnailPath: string
16
17 videosLength: number
18
19 type: VideoConstant<VideoPlaylistType>
20
21 createdAt: Date | string
22 updatedAt: Date | string
23
24 ownerAccount: AccountSummary
25 videoChannel?: VideoChannelSummary
26}
diff --git a/shared/models/videos/rate/account-video-rate.model.ts b/shared/models/videos/rate/account-video-rate.model.ts
new file mode 100644
index 000000000..e789367dc
--- /dev/null
+++ b/shared/models/videos/rate/account-video-rate.model.ts
@@ -0,0 +1,7 @@
1import { UserVideoRateType } from './user-video-rate.type'
2import { Video } from '../video.model'
3
4export interface AccountVideoRate {
5 video: Video
6 rating: UserVideoRateType
7}
diff --git a/shared/models/videos/thumbnail.type.ts b/shared/models/videos/thumbnail.type.ts
new file mode 100644
index 000000000..d6c2bef7b
--- /dev/null
+++ b/shared/models/videos/thumbnail.type.ts
@@ -0,0 +1,4 @@
1export enum ThumbnailType {
2 MINIATURE = 1,
3 PREVIEW = 2
4}
diff --git a/shared/models/videos/video-create.model.ts b/shared/models/videos/video-create.model.ts
index 190d63783..53631bf79 100644
--- a/shared/models/videos/video-create.model.ts
+++ b/shared/models/videos/video-create.model.ts
@@ -13,6 +13,8 @@ export interface VideoCreate {
13 name: string 13 name: string
14 tags?: string[] 14 tags?: string[]
15 commentsEnabled?: boolean 15 commentsEnabled?: boolean
16 downloadEnabled?: boolean
16 privacy: VideoPrivacy 17 privacy: VideoPrivacy
17 scheduleUpdate?: VideoScheduleUpdate 18 scheduleUpdate?: VideoScheduleUpdate
19 originallyPublishedAt: Date | string
18} 20}
diff --git a/shared/models/videos/video-streaming-playlist.model.ts b/shared/models/videos/video-streaming-playlist.model.ts
new file mode 100644
index 000000000..17f8fe865
--- /dev/null
+++ b/shared/models/videos/video-streaming-playlist.model.ts
@@ -0,0 +1,12 @@
1import { VideoStreamingPlaylistType } from './video-streaming-playlist.type'
2
3export class VideoStreamingPlaylist {
4 id: number
5 type: VideoStreamingPlaylistType
6 playlistUrl: string
7 segmentsSha256Url: string
8
9 redundancies: {
10 baseUrl: string
11 }[]
12}
diff --git a/shared/models/videos/video-streaming-playlist.type.ts b/shared/models/videos/video-streaming-playlist.type.ts
new file mode 100644
index 000000000..3b403f295
--- /dev/null
+++ b/shared/models/videos/video-streaming-playlist.type.ts
@@ -0,0 +1,3 @@
1export enum VideoStreamingPlaylistType {
2 HLS = 1
3}
diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts
index ed141a824..4ef904156 100644
--- a/shared/models/videos/video-update.model.ts
+++ b/shared/models/videos/video-update.model.ts
@@ -11,10 +11,12 @@ export interface VideoUpdate {
11 privacy?: VideoPrivacy 11 privacy?: VideoPrivacy
12 tags?: string[] 12 tags?: string[]
13 commentsEnabled?: boolean 13 commentsEnabled?: boolean
14 downloadEnabled?: boolean
14 nsfw?: boolean 15 nsfw?: boolean
15 waitTranscoding?: boolean 16 waitTranscoding?: boolean
16 channelId?: number 17 channelId?: number
17 thumbnailfile?: Blob 18 thumbnailfile?: Blob
18 previewfile?: Blob 19 previewfile?: Blob
19 scheduleUpdate?: VideoScheduleUpdate 20 scheduleUpdate?: VideoScheduleUpdate
21 originallyPublishedAt?: Date | string
20} 22}
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 022876a0b..963268674 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -1,10 +1,11 @@
1import { VideoResolution, VideoState } from '../../index' 1import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index'
2import { Account } from '../actors' 2import { Account } from '../actors'
3import { Avatar } from '../avatars/avatar.model' 3import { Avatar } from '../avatars/avatar.model'
4import { VideoChannel } from './channel/video-channel.model' 4import { VideoChannel } from './channel/video-channel.model'
5import { VideoPrivacy } from './video-privacy.enum' 5import { VideoPrivacy } from './video-privacy.enum'
6import { VideoScheduleUpdate } from './video-schedule-update.model' 6import { VideoScheduleUpdate } from './video-schedule-update.model'
7import { VideoConstant } from './video-constant.model' 7import { VideoConstant } from './video-constant.model'
8import { VideoStreamingPlaylist } from './video-streaming-playlist.model'
8 9
9export interface VideoFile { 10export interface VideoFile {
10 magnetUri: string 11 magnetUri: string
@@ -17,24 +18,10 @@ export interface VideoFile {
17 fps: number 18 fps: number
18} 19}
19 20
20export interface VideoChannelAttribute { 21export interface PlaylistElement {
21 id: number 22 position: number
22 uuid: string 23 startTimestamp: number
23 name: string 24 stopTimestamp: number
24 displayName: string
25 url: string
26 host: string
27 avatar?: Avatar
28}
29
30export interface AccountAttribute {
31 id: number
32 uuid: string
33 name: string
34 displayName: string
35 url: string
36 host: string
37 avatar?: Avatar
38} 25}
39 26
40export interface Video { 27export interface Video {
@@ -43,6 +30,7 @@ export interface Video {
43 createdAt: Date | string 30 createdAt: Date | string
44 updatedAt: Date | string 31 updatedAt: Date | string
45 publishedAt: Date | string 32 publishedAt: Date | string
33 originallyPublishedAt: Date | string
46 category: VideoConstant<number> 34 category: VideoConstant<number>
47 licence: VideoConstant<number> 35 licence: VideoConstant<number>
48 language: VideoConstant<string> 36 language: VideoConstant<string>
@@ -66,12 +54,14 @@ export interface Video {
66 blacklisted?: boolean 54 blacklisted?: boolean
67 blacklistedReason?: string 55 blacklistedReason?: string
68 56
69 account: AccountAttribute 57 account: AccountSummary
70 channel: VideoChannelAttribute 58 channel: VideoChannelSummary
71 59
72 userHistory?: { 60 userHistory?: {
73 currentTime: number 61 currentTime: number
74 } 62 }
63
64 playlistElement?: PlaylistElement
75} 65}
76 66
77export interface VideoDetails extends Video { 67export interface VideoDetails extends Video {
@@ -82,8 +72,13 @@ export interface VideoDetails extends Video {
82 files: VideoFile[] 72 files: VideoFile[]
83 account: Account 73 account: Account
84 commentsEnabled: boolean 74 commentsEnabled: boolean
75 downloadEnabled: boolean
85 76
86 // Not optional in details (unlike in Video) 77 // Not optional in details (unlike in Video)
87 waitTranscoding: boolean 78 waitTranscoding: boolean
88 state: VideoConstant<VideoState> 79 state: VideoConstant<VideoState>
80
81 trackerUrls: string[]
82
83 streamingPlaylists: VideoStreamingPlaylist[]
89} 84}
diff --git a/shared/utils/miscs/sql.ts b/shared/utils/miscs/sql.ts
deleted file mode 100644
index 027f78131..000000000
--- a/shared/utils/miscs/sql.ts
+++ /dev/null
@@ -1,38 +0,0 @@
1import * as Sequelize from 'sequelize'
2
3function getSequelize (serverNumber: number) {
4 const dbname = 'peertube_test' + serverNumber
5 const username = 'peertube'
6 const password = 'peertube'
7 const host = 'localhost'
8 const port = 5432
9
10 return new Sequelize(dbname, username, password, {
11 dialect: 'postgres',
12 host,
13 port,
14 operatorsAliases: false,
15 logging: false
16 })
17}
18
19function setActorField (serverNumber: number, to: string, field: string, value: string) {
20 const seq = getSequelize(serverNumber)
21
22 const options = { type: Sequelize.QueryTypes.UPDATE }
23
24 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
25}
26
27function setVideoField (serverNumber: number, uuid: string, field: string, value: string) {
28 const seq = getSequelize(serverNumber)
29
30 const options = { type: Sequelize.QueryTypes.UPDATE }
31
32 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
33}
34
35export {
36 setVideoField,
37 setActorField
38}
diff --git a/shared/utils/server/servers.ts b/shared/utils/server/servers.ts
deleted file mode 100644
index cb57e0a69..000000000
--- a/shared/utils/server/servers.ts
+++ /dev/null
@@ -1,210 +0,0 @@
1/* tslint:disable:no-unused-expression */
2
3import { ChildProcess, exec, fork } from 'child_process'
4import { join } from 'path'
5import { root, wait } from '../miscs/miscs'
6import { readdir, readFile } from 'fs-extra'
7import { existsSync } from 'fs'
8import { expect } from 'chai'
9
10interface ServerInfo {
11 app: ChildProcess,
12 url: string
13 host: string
14 serverNumber: number
15
16 client: {
17 id: string,
18 secret: string
19 }
20
21 user: {
22 username: string,
23 password: string,
24 email?: string
25 }
26
27 accessToken?: string
28
29 video?: {
30 id: number
31 uuid: string
32 name: string
33 account: {
34 name: string
35 }
36 }
37
38 remoteVideo?: {
39 id: number
40 uuid: string
41 }
42}
43
44function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
45 let apps = []
46 let i = 0
47
48 return new Promise<ServerInfo[]>(res => {
49 function anotherServerDone (serverNumber, app) {
50 apps[serverNumber - 1] = app
51 i++
52 if (i === totalServers) {
53 return res(apps)
54 }
55 }
56
57 flushTests()
58 .then(() => {
59 for (let j = 1; j <= totalServers; j++) {
60 runServer(j, configOverride).then(app => anotherServerDone(j, app))
61 }
62 })
63 })
64}
65
66function flushTests () {
67 return new Promise<void>((res, rej) => {
68 return exec('npm run clean:server:test', err => {
69 if (err) return rej(err)
70
71 return res()
72 })
73 })
74}
75
76function runServer (serverNumber: number, configOverride?: Object, args = []) {
77 const server: ServerInfo = {
78 app: null,
79 serverNumber: serverNumber,
80 url: `http://localhost:${9000 + serverNumber}`,
81 host: `localhost:${9000 + serverNumber}`,
82 client: {
83 id: null,
84 secret: null
85 },
86 user: {
87 username: null,
88 password: null
89 }
90 }
91
92 // These actions are async so we need to be sure that they have both been done
93 const serverRunString = {
94 'Server listening': false
95 }
96 const key = 'Database peertube_test' + serverNumber + ' is ready'
97 serverRunString[key] = false
98
99 const regexps = {
100 client_id: 'Client id: (.+)',
101 client_secret: 'Client secret: (.+)',
102 user_username: 'Username: (.+)',
103 user_password: 'User password: (.+)'
104 }
105
106 // Share the environment
107 const env = Object.create(process.env)
108 env['NODE_ENV'] = 'test'
109 env['NODE_APP_INSTANCE'] = serverNumber.toString()
110
111 if (configOverride !== undefined) {
112 env['NODE_CONFIG'] = JSON.stringify(configOverride)
113 }
114
115 const options = {
116 silent: true,
117 env: env,
118 detached: true
119 }
120
121 return new Promise<ServerInfo>(res => {
122 server.app = fork(join(root(), 'dist', 'server.js'), args, options)
123 server.app.stdout.on('data', function onStdout (data) {
124 let dontContinue = false
125
126 // Capture things if we want to
127 for (const key of Object.keys(regexps)) {
128 const regexp = regexps[key]
129 const matches = data.toString().match(regexp)
130 if (matches !== null) {
131 if (key === 'client_id') server.client.id = matches[1]
132 else if (key === 'client_secret') server.client.secret = matches[1]
133 else if (key === 'user_username') server.user.username = matches[1]
134 else if (key === 'user_password') server.user.password = matches[1]
135 }
136 }
137
138 // Check if all required sentences are here
139 for (const key of Object.keys(serverRunString)) {
140 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
141 if (serverRunString[key] === false) dontContinue = true
142 }
143
144 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
145 if (dontContinue === true) return
146
147 server.app.stdout.removeListener('data', onStdout)
148
149 process.on('exit', () => {
150 try {
151 process.kill(server.app.pid)
152 } catch { /* empty */ }
153 })
154
155 res(server)
156 })
157
158 })
159}
160
161async function reRunServer (server: ServerInfo, configOverride?: any) {
162 const newServer = await runServer(server.serverNumber, configOverride)
163 server.app = newServer.app
164
165 return server
166}
167
168async function checkTmpIsEmpty (server: ServerInfo) {
169 const testDirectory = 'test' + server.serverNumber
170
171 const directoryPath = join(root(), testDirectory, 'tmp')
172
173 const directoryExists = existsSync(directoryPath)
174 expect(directoryExists).to.be.true
175
176 const files = await readdir(directoryPath)
177 expect(files).to.have.lengthOf(0)
178}
179
180function killallServers (servers: ServerInfo[]) {
181 for (const server of servers) {
182 process.kill(-server.app.pid)
183 }
184}
185
186async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
187 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log')
188
189 while (true) {
190 const buf = await readFile(logfile)
191
192 const matches = buf.toString().match(new RegExp(str, 'g'))
193 if (matches && matches.length === count) return
194
195 await wait(1000)
196 }
197}
198
199// ---------------------------------------------------------------------------
200
201export {
202 checkTmpIsEmpty,
203 ServerInfo,
204 flushAndRunMultipleServers,
205 flushTests,
206 runServer,
207 killallServers,
208 reRunServer,
209 waitUntilLog
210}