diff options
Diffstat (limited to 'shared')
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 @@ | |||
1 | import { stat } from 'fs-extra' | ||
2 | |||
3 | async 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 | |||
23 | export { | ||
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 @@ | |||
1 | function randomInt (low: number, high: number) { | ||
2 | return Math.floor(Math.random() * (high - low) + low) | ||
3 | } | ||
4 | |||
5 | export { | ||
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' | |||
8 | export * from './miscs/stubs' | 8 | export * from './miscs/stubs' |
9 | export * from './miscs/sql' | 9 | export * from './miscs/sql' |
10 | export * from './server/follows' | 10 | export * from './server/follows' |
11 | export * from './requests/activitypub' | ||
12 | export * from './requests/requests' | 11 | export * from './requests/requests' |
13 | export * from './requests/check-api-params' | 12 | export * from './requests/check-api-params' |
14 | export * from './server/servers' | 13 | export * from './server/servers' |
15 | export * from './videos/services' | 14 | export * from './videos/services' |
15 | export * from './videos/video-playlists' | ||
16 | export * from './users/users' | 16 | export * from './users/users' |
17 | export * from './users/accounts' | ||
17 | export * from './videos/video-abuses' | 18 | export * from './videos/video-abuses' |
18 | export * from './videos/video-blacklist' | 19 | export * from './videos/video-blacklist' |
19 | export * from './videos/video-channels' | 20 | export * from './videos/video-channels' |
21 | export * from './videos/video-comments' | ||
22 | export * from './videos/video-streaming-playlists' | ||
20 | export * from './videos/videos' | 23 | export * from './videos/videos' |
21 | export * from './videos/video-change-ownership' | 24 | export * from './videos/video-change-ownership' |
22 | export * from './feeds/feeds' | 25 | export * 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 @@ | |||
1 | import { makeGetRequest } from '../requests/requests' | ||
2 | import { LogLevel } from '../../models/server/log-level.type' | ||
3 | |||
4 | function 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 | |||
16 | export { | ||
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 @@ | |||
1 | import { fork, ChildProcess } from 'child_process' | 1 | import { ChildProcess, fork } from 'child_process' |
2 | import { randomInt } from '../../core-utils/miscs/miscs' | ||
3 | import { parallelTests } from '../server/servers' | ||
2 | 4 | ||
3 | class MockSmtpServer { | 5 | class 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 | ||
35 | function root () { | 35 | function 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 @@ | |||
1 | import { QueryTypes, Sequelize } from 'sequelize' | ||
2 | |||
3 | let sequelizes: { [ id: number ]: Sequelize } = {} | ||
4 | |||
5 | function 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 | |||
26 | function 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 | |||
34 | function 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 | |||
42 | function 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 | |||
50 | async 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 | |||
65 | async 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 | |||
74 | export { | ||
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 @@ | |||
1 | import { doRequest } from '../../../server/helpers/requests' | 1 | import { doRequest } from '../../../server/helpers/requests' |
2 | import { HTTP_SIGNATURE } from '../../../server/initializers' | 2 | import { HTTP_SIGNATURE } from '../../../server/initializers/constants' |
3 | import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' | 3 | import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils' |
4 | import { activityPubContextify } from '../../../server/helpers/activitypub' | 4 | import { 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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { buildAbsoluteFixturePath, root } from '../miscs/miscs' | 2 | import { buildAbsoluteFixturePath, root } from '../miscs/miscs' |
3 | import { isAbsolute, join } from 'path' | 3 | import { isAbsolute, join } from 'path' |
4 | import { parse } from 'url' | ||
5 | |||
6 | function get4KFileUrl () { | ||
7 | return 'https://download.cpy.re/peertube/4k_file.txt' | ||
8 | } | ||
9 | |||
10 | function 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 | ||
5 | function makeGetRequest (options: { | 16 | function 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 | ||
160 | export { | 174 | export { |
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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { ServerInfo } from './servers' | 2 | import { ServerInfo } from './servers' |
3 | import { waitJobs } from './jobs' | 3 | import { waitJobs } from './jobs' |
4 | import { makeGetRequest, makePostBodyRequest } from '..' | ||
4 | 5 | ||
5 | function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { | 6 | function 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 | ||
20 | function 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 | |||
31 | function 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 | |||
19 | function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { | 42 | function 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 | ||
33 | async function follow (follower: string, following: string[], accessToken: string, expectedStatus = 204) { | 56 | function 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 | ||
47 | async function unfollow (url: string, accessToken: string, target: ServerInfo, expectedStatus = 204) { | 68 | async 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 | |||
78 | function 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 | ||
59 | async function doubleFollow (server1: ServerInfo, server2: ServerInfo) { | 88 | async 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 | |||
3 | import { ChildProcess, exec, fork } from 'child_process' | ||
4 | import { join } from 'path' | ||
5 | import { root, wait } from '../miscs/miscs' | ||
6 | import { copy, readdir, readFile, remove } from 'fs-extra' | ||
7 | import { existsSync } from 'fs' | ||
8 | import { expect } from 'chai' | ||
9 | import { VideoChannel } from '../../models/videos' | ||
10 | import { randomInt } from '../../core-utils/miscs/miscs' | ||
11 | |||
12 | interface 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 | |||
55 | function parallelTests () { | ||
56 | return process.env.MOCHA_PARALLEL === 'true' | ||
57 | } | ||
58 | |||
59 | function 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 | |||
78 | function 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 | |||
90 | function randomServer () { | ||
91 | const low = 10 | ||
92 | const high = 10000 | ||
93 | |||
94 | return randomInt(low, high) | ||
95 | } | ||
96 | |||
97 | async 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 | |||
126 | async 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 | |||
237 | async function reRunServer (server: ServerInfo, configOverride?: any) { | ||
238 | const newServer = await runServer(server, configOverride) | ||
239 | server.app = newServer.app | ||
240 | |||
241 | return server | ||
242 | } | ||
243 | |||
244 | async function checkTmpIsEmpty (server: ServerInfo) { | ||
245 | return checkDirectoryIsEmpty(server, 'tmp') | ||
246 | } | ||
247 | |||
248 | async 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 | |||
260 | function 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 | |||
269 | function 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 | |||
286 | async 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 | |||
301 | export { | ||
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 | ||
3 | import * as request from 'supertest' | ||
3 | import { expect } from 'chai' | 4 | import { expect } from 'chai' |
4 | import { existsSync, readdir } from 'fs-extra' | 5 | import { existsSync, readdir } from 'fs-extra' |
5 | import { join } from 'path' | 6 | import { join } from 'path' |
6 | import { Account } from '../../models/actors' | 7 | import { Account } from '../../models/actors' |
7 | import { root } from '../miscs/miscs' | 8 | import { root } from '../miscs/miscs' |
8 | import { makeGetRequest } from '../requests/requests' | 9 | import { makeGetRequest } from '../requests/requests' |
10 | import { VideoRateType } from '../../models/videos' | ||
9 | 11 | ||
10 | function getAccountsList (url: string, sort = '-createdAt', statusCodeExpected = 200) { | 12 | function 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 | ||
58 | function 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 | ||
58 | export { | 74 | export { |
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 | ||
21 | function getUserNotifications ( | 21 | async 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 | ||
301 | async 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 | |||
298 | async function checkCommentMention ( | 330 | async 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 | ||
422 | async 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 | |||
390 | async function checkNewBlacklistOnMyVideo ( | 447 | async 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 | ||
4 | import { UserRole } from '../../index' | 4 | import { UserRole } from '../../index' |
5 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' | 5 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' |
6 | import { ServerInfo, userLogin } from '..' | ||
7 | import { UserAdminFlag } from '../../models/users/user-flag.model' | ||
6 | 8 | ||
7 | function createUser ( | 9 | type 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 | } | ||
19 | function 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 | ||
51 | async 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 | |||
35 | function registerUser (url: string, username: string, password: string, specialStatus = 204) { | 58 | function 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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { VideoBlacklistType } from '../../models/videos' | ||
3 | import { makeGetRequest } from '..' | ||
2 | 4 | ||
3 | function addVideoToBlacklist ( | 5 | function 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 | ||
42 | function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) { | 44 | function 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 | ||
54 | function 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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | 2 | ||
3 | function changeVideoOwnership (url: string, token: string, videoId: number | string, username) { | 3 | function 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 | ||
14 | function getVideoChangeOwnershipList (url: string, token: string) { | 14 | function 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 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' | 2 | import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' |
3 | import { updateAvatarRequest } from '../requests/requests' | 3 | import { updateAvatarRequest } from '../requests/requests' |
4 | import { getMyUserInformation, ServerInfo } from '..' | ||
5 | import { User } from '../..' | ||
4 | 6 | ||
5 | function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { | 7 | function 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 | ||
110 | function 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 | ||
110 | export { | 125 | export { |
@@ -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 @@ | |||
1 | import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest, makeUploadRequest } from '../requests/requests' | ||
2 | import { VideoPlaylistCreate } from '../../models/videos/playlist/video-playlist-create.model' | ||
3 | import { omit } from 'lodash' | ||
4 | import { VideoPlaylistUpdate } from '../../models/videos/playlist/video-playlist-update.model' | ||
5 | import { VideoPlaylistElementCreate } from '../../models/videos/playlist/video-playlist-element-create.model' | ||
6 | import { VideoPlaylistElementUpdate } from '../../models/videos/playlist/video-playlist-element-update.model' | ||
7 | import { videoUUIDToId } from './videos' | ||
8 | import { join } from 'path' | ||
9 | import { root } from '..' | ||
10 | import { readdir } from 'fs-extra' | ||
11 | import { expect } from 'chai' | ||
12 | import { VideoPlaylistType } from '../../models/videos/playlist/video-playlist-type.model' | ||
13 | |||
14 | function 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 | |||
31 | function 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 | |||
48 | function 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 | |||
65 | function 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 | |||
92 | function 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 | |||
102 | function 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 | |||
113 | function 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 | |||
124 | function 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 | |||
149 | function 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 | |||
175 | async 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 | |||
195 | function 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 | |||
214 | function 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 | |||
231 | function 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 | |||
253 | async 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 | |||
270 | function 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 | |||
280 | function 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 | |||
294 | export { | ||
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 @@ | |||
1 | import { makeRawRequest } from '../requests/requests' | ||
2 | import { sha256 } from '../../../server/helpers/core-utils' | ||
3 | import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model' | ||
4 | import { expect } from 'chai' | ||
5 | |||
6 | function getPlaylist (url: string, statusCodeExpected = 200) { | ||
7 | return makeRawRequest(url, statusCodeExpected) | ||
8 | } | ||
9 | |||
10 | function getSegment (url: string, statusCodeExpected = 200, range?: string) { | ||
11 | return makeRawRequest(url, statusCodeExpected, range) | ||
12 | } | ||
13 | |||
14 | function getSegmentSha256 (url: string, statusCodeExpected = 200) { | ||
15 | return makeRawRequest(url, statusCodeExpected) | ||
16 | } | ||
17 | |||
18 | async 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 | |||
46 | export { | ||
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 | ||
3 | import { expect } from 'chai' | 3 | import { expect } from 'chai' |
4 | import { existsSync, readdir, readFile } from 'fs-extra' | 4 | import { pathExists, readdir, readFile } from 'fs-extra' |
5 | import * as parseTorrent from 'parse-torrent' | 5 | import * as parseTorrent from 'parse-torrent' |
6 | import { extname, join } from 'path' | 6 | import { extname, join } from 'path' |
7 | import * as request from 'supertest' | 7 | import * as request from 'supertest' |
@@ -16,11 +16,13 @@ import { | |||
16 | ServerInfo, | 16 | ServerInfo, |
17 | testImage | 17 | testImage |
18 | } from '../' | 18 | } from '../' |
19 | 19 | import * as validator from 'validator' | |
20 | import { VideoDetails, VideoPrivacy } from '../../models/videos' | 20 | import { VideoDetails, VideoPrivacy } from '../../models/videos' |
21 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' | 21 | import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, loadLanguages, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants' |
22 | import { dateIsValid, webtorrentAdd } from '../miscs/miscs' | 22 | import { dateIsValid, webtorrentAdd } from '../miscs/miscs' |
23 | 23 | ||
24 | loadLanguages() | ||
25 | |||
24 | type VideoAttributes = { | 26 | type 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 | ||
228 | function 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 | |||
224 | function getVideosListPagination (url: string, start: number, count: number, sort?: string) { | 250 | function 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 | |||
271 | async function checkVideoFilesWereRemoved ( | 297 | async 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 | ||
602 | async 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 | |||
609 | async 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 | ||
552 | export { | 620 | export { |
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' | |||
6 | import { VideoCommentObject } from './objects/video-comment-object' | 6 | import { VideoCommentObject } from './objects/video-comment-object' |
7 | import { ViewObject } from './objects/view-object' | 7 | import { ViewObject } from './objects/view-object' |
8 | import { APObject } from './objects/object.model' | 8 | import { APObject } from './objects/object.model' |
9 | import { PlaylistObject } from './objects/playlist-object' | ||
9 | 10 | ||
10 | export type Activity = ActivityCreate | ActivityUpdate | | 11 | export type Activity = ActivityCreate | ActivityUpdate | |
11 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | | 12 | ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | |
@@ -31,12 +32,12 @@ export interface BaseActivity { | |||
31 | 32 | ||
32 | export interface ActivityCreate extends BaseActivity { | 33 | export 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 | ||
37 | export interface ActivityUpdate extends BaseActivity { | 38 | export 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 | ||
42 | export interface ActivityDelete extends BaseActivity { | 43 | export 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 @@ | |||
1 | import { ActivityVideoUrlObject } from './common-objects' | 1 | import { ActivityVideoUrlObject, ActivityPlaylistUrlObject } from './common-objects' |
2 | 2 | ||
3 | export interface CacheFileObject { | 3 | export 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 | ||
31 | export type ActivityUrlObject = | 31 | export 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 | 40 | export 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) | 45 | export 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 | |||
54 | export 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 | |||
63 | export 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 | |||
71 | export type ActivityUrlObject = ActivityVideoUrlObject | ActivityPlaylistUrlObject | ActivityBitTorrentUrlObject | ActivityHtmlUrlObject | ||
50 | 72 | ||
51 | export interface ActivityPubAttributedTo { | 73 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { ActivityIconObject } from './common-objects' | ||
2 | |||
3 | export 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 @@ | |||
1 | import { Actor } from './actor.model' | 1 | import { Actor } from './actor.model' |
2 | import { Avatar } from '../avatars' | ||
2 | 3 | ||
3 | export interface Account extends Actor { | 4 | export 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 | |||
11 | export 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' ] | |||
3 | export const I18N_LOCALES = { | 3 | export 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 | ||
36 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) | 41 | export 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 @@ | |||
1 | import { Video, VideoChannelAttribute, VideoConstant } from '../videos' | 1 | import { Video, VideoChannelSummary, VideoConstant } from '../videos' |
2 | 2 | ||
3 | export interface VideosOverview { | 3 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | export * from './about.model' | 1 | export * from './about.model' |
2 | export * from './contact-form.model' | 2 | export * from './contact-form.model' |
3 | export * from './custom-config.model' | 3 | export * from './custom-config.model' |
4 | export * from './debug.model' | ||
4 | export * from './job.model' | 5 | export * from './job.model' |
5 | export * from './server-config.model' | 6 | export * from './server-config.model' |
6 | export * from './server-stats.model' | 7 | export * 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 @@ | |||
1 | import { UserRole } from './user-role' | 1 | import { UserRole } from './user-role' |
2 | import { UserAdminFlag } from './user-flag.model' | ||
2 | 3 | ||
3 | export interface UserCreate { | 4 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { FollowState } from '../actors' | ||
2 | |||
1 | export enum UserNotificationType { | 3 | export 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 | ||
19 | export interface VideoInfo { | 25 | export 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 @@ | |||
1 | import { UserRole } from './user-role' | 1 | import { UserRole } from './user-role' |
2 | import { UserAdminFlag } from './user-flag.model' | ||
2 | 3 | ||
3 | export interface UserUpdate { | 4 | export 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' | |||
3 | import { UserRole } from './user-role' | 3 | import { UserRole } from './user-role' |
4 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' | 4 | import { NSFWPolicyType } from '../videos/nsfw-policy.type' |
5 | import { UserNotificationSetting } from './user-notification-setting.model' | 5 | import { UserNotificationSetting } from './user-notification-setting.model' |
6 | import { UserAdminFlag } from './user-flag.model' | ||
6 | 7 | ||
7 | export interface User { | 8 | export 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 @@ | |||
1 | import { Video } from '../video.model' | ||
2 | |||
3 | export enum VideoBlacklistType { | ||
4 | MANUAL = 1, | ||
5 | AUTO_BEFORE_PUBLISHED = 2 | ||
6 | } | ||
7 | |||
1 | export interface VideoBlacklist { | 8 | export 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 @@ | |||
1 | import { Actor } from '../../actors/actor.model' | 1 | import { Actor } from '../../actors/actor.model' |
2 | import { Video } from '../video.model' | ||
3 | import { Account } from '../../actors/index' | 2 | import { Account } from '../../actors/index' |
3 | import { Avatar } from '../../avatars' | ||
4 | 4 | ||
5 | export interface VideoChannel extends Actor { | 5 | export 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 | |||
13 | export 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 @@ | |||
1 | export * from './rate/user-video-rate-update.model' | 1 | export * from './rate/user-video-rate-update.model' |
2 | export * from './rate/user-video-rate.model' | 2 | export * from './rate/user-video-rate.model' |
3 | export * from './rate/account-video-rate.model' | ||
3 | export * from './rate/user-video-rate.type' | 4 | export * from './rate/user-video-rate.type' |
4 | export * from './abuse/video-abuse-state.model' | 5 | export * from './abuse/video-abuse-state.model' |
5 | export * from './abuse/video-abuse-create.model' | 6 | export * from './abuse/video-abuse-create.model' |
@@ -11,6 +12,13 @@ export * from './blacklist/video-blacklist-update.model' | |||
11 | export * from './channel/video-channel-create.model' | 12 | export * from './channel/video-channel-create.model' |
12 | export * from './channel/video-channel-update.model' | 13 | export * from './channel/video-channel-update.model' |
13 | export * from './channel/video-channel.model' | 14 | export * from './channel/video-channel.model' |
15 | export * from './playlist/video-playlist-create.model' | ||
16 | export * from './playlist/video-playlist-element-create.model' | ||
17 | export * from './playlist/video-playlist-element-update.model' | ||
18 | export * from './playlist/video-playlist-privacy.model' | ||
19 | export * from './playlist/video-playlist-type.model' | ||
20 | export * from './playlist/video-playlist-update.model' | ||
21 | export * from './playlist/video-playlist.model' | ||
14 | export * from './video-change-ownership.model' | 22 | export * from './video-change-ownership.model' |
15 | export * from './video-change-ownership-create.model' | 23 | export * from './video-change-ownership-create.model' |
16 | export * from './video-create.model' | 24 | export * from './video-create.model' |
@@ -27,4 +35,4 @@ export * from './caption/video-caption-update.model' | |||
27 | export * from './import/video-import-create.model' | 35 | export * from './import/video-import-create.model' |
28 | export * from './import/video-import-state.enum' | 36 | export * from './import/video-import-state.enum' |
29 | export * from './import/video-import.model' | 37 | export * from './import/video-import.model' |
30 | export { VideoConstant } from './video-constant.model' | 38 | export * 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 @@ | |||
1 | export 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 @@ | |||
1 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
2 | |||
3 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
2 | |||
3 | export 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 @@ | |||
1 | import { AccountSummary } from '../../actors/index' | ||
2 | import { VideoChannelSummary, VideoConstant } from '..' | ||
3 | import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' | ||
4 | import { VideoPlaylistType } from './video-playlist-type.model' | ||
5 | |||
6 | export 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 @@ | |||
1 | import { UserVideoRateType } from './user-video-rate.type' | ||
2 | import { Video } from '../video.model' | ||
3 | |||
4 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { VideoStreamingPlaylistType } from './video-streaming-playlist.type' | ||
2 | |||
3 | export 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 @@ | |||
1 | export 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 @@ | |||
1 | import { VideoResolution, VideoState } from '../../index' | 1 | import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index' |
2 | import { Account } from '../actors' | 2 | import { Account } from '../actors' |
3 | import { Avatar } from '../avatars/avatar.model' | 3 | import { Avatar } from '../avatars/avatar.model' |
4 | import { VideoChannel } from './channel/video-channel.model' | 4 | import { VideoChannel } from './channel/video-channel.model' |
5 | import { VideoPrivacy } from './video-privacy.enum' | 5 | import { VideoPrivacy } from './video-privacy.enum' |
6 | import { VideoScheduleUpdate } from './video-schedule-update.model' | 6 | import { VideoScheduleUpdate } from './video-schedule-update.model' |
7 | import { VideoConstant } from './video-constant.model' | 7 | import { VideoConstant } from './video-constant.model' |
8 | import { VideoStreamingPlaylist } from './video-streaming-playlist.model' | ||
8 | 9 | ||
9 | export interface VideoFile { | 10 | export 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 | ||
20 | export interface VideoChannelAttribute { | 21 | export 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 | |||
30 | export 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 | ||
40 | export interface Video { | 27 | export 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 | ||
77 | export interface VideoDetails extends Video { | 67 | export 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 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | function 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 | |||
19 | function 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 | |||
27 | function 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 | |||
35 | export { | ||
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 | |||
3 | import { ChildProcess, exec, fork } from 'child_process' | ||
4 | import { join } from 'path' | ||
5 | import { root, wait } from '../miscs/miscs' | ||
6 | import { readdir, readFile } from 'fs-extra' | ||
7 | import { existsSync } from 'fs' | ||
8 | import { expect } from 'chai' | ||
9 | |||
10 | interface 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 | |||
44 | function 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 | |||
66 | function 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 | |||
76 | function 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 | |||
161 | async 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 | |||
168 | async 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 | |||
180 | function killallServers (servers: ServerInfo[]) { | ||
181 | for (const server of servers) { | ||
182 | process.kill(-server.app.pid) | ||
183 | } | ||
184 | } | ||
185 | |||
186 | async 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 | |||
201 | export { | ||
202 | checkTmpIsEmpty, | ||
203 | ServerInfo, | ||
204 | flushAndRunMultipleServers, | ||
205 | flushTests, | ||
206 | runServer, | ||
207 | killallServers, | ||
208 | reRunServer, | ||
209 | waitUntilLog | ||
210 | } | ||