aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/core-utils/miscs/miscs.ts2
-rw-r--r--shared/extra-utils/index.ts2
-rw-r--r--shared/extra-utils/instances-index/mock-instances-index.ts2
-rw-r--r--shared/extra-utils/miscs/email.ts4
-rw-r--r--shared/extra-utils/miscs/miscs.ts33
-rw-r--r--shared/extra-utils/miscs/sql.ts13
-rw-r--r--shared/extra-utils/overviews/overviews.ts27
-rw-r--r--shared/extra-utils/plugins/mock-blocklist.ts29
-rw-r--r--shared/extra-utils/requests/requests.ts66
-rw-r--r--shared/extra-utils/search/videos.ts2
-rw-r--r--shared/extra-utils/server/clients.ts4
-rw-r--r--shared/extra-utils/server/contact-form.ts10
-rw-r--r--shared/extra-utils/server/follows.ts26
-rw-r--r--shared/extra-utils/server/jobs.ts22
-rw-r--r--shared/extra-utils/server/plugins.ts96
-rw-r--r--shared/extra-utils/server/redundancy.ts70
-rw-r--r--shared/extra-utils/server/servers.ts30
-rw-r--r--shared/extra-utils/users/accounts.ts2
-rw-r--r--shared/extra-utils/users/blocklist.ts2
-rw-r--r--shared/extra-utils/users/login.ts55
-rw-r--r--shared/extra-utils/users/user-notifications.ts93
-rw-r--r--shared/extra-utils/users/users.ts43
-rw-r--r--shared/extra-utils/videos/video-abuses.ts55
-rw-r--r--shared/extra-utils/videos/video-blacklist.ts28
-rw-r--r--shared/extra-utils/videos/video-captions.ts6
-rw-r--r--shared/extra-utils/videos/video-channels.ts29
-rw-r--r--shared/extra-utils/videos/video-comments.ts2
-rw-r--r--shared/extra-utils/videos/video-imports.ts2
-rw-r--r--shared/extra-utils/videos/video-playlists.ts50
-rw-r--r--shared/extra-utils/videos/video-streaming-playlists.ts2
-rw-r--r--shared/extra-utils/videos/videos.ts40
-rw-r--r--shared/models/activitypub/activity.ts43
-rw-r--r--shared/models/activitypub/activitypub-actor.ts8
-rw-r--r--shared/models/activitypub/activitypub-signature.ts4
-rw-r--r--shared/models/activitypub/context.ts1
-rw-r--r--shared/models/activitypub/objects/cache-file-object.ts2
-rw-r--r--shared/models/activitypub/objects/common-objects.ts44
-rw-r--r--shared/models/activitypub/objects/video-abuse-object.ts2
-rw-r--r--shared/models/activitypub/objects/video-torrent-object.ts8
-rw-r--r--shared/models/activitypub/objects/view-object.ts2
-rw-r--r--shared/models/i18n/i18n.ts56
-rw-r--r--shared/models/nodeinfo/index.d.ts2
-rw-r--r--shared/models/overviews/videos-overview.ts30
-rw-r--r--shared/models/plugins/client-hook.model.ts7
-rw-r--r--shared/models/plugins/peertube-plugin-latest-version.model.ts2
-rw-r--r--shared/models/plugins/plugin-client-scope.type.ts2
-rw-r--r--shared/models/plugins/plugin-package-json.model.ts12
-rw-r--r--shared/models/plugins/plugin-playlist-privacy-manager.model.ts8
-rw-r--r--shared/models/plugins/plugin-settings-manager.model.ts6
-rw-r--r--shared/models/plugins/plugin-video-privacy-manager.model.ts9
-rw-r--r--shared/models/plugins/register-server-auth.model.ts52
-rw-r--r--shared/models/plugins/register-server-setting.model.ts4
-rw-r--r--shared/models/plugins/server-hook.model.ts2
-rw-r--r--shared/models/redundancy/index.ts4
-rw-r--r--shared/models/redundancy/video-redundancies-filters.model.ts1
-rw-r--r--shared/models/redundancy/video-redundancy-config-filter.type.ts1
-rw-r--r--shared/models/redundancy/video-redundancy.model.ts35
-rw-r--r--shared/models/redundancy/videos-redundancy-strategy.model.ts (renamed from shared/models/redundancy/videos-redundancy.model.ts)3
-rw-r--r--shared/models/server/custom-config.model.ts4
-rw-r--r--shared/models/server/emailer.model.ts12
-rw-r--r--shared/models/server/index.ts1
-rw-r--r--shared/models/server/job.model.ts126
-rw-r--r--shared/models/server/server-config.model.ts33
-rw-r--r--shared/models/server/server-stats.model.ts22
-rw-r--r--shared/models/users/user-right.enum.ts4
-rw-r--r--shared/models/users/user-role.ts8
-rw-r--r--shared/models/users/user.model.ts10
-rw-r--r--shared/models/videos/abuse/video-abuse-video-is.type.ts1
-rw-r--r--shared/models/videos/abuse/video-abuse.model.ts13
-rw-r--r--shared/models/videos/blacklist/video-blacklist.model.ts5
-rw-r--r--shared/models/videos/channel/video-channel.model.ts6
-rw-r--r--shared/models/videos/video-file-metadata.ts18
-rw-r--r--shared/models/videos/video-file.model.ts3
-rw-r--r--shared/models/videos/video-transcoding-fps.model.ts8
-rw-r--r--shared/models/videos/video.model.ts2
75 files changed, 1067 insertions, 406 deletions
diff --git a/shared/core-utils/miscs/miscs.ts b/shared/core-utils/miscs/miscs.ts
index 5de024c08..1eee22d82 100644
--- a/shared/core-utils/miscs/miscs.ts
+++ b/shared/core-utils/miscs/miscs.ts
@@ -11,7 +11,7 @@ function compareSemVer (a: string, b: string) {
11 const l = Math.min(segmentsA.length, segmentsB.length) 11 const l = Math.min(segmentsA.length, segmentsB.length)
12 12
13 for (let i = 0; i < l; i++) { 13 for (let i = 0; i < l; i++) {
14 const diff = parseInt(segmentsA[ i ], 10) - parseInt(segmentsB[ i ], 10) 14 const diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10)
15 15
16 if (diff) return diff 16 if (diff) return diff
17 } 17 }
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts
index 78acf72aa..d3f010b20 100644
--- a/shared/extra-utils/index.ts
+++ b/shared/extra-utils/index.ts
@@ -18,6 +18,7 @@ export * from './users/users'
18export * from './users/accounts' 18export * from './users/accounts'
19export * from './videos/video-abuses' 19export * from './videos/video-abuses'
20export * from './videos/video-blacklist' 20export * from './videos/video-blacklist'
21export * from './videos/video-captions'
21export * from './videos/video-channels' 22export * from './videos/video-channels'
22export * from './videos/video-comments' 23export * from './videos/video-comments'
23export * from './videos/video-streaming-playlists' 24export * from './videos/video-streaming-playlists'
@@ -26,3 +27,4 @@ export * from './videos/video-change-ownership'
26export * from './feeds/feeds' 27export * from './feeds/feeds'
27export * from './instances-index/mock-instances-index' 28export * from './instances-index/mock-instances-index'
28export * from './search/videos' 29export * from './search/videos'
30export * from './plugins/mock-blocklist'
diff --git a/shared/extra-utils/instances-index/mock-instances-index.ts b/shared/extra-utils/instances-index/mock-instances-index.ts
index cfa4523c1..c58e8bcf8 100644
--- a/shared/extra-utils/instances-index/mock-instances-index.ts
+++ b/shared/extra-utils/instances-index/mock-instances-index.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2 2
3export class MockInstancesIndex { 3export class MockInstancesIndex {
4 private indexInstances: { host: string, createdAt: string }[] = [] 4 private readonly indexInstances: { host: string, createdAt: string }[] = []
5 5
6 initialize () { 6 initialize () {
7 return new Promise(res => { 7 return new Promise(res => {
diff --git a/shared/extra-utils/miscs/email.ts b/shared/extra-utils/miscs/email.ts
index b2a1093da..d6219deb3 100644
--- a/shared/extra-utils/miscs/email.ts
+++ b/shared/extra-utils/miscs/email.ts
@@ -12,7 +12,7 @@ class MockSmtpServer {
12 private constructor () { 12 private constructor () {
13 this.emailChildProcess = fork(`${__dirname}/email-child-process`, []) 13 this.emailChildProcess = fork(`${__dirname}/email-child-process`, [])
14 14
15 this.emailChildProcess.on('message', (msg) => { 15 this.emailChildProcess.on('message', (msg: any) => {
16 if (msg.email) { 16 if (msg.email) {
17 return this.emails.push(msg.email) 17 return this.emails.push(msg.email)
18 } 18 }
@@ -36,7 +36,7 @@ class MockSmtpServer {
36 this.emailChildProcess.on('exit', () => { 36 this.emailChildProcess.on('exit', () => {
37 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'))
38 }) 38 })
39 this.emailChildProcess.on('message', (msg) => { 39 this.emailChildProcess.on('message', (msg: any) => {
40 if (msg.err) { 40 if (msg.err) {
41 return rej(new Error(msg.err)) 41 return rej(new Error(msg.err))
42 } 42 }
diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts
index 6b0f6d990..f4e86b85a 100644
--- a/shared/extra-utils/miscs/miscs.ts
+++ b/shared/extra-utils/miscs/miscs.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { basename, dirname, isAbsolute, join, resolve } from 'path' 4import { basename, dirname, isAbsolute, join, resolve } from 'path'
@@ -10,11 +10,11 @@ import * as ffmpeg from 'fluent-ffmpeg'
10const expect = chai.expect 10const expect = chai.expect
11let webtorrent: WebTorrent.Instance 11let webtorrent: WebTorrent.Instance
12 12
13function immutableAssign <T, U> (target: T, source: U) { 13function immutableAssign<T, U> (target: T, source: U) {
14 return Object.assign<{}, T, U>({}, target, source) 14 return Object.assign<{}, T, U>({}, target, source)
15} 15}
16 16
17 // Default interval -> 5 minutes 17// Default interval -> 5 minutes
18function dateIsValid (dateString: string, interval = 300000) { 18function dateIsValid (dateString: string, interval = 300000) {
19 const dateToCheck = new Date(dateString) 19 const dateToCheck = new Date(dateString)
20 const now = new Date() 20 const now = new Date()
@@ -89,7 +89,7 @@ async function generateHighBitrateVideo () {
89 // a large file in the repo. The video needs to have a certain minimum length so 89 // a large file in the repo. The video needs to have a certain minimum length so
90 // that FFmpeg properly applies bitrate limits. 90 // that FFmpeg properly applies bitrate limits.
91 // https://stackoverflow.com/a/15795112 91 // https://stackoverflow.com/a/15795112
92 return new Promise<string>(async (res, rej) => { 92 return new Promise<string>((res, rej) => {
93 ffmpeg() 93 ffmpeg()
94 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) 94 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
95 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) 95 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
@@ -104,6 +104,28 @@ async function generateHighBitrateVideo () {
104 return tempFixturePath 104 return tempFixturePath
105} 105}
106 106
107async function generateVideoWithFramerate (fps = 60) {
108 const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true)
109
110 await ensureDir(dirname(tempFixturePath))
111
112 const exists = await pathExists(tempFixturePath)
113 if (!exists) {
114 return new Promise<string>((res, rej) => {
115 ffmpeg()
116 .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ])
117 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
118 .outputOptions([ `-r ${fps}` ])
119 .output(tempFixturePath)
120 .on('error', rej)
121 .on('end', () => res(tempFixturePath))
122 .run()
123 })
124 }
125
126 return tempFixturePath
127}
128
107// --------------------------------------------------------------------------- 129// ---------------------------------------------------------------------------
108 130
109export { 131export {
@@ -115,5 +137,6 @@ export {
115 testImage, 137 testImage,
116 buildAbsoluteFixturePath, 138 buildAbsoluteFixturePath,
117 root, 139 root,
118 generateHighBitrateVideo 140 generateHighBitrateVideo,
141 generateVideoWithFramerate
119} 142}
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
index 167649c6d..5bd5d5d8a 100644
--- a/shared/extra-utils/miscs/sql.ts
+++ b/shared/extra-utils/miscs/sql.ts
@@ -1,7 +1,7 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { ServerInfo } from '../server/servers' 2import { ServerInfo } from '../server/servers'
3 3
4let sequelizes: { [ id: number ]: Sequelize } = {} 4const sequelizes: { [ id: number ]: Sequelize } = {}
5 5
6function getSequelize (internalServerNumber: number) { 6function getSequelize (internalServerNumber: number) {
7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber] 7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber]
@@ -52,22 +52,23 @@ async function countVideoViewsOf (internalServerNumber: number, uuid: string) {
52 const seq = getSequelize(internalServerNumber) 52 const seq = getSequelize(internalServerNumber)
53 53
54 // tslint:disable 54 // tslint:disable
55 const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` 55 const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' +
56 `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
56 57
57 const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } 58 const options = { type: QueryTypes.SELECT as QueryTypes.SELECT }
58 const [ { total } ] = await seq.query<{ total: number }>(query, options) 59 const [ { total } ] = await seq.query<{ total: number }>(query, options)
59 60
60 if (!total) return 0 61 if (!total) return 0
61 62
62 // FIXME: check if we really need parseInt
63 return parseInt(total + '', 10) 63 return parseInt(total + '', 10)
64} 64}
65 65
66async function closeAllSequelize (servers: ServerInfo[]) { 66async function closeAllSequelize (servers: ServerInfo[]) {
67 for (const server of servers) { 67 for (const server of servers) {
68 if (sequelizes[ server.internalServerNumber ]) { 68 if (sequelizes[server.internalServerNumber]) {
69 await sequelizes[ server.internalServerNumber ].close() 69 await sequelizes[server.internalServerNumber].close()
70 delete sequelizes[ server.internalServerNumber ] 70 // eslint-disable-next-line
71 delete sequelizes[server.internalServerNumber]
71 } 72 }
72 } 73 }
73} 74}
diff --git a/shared/extra-utils/overviews/overviews.ts b/shared/extra-utils/overviews/overviews.ts
index 23e3ceb1e..ae4d31aa3 100644
--- a/shared/extra-utils/overviews/overviews.ts
+++ b/shared/extra-utils/overviews/overviews.ts
@@ -1,18 +1,33 @@
1import { makeGetRequest } from '../requests/requests' 1import { makeGetRequest } from '../requests/requests'
2 2
3function getVideosOverview (url: string, useCache = false) { 3function getVideosOverview (url: string, page: number, statusCodeExpected = 200) {
4 const path = '/api/v1/overviews/videos' 4 const path = '/api/v1/overviews/videos'
5 5
6 const query = { 6 const query = { page }
7 t: useCache ? undefined : new Date().getTime()
8 }
9 7
10 return makeGetRequest({ 8 return makeGetRequest({
11 url, 9 url,
12 path, 10 path,
13 query, 11 query,
14 statusCodeExpected: 200 12 statusCodeExpected
15 }) 13 })
16} 14}
17 15
18export { getVideosOverview } 16function getVideosOverviewWithToken (url: string, page: number, token: string, statusCodeExpected = 200) {
17 const path = '/api/v1/overviews/videos'
18
19 const query = { page }
20
21 return makeGetRequest({
22 url,
23 path,
24 query,
25 token,
26 statusCodeExpected
27 })
28}
29
30export {
31 getVideosOverview,
32 getVideosOverviewWithToken
33}
diff --git a/shared/extra-utils/plugins/mock-blocklist.ts b/shared/extra-utils/plugins/mock-blocklist.ts
new file mode 100644
index 000000000..6fe3dee9f
--- /dev/null
+++ b/shared/extra-utils/plugins/mock-blocklist.ts
@@ -0,0 +1,29 @@
1import * as express from 'express'
2
3type BlocklistResponse = {
4 data: {
5 value: string
6 action?: 'add' | 'remove'
7 updatedAt?: string
8 }[]
9}
10
11export class MockBlocklist {
12 private body: BlocklistResponse
13
14 initialize () {
15 return new Promise(res => {
16 const app = express()
17
18 app.get('/blocklist', (req: express.Request, res: express.Response) => {
19 return res.json(this.body)
20 })
21
22 app.listen(42100, () => res())
23 })
24 }
25
26 replace (body: BlocklistResponse) {
27 this.body = body
28 }
29}
diff --git a/shared/extra-utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts
index 3532fb429..0e9d67f0b 100644
--- a/shared/extra-utils/requests/requests.ts
+++ b/shared/extra-utils/requests/requests.ts
@@ -1,26 +1,30 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { buildAbsoluteFixturePath, root } from '../miscs/miscs' 4import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
3import { isAbsolute, join } from 'path' 5import { isAbsolute, join } from 'path'
4import { parse } from 'url' 6import { URL } from 'url'
7import { decode } from 'querystring'
5 8
6function get4KFileUrl () { 9function get4KFileUrl () {
7 return 'https://download.cpy.re/peertube/4k_file.txt' 10 return 'https://download.cpy.re/peertube/4k_file.txt'
8} 11}
9 12
10function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) { 13function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
11 const { host, protocol, pathname } = parse(url) 14 const { host, protocol, pathname } = new URL(url)
12 15
13 return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range }) 16 return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range })
14} 17}
15 18
16function makeGetRequest (options: { 19function makeGetRequest (options: {
17 url: string, 20 url: string
18 path?: string, 21 path?: string
19 query?: any, 22 query?: any
20 token?: string, 23 token?: string
21 statusCodeExpected?: number, 24 statusCodeExpected?: number
22 contentType?: string, 25 contentType?: string
23 range?: string 26 range?: string
27 redirects?: number
24}) { 28}) {
25 if (!options.statusCodeExpected) options.statusCodeExpected = 400 29 if (!options.statusCodeExpected) options.statusCodeExpected = 400
26 if (options.contentType === undefined) options.contentType = 'application/json' 30 if (options.contentType === undefined) options.contentType = 'application/json'
@@ -31,14 +35,15 @@ function makeGetRequest (options: {
31 if (options.token) req.set('Authorization', 'Bearer ' + options.token) 35 if (options.token) req.set('Authorization', 'Bearer ' + options.token)
32 if (options.query) req.query(options.query) 36 if (options.query) req.query(options.query)
33 if (options.range) req.set('Range', options.range) 37 if (options.range) req.set('Range', options.range)
38 if (options.redirects) req.redirects(options.redirects)
34 39
35 return req.expect(options.statusCodeExpected) 40 return req.expect(options.statusCodeExpected)
36} 41}
37 42
38function makeDeleteRequest (options: { 43function makeDeleteRequest (options: {
39 url: string, 44 url: string
40 path: string, 45 path: string
41 token?: string, 46 token?: string
42 statusCodeExpected?: number 47 statusCodeExpected?: number
43}) { 48}) {
44 if (!options.statusCodeExpected) options.statusCodeExpected = 400 49 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -53,12 +58,12 @@ function makeDeleteRequest (options: {
53} 58}
54 59
55function makeUploadRequest (options: { 60function makeUploadRequest (options: {
56 url: string, 61 url: string
57 method?: 'POST' | 'PUT', 62 method?: 'POST' | 'PUT'
58 path: string, 63 path: string
59 token?: string, 64 token?: string
60 fields: { [ fieldName: string ]: any }, 65 fields: { [ fieldName: string ]: any }
61 attaches: { [ attachName: string ]: any | any[] }, 66 attaches: { [ attachName: string ]: any | any[] }
62 statusCodeExpected?: number 67 statusCodeExpected?: number
63}) { 68}) {
64 if (!options.statusCodeExpected) options.statusCodeExpected = 400 69 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -101,10 +106,10 @@ function makeUploadRequest (options: {
101} 106}
102 107
103function makePostBodyRequest (options: { 108function makePostBodyRequest (options: {
104 url: string, 109 url: string
105 path: string, 110 path: string
106 token?: string, 111 token?: string
107 fields?: { [ fieldName: string ]: any }, 112 fields?: { [ fieldName: string ]: any }
108 statusCodeExpected?: number 113 statusCodeExpected?: number
109}) { 114}) {
110 if (!options.fields) options.fields = {} 115 if (!options.fields) options.fields = {}
@@ -121,10 +126,10 @@ function makePostBodyRequest (options: {
121} 126}
122 127
123function makePutBodyRequest (options: { 128function makePutBodyRequest (options: {
124 url: string, 129 url: string
125 path: string, 130 path: string
126 token?: string, 131 token?: string
127 fields: { [ fieldName: string ]: any }, 132 fields: { [ fieldName: string ]: any }
128 statusCodeExpected?: number 133 statusCodeExpected?: number
129}) { 134}) {
130 if (!options.statusCodeExpected) options.statusCodeExpected = 400 135 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -147,9 +152,9 @@ function makeHTMLRequest (url: string, path: string) {
147} 152}
148 153
149function updateAvatarRequest (options: { 154function updateAvatarRequest (options: {
150 url: string, 155 url: string
151 path: string, 156 path: string
152 accessToken: string, 157 accessToken: string
153 fixture: string 158 fixture: string
154}) { 159}) {
155 let filePath = '' 160 let filePath = ''
@@ -169,12 +174,17 @@ function updateAvatarRequest (options: {
169 }) 174 })
170} 175}
171 176
177function decodeQueryString (path: string) {
178 return decode(path.split('?')[1])
179}
180
172// --------------------------------------------------------------------------- 181// ---------------------------------------------------------------------------
173 182
174export { 183export {
175 get4KFileUrl, 184 get4KFileUrl,
176 makeHTMLRequest, 185 makeHTMLRequest,
177 makeGetRequest, 186 makeGetRequest,
187 decodeQueryString,
178 makeUploadRequest, 188 makeUploadRequest,
179 makePostBodyRequest, 189 makePostBodyRequest,
180 makePutBodyRequest, 190 makePutBodyRequest,
diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts
index da806e692..4c52ea11c 100644
--- a/shared/extra-utils/search/videos.ts
+++ b/shared/extra-utils/search/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as request from 'supertest' 3import * as request from 'supertest'
4import { VideosSearchQuery } from '../../models/search' 4import { VideosSearchQuery } from '../../models/search'
diff --git a/shared/extra-utils/server/clients.ts b/shared/extra-utils/server/clients.ts
index 273aac747..dc631e823 100644
--- a/shared/extra-utils/server/clients.ts
+++ b/shared/extra-utils/server/clients.ts
@@ -1,12 +1,12 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import * as urlUtil from 'url' 2import { URL } from 'url'
3 3
4function getClient (url: string) { 4function getClient (url: string) {
5 const path = '/api/v1/oauth-clients/local' 5 const path = '/api/v1/oauth-clients/local'
6 6
7 return request(url) 7 return request(url)
8 .get(path) 8 .get(path)
9 .set('Host', urlUtil.parse(url).host) 9 .set('Host', new URL(url).host)
10 .set('Accept', 'application/json') 10 .set('Accept', 'application/json')
11 .expect(200) 11 .expect(200)
12 .expect('Content-Type', /json/) 12 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/contact-form.ts b/shared/extra-utils/server/contact-form.ts
index e002e03dd..d50f83241 100644
--- a/shared/extra-utils/server/contact-form.ts
+++ b/shared/extra-utils/server/contact-form.ts
@@ -2,11 +2,11 @@ import * as request from 'supertest'
2import { ContactForm } from '../../models/server' 2import { ContactForm } from '../../models/server'
3 3
4function sendContactForm (options: { 4function sendContactForm (options: {
5 url: string, 5 url: string
6 fromEmail: string, 6 fromEmail: string
7 fromName: string, 7 fromName: string
8 subject: string, 8 subject: string
9 body: string, 9 body: string
10 expectedStatus?: number 10 expectedStatus?: number
11}) { 11}) {
12 const path = '/api/v1/server/contact' 12 const path = '/api/v1/server/contact'
diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts
index 3f7729c20..006d59199 100644
--- a/shared/extra-utils/server/follows.ts
+++ b/shared/extra-utils/server/follows.ts
@@ -5,12 +5,12 @@ import { makePostBodyRequest } from '../requests/requests'
5import { ActivityPubActorType, FollowState } from '@shared/models' 5import { ActivityPubActorType, FollowState } from '@shared/models'
6 6
7function getFollowersListPaginationAndSort (options: { 7function getFollowersListPaginationAndSort (options: {
8 url: string, 8 url: string
9 start: number, 9 start: number
10 count: number, 10 count: number
11 sort: string, 11 sort: string
12 search?: string, 12 search?: string
13 actorType?: ActivityPubActorType, 13 actorType?: ActivityPubActorType
14 state?: FollowState 14 state?: FollowState
15}) { 15}) {
16 const { url, start, count, sort, search, state, actorType } = options 16 const { url, start, count, sort, search, state, actorType } = options
@@ -56,12 +56,12 @@ function rejectFollower (url: string, token: string, follower: string, statusCod
56} 56}
57 57
58function getFollowingListPaginationAndSort (options: { 58function getFollowingListPaginationAndSort (options: {
59 url: string, 59 url: string
60 start: number, 60 start: number
61 count: number, 61 count: number
62 sort: string, 62 sort: string
63 search?: string, 63 search?: string
64 actorType?: ActivityPubActorType, 64 actorType?: ActivityPubActorType
65 state?: FollowState 65 state?: FollowState
66}) { 66}) {
67 const { url, start, count, sort, search, state, actorType } = options 67 const { url, start, count, sort, search, state, actorType } = options
@@ -92,7 +92,7 @@ function follow (follower: string, following: string[], accessToken: string, exp
92 .post(path) 92 .post(path)
93 .set('Accept', 'application/json') 93 .set('Accept', 'application/json')
94 .set('Authorization', 'Bearer ' + accessToken) 94 .set('Authorization', 'Bearer ' + accessToken)
95 .send({ 'hosts': followingHosts }) 95 .send({ hosts: followingHosts })
96 .expect(expectedStatus) 96 .expect(expectedStatus)
97} 97}
98 98
diff --git a/shared/extra-utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts
index 56fe5fa2a..d984b3d1e 100644
--- a/shared/extra-utils/server/jobs.ts
+++ b/shared/extra-utils/server/jobs.ts
@@ -8,20 +8,20 @@ function getJobsList (url: string, accessToken: string, state: JobState) {
8 const path = '/api/v1/jobs/' + state 8 const path = '/api/v1/jobs/' + state
9 9
10 return request(url) 10 return request(url)
11 .get(path) 11 .get(path)
12 .set('Accept', 'application/json') 12 .set('Accept', 'application/json')
13 .set('Authorization', 'Bearer ' + accessToken) 13 .set('Authorization', 'Bearer ' + accessToken)
14 .expect(200) 14 .expect(200)
15 .expect('Content-Type', /json/) 15 .expect('Content-Type', /json/)
16} 16}
17 17
18function getJobsListPaginationAndSort (options: { 18function getJobsListPaginationAndSort (options: {
19 url: string, 19 url: string
20 accessToken: string, 20 accessToken: string
21 state: JobState, 21 state: JobState
22 start: number, 22 start: number
23 count: number, 23 count: number
24 sort: string, 24 sort: string
25 jobType?: JobType 25 jobType?: JobType
26}) { 26}) {
27 const { url, accessToken, state, start, count, sort, jobType } = options 27 const { url, accessToken, state, start, count, sort, jobType } = options
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts
index 5c0d1e511..b6b5e3958 100644
--- a/shared/extra-utils/server/plugins.ts
+++ b/shared/extra-utils/server/plugins.ts
@@ -7,13 +7,13 @@ import { root } from '../miscs/miscs'
7import { join } from 'path' 7import { join } from 'path'
8 8
9function listPlugins (parameters: { 9function listPlugins (parameters: {
10 url: string, 10 url: string
11 accessToken: string, 11 accessToken: string
12 start?: number, 12 start?: number
13 count?: number, 13 count?: number
14 sort?: string, 14 sort?: string
15 pluginType?: PluginType, 15 pluginType?: PluginType
16 uninstalled?: boolean, 16 uninstalled?: boolean
17 expectedStatus?: number 17 expectedStatus?: number
18}) { 18}) {
19 const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = 200 } = parameters 19 const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = 200 } = parameters
@@ -35,13 +35,13 @@ function listPlugins (parameters: {
35} 35}
36 36
37function listAvailablePlugins (parameters: { 37function listAvailablePlugins (parameters: {
38 url: string, 38 url: string
39 accessToken: string, 39 accessToken: string
40 start?: number, 40 start?: number
41 count?: number, 41 count?: number
42 sort?: string, 42 sort?: string
43 pluginType?: PluginType, 43 pluginType?: PluginType
44 currentPeerTubeEngine?: string, 44 currentPeerTubeEngine?: string
45 search?: string 45 search?: string
46 expectedStatus?: number 46 expectedStatus?: number
47}) { 47}) {
@@ -67,9 +67,9 @@ function listAvailablePlugins (parameters: {
67} 67}
68 68
69function getPlugin (parameters: { 69function getPlugin (parameters: {
70 url: string, 70 url: string
71 accessToken: string, 71 accessToken: string
72 npmName: string, 72 npmName: string
73 expectedStatus?: number 73 expectedStatus?: number
74}) { 74}) {
75 const { url, accessToken, npmName, expectedStatus = 200 } = parameters 75 const { url, accessToken, npmName, expectedStatus = 200 } = parameters
@@ -84,10 +84,10 @@ function getPlugin (parameters: {
84} 84}
85 85
86function updatePluginSettings (parameters: { 86function updatePluginSettings (parameters: {
87 url: string, 87 url: string
88 accessToken: string, 88 accessToken: string
89 npmName: string, 89 npmName: string
90 settings: any, 90 settings: any
91 expectedStatus?: number 91 expectedStatus?: number
92}) { 92}) {
93 const { url, accessToken, npmName, settings, expectedStatus = 204 } = parameters 93 const { url, accessToken, npmName, settings, expectedStatus = 204 } = parameters
@@ -103,9 +103,9 @@ function updatePluginSettings (parameters: {
103} 103}
104 104
105function getPluginRegisteredSettings (parameters: { 105function getPluginRegisteredSettings (parameters: {
106 url: string, 106 url: string
107 accessToken: string, 107 accessToken: string
108 npmName: string, 108 npmName: string
109 expectedStatus?: number 109 expectedStatus?: number
110}) { 110}) {
111 const { url, accessToken, npmName, expectedStatus = 200 } = parameters 111 const { url, accessToken, npmName, expectedStatus = 200 } = parameters
@@ -120,8 +120,8 @@ function getPluginRegisteredSettings (parameters: {
120} 120}
121 121
122function getPublicSettings (parameters: { 122function getPublicSettings (parameters: {
123 url: string, 123 url: string
124 npmName: string, 124 npmName: string
125 expectedStatus?: number 125 expectedStatus?: number
126}) { 126}) {
127 const { url, npmName, expectedStatus = 200 } = parameters 127 const { url, npmName, expectedStatus = 200 } = parameters
@@ -135,8 +135,8 @@ function getPublicSettings (parameters: {
135} 135}
136 136
137function getPluginTranslations (parameters: { 137function getPluginTranslations (parameters: {
138 url: string, 138 url: string
139 locale: string, 139 locale: string
140 expectedStatus?: number 140 expectedStatus?: number
141}) { 141}) {
142 const { url, locale, expectedStatus = 200 } = parameters 142 const { url, locale, expectedStatus = 200 } = parameters
@@ -150,9 +150,9 @@ function getPluginTranslations (parameters: {
150} 150}
151 151
152function installPlugin (parameters: { 152function installPlugin (parameters: {
153 url: string, 153 url: string
154 accessToken: string, 154 accessToken: string
155 path?: string, 155 path?: string
156 npmName?: string 156 npmName?: string
157 expectedStatus?: number 157 expectedStatus?: number
158}) { 158}) {
@@ -169,9 +169,9 @@ function installPlugin (parameters: {
169} 169}
170 170
171function updatePlugin (parameters: { 171function updatePlugin (parameters: {
172 url: string, 172 url: string
173 accessToken: string, 173 accessToken: string
174 path?: string, 174 path?: string
175 npmName?: string 175 npmName?: string
176 expectedStatus?: number 176 expectedStatus?: number
177}) { 177}) {
@@ -188,8 +188,8 @@ function updatePlugin (parameters: {
188} 188}
189 189
190function uninstallPlugin (parameters: { 190function uninstallPlugin (parameters: {
191 url: string, 191 url: string
192 accessToken: string, 192 accessToken: string
193 npmName: string 193 npmName: string
194 expectedStatus?: number 194 expectedStatus?: number
195}) { 195}) {
@@ -235,6 +235,27 @@ function getPluginTestPath (suffix = '') {
235 return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix) 235 return join(root(), 'server', 'tests', 'fixtures', 'peertube-plugin-test' + suffix)
236} 236}
237 237
238function getExternalAuth (options: {
239 url: string
240 npmName: string
241 npmVersion: string
242 authName: string
243 query?: any
244 statusCodeExpected?: number
245}) {
246 const { url, npmName, npmVersion, authName, statusCodeExpected, query } = options
247
248 const path = '/plugins/' + npmName + '/' + npmVersion + '/auth/' + authName
249
250 return makeGetRequest({
251 url,
252 path,
253 query,
254 statusCodeExpected: statusCodeExpected || 200,
255 redirects: 0
256 })
257}
258
238export { 259export {
239 listPlugins, 260 listPlugins,
240 listAvailablePlugins, 261 listAvailablePlugins,
@@ -250,5 +271,6 @@ export {
250 updatePluginPackageJSON, 271 updatePluginPackageJSON,
251 getPluginPackageJSON, 272 getPluginPackageJSON,
252 getPluginTestPath, 273 getPluginTestPath,
253 getPublicSettings 274 getPublicSettings,
275 getExternalAuth
254} 276}
diff --git a/shared/extra-utils/server/redundancy.ts b/shared/extra-utils/server/redundancy.ts
index c39ff2c8b..08467e4c0 100644
--- a/shared/extra-utils/server/redundancy.ts
+++ b/shared/extra-utils/server/redundancy.ts
@@ -1,6 +1,7 @@
1import { makePutBodyRequest } from '../requests/requests' 1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
2import { VideoRedundanciesTarget } from '@shared/models'
2 3
3async function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) { 4function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) {
4 const path = '/api/v1/server/redundancy/' + host 5 const path = '/api/v1/server/redundancy/' + host
5 6
6 return makePutBodyRequest({ 7 return makePutBodyRequest({
@@ -12,6 +13,69 @@ async function updateRedundancy (url: string, accessToken: string, host: string,
12 }) 13 })
13} 14}
14 15
16function listVideoRedundancies (options: {
17 url: string
18 accessToken: string
19 target: VideoRedundanciesTarget
20 start?: number
21 count?: number
22 sort?: string
23 statusCodeExpected?: number
24}) {
25 const path = '/api/v1/server/redundancy/videos'
26
27 const { url, accessToken, target, statusCodeExpected, start, count, sort } = options
28
29 return makeGetRequest({
30 url,
31 token: accessToken,
32 path,
33 query: {
34 start: start ?? 0,
35 count: count ?? 5,
36 sort: sort ?? 'name',
37 target
38 },
39 statusCodeExpected: statusCodeExpected || 200
40 })
41}
42
43function addVideoRedundancy (options: {
44 url: string
45 accessToken: string
46 videoId: number
47}) {
48 const path = '/api/v1/server/redundancy/videos'
49 const { url, accessToken, videoId } = options
50
51 return makePostBodyRequest({
52 url,
53 token: accessToken,
54 path,
55 fields: { videoId },
56 statusCodeExpected: 204
57 })
58}
59
60function removeVideoRedundancy (options: {
61 url: string
62 accessToken: string
63 redundancyId: number
64}) {
65 const { url, accessToken, redundancyId } = options
66 const path = '/api/v1/server/redundancy/videos/' + redundancyId
67
68 return makeDeleteRequest({
69 url,
70 token: accessToken,
71 path,
72 statusCodeExpected: 204
73 })
74}
75
15export { 76export {
16 updateRedundancy 77 updateRedundancy,
78 listVideoRedundancies,
79 addVideoRedundancy,
80 removeVideoRedundancy
17} 81}
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index a0720d778..0f883d839 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -1,16 +1,15 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2 2
3import { ChildProcess, exec, fork } from 'child_process' 3import { ChildProcess, exec, fork } from 'child_process'
4import { join } from 'path' 4import { join } from 'path'
5import { root, wait } from '../miscs/miscs' 5import { root, wait } from '../miscs/miscs'
6import { copy, pathExists, readdir, readFile, remove } from 'fs-extra' 6import { copy, pathExists, readdir, readFile, remove } from 'fs-extra'
7import { existsSync } from 'fs'
8import { expect } from 'chai' 7import { expect } from 'chai'
9import { VideoChannel } from '../../models/videos' 8import { VideoChannel } from '../../models/videos'
10import { randomInt } from '../../core-utils/miscs/miscs' 9import { randomInt } from '../../core-utils/miscs/miscs'
11 10
12interface ServerInfo { 11interface ServerInfo {
13 app: ChildProcess, 12 app: ChildProcess
14 url: string 13 url: string
15 host: string 14 host: string
16 15
@@ -20,13 +19,13 @@ interface ServerInfo {
20 serverNumber: number 19 serverNumber: number
21 20
22 client: { 21 client: {
23 id: string, 22 id: string
24 secret: string 23 secret: string
25 } 24 }
26 25
27 user: { 26 user: {
28 username: string, 27 username: string
29 password: string, 28 password: string
30 email?: string 29 email?: string
31 } 30 }
32 31
@@ -57,7 +56,7 @@ function parallelTests () {
57} 56}
58 57
59function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) { 58function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
60 let apps = [] 59 const apps = []
61 let i = 0 60 let i = 0
62 61
63 return new Promise<ServerInfo[]>(res => { 62 return new Promise<ServerInfo[]>(res => {
@@ -203,20 +202,20 @@ async function runServer (server: ServerInfo, configOverrideArg?: any, args = []
203 202
204 // Capture things if we want to 203 // Capture things if we want to
205 for (const key of Object.keys(regexps)) { 204 for (const key of Object.keys(regexps)) {
206 const regexp = regexps[ key ] 205 const regexp = regexps[key]
207 const matches = data.toString().match(regexp) 206 const matches = data.toString().match(regexp)
208 if (matches !== null) { 207 if (matches !== null) {
209 if (key === 'client_id') server.client.id = matches[ 1 ] 208 if (key === 'client_id') server.client.id = matches[1]
210 else if (key === 'client_secret') server.client.secret = matches[ 1 ] 209 else if (key === 'client_secret') server.client.secret = matches[1]
211 else if (key === 'user_username') server.user.username = matches[ 1 ] 210 else if (key === 'user_username') server.user.username = matches[1]
212 else if (key === 'user_password') server.user.password = matches[ 1 ] 211 else if (key === 'user_password') server.user.password = matches[1]
213 } 212 }
214 } 213 }
215 214
216 // Check if all required sentences are here 215 // Check if all required sentences are here
217 for (const key of Object.keys(serverRunString)) { 216 for (const key of Object.keys(serverRunString)) {
218 if (data.toString().indexOf(key) !== -1) serverRunString[ key ] = true 217 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
219 if (serverRunString[ key ] === false) dontContinue = true 218 if (serverRunString[key] === false) dontContinue = true
220 } 219 }
221 220
222 // If no, there is maybe one thing not already initialized (client/user credentials generation...) 221 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
@@ -286,7 +285,7 @@ function cleanupTests (servers: ServerInfo[]) {
286 return Promise.all(p) 285 return Promise.all(p)
287} 286}
288 287
289async function waitUntilLog (server: ServerInfo, str: string, count = 1) { 288async function waitUntilLog (server: ServerInfo, str: string, count = 1, strictCount = true) {
290 const logfile = join(root(), 'test' + server.internalServerNumber, 'logs/peertube.log') 289 const logfile = join(root(), 'test' + server.internalServerNumber, 'logs/peertube.log')
291 290
292 while (true) { 291 while (true) {
@@ -294,6 +293,7 @@ async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
294 293
295 const matches = buf.toString().match(new RegExp(str, 'g')) 294 const matches = buf.toString().match(new RegExp(str, 'g'))
296 if (matches && matches.length === count) return 295 if (matches && matches.length === count) return
296 if (matches && strictCount === false && matches.length >= count) return
297 297
298 await wait(1000) 298 await wait(1000)
299 } 299 }
diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
index 627e17cc3..f87706f6a 100644
--- a/shared/extra-utils/users/accounts.ts
+++ b/shared/extra-utils/users/accounts.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as request from 'supertest' 3import * as request from 'supertest'
4import { expect } from 'chai' 4import { expect } from 'chai'
diff --git a/shared/extra-utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts
index 5feb84179..39e720b42 100644
--- a/shared/extra-utils/users/blocklist.ts
+++ b/shared/extra-utils/users/blocklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests' 3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests'
4 4
diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts
index f9bfb3cb3..275bb0826 100644
--- a/shared/extra-utils/users/login.ts
+++ b/shared/extra-utils/users/login.ts
@@ -27,12 +27,40 @@ function login (url: string, client: Client, user: User, expectedStatus = 200) {
27 .expect(expectedStatus) 27 .expect(expectedStatus)
28} 28}
29 29
30function logout (url: string, token: string, expectedStatus = 200) {
31 const path = '/api/v1/users/revoke-token'
32
33 return request(url)
34 .post(path)
35 .set('Authorization', 'Bearer ' + token)
36 .type('form')
37 .expect(expectedStatus)
38}
39
30async function serverLogin (server: Server) { 40async function serverLogin (server: Server) {
31 const res = await login(server.url, server.client, server.user, 200) 41 const res = await login(server.url, server.client, server.user, 200)
32 42
33 return res.body.access_token as string 43 return res.body.access_token as string
34} 44}
35 45
46function refreshToken (server: ServerInfo, refreshToken: string, expectedStatus = 200) {
47 const path = '/api/v1/users/token'
48
49 const body = {
50 client_id: server.client.id,
51 client_secret: server.client.secret,
52 refresh_token: refreshToken,
53 response_type: 'code',
54 grant_type: 'refresh_token'
55 }
56
57 return request(server.url)
58 .post(path)
59 .type('form')
60 .send(body)
61 .expect(expectedStatus)
62}
63
36async function userLogin (server: Server, user: User, expectedStatus = 200) { 64async function userLogin (server: Server, user: User, expectedStatus = 200) {
37 const res = await login(server.url, server.client, user, expectedStatus) 65 const res = await login(server.url, server.client, user, expectedStatus)
38 66
@@ -60,22 +88,45 @@ function setAccessTokensToServers (servers: ServerInfo[]) {
60 const tasks: Promise<any>[] = [] 88 const tasks: Promise<any>[] = []
61 89
62 for (const server of servers) { 90 for (const server of servers) {
63 const p = serverLogin(server).then(t => server.accessToken = t) 91 const p = serverLogin(server).then(t => { server.accessToken = t })
64 tasks.push(p) 92 tasks.push(p)
65 } 93 }
66 94
67 return Promise.all(tasks) 95 return Promise.all(tasks)
68} 96}
69 97
98function loginUsingExternalToken (server: Server, username: string, externalAuthToken: string, expectedStatus = 200) {
99 const path = '/api/v1/users/token'
100
101 const body = {
102 client_id: server.client.id,
103 client_secret: server.client.secret,
104 username: username,
105 response_type: 'code',
106 grant_type: 'password',
107 scope: 'upload',
108 externalAuthToken
109 }
110
111 return request(server.url)
112 .post(path)
113 .type('form')
114 .send(body)
115 .expect(expectedStatus)
116}
117
70// --------------------------------------------------------------------------- 118// ---------------------------------------------------------------------------
71 119
72export { 120export {
73 login, 121 login,
122 logout,
74 serverLogin, 123 serverLogin,
124 refreshToken,
75 userLogin, 125 userLogin,
76 getAccessToken, 126 getAccessToken,
77 setAccessTokensToServers, 127 setAccessTokensToServers,
78 Server, 128 Server,
79 Client, 129 Client,
80 User 130 User,
131 loginUsingExternalToken
81} 132}
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts
index 9a5fd7e86..bd00894c4 100644
--- a/shared/extra-utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' 3import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' 4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
@@ -54,6 +54,7 @@ function markAsReadNotifications (url: string, token: string, ids: number[], sta
54 statusCodeExpected 54 statusCodeExpected
55 }) 55 })
56} 56}
57
57function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) { 58function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
58 const path = '/api/v1/users/me/notifications/read-all' 59 const path = '/api/v1/users/me/notifications/read-all'
59 60
@@ -77,7 +78,7 @@ type CheckerBaseParams = {
77 server: ServerInfo 78 server: ServerInfo
78 emails: object[] 79 emails: object[]
79 socketNotifications: UserNotification[] 80 socketNotifications: UserNotification[]
80 token: string, 81 token: string
81 check?: { web: boolean, mail: boolean } 82 check?: { web: boolean, mail: boolean }
82} 83}
83 84
@@ -109,10 +110,10 @@ async function checkNotification (
109 110
110 if (checkType === 'presence') { 111 if (checkType === 'presence') {
111 const obj = inspect(base.socketNotifications, { depth: 5 }) 112 const obj = inspect(base.socketNotifications, { depth: 5 })
112 expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined 113 expect(socketNotification, 'The socket notification is absent when is should be present. ' + obj).to.not.be.undefined
113 } else { 114 } else {
114 const obj = inspect(socketNotification, { depth: 5 }) 115 const obj = inspect(socketNotification, { depth: 5 })
115 expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined 116 expect(socketNotification, 'The socket notification is present when is should not be present. ' + obj).to.be.undefined
116 } 117 }
117 } 118 }
118 119
@@ -124,9 +125,9 @@ async function checkNotification (
124 .find(e => emailNotificationFinder(e)) 125 .find(e => emailNotificationFinder(e))
125 126
126 if (checkType === 'presence') { 127 if (checkType === 'presence') {
127 expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined 128 expect(email, 'The email is absent when is should be present. ' + inspect(base.emails)).to.not.be.undefined
128 } else { 129 } else {
129 expect(email, 'The email is present. ' + inspect(email)).to.be.undefined 130 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
130 } 131 }
131 } 132 }
132} 133}
@@ -171,12 +172,12 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName
171 } 172 }
172 } 173 }
173 174
174 function emailFinder (email: object) { 175 function emailNotificationFinder (email: object) {
175 const text = email[ 'text' ] 176 const text = email['text']
176 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1 177 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
177 } 178 }
178 179
179 await checkNotification(base, notificationChecker, emailFinder, type) 180 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
180} 181}
181 182
182async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) { 183async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
@@ -194,12 +195,12 @@ async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string
194 } 195 }
195 } 196 }
196 197
197 function emailFinder (email: object) { 198 function emailNotificationFinder (email: object) {
198 const text: string = email[ 'text' ] 199 const text: string = email['text']
199 return text.includes(videoUUID) && text.includes('Your video') 200 return text.includes(videoUUID) && text.includes('Your video')
200 } 201 }
201 202
202 await checkNotification(base, notificationChecker, emailFinder, type) 203 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
203} 204}
204 205
205async function checkMyVideoImportIsFinished ( 206async function checkMyVideoImportIsFinished (
@@ -225,14 +226,14 @@ async function checkMyVideoImportIsFinished (
225 } 226 }
226 } 227 }
227 228
228 function emailFinder (email: object) { 229 function emailNotificationFinder (email: object) {
229 const text: string = email[ 'text' ] 230 const text: string = email['text']
230 const toFind = success ? ' finished' : ' error' 231 const toFind = success ? ' finished' : ' error'
231 232
232 return text.includes(url) && text.includes(toFind) 233 return text.includes(url) && text.includes(toFind)
233 } 234 }
234 235
235 await checkNotification(base, notificationChecker, emailFinder, type) 236 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
236} 237}
237 238
238async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) { 239async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
@@ -250,13 +251,13 @@ async function checkUserRegistered (base: CheckerBaseParams, username: string, t
250 } 251 }
251 } 252 }
252 253
253 function emailFinder (email: object) { 254 function emailNotificationFinder (email: object) {
254 const text: string = email[ 'text' ] 255 const text: string = email['text']
255 256
256 return text.includes(' registered ') && text.includes(username) 257 return text.includes(' registered.') && text.includes(username)
257 } 258 }
258 259
259 await checkNotification(base, notificationChecker, emailFinder, type) 260 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
260} 261}
261 262
262async function checkNewActorFollow ( 263async function checkNewActorFollow (
@@ -290,13 +291,13 @@ async function checkNewActorFollow (
290 } 291 }
291 } 292 }
292 293
293 function emailFinder (email: object) { 294 function emailNotificationFinder (email: object) {
294 const text: string = email[ 'text' ] 295 const text: string = email['text']
295 296
296 return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName) 297 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
297 } 298 }
298 299
299 await checkNotification(base, notificationChecker, emailFinder, type) 300 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
300} 301}
301 302
302async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) { 303async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: string, type: CheckerType) {
@@ -319,13 +320,13 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost:
319 } 320 }
320 } 321 }
321 322
322 function emailFinder (email: object) { 323 function emailNotificationFinder (email: object) {
323 const text: string = email[ 'text' ] 324 const text: string = email['text']
324 325
325 return text.includes('instance has a new follower') && text.includes(followerHost) 326 return text.includes('instance has a new follower') && text.includes(followerHost)
326 } 327 }
327 328
328 await checkNotification(base, notificationChecker, emailFinder, type) 329 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
329} 330}
330 331
331async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) { 332async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) {
@@ -350,13 +351,13 @@ async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost
350 } 351 }
351 } 352 }
352 353
353 function emailFinder (email: object) { 354 function emailNotificationFinder (email: object) {
354 const text: string = email[ 'text' ] 355 const text: string = email['text']
355 356
356 return text.includes(' automatically followed a new instance') && text.includes(followingHost) 357 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
357 } 358 }
358 359
359 await checkNotification(base, notificationChecker, emailFinder, type) 360 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
360} 361}
361 362
362async function checkCommentMention ( 363async function checkCommentMention (
@@ -384,16 +385,17 @@ async function checkCommentMention (
384 } 385 }
385 } 386 }
386 387
387 function emailFinder (email: object) { 388 function emailNotificationFinder (email: object) {
388 const text: string = email[ 'text' ] 389 const text: string = email['text']
389 390
390 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName) 391 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
391 } 392 }
392 393
393 await checkNotification(base, notificationChecker, emailFinder, type) 394 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
394} 395}
395 396
396let lastEmailCount = 0 397let lastEmailCount = 0
398
397async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { 399async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
398 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO 400 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
399 401
@@ -413,11 +415,12 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
413 } 415 }
414 416
415 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}` 417 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
416 function emailFinder (email: object) { 418
417 return email[ 'text' ].indexOf(commentUrl) !== -1 419 function emailNotificationFinder (email: object) {
420 return email['text'].indexOf(commentUrl) !== -1
418 } 421 }
419 422
420 await checkNotification(base, notificationChecker, emailFinder, type) 423 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
421 424
422 if (type === 'presence') { 425 if (type === 'presence') {
423 // We cannot detect email duplicates, so check we received another email 426 // We cannot detect email duplicates, so check we received another email
@@ -443,12 +446,12 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
443 } 446 }
444 } 447 }
445 448
446 function emailFinder (email: object) { 449 function emailNotificationFinder (email: object) {
447 const text = email[ 'text' ] 450 const text = email['text']
448 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 451 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
449 } 452 }
450 453
451 await checkNotification(base, notificationChecker, emailFinder, type) 454 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
452} 455}
453 456
454async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) { 457async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
@@ -468,12 +471,12 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi
468 } 471 }
469 } 472 }
470 473
471 function emailFinder (email: object) { 474 function emailNotificationFinder (email: object) {
472 const text = email[ 'text' ] 475 const text = email['text']
473 return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1 476 return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
474 } 477 }
475 478
476 await checkNotification(base, notificationChecker, emailFinder, type) 479 await checkNotification(base, notificationChecker, emailNotificationFinder, type)
477} 480}
478 481
479async function checkNewBlacklistOnMyVideo ( 482async function checkNewBlacklistOnMyVideo (
@@ -495,12 +498,12 @@ async function checkNewBlacklistOnMyVideo (
495 checkVideo(video, videoName, videoUUID) 498 checkVideo(video, videoName, videoUUID)
496 } 499 }
497 500
498 function emailFinder (email: object) { 501 function emailNotificationFinder (email: object) {
499 const text = email[ 'text' ] 502 const text = email['text']
500 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 503 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
501 } 504 }
502 505
503 await checkNotification(base, notificationChecker, emailFinder, 'presence') 506 await checkNotification(base, notificationChecker, emailNotificationFinder, 'presence')
504} 507}
505 508
506// --------------------------------------------------------------------------- 509// ---------------------------------------------------------------------------
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 2fe0e55c2..54b506bce 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -9,14 +9,14 @@ import { UserUpdateMe } from '../../models/users'
9import { omit } from 'lodash' 9import { omit } from 'lodash'
10 10
11type CreateUserArgs = { 11type CreateUserArgs = {
12 url: string, 12 url: string
13 accessToken: string, 13 accessToken: string
14 username: string, 14 username: string
15 password: string, 15 password: string
16 videoQuota?: number, 16 videoQuota?: number
17 videoQuotaDaily?: number, 17 videoQuotaDaily?: number
18 role?: UserRole, 18 role?: UserRole
19 adminFlags?: UserAdminFlag, 19 adminFlags?: UserAdminFlag
20 specialStatus?: number 20 specialStatus?: number
21} 21}
22function createUser (parameters: CreateUserArgs) { 22function createUser (parameters: CreateUserArgs) {
@@ -74,8 +74,8 @@ function registerUser (url: string, username: string, password: string, specialS
74} 74}
75 75
76function registerUserWithChannel (options: { 76function registerUserWithChannel (options: {
77 url: string, 77 url: string
78 user: { username: string, password: string, displayName?: string }, 78 user: { username: string, password: string, displayName?: string }
79 channel: { name: string, displayName: string } 79 channel: { name: string, displayName: string }
80}) { 80}) {
81 const path = '/api/v1/users/register' 81 const path = '/api/v1/users/register'
@@ -130,11 +130,12 @@ function getMyUserVideoQuotaUsed (url: string, accessToken: string, specialStatu
130 .expect('Content-Type', /json/) 130 .expect('Content-Type', /json/)
131} 131}
132 132
133function getUserInformation (url: string, accessToken: string, userId: number) { 133function getUserInformation (url: string, accessToken: string, userId: number, withStats = false) {
134 const path = '/api/v1/users/' + userId 134 const path = '/api/v1/users/' + userId
135 135
136 return request(url) 136 return request(url)
137 .get(path) 137 .get(path)
138 .query({ withStats })
138 .set('Accept', 'application/json') 139 .set('Accept', 'application/json')
139 .set('Authorization', 'Bearer ' + accessToken) 140 .set('Authorization', 'Bearer ' + accessToken)
140 .expect(200) 141 .expect(200)
@@ -230,8 +231,8 @@ function updateMyUser (options: { url: string, accessToken: string } & UserUpdat
230} 231}
231 232
232function updateMyAvatar (options: { 233function updateMyAvatar (options: {
233 url: string, 234 url: string
234 accessToken: string, 235 accessToken: string
235 fixture: string 236 fixture: string
236}) { 237}) {
237 const path = '/api/v1/users/me/avatar/pick' 238 const path = '/api/v1/users/me/avatar/pick'
@@ -241,14 +242,14 @@ function updateMyAvatar (options: {
241 242
242function updateUser (options: { 243function updateUser (options: {
243 url: string 244 url: string
244 userId: number, 245 userId: number
245 accessToken: string, 246 accessToken: string
246 email?: string, 247 email?: string
247 emailVerified?: boolean, 248 emailVerified?: boolean
248 videoQuota?: number, 249 videoQuota?: number
249 videoQuotaDaily?: number, 250 videoQuotaDaily?: number
250 password?: string, 251 password?: string
251 adminFlags?: UserAdminFlag, 252 adminFlags?: UserAdminFlag
252 role?: UserRole 253 role?: UserRole
253}) { 254}) {
254 const path = '/api/v1/users/' + options.userId 255 const path = '/api/v1/users/' + options.userId
diff --git a/shared/extra-utils/videos/video-abuses.ts b/shared/extra-utils/videos/video-abuses.ts
index 7f011ec0f..81582bfc7 100644
--- a/shared/extra-utils/videos/video-abuses.ts
+++ b/shared/extra-utils/videos/video-abuses.ts
@@ -1,6 +1,8 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model' 2import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model'
3import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests' 3import { makeDeleteRequest, makePutBodyRequest, makeGetRequest } from '../requests/requests'
4import { VideoAbuseState } from '@shared/models'
5import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type'
4 6
5function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) { 7function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
6 const path = '/api/v1/videos/' + videoId + '/abuse' 8 const path = '/api/v1/videos/' + videoId + '/abuse'
@@ -13,16 +15,51 @@ function reportVideoAbuse (url: string, token: string, videoId: number | string,
13 .expect(specialStatus) 15 .expect(specialStatus)
14} 16}
15 17
16function getVideoAbusesList (url: string, token: string) { 18function getVideoAbusesList (options: {
19 url: string
20 token: string
21 id?: number
22 search?: string
23 state?: VideoAbuseState
24 videoIs?: VideoAbuseVideoIs
25 searchReporter?: string
26 searchReportee?: string
27 searchVideo?: string
28 searchVideoChannel?: string
29}) {
30 const {
31 url,
32 token,
33 id,
34 search,
35 state,
36 videoIs,
37 searchReporter,
38 searchReportee,
39 searchVideo,
40 searchVideoChannel
41 } = options
17 const path = '/api/v1/videos/abuse' 42 const path = '/api/v1/videos/abuse'
18 43
19 return request(url) 44 const query = {
20 .get(path) 45 sort: 'createdAt',
21 .query({ sort: 'createdAt' }) 46 id,
22 .set('Accept', 'application/json') 47 search,
23 .set('Authorization', 'Bearer ' + token) 48 state,
24 .expect(200) 49 videoIs,
25 .expect('Content-Type', /json/) 50 searchReporter,
51 searchReportee,
52 searchVideo,
53 searchVideoChannel
54 }
55
56 return makeGetRequest({
57 url,
58 path,
59 token,
60 query,
61 statusCodeExpected: 200
62 })
26} 63}
27 64
28function updateVideoAbuse ( 65function updateVideoAbuse (
diff --git a/shared/extra-utils/videos/video-blacklist.ts b/shared/extra-utils/videos/video-blacklist.ts
index e25a292fc..ba139ef95 100644
--- a/shared/extra-utils/videos/video-blacklist.ts
+++ b/shared/extra-utils/videos/video-blacklist.ts
@@ -13,11 +13,11 @@ function addVideoToBlacklist (
13 const path = '/api/v1/videos/' + videoId + '/blacklist' 13 const path = '/api/v1/videos/' + videoId + '/blacklist'
14 14
15 return request(url) 15 return request(url)
16 .post(path) 16 .post(path)
17 .send({ reason, unfederate }) 17 .send({ reason, unfederate })
18 .set('Accept', 'application/json') 18 .set('Accept', 'application/json')
19 .set('Authorization', 'Bearer ' + token) 19 .set('Authorization', 'Bearer ' + token)
20 .expect(specialStatus) 20 .expect(specialStatus)
21} 21}
22 22
23function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) { 23function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
@@ -35,20 +35,20 @@ function removeVideoFromBlacklist (url: string, token: string, videoId: number |
35 const path = '/api/v1/videos/' + videoId + '/blacklist' 35 const path = '/api/v1/videos/' + videoId + '/blacklist'
36 36
37 return request(url) 37 return request(url)
38 .delete(path) 38 .delete(path)
39 .set('Accept', 'application/json') 39 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + token) 40 .set('Authorization', 'Bearer ' + token)
41 .expect(specialStatus) 41 .expect(specialStatus)
42} 42}
43 43
44function getBlacklistedVideosList (parameters: { 44function getBlacklistedVideosList (parameters: {
45 url: string, 45 url: string
46 token: string, 46 token: string
47 sort?: string, 47 sort?: string
48 type?: VideoBlacklistType, 48 type?: VideoBlacklistType
49 specialStatus?: number 49 specialStatus?: number
50}) { 50}) {
51 let { url, token, sort, type, specialStatus = 200 } = parameters 51 const { url, token, sort, type, specialStatus = 200 } = parameters
52 const path = '/api/v1/videos/blacklist/' 52 const path = '/api/v1/videos/blacklist/'
53 53
54 const query = { sort, type } 54 const query = { sort, type }
diff --git a/shared/extra-utils/videos/video-captions.ts b/shared/extra-utils/videos/video-captions.ts
index 8d67f617b..5bd533bba 100644
--- a/shared/extra-utils/videos/video-captions.ts
+++ b/shared/extra-utils/videos/video-captions.ts
@@ -6,12 +6,12 @@ import { buildAbsoluteFixturePath } from '../miscs/miscs'
6const expect = chai.expect 6const expect = chai.expect
7 7
8function createVideoCaption (args: { 8function createVideoCaption (args: {
9 url: string, 9 url: string
10 accessToken: string 10 accessToken: string
11 videoId: string | number 11 videoId: string | number
12 language: string 12 language: string
13 fixture: string, 13 fixture: string
14 mimeType?: string, 14 mimeType?: string
15 statusCodeExpected?: number 15 statusCodeExpected?: number
16}) { 16}) {
17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language 17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 053842331..55f08b996 100644
--- a/shared/extra-utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' 4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
3import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' 5import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
@@ -6,7 +8,7 @@ import { ServerInfo } from '../server/servers'
6import { User } from '../../models/users/user.model' 8import { User } from '../../models/users/user.model'
7import { getMyUserInformation } from '../users/users' 9import { getMyUserInformation } from '../users/users'
8 10
9function getVideoChannelsList (url: string, start: number, count: number, sort?: string) { 11function getVideoChannelsList (url: string, start: number, count: number, sort?: string, withStats?: boolean) {
10 const path = '/api/v1/video-channels' 12 const path = '/api/v1/video-channels'
11 13
12 const req = request(url) 14 const req = request(url)
@@ -15,6 +17,7 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
15 .query({ count: count }) 17 .query({ count: count })
16 18
17 if (sort) req.query({ sort }) 19 if (sort) req.query({ sort })
20 if (withStats) req.query({ withStats })
18 21
19 return req.set('Accept', 'application/json') 22 return req.set('Accept', 'application/json')
20 .expect(200) 23 .expect(200)
@@ -22,14 +25,15 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
22} 25}
23 26
24function getAccountVideoChannelsList (parameters: { 27function getAccountVideoChannelsList (parameters: {
25 url: string, 28 url: string
26 accountName: string, 29 accountName: string
27 start?: number, 30 start?: number
28 count?: number, 31 count?: number
29 sort?: string, 32 sort?: string
30 specialStatus?: number 33 specialStatus?: number
34 withStats?: boolean
31}) { 35}) {
32 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200 } = parameters 36 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200, withStats = false } = parameters
33 37
34 const path = '/api/v1/accounts/' + accountName + '/video-channels' 38 const path = '/api/v1/accounts/' + accountName + '/video-channels'
35 39
@@ -39,7 +43,8 @@ function getAccountVideoChannelsList (parameters: {
39 query: { 43 query: {
40 start, 44 start,
41 count, 45 count,
42 sort 46 sort,
47 withStats
43 }, 48 },
44 statusCodeExpected: specialStatus 49 statusCodeExpected: specialStatus
45 }) 50 })
@@ -113,9 +118,9 @@ function getVideoChannel (url: string, channelName: string) {
113} 118}
114 119
115function updateVideoChannelAvatar (options: { 120function updateVideoChannelAvatar (options: {
116 url: string, 121 url: string
117 accessToken: string, 122 accessToken: string
118 fixture: string, 123 fixture: string
119 videoChannelName: string | number 124 videoChannelName: string | number
120}) { 125}) {
121 126
@@ -129,7 +134,7 @@ function setDefaultVideoChannel (servers: ServerInfo[]) {
129 134
130 for (const server of servers) { 135 for (const server of servers) {
131 const p = getMyUserInformation(server.url, server.accessToken) 136 const p = getMyUserInformation(server.url, server.accessToken)
132 .then(res => server.videoChannel = (res.body as User).videoChannels[0]) 137 .then(res => { server.videoChannel = (res.body as User).videoChannels[0] })
133 138
134 tasks.push(p) 139 tasks.push(p)
135 } 140 }
diff --git a/shared/extra-utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts
index 0ebf69ced..81c48412d 100644
--- a/shared/extra-utils/videos/video-comments.ts
+++ b/shared/extra-utils/videos/video-comments.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { makeDeleteRequest } from '../requests/requests' 4import { makeDeleteRequest } from '../requests/requests'
3 5
diff --git a/shared/extra-utils/videos/video-imports.ts b/shared/extra-utils/videos/video-imports.ts
index 150cc94ed..8e5abd2f5 100644
--- a/shared/extra-utils/videos/video-imports.ts
+++ b/shared/extra-utils/videos/video-imports.ts
@@ -7,7 +7,7 @@ function getYoutubeVideoUrl () {
7} 7}
8 8
9function getMagnetURI () { 9function getMagnetURI () {
10 // tslint:disable:max-line-length 10 // eslint-disable-next-line max-len
11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' 11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4'
12} 12}
13 13
diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts
index 6762c5973..5bcc02570 100644
--- a/shared/extra-utils/videos/video-playlists.ts
+++ b/shared/extra-utils/videos/video-playlists.ts
@@ -123,9 +123,9 @@ function deleteVideoPlaylist (url: string, token: string, playlistId: number | s
123} 123}
124 124
125function createVideoPlaylist (options: { 125function createVideoPlaylist (options: {
126 url: string, 126 url: string
127 token: string, 127 token: string
128 playlistAttrs: VideoPlaylistCreate, 128 playlistAttrs: VideoPlaylistCreate
129 expectedStatus?: number 129 expectedStatus?: number
130}) { 130}) {
131 const path = '/api/v1/video-playlists' 131 const path = '/api/v1/video-playlists'
@@ -148,10 +148,10 @@ function createVideoPlaylist (options: {
148} 148}
149 149
150function updateVideoPlaylist (options: { 150function updateVideoPlaylist (options: {
151 url: string, 151 url: string
152 token: string, 152 token: string
153 playlistAttrs: VideoPlaylistUpdate, 153 playlistAttrs: VideoPlaylistUpdate
154 playlistId: number | string, 154 playlistId: number | string
155 expectedStatus?: number 155 expectedStatus?: number
156}) { 156}) {
157 const path = '/api/v1/video-playlists/' + options.playlistId 157 const path = '/api/v1/video-playlists/' + options.playlistId
@@ -174,9 +174,9 @@ function updateVideoPlaylist (options: {
174} 174}
175 175
176async function addVideoInPlaylist (options: { 176async function addVideoInPlaylist (options: {
177 url: string, 177 url: string
178 token: string, 178 token: string
179 playlistId: number | string, 179 playlistId: number | string
180 elementAttrs: VideoPlaylistElementCreate | { videoId: string } 180 elementAttrs: VideoPlaylistElementCreate | { videoId: string }
181 expectedStatus?: number 181 expectedStatus?: number
182}) { 182}) {
@@ -194,11 +194,11 @@ async function addVideoInPlaylist (options: {
194} 194}
195 195
196function updateVideoPlaylistElement (options: { 196function updateVideoPlaylistElement (options: {
197 url: string, 197 url: string
198 token: string, 198 token: string
199 playlistId: number | string, 199 playlistId: number | string
200 playlistElementId: number | string, 200 playlistElementId: number | string
201 elementAttrs: VideoPlaylistElementUpdate, 201 elementAttrs: VideoPlaylistElementUpdate
202 expectedStatus?: number 202 expectedStatus?: number
203}) { 203}) {
204 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId 204 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId
@@ -213,10 +213,10 @@ function updateVideoPlaylistElement (options: {
213} 213}
214 214
215function removeVideoFromPlaylist (options: { 215function removeVideoFromPlaylist (options: {
216 url: string, 216 url: string
217 token: string, 217 token: string
218 playlistId: number | string, 218 playlistId: number | string
219 playlistElementId: number, 219 playlistElementId: number
220 expectedStatus?: number 220 expectedStatus?: number
221}) { 221}) {
222 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId 222 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId
@@ -230,14 +230,14 @@ function removeVideoFromPlaylist (options: {
230} 230}
231 231
232function reorderVideosPlaylist (options: { 232function reorderVideosPlaylist (options: {
233 url: string, 233 url: string
234 token: string, 234 token: string
235 playlistId: number | string, 235 playlistId: number | string
236 elementAttrs: { 236 elementAttrs: {
237 startPosition: number, 237 startPosition: number
238 insertAfterPosition: number, 238 insertAfterPosition: number
239 reorderLength?: number 239 reorderLength?: number
240 }, 240 }
241 expectedStatus?: number 241 expectedStatus?: number
242}) { 242}) {
243 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' 243 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
diff --git a/shared/extra-utils/videos/video-streaming-playlists.ts b/shared/extra-utils/videos/video-streaming-playlists.ts
index eb25011cb..e54da84aa 100644
--- a/shared/extra-utils/videos/video-streaming-playlists.ts
+++ b/shared/extra-utils/videos/video-streaming-playlists.ts
@@ -37,7 +37,7 @@ async function checkSegmentHash (
37 37
38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) 38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
39 39
40 const sha256Server = resSha.body[ videoName ][range] 40 const sha256Server = resSha.body[videoName][range]
41 expect(sha256(res2.body)).to.equal(sha256Server) 41 expect(sha256(res2.body)).to.equal(sha256Server)
42} 42}
43 43
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index 7a77a03ad..0d36a38a2 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { pathExists, readdir, readFile } from 'fs-extra' 4import { pathExists, readdir, readFile } from 'fs-extra'
@@ -95,6 +95,14 @@ function getVideo (url: string, id: number | string, expectedStatus = 200) {
95 .expect(expectedStatus) 95 .expect(expectedStatus)
96} 96}
97 97
98function getVideoFileMetadataUrl (url: string) {
99 return request(url)
100 .get('/')
101 .set('Accept', 'application/json')
102 .expect(200)
103 .expect('Content-Type', /json/)
104}
105
98function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) { 106function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
99 const path = '/api/v1/videos/' + id + '/views' 107 const path = '/api/v1/videos/' + id + '/views'
100 108
@@ -488,7 +496,7 @@ async function completeVideoCheck (
488 description: string 496 description: string
489 publishedAt?: string 497 publishedAt?: string
490 support: string 498 support: string
491 originallyPublishedAt?: string, 499 originallyPublishedAt?: string
492 account: { 500 account: {
493 name: string 501 name: string
494 host: string 502 host: string
@@ -509,7 +517,7 @@ async function completeVideoCheck (
509 files: { 517 files: {
510 resolution: number 518 resolution: number
511 size: number 519 size: number
512 }[], 520 }[]
513 thumbnailfile?: string 521 thumbnailfile?: string
514 previewfile?: string 522 previewfile?: string
515 } 523 }
@@ -583,9 +591,10 @@ async function completeVideoCheck (
583 591
584 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100) 592 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
585 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) 593 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
586 expect(file.size, 594 expect(
587 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')') 595 file.size,
588 .to.be.above(minSize).and.below(maxSize) 596 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
597 ).to.be.above(minSize).and.below(maxSize)
589 598
590 const torrent = await webtorrentAdd(file.magnetUri, true) 599 const torrent = await webtorrentAdd(file.magnetUri, true)
591 expect(torrent.files).to.be.an('array') 600 expect(torrent.files).to.be.an('array')
@@ -607,15 +616,28 @@ async function videoUUIDToId (url: string, id: number | string) {
607 return res.body.id 616 return res.body.id
608} 617}
609 618
610async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) { 619async function uploadVideoAndGetId (options: {
620 server: ServerInfo
621 videoName: string
622 nsfw?: boolean
623 privacy?: VideoPrivacy
624 token?: string
625}) {
611 const videoAttrs: any = { name: options.videoName } 626 const videoAttrs: any = { name: options.videoName }
612 if (options.nsfw) videoAttrs.nsfw = options.nsfw 627 if (options.nsfw) videoAttrs.nsfw = options.nsfw
628 if (options.privacy) videoAttrs.privacy = options.privacy
613 629
614 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) 630 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
615 631
616 return { id: res.body.video.id, uuid: res.body.video.uuid } 632 return { id: res.body.video.id, uuid: res.body.video.uuid }
617} 633}
618 634
635async function getLocalIdByUUID (url: string, uuid: string) {
636 const res = await getVideo(url, uuid)
637
638 return res.body.id
639}
640
619// --------------------------------------------------------------------------- 641// ---------------------------------------------------------------------------
620 642
621export { 643export {
@@ -629,6 +651,7 @@ export {
629 getAccountVideos, 651 getAccountVideos,
630 getVideoChannelVideos, 652 getVideoChannelVideos,
631 getVideo, 653 getVideo,
654 getVideoFileMetadataUrl,
632 getVideoWithToken, 655 getVideoWithToken,
633 getVideosList, 656 getVideosList,
634 getVideosListPagination, 657 getVideosListPagination,
@@ -645,5 +668,6 @@ export {
645 completeVideoCheck, 668 completeVideoCheck,
646 checkVideoFilesWereRemoved, 669 checkVideoFilesWereRemoved,
647 getPlaylistVideos, 670 getPlaylistVideos,
648 uploadVideoAndGetId 671 uploadVideoAndGetId,
672 getLocalIdByUUID
649} 673}
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index 492b672c7..20ecf176c 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -8,12 +8,33 @@ import { ViewObject } from './objects/view-object'
8import { APObject } from './objects/object.model' 8import { APObject } from './objects/object.model'
9import { PlaylistObject } from './objects/playlist-object' 9import { PlaylistObject } from './objects/playlist-object'
10 10
11export type Activity = ActivityCreate | ActivityUpdate | 11export type Activity =
12 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | 12 ActivityCreate |
13 ActivityUndo | ActivityLike | ActivityReject | ActivityView | ActivityDislike | ActivityFlag 13 ActivityUpdate |
14 14 ActivityDelete |
15export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like' | 'Reject' | 15 ActivityFollow |
16 'View' | 'Dislike' | 'Flag' 16 ActivityAccept |
17 ActivityAnnounce |
18 ActivityUndo |
19 ActivityLike |
20 ActivityReject |
21 ActivityView |
22 ActivityDislike |
23 ActivityFlag
24
25export type ActivityType =
26 'Create' |
27 'Update' |
28 'Delete' |
29 'Follow' |
30 'Accept' |
31 'Announce' |
32 'Undo' |
33 'Like' |
34 'Reject' |
35 'View' |
36 'Dislike' |
37 'Flag'
17 38
18export interface ActivityAudience { 39export interface ActivityAudience {
19 to: string[] 40 to: string[]
@@ -66,17 +87,17 @@ export interface ActivityAnnounce extends BaseActivity {
66} 87}
67 88
68export interface ActivityUndo extends BaseActivity { 89export interface ActivityUndo extends BaseActivity {
69 type: 'Undo', 90 type: 'Undo'
70 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce 91 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce
71} 92}
72 93
73export interface ActivityLike extends BaseActivity { 94export interface ActivityLike extends BaseActivity {
74 type: 'Like', 95 type: 'Like'
75 object: APObject 96 object: APObject
76} 97}
77 98
78export interface ActivityView extends BaseActivity { 99export interface ActivityView extends BaseActivity {
79 type: 'View', 100 type: 'View'
80 actor: string 101 actor: string
81 object: APObject 102 object: APObject
82} 103}
@@ -89,7 +110,7 @@ export interface ActivityDislike extends BaseActivity {
89} 110}
90 111
91export interface ActivityFlag extends BaseActivity { 112export interface ActivityFlag extends BaseActivity {
92 type: 'Flag', 113 type: 'Flag'
93 content: string, 114 content: string
94 object: APObject | APObject[] 115 object: APObject | APObject[]
95} 116}
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index b8a2dc925..f022f3d02 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -1,4 +1,4 @@
1import { ActivityPubAttributedTo } from './objects/common-objects' 1import { ActivityIconObject, ActivityPubAttributedTo } from './objects/common-objects'
2 2
3export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization' 3export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization'
4 4
@@ -27,9 +27,5 @@ export interface ActivityPubActor {
27 publicKeyPem: string 27 publicKeyPem: string
28 } 28 }
29 29
30 icon: { 30 icon: ActivityIconObject
31 type: 'Image'
32 mediaType: 'image/png'
33 url: string
34 }
35} 31}
diff --git a/shared/models/activitypub/activitypub-signature.ts b/shared/models/activitypub/activitypub-signature.ts
index 1d9f4b3b3..fafdc246d 100644
--- a/shared/models/activitypub/activitypub-signature.ts
+++ b/shared/models/activitypub/activitypub-signature.ts
@@ -1,6 +1,6 @@
1export interface ActivityPubSignature { 1export interface ActivityPubSignature {
2 type: 'GraphSignature2012' 2 type: string
3 created: Date, 3 created: Date
4 creator: string 4 creator: string
5 signatureValue: string 5 signatureValue: string
6} 6}
diff --git a/shared/models/activitypub/context.ts b/shared/models/activitypub/context.ts
new file mode 100644
index 000000000..bd795a2fd
--- /dev/null
+++ b/shared/models/activitypub/context.ts
@@ -0,0 +1 @@
export type ContextType = 'All' | 'View' | 'Announce' | 'CacheFile'
diff --git a/shared/models/activitypub/objects/cache-file-object.ts b/shared/models/activitypub/objects/cache-file-object.ts
index 4b0a3a724..19a817582 100644
--- a/shared/models/activitypub/objects/cache-file-object.ts
+++ b/shared/models/activitypub/objects/cache-file-object.ts
@@ -2,7 +2,7 @@ import { ActivityVideoUrlObject, ActivityPlaylistUrlObject } from './common-obje
2 2
3export interface CacheFileObject { 3export interface CacheFileObject {
4 id: string 4 id: string
5 type: 'CacheFile', 5 type: 'CacheFile'
6 object: string 6 object: string
7 expires: string 7 expires: string
8 url: ActivityVideoUrlObject | ActivityPlaylistUrlObject 8 url: ActivityVideoUrlObject | ActivityPlaylistUrlObject
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index de1116ab3..bb3ffe678 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -1,14 +1,15 @@
1export interface ActivityIdentifierObject { 1export interface ActivityIdentifierObject {
2 identifier: string 2 identifier: string
3 name: string 3 name: string
4 url?: string
4} 5}
5 6
6export interface ActivityIconObject { 7export interface ActivityIconObject {
7 type: 'Image' 8 type: 'Image'
8 url: string 9 url: string
9 mediaType: 'image/jpeg' 10 mediaType: 'image/jpeg' | 'image/png'
10 width: number 11 width?: number
11 height: number 12 height?: number
12} 13}
13 14
14export type ActivityVideoUrlObject = { 15export type ActivityVideoUrlObject = {
@@ -27,6 +28,15 @@ export type ActivityPlaylistSegmentHashesObject = {
27 href: string 28 href: string
28} 29}
29 30
31export type ActivityVideoFileMetadataObject = {
32 type: 'Link'
33 rel: [ 'metadata', any ]
34 mediaType: 'application/json'
35 height: number
36 href: string
37 fps: number
38}
39
30export type ActivityPlaylistInfohashesObject = { 40export type ActivityPlaylistInfohashesObject = {
31 type: 'Infohash' 41 type: 'Infohash'
32 name: string 42 name: string
@@ -71,19 +81,23 @@ export interface ActivityMentionObject {
71 name: string 81 name: string
72} 82}
73 83
74export type ActivityTagObject = ActivityPlaylistSegmentHashesObject | 84export type ActivityTagObject =
75 ActivityPlaylistInfohashesObject | 85 ActivityPlaylistSegmentHashesObject
76 ActivityVideoUrlObject | 86 | ActivityPlaylistInfohashesObject
77 ActivityHashTagObject | 87 | ActivityVideoUrlObject
78 ActivityMentionObject | 88 | ActivityHashTagObject
79 ActivityBitTorrentUrlObject | 89 | ActivityMentionObject
80 ActivityMagnetUrlObject 90 | ActivityBitTorrentUrlObject
91 | ActivityMagnetUrlObject
92 | ActivityVideoFileMetadataObject
81 93
82export type ActivityUrlObject = ActivityVideoUrlObject | 94export type ActivityUrlObject =
83 ActivityPlaylistUrlObject | 95 ActivityVideoUrlObject
84 ActivityBitTorrentUrlObject | 96 | ActivityPlaylistUrlObject
85 ActivityMagnetUrlObject | 97 | ActivityBitTorrentUrlObject
86 ActivityHtmlUrlObject 98 | ActivityMagnetUrlObject
99 | ActivityHtmlUrlObject
100 | ActivityVideoFileMetadataObject
87 101
88export interface ActivityPubAttributedTo { 102export interface ActivityPubAttributedTo {
89 type: 'Group' | 'Person' 103 type: 'Group' | 'Person'
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts
index 5f1264a76..d9622b414 100644
--- a/shared/models/activitypub/objects/video-abuse-object.ts
+++ b/shared/models/activitypub/objects/video-abuse-object.ts
@@ -1,5 +1,5 @@
1export interface VideoAbuseObject { 1export interface VideoAbuseObject {
2 type: 'Flag', 2 type: 'Flag'
3 content: string 3 content: string
4 object: string | string[] 4 object: string | string[]
5} 5}
diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts
index 239822bc4..11de8fc56 100644
--- a/shared/models/activitypub/objects/video-torrent-object.ts
+++ b/shared/models/activitypub/objects/video-torrent-object.ts
@@ -20,8 +20,8 @@ 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 downloadEnabled: boolean
25 waitTranscoding: boolean 25 waitTranscoding: boolean
26 state: VideoState 26 state: VideoState
27 published: string 27 published: string
@@ -30,7 +30,9 @@ export interface VideoTorrentObject {
30 mediaType: 'text/markdown' 30 mediaType: 'text/markdown'
31 content: string 31 content: string
32 support: string 32 support: string
33 icon: ActivityIconObject 33
34 icon: ActivityIconObject[]
35
34 url: ActivityUrlObject[] 36 url: ActivityUrlObject[]
35 likes: string 37 likes: string
36 dislikes: string 38 dislikes: string
diff --git a/shared/models/activitypub/objects/view-object.ts b/shared/models/activitypub/objects/view-object.ts
index 00348116a..4dd21ce8e 100644
--- a/shared/models/activitypub/objects/view-object.ts
+++ b/shared/models/activitypub/objects/view-object.ts
@@ -1,5 +1,5 @@
1export interface ViewObject { 1export interface ViewObject {
2 type: 'View', 2 type: 'View'
3 actor: string 3 actor: string
4 object: string 4 object: string
5} 5}
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts
index 032944281..46940772f 100644
--- a/shared/models/i18n/i18n.ts
+++ b/shared/models/i18n/i18n.ts
@@ -1,46 +1,56 @@
1export const LOCALE_FILES = [ 'player', 'server' ] 1export const LOCALE_FILES = [ 'player', 'server' ]
2 2
3export const I18N_LOCALES = { 3export const I18N_LOCALES = {
4 // Always first to avoid issues when using express acceptLanguages function when no accept language header is set
4 'en-US': 'English', 5 'en-US': 'English',
5 'fr-FR': 'Français', 6
6 'ja-JP': '日本語',
7 'eu-ES': 'Euskara',
8 'ca-ES': 'Català', 7 'ca-ES': 'Català',
9 'cs-CZ': 'Čeština', 8 'cs-CZ': 'Čeština',
10 'eo': 'Esperanto',
11 'el-GR': 'ελληνικά',
12 'de-DE': 'Deutsch', 9 'de-DE': 'Deutsch',
13 'it-IT': 'Italiano', 10 'el-GR': 'ελληνικά',
14 'nl-NL': 'Nederlands', 11 'eo': 'Esperanto',
15 'es-ES': 'Español', 12 'es-ES': 'Español',
16 'oc': 'Occitan', 13 'eu-ES': 'Euskara',
14 'fi-FI': 'suomi',
15 'fr-FR': 'Français',
17 'gd': 'Gàidhlig', 16 'gd': 'Gàidhlig',
18 'zh-Hant-TW': '繁體中文(台灣)', 17 'hu-HU': 'magyar',
18 'it-IT': 'Italiano',
19 'ja-JP': '日本語',
20 'nl-NL': 'Nederlands',
21 'pl-PL': 'Polski',
19 'pt-BR': 'Português (Brasil)', 22 'pt-BR': 'Português (Brasil)',
20 'pt-PT': 'Português (Portugal)', 23 'pt-PT': 'Português (Portugal)',
21 'sv-SE': 'svenska',
22 'pl-PL': 'Polski',
23 'fi-FI': 'suomi',
24 'ru-RU': 'русский', 24 'ru-RU': 'русский',
25 'zh-Hans-CN': '简体中文(中国)' 25 'sv-SE': 'svenska',
26 'th-TH': 'ไทย',
27 'zh-Hans-CN': '简体中文(中国)',
28 'zh-Hant-TW': '繁體中文(台灣)'
26} 29}
27 30
28const I18N_LOCALE_ALIAS = { 31const I18N_LOCALE_ALIAS = {
29 'en': 'en-US',
30 'fr': 'fr-FR',
31 'eu': 'eu-ES',
32 'ca': 'ca-ES', 32 'ca': 'ca-ES',
33 'cs': 'cs-CZ', 33 'cs': 'cs-CZ',
34 'de': 'de-DE', 34 'de': 'de-DE',
35 'el': 'el-GR',
36 'en': 'en-US',
35 'es': 'es-ES', 37 'es': 'es-ES',
36 'pt': 'pt-PT', 38 'eu': 'eu-ES',
37 'fi': 'fi-FI', 39 'fi': 'fi-FI',
38 'sv': 'sv-SE', 40 'fr': 'fr-FR',
41 'ja': 'ja-JP',
42 'it': 'it-IT',
43 'hu': 'hu-HU',
44 'nl': 'nl-NL',
39 'pl': 'pl-PL', 45 'pl': 'pl-PL',
46 'pt': 'pt-BR',
40 'ru': 'ru-RU', 47 'ru': 'ru-RU',
41 'nl': 'nl-NL', 48 'sv': 'sv-SE',
49 'th': 'th-TH',
42 'zh': 'zh-Hans-CN', 50 'zh': 'zh-Hans-CN',
51 'zh-Hans': 'zh-Hans-CN',
43 'zh-CN': 'zh-Hans-CN', 52 'zh-CN': 'zh-Hans-CN',
53 'zh-Hant': 'zh-Hant-TW',
44 'zh-TW': 'zh-Hant-TW' 54 'zh-TW': 'zh-Hant-TW'
45} 55}
46 56
@@ -56,16 +66,18 @@ export function isDefaultLocale (locale: string) {
56} 66}
57 67
58export function peertubeTranslate (str: string, translations?: { [ id: string ]: string }) { 68export function peertubeTranslate (str: string, translations?: { [ id: string ]: string }) {
69 // FIXME: remove disable rule when the client is upgraded to typescript 3.7
70 // eslint-disable-next-line
59 return translations && translations[str] ? translations[str] : str 71 return translations && translations[str] ? translations[str] : str
60} 72}
61 73
62const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l) 74const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l)
63export function is18nPath (path: string) { 75export function is18nPath (path: string) {
64 return possiblePaths.indexOf(path) !== -1 76 return possiblePaths.includes(path)
65} 77}
66 78
67export function is18nLocale (locale: string) { 79export function is18nLocale (locale: string) {
68 return POSSIBLE_LOCALES.indexOf(locale) !== -1 80 return POSSIBLE_LOCALES.includes(locale)
69} 81}
70 82
71export function getCompleteLocale (locale: string) { 83export function getCompleteLocale (locale: string) {
@@ -77,7 +89,7 @@ export function getCompleteLocale (locale: string) {
77} 89}
78 90
79export function getShortLocale (locale: string) { 91export function getShortLocale (locale: string) {
80 if (locale.indexOf('-') === -1) return locale 92 if (locale.includes('-') === false) return locale
81 93
82 return locale.split('-')[0] 94 return locale.split('-')[0]
83} 95}
diff --git a/shared/models/nodeinfo/index.d.ts b/shared/models/nodeinfo/index.d.ts
index 0a2d0492e..336cb66d2 100644
--- a/shared/models/nodeinfo/index.d.ts
+++ b/shared/models/nodeinfo/index.d.ts
@@ -98,7 +98,7 @@ export interface HttpNodeinfoDiasporaSoftwareNsSchema20 {
98 * The amount of users that signed in at least once in the last 30 days. 98 * The amount of users that signed in at least once in the last 30 days.
99 */ 99 */
100 activeMonth?: number 100 activeMonth?: number
101 }; 101 }
102 /** 102 /**
103 * The amount of posts that were made by users that are registered on this server. 103 * The amount of posts that were made by users that are registered on this server.
104 */ 104 */
diff --git a/shared/models/overviews/videos-overview.ts b/shared/models/overviews/videos-overview.ts
index e725f166b..0f3cb4a52 100644
--- a/shared/models/overviews/videos-overview.ts
+++ b/shared/models/overviews/videos-overview.ts
@@ -1,18 +1,24 @@
1import { Video, VideoChannelSummary, VideoConstant } from '../videos' 1import { Video, VideoChannelSummary, VideoConstant } from '../videos'
2 2
3export interface ChannelOverview {
4 channel: VideoChannelSummary
5 videos: Video[]
6}
7
8export interface CategoryOverview {
9 category: VideoConstant<number>
10 videos: Video[]
11}
12
13export interface TagOverview {
14 tag: string
15 videos: Video[]
16}
17
3export interface VideosOverview { 18export interface VideosOverview {
4 channels: { 19 channels: ChannelOverview[]
5 channel: VideoChannelSummary
6 videos: Video[]
7 }[]
8 20
9 categories: { 21 categories: CategoryOverview[]
10 category: VideoConstant<number>
11 videos: Video[]
12 }[]
13 22
14 tags: { 23 tags: TagOverview[]
15 tag: string
16 videos: Video[]
17 }[]
18} 24}
diff --git a/shared/models/plugins/client-hook.model.ts b/shared/models/plugins/client-hook.model.ts
index ecbe8bd3c..b53b8de99 100644
--- a/shared/models/plugins/client-hook.model.ts
+++ b/shared/models/plugins/client-hook.model.ts
@@ -65,6 +65,13 @@ export const clientActionHookObject = {
65 'action:video-watch.video.loaded': true, 65 'action:video-watch.video.loaded': true,
66 // Fired when the player finished loading 66 // Fired when the player finished loading
67 'action:video-watch.player.loaded': true, 67 'action:video-watch.player.loaded': true,
68 // Fired when the video watch page comments(threads) are loaded and load more comments on scroll
69 'action:video-watch.video-threads.loaded': true,
70 // Fired when a user click on 'View x replies' and they're loaded
71 'action:video-watch.video-thread-replies.loaded': true,
72
73 // Fired when the login page is being initialized
74 'action:login.init': true,
68 75
69 // Fired when the search page is being initialized 76 // Fired when the search page is being initialized
70 'action:search.init': true, 77 'action:search.init': true,
diff --git a/shared/models/plugins/peertube-plugin-latest-version.model.ts b/shared/models/plugins/peertube-plugin-latest-version.model.ts
index dec4618fa..811a64429 100644
--- a/shared/models/plugins/peertube-plugin-latest-version.model.ts
+++ b/shared/models/plugins/peertube-plugin-latest-version.model.ts
@@ -1,5 +1,5 @@
1export interface PeertubePluginLatestVersionRequest { 1export interface PeertubePluginLatestVersionRequest {
2 currentPeerTubeEngine?: string, 2 currentPeerTubeEngine?: string
3 3
4 npmNames: string[] 4 npmNames: string[]
5} 5}
diff --git a/shared/models/plugins/plugin-client-scope.type.ts b/shared/models/plugins/plugin-client-scope.type.ts
index 1c6d884f0..d112434e8 100644
--- a/shared/models/plugins/plugin-client-scope.type.ts
+++ b/shared/models/plugins/plugin-client-scope.type.ts
@@ -1 +1 @@
export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' export type PluginClientScope = 'common' | 'video-watch' | 'search' | 'signup' | 'login'
diff --git a/shared/models/plugins/plugin-package-json.model.ts b/shared/models/plugins/plugin-package-json.model.ts
index 3f3077671..c26e9ae5b 100644
--- a/shared/models/plugins/plugin-package-json.model.ts
+++ b/shared/models/plugins/plugin-package-json.model.ts
@@ -5,7 +5,7 @@ export type PluginTranslationPaths = {
5} 5}
6 6
7export type ClientScript = { 7export type ClientScript = {
8 script: string, 8 script: string
9 scopes: PluginClientScope[] 9 scopes: PluginClientScope[]
10} 10}
11 11
@@ -13,12 +13,12 @@ export type PluginPackageJson = {
13 name: string 13 name: string
14 version: string 14 version: string
15 description: string 15 description: string
16 engine: { peertube: string }, 16 engine: { peertube: string }
17 17
18 homepage: string, 18 homepage: string
19 author: string, 19 author: string
20 bugs: string, 20 bugs: string
21 library: string, 21 library: string
22 22
23 staticDirs: { [ name: string ]: string } 23 staticDirs: { [ name: string ]: string }
24 css: string[] 24 css: string[]
diff --git a/shared/models/plugins/plugin-playlist-privacy-manager.model.ts b/shared/models/plugins/plugin-playlist-privacy-manager.model.ts
new file mode 100644
index 000000000..f9630c77f
--- /dev/null
+++ b/shared/models/plugins/plugin-playlist-privacy-manager.model.ts
@@ -0,0 +1,8 @@
1import { VideoPlaylistPrivacy } from '@shared/models'
2
3export interface PluginPlaylistPrivacyManager {
4 // PUBLIC = 1,
5 // UNLISTED = 2,
6 // PRIVATE = 3
7 deletePlaylistPrivacy: (privacyKey: VideoPlaylistPrivacy) => boolean
8}
diff --git a/shared/models/plugins/plugin-settings-manager.model.ts b/shared/models/plugins/plugin-settings-manager.model.ts
index 63390a190..db88ae6e7 100644
--- a/shared/models/plugins/plugin-settings-manager.model.ts
+++ b/shared/models/plugins/plugin-settings-manager.model.ts
@@ -1,7 +1,11 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2 2
3export interface PluginSettingsManager { 3export interface PluginSettingsManager {
4 getSetting: (name: string) => Bluebird<string> 4 getSetting: (name: string) => Bluebird<string | boolean>
5
6 getSettings: (names: string[]) => Bluebird<{ [settingName: string]: string | boolean }>
5 7
6 setSetting: (name: string, value: string) => Bluebird<any> 8 setSetting: (name: string, value: string) => Bluebird<any>
9
10 onSettingsChange: (cb: (names: string[]) => void) => void
7} 11}
diff --git a/shared/models/plugins/plugin-video-privacy-manager.model.ts b/shared/models/plugins/plugin-video-privacy-manager.model.ts
new file mode 100644
index 000000000..d602ba297
--- /dev/null
+++ b/shared/models/plugins/plugin-video-privacy-manager.model.ts
@@ -0,0 +1,9 @@
1import { VideoPrivacy } from '@shared/models'
2
3export interface PluginVideoPrivacyManager {
4 // PUBLIC = 1
5 // UNLISTED = 2
6 // PRIVATE = 3
7 // INTERNAL = 4
8 deletePrivacy: (privacyKey: VideoPrivacy) => boolean
9}
diff --git a/shared/models/plugins/register-server-auth.model.ts b/shared/models/plugins/register-server-auth.model.ts
new file mode 100644
index 000000000..4ffce9456
--- /dev/null
+++ b/shared/models/plugins/register-server-auth.model.ts
@@ -0,0 +1,52 @@
1import { UserRole } from '@shared/models'
2import { MOAuthToken, MUser } from '@server/typings/models'
3import * as express from 'express'
4
5export type RegisterServerAuthOptions = RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions
6
7export interface RegisterServerAuthenticatedResult {
8 username: string
9 email: string
10 role?: UserRole
11 displayName?: string
12}
13
14export interface RegisterServerExternalAuthenticatedResult extends RegisterServerAuthenticatedResult {
15 req: express.Request
16 res: express.Response
17}
18
19interface RegisterServerAuthBase {
20 // Authentication name (a plugin can register multiple auth strategies)
21 authName: string
22
23 // Called by PeerTube when a user from your plugin logged out
24 onLogout?(user: MUser): void
25
26 // Your plugin can hook PeerTube access/refresh token validity
27 // So you can control for your plugin the user session lifetime
28 hookTokenValidity?(options: { token: MOAuthToken, type: 'access' | 'refresh' }): Promise<{ valid: boolean }>
29}
30
31export interface RegisterServerAuthPassOptions extends RegisterServerAuthBase {
32 // Weight of this authentication so PeerTube tries the auth methods in DESC weight order
33 getWeight(): number
34
35 // Used by PeerTube to login a user
36 // Returns null if the login failed, or { username, email } on success
37 login(body: {
38 id: string
39 password: string
40 }): Promise<RegisterServerAuthenticatedResult | null>
41}
42
43export interface RegisterServerAuthExternalOptions extends RegisterServerAuthBase {
44 // Will be displayed in a block next to the login form
45 authDisplayName: () => string
46
47 onAuthRequest: (req: express.Request, res: express.Response) => void
48}
49
50export interface RegisterServerAuthExternalResult {
51 userAuthenticated (options: RegisterServerExternalAuthenticatedResult): void
52}
diff --git a/shared/models/plugins/register-server-setting.model.ts b/shared/models/plugins/register-server-setting.model.ts
index 65a181705..920c3480f 100644
--- a/shared/models/plugins/register-server-setting.model.ts
+++ b/shared/models/plugins/register-server-setting.model.ts
@@ -1,7 +1,7 @@
1export interface RegisterServerSettingOptions { 1export interface RegisterServerSettingOptions {
2 name: string 2 name: string
3 label: string 3 label: string
4 type: 'input' 4 type: 'input' | 'input-checkbox' | 'input-textarea' | 'markdown-text' | 'markdown-enhanced'
5 5
6 // If the setting is not private, anyone can view its value (client code included) 6 // If the setting is not private, anyone can view its value (client code included)
7 // If the setting is private, only server-side hooks can access it 7 // If the setting is private, only server-side hooks can access it
@@ -9,7 +9,7 @@ export interface RegisterServerSettingOptions {
9 private: boolean 9 private: boolean
10 10
11 // Default setting value 11 // Default setting value
12 default?: string 12 default?: string | boolean
13} 13}
14 14
15export interface RegisteredServerSettings { 15export interface RegisteredServerSettings {
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts
index 80ecd9e24..20f89b86d 100644
--- a/shared/models/plugins/server-hook.model.ts
+++ b/shared/models/plugins/server-hook.model.ts
@@ -70,7 +70,7 @@ export const serverActionHookObject = {
70 // Fired when a user is updated by an admin/moderator 70 // Fired when a user is updated by an admin/moderator
71 'action:api.user.updated': true, 71 'action:api.user.updated': true,
72 72
73 // Fired when a user got a new oauth2 token 73 // Fired when a user got a new oauth2 token
74 'action:api.user.oauth2-got-token': true 74 'action:api.user.oauth2-got-token': true
75} 75}
76 76
diff --git a/shared/models/redundancy/index.ts b/shared/models/redundancy/index.ts
index 61bf0fca7..649cc489f 100644
--- a/shared/models/redundancy/index.ts
+++ b/shared/models/redundancy/index.ts
@@ -1 +1,3 @@
1export * from './videos-redundancy.model' 1export * from './videos-redundancy-strategy.model'
2export * from './video-redundancies-filters.model'
3export * from './video-redundancy.model'
diff --git a/shared/models/redundancy/video-redundancies-filters.model.ts b/shared/models/redundancy/video-redundancies-filters.model.ts
new file mode 100644
index 000000000..05ba7dfd3
--- /dev/null
+++ b/shared/models/redundancy/video-redundancies-filters.model.ts
@@ -0,0 +1 @@
export type VideoRedundanciesTarget = 'my-videos' | 'remote-videos'
diff --git a/shared/models/redundancy/video-redundancy-config-filter.type.ts b/shared/models/redundancy/video-redundancy-config-filter.type.ts
new file mode 100644
index 000000000..bb1ae701c
--- /dev/null
+++ b/shared/models/redundancy/video-redundancy-config-filter.type.ts
@@ -0,0 +1 @@
export type VideoRedundancyConfigFilter = 'nobody' | 'anybody' | 'followings'
diff --git a/shared/models/redundancy/video-redundancy.model.ts b/shared/models/redundancy/video-redundancy.model.ts
new file mode 100644
index 000000000..fa6e05832
--- /dev/null
+++ b/shared/models/redundancy/video-redundancy.model.ts
@@ -0,0 +1,35 @@
1export interface VideoRedundancy {
2 id: number
3 name: string
4 url: string
5 uuid: string
6
7 redundancies: {
8 files: FileRedundancyInformation[]
9
10 streamingPlaylists: StreamingPlaylistRedundancyInformation[]
11 }
12}
13
14interface RedundancyInformation {
15 id: number
16 fileUrl: string
17 strategy: string
18
19 createdAt: Date | string
20 updatedAt: Date | string
21
22 expiresOn: Date | string
23
24 size: number
25}
26
27// eslint-disable-next-line @typescript-eslint/no-empty-interface
28export interface FileRedundancyInformation extends RedundancyInformation {
29
30}
31
32// eslint-disable-next-line @typescript-eslint/no-empty-interface
33export interface StreamingPlaylistRedundancyInformation extends RedundancyInformation {
34
35}
diff --git a/shared/models/redundancy/videos-redundancy.model.ts b/shared/models/redundancy/videos-redundancy-strategy.model.ts
index a8c2743c1..15409abf0 100644
--- a/shared/models/redundancy/videos-redundancy.model.ts
+++ b/shared/models/redundancy/videos-redundancy-strategy.model.ts
@@ -1,4 +1,5 @@
1export type VideoRedundancyStrategy = 'most-views' | 'trending' | 'recently-added' 1export type VideoRedundancyStrategy = 'most-views' | 'trending' | 'recently-added'
2export type VideoRedundancyStrategyWithManual = VideoRedundancyStrategy | 'manual'
2 3
3export type MostViewsRedundancyStrategy = { 4export type MostViewsRedundancyStrategy = {
4 strategy: 'most-views' 5 strategy: 'most-views'
@@ -19,4 +20,4 @@ export type RecentlyAddedStrategy = {
19 minLifetime: number 20 minLifetime: number
20} 21}
21 22
22export type VideosRedundancy = MostViewsRedundancyStrategy | TrendingRedundancyStrategy | RecentlyAddedStrategy 23export type VideosRedundancyStrategy = MostViewsRedundancyStrategy | TrendingRedundancyStrategy | RecentlyAddedStrategy
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 032b91a29..07e17bda2 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -97,7 +97,7 @@ export interface CustomConfig {
97 videos: { 97 videos: {
98 http: { 98 http: {
99 enabled: boolean 99 enabled: boolean
100 }, 100 }
101 torrent: { 101 torrent: {
102 enabled: boolean 102 enabled: boolean
103 } 103 }
@@ -114,7 +114,7 @@ export interface CustomConfig {
114 114
115 followers: { 115 followers: {
116 instance: { 116 instance: {
117 enabled: boolean, 117 enabled: boolean
118 manualApproval: boolean 118 manualApproval: boolean
119 } 119 }
120 } 120 }
diff --git a/shared/models/server/emailer.model.ts b/shared/models/server/emailer.model.ts
new file mode 100644
index 000000000..069ef0bab
--- /dev/null
+++ b/shared/models/server/emailer.model.ts
@@ -0,0 +1,12 @@
1export type SendEmailOptions = {
2 to: string[]
3
4 template?: string
5 locals?: { [key: string]: any }
6
7 // override defaults
8 subject?: string
9 text?: string
10 from?: string | { name?: string, address: string }
11 replyTo?: string
12}
diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts
index bf61ab270..b0afb2c66 100644
--- a/shared/models/server/index.ts
+++ b/shared/models/server/index.ts
@@ -2,6 +2,7 @@ export * from './about.model'
2export * from './contact-form.model' 2export * from './contact-form.model'
3export * from './custom-config.model' 3export * from './custom-config.model'
4export * from './debug.model' 4export * from './debug.model'
5export * from './emailer.model'
5export * from './job.model' 6export * from './job.model'
6export * from './server-config.model' 7export * from './server-config.model'
7export * from './server-stats.model' 8export * from './server-stats.model'
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index b82a633b2..57d61c480 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -1,23 +1,125 @@
1import { SendEmailOptions } from './emailer.model'
2import { VideoResolution } from '@shared/models'
3import { ContextType } from '../activitypub/context'
4
1export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' 5export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed'
2 6
3export type JobType = 'activitypub-http-unicast' | 7export type JobType =
4 'activitypub-http-broadcast' | 8 | 'activitypub-http-unicast'
5 'activitypub-http-fetcher' | 9 | 'activitypub-http-broadcast'
6 'activitypub-follow' | 10 | 'activitypub-http-fetcher'
7 'video-file-import' | 11 | 'activitypub-follow'
8 'video-transcoding' | 12 | 'video-file-import'
9 'email' | 13 | 'video-transcoding'
10 'video-import' | 14 | 'email'
11 'videos-views' | 15 | 'video-import'
12 'activitypub-refresher' 16 | 'videos-views'
17 | 'activitypub-refresher'
18 | 'video-redundancy'
13 19
14export interface Job { 20export interface Job {
15 id: number 21 id: number
16 state: JobState 22 state: JobState
17 type: JobType 23 type: JobType
18 data: any, 24 data: any
19 error: any, 25 error: any
20 createdAt: Date | string 26 createdAt: Date | string
21 finishedOn: Date | string 27 finishedOn: Date | string
22 processedOn: Date | string 28 processedOn: Date | string
23} 29}
30
31export type ActivitypubHttpBroadcastPayload = {
32 uris: string[]
33 signatureActorId?: number
34 body: any
35 contextType?: ContextType
36}
37
38export type ActivitypubFollowPayload = {
39 followerActorId: number
40 name: string
41 host: string
42 isAutoFollow?: boolean
43 assertIsChannel?: boolean
44}
45
46export type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists'
47export type ActivitypubHttpFetcherPayload = {
48 uri: string
49 type: FetchType
50 videoId?: number
51 accountId?: number
52}
53
54export type ActivitypubHttpUnicastPayload = {
55 uri: string
56 signatureActorId?: number
57 body: any
58 contextType?: ContextType
59}
60
61export type RefreshPayload = {
62 type: 'video' | 'video-playlist' | 'actor'
63 url: string
64}
65
66export type EmailPayload = SendEmailOptions
67
68export type VideoFileImportPayload = {
69 videoUUID: string
70 filePath: string
71}
72
73export type VideoImportYoutubeDLPayload = {
74 type: 'youtube-dl'
75 videoImportId: number
76
77 generateThumbnail: boolean
78 generatePreview: boolean
79
80 fileExt?: string
81}
82export type VideoImportTorrentPayload = {
83 type: 'magnet-uri' | 'torrent-file'
84 videoImportId: number
85}
86export type VideoImportPayload = VideoImportYoutubeDLPayload | VideoImportTorrentPayload
87
88export type VideoRedundancyPayload = {
89 videoId: number
90}
91
92// Video transcoding payloads
93
94interface BaseTranscodingPayload {
95 videoUUID: string
96 isNewVideo?: boolean
97}
98
99interface HLSTranscodingPayload extends BaseTranscodingPayload {
100 type: 'hls'
101 isPortraitMode?: boolean
102 resolution: VideoResolution
103 copyCodecs: boolean
104}
105
106export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
107 type: 'new-resolution'
108 isPortraitMode?: boolean
109 resolution: VideoResolution
110}
111
112export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
113 type: 'merge-audio'
114 resolution: VideoResolution
115}
116
117export interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
118 type: 'optimize'
119}
120
121export type VideoTranscodingPayload =
122 HLSTranscodingPayload
123 | NewResolutionTranscodingPayload
124 | OptimizeTranscodingPayload
125 | MergeAudioTranscodingPayload
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index f1bb2153c..a1f9b3b5d 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -12,6 +12,22 @@ export interface ServerConfigTheme extends ServerConfigPlugin {
12 css: string[] 12 css: string[]
13} 13}
14 14
15export interface RegisteredExternalAuthConfig {
16 npmName: string
17 name: string
18 version: string
19 authName: string
20 authDisplayName: string
21}
22
23export interface RegisteredIdAndPassAuthConfig {
24 npmName: string
25 name: string
26 version: string
27 authName: string
28 weight: number
29}
30
15export interface ServerConfig { 31export interface ServerConfig {
16 serverVersion: string 32 serverVersion: string
17 serverCommit?: string 33 serverCommit?: string
@@ -28,8 +44,19 @@ export interface ServerConfig {
28 } 44 }
29 } 45 }
30 46
47 search: {
48 remoteUri: {
49 users: boolean
50 anonymous: boolean
51 }
52 }
53
31 plugin: { 54 plugin: {
32 registered: ServerConfigPlugin[] 55 registered: ServerConfigPlugin[]
56
57 registeredExternalAuths: RegisteredExternalAuthConfig[]
58
59 registeredIdAndPassAuths: RegisteredIdAndPassAuthConfig[]
33 } 60 }
34 61
35 theme: { 62 theme: {
@@ -46,7 +73,7 @@ export interface ServerConfig {
46 } 73 }
47 74
48 signup: { 75 signup: {
49 allowed: boolean, 76 allowed: boolean
50 allowedForCurrentIP: boolean 77 allowedForCurrentIP: boolean
51 requiresEmailVerification: boolean 78 requiresEmailVerification: boolean
52 } 79 }
@@ -97,7 +124,7 @@ export interface ServerConfig {
97 max: number 124 max: number
98 } 125 }
99 extensions: string[] 126 extensions: string[]
100 }, 127 }
101 file: { 128 file: {
102 extensions: string[] 129 extensions: string[]
103 } 130 }
@@ -107,7 +134,7 @@ export interface ServerConfig {
107 file: { 134 file: {
108 size: { 135 size: {
109 max: number 136 max: number
110 }, 137 }
111 extensions: string[] 138 extensions: string[]
112 } 139 }
113 } 140 }
diff --git a/shared/models/server/server-stats.model.ts b/shared/models/server/server-stats.model.ts
index 74f3de5d3..75d7dc554 100644
--- a/shared/models/server/server-stats.model.ts
+++ b/shared/models/server/server-stats.model.ts
@@ -1,7 +1,11 @@
1import { VideoRedundancyStrategy } from '../redundancy' 1import { VideoRedundancyStrategyWithManual } from '../redundancy'
2 2
3export interface ServerStats { 3export interface ServerStats {
4 totalUsers: number 4 totalUsers: number
5 totalDailyActiveUsers: number
6 totalWeeklyActiveUsers: number
7 totalMonthlyActiveUsers: number
8
5 totalLocalVideos: number 9 totalLocalVideos: number
6 totalLocalVideoViews: number 10 totalLocalVideoViews: number
7 totalLocalVideoComments: number 11 totalLocalVideoComments: number
@@ -13,11 +17,13 @@ export interface ServerStats {
13 totalInstanceFollowers: number 17 totalInstanceFollowers: number
14 totalInstanceFollowing: number 18 totalInstanceFollowing: number
15 19
16 videosRedundancy: { 20 videosRedundancy: VideosRedundancyStats[]
17 strategy: VideoRedundancyStrategy 21}
18 totalSize: number 22
19 totalUsed: number 23export interface VideosRedundancyStats {
20 totalVideoFiles: number 24 strategy: VideoRedundancyStrategyWithManual
21 totalVideos: number 25 totalSize: number
22 }[] 26 totalUsed: number
27 totalVideoFiles: number
28 totalVideos: number
23} 29}
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 4a28a229d..2f88a65de 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -33,5 +33,7 @@ export enum UserRight {
33 SEE_ALL_VIDEOS, 33 SEE_ALL_VIDEOS,
34 CHANGE_VIDEO_OWNERSHIP, 34 CHANGE_VIDEO_OWNERSHIP,
35 35
36 MANAGE_PLUGINS 36 MANAGE_PLUGINS,
37
38 MANAGE_VIDEOS_REDUNDANCIES
37} 39}
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts
index 0b6554e51..2b08b5850 100644
--- a/shared/models/users/user-role.ts
+++ b/shared/models/users/user-role.ts
@@ -7,15 +7,13 @@ export enum UserRole {
7 USER = 2 7 USER = 2
8} 8}
9 9
10// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed 10export const USER_ROLE_LABELS: { [ id in UserRole ]: string } = {
11export const USER_ROLE_LABELS: { [ id: number ]: string } = {
12 [UserRole.USER]: 'User', 11 [UserRole.USER]: 'User',
13 [UserRole.MODERATOR]: 'Moderator', 12 [UserRole.MODERATOR]: 'Moderator',
14 [UserRole.ADMINISTRATOR]: 'Administrator' 13 [UserRole.ADMINISTRATOR]: 'Administrator'
15} 14}
16 15
17// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed 16const userRoleRights: { [ id in UserRole ]: UserRight[] } = {
18const userRoleRights: { [ id: number ]: UserRight[] } = {
19 [UserRole.ADMINISTRATOR]: [ 17 [UserRole.ADMINISTRATOR]: [
20 UserRight.ALL 18 UserRight.ALL
21 ], 19 ],
@@ -40,5 +38,5 @@ const userRoleRights: { [ id: number ]: UserRight[] } = {
40export function hasUserRight (userRole: UserRole, userRight: UserRight) { 38export function hasUserRight (userRole: UserRole, userRight: UserRight) {
41 const userRights = userRoleRights[userRole] 39 const userRights = userRoleRights[userRole]
42 40
43 return userRights.indexOf(UserRight.ALL) !== -1 || userRights.indexOf(userRight) !== -1 41 return userRights.includes(UserRight.ALL) || userRights.includes(userRight)
44} 42}
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 168851196..6c959ceea 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -1,6 +1,5 @@
1import { Account } from '../actors' 1import { Account } from '../actors'
2import { VideoChannel } from '../videos/channel/video-channel.model' 2import { VideoChannel } from '../videos/channel/video-channel.model'
3import { VideoPlaylist } from '../videos/playlist/video-playlist.model'
4import { UserRole } from './user-role' 3import { UserRole } from './user-role'
5import { NSFWPolicyType } from '../videos/nsfw-policy.type' 4import { NSFWPolicyType } from '../videos/nsfw-policy.type'
6import { UserNotificationSetting } from './user-notification-setting.model' 5import { UserNotificationSetting } from './user-notification-setting.model'
@@ -32,6 +31,11 @@ export interface User {
32 videoQuotaDaily: number 31 videoQuotaDaily: number
33 videoQuotaUsed?: number 32 videoQuotaUsed?: number
34 videoQuotaUsedDaily?: number 33 videoQuotaUsedDaily?: number
34 videosCount?: number
35 videoAbusesCount?: number
36 videoAbusesAcceptedCount?: number
37 videoAbusesCreatedCount?: number
38 videoCommentsCount? : number
35 39
36 theme: string 40 theme: string
37 41
@@ -46,6 +50,10 @@ export interface User {
46 noWelcomeModal: boolean 50 noWelcomeModal: boolean
47 51
48 createdAt: Date 52 createdAt: Date
53
54 pluginAuth: string | null
55
56 lastLoginDate: Date | null
49} 57}
50 58
51export interface MyUserSpecialPlaylist { 59export interface MyUserSpecialPlaylist {
diff --git a/shared/models/videos/abuse/video-abuse-video-is.type.ts b/shared/models/videos/abuse/video-abuse-video-is.type.ts
new file mode 100644
index 000000000..e86018993
--- /dev/null
+++ b/shared/models/videos/abuse/video-abuse-video-is.type.ts
@@ -0,0 +1 @@
export type VideoAbuseVideoIs = 'deleted' | 'blacklisted'
diff --git a/shared/models/videos/abuse/video-abuse.model.ts b/shared/models/videos/abuse/video-abuse.model.ts
index 4f668795a..f2c2cdc41 100644
--- a/shared/models/videos/abuse/video-abuse.model.ts
+++ b/shared/models/videos/abuse/video-abuse.model.ts
@@ -1,6 +1,7 @@
1import { Account } from '../../actors/index' 1import { Account } from '../../actors/index'
2import { VideoConstant } from '../video-constant.model' 2import { VideoConstant } from '../video-constant.model'
3import { VideoAbuseState } from './video-abuse-state.model' 3import { VideoAbuseState } from './video-abuse-state.model'
4import { VideoChannel } from '../channel/video-channel.model'
4 5
5export interface VideoAbuse { 6export interface VideoAbuse {
6 id: number 7 id: number
@@ -14,7 +15,19 @@ export interface VideoAbuse {
14 id: number 15 id: number
15 name: string 16 name: string
16 uuid: string 17 uuid: string
18 nsfw: boolean
19 deleted: boolean
20 blacklisted: boolean
21 thumbnailPath?: string
22 channel?: VideoChannel
17 } 23 }
18 24
19 createdAt: Date 25 createdAt: Date
26 updatedAt: Date
27
28 count?: number
29 nth?: number
30
31 countReportsForReporter?: number
32 countReportsForReportee?: number
20} 33}
diff --git a/shared/models/videos/blacklist/video-blacklist.model.ts b/shared/models/videos/blacklist/video-blacklist.model.ts
index 68d59e489..a6e0ef175 100644
--- a/shared/models/videos/blacklist/video-blacklist.model.ts
+++ b/shared/models/videos/blacklist/video-blacklist.model.ts
@@ -7,11 +7,12 @@ export enum VideoBlacklistType {
7 7
8export interface VideoBlacklist { 8export interface VideoBlacklist {
9 id: number 9 id: number
10 createdAt: Date
11 updatedAt: Date
12 unfederated: boolean 10 unfederated: boolean
13 reason?: string 11 reason?: string
14 type: VideoBlacklistType 12 type: VideoBlacklistType
15 13
16 video: Video 14 video: Video
15
16 createdAt: Date
17 updatedAt: Date
17} 18}
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index de4c26b3d..421004e68 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -2,12 +2,18 @@ import { Actor } from '../../actors/actor.model'
2import { Account } from '../../actors/index' 2import { Account } from '../../actors/index'
3import { Avatar } from '../../avatars' 3import { Avatar } from '../../avatars'
4 4
5export type ViewsPerDate = {
6 date: Date
7 views: number
8}
9
5export interface VideoChannel extends Actor { 10export interface VideoChannel extends Actor {
6 displayName: string 11 displayName: string
7 description: string 12 description: string
8 support: string 13 support: string
9 isLocal: boolean 14 isLocal: boolean
10 ownerAccount?: Account 15 ownerAccount?: Account
16 viewsPerDay?: ViewsPerDate[] // chronologically ordered
11} 17}
12 18
13export interface VideoChannelSummary { 19export interface VideoChannelSummary {
diff --git a/shared/models/videos/video-file-metadata.ts b/shared/models/videos/video-file-metadata.ts
new file mode 100644
index 000000000..15683cacf
--- /dev/null
+++ b/shared/models/videos/video-file-metadata.ts
@@ -0,0 +1,18 @@
1import { FfprobeData } from "fluent-ffmpeg"
2import { DeepOmit } from "@server/models/utils"
3
4export type VideoFileMetadataModel = DeepOmit<FfprobeData, 'filename'>
5
6export class VideoFileMetadata implements VideoFileMetadataModel {
7 streams: { [x: string]: any, [x: number]: any }[]
8 format: { [x: string]: any, [x: number]: any }
9 chapters: any[]
10
11 constructor (hash: Partial<VideoFileMetadataModel>) {
12 this.chapters = hash.chapters
13 this.format = hash.format
14 this.streams = hash.streams
15
16 delete this.format.filename
17 }
18}
diff --git a/shared/models/videos/video-file.model.ts b/shared/models/videos/video-file.model.ts
index 04da0627e..6cc2d5aee 100644
--- a/shared/models/videos/video-file.model.ts
+++ b/shared/models/videos/video-file.model.ts
@@ -1,4 +1,5 @@
1import { VideoConstant, VideoResolution } from '@shared/models' 1import { VideoConstant, VideoResolution } from '@shared/models'
2import { FfprobeData } from 'fluent-ffmpeg'
2 3
3export interface VideoFile { 4export interface VideoFile {
4 magnetUri: string 5 magnetUri: string
@@ -9,4 +10,6 @@ export interface VideoFile {
9 fileUrl: string 10 fileUrl: string
10 fileDownloadUrl: string 11 fileDownloadUrl: string
11 fps: number 12 fps: number
13 metadata?: FfprobeData
14 metadataUrl?: string
12} 15}
diff --git a/shared/models/videos/video-transcoding-fps.model.ts b/shared/models/videos/video-transcoding-fps.model.ts
index 82022d2f1..25fc1c2da 100644
--- a/shared/models/videos/video-transcoding-fps.model.ts
+++ b/shared/models/videos/video-transcoding-fps.model.ts
@@ -1,6 +1,8 @@
1export type VideoTranscodingFPS = { 1export type VideoTranscodingFPS = {
2 MIN: number, 2 MIN: number
3 AVERAGE: number, 3 STANDARD: number[]
4 MAX: number, 4 HD_STANDARD: number[]
5 AVERAGE: number
6 MAX: number
5 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number 7 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number
6} 8}
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 7576439fe..a69152759 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -1,4 +1,4 @@
1import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index' 1import { AccountSummary, VideoChannelSummary, VideoState } from '../../index'
2import { Account } from '../actors' 2import { Account } from '../actors'
3import { VideoChannel } from './channel/video-channel.model' 3import { VideoChannel } from './channel/video-channel.model'
4import { VideoPrivacy } from './video-privacy.enum' 4import { VideoPrivacy } from './video-privacy.enum'