aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml37
-rw-r--r--README.md2
-rw-r--r--client/src/app/app.component.ts2
-rw-r--r--client/src/app/core/plugins/plugin.service.ts3
-rw-r--r--client/src/app/shared/misc/utils.ts36
-rw-r--r--client/src/app/videos/videos-routing.module.ts2
-rw-r--r--client/src/sass/application.scss1
-rw-r--r--client/src/sass/player/_player-variables.scss6
-rw-r--r--client/src/sass/player/peertube-skin.scss6
-rw-r--r--config/default.yaml2
-rw-r--r--config/production.yaml.example2
-rw-r--r--server/helpers/custom-validators/plugins.ts8
-rw-r--r--server/initializers/checker-before-init.ts2
-rw-r--r--server/initializers/config.ts4
-rw-r--r--server/lib/emailer.ts34
-rw-r--r--server/lib/peertube-socket.ts16
-rw-r--r--support/doc/api/openapi.yaml33
-rw-r--r--support/doc/tools.md20
18 files changed, 164 insertions, 52 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2f69eb1d2..b8c9c5fda 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,13 +3,13 @@ image: chocobozzz/peertube-ci:10
3stages: 3stages:
4 - build-and-lint 4 - build-and-lint
5 - test 5 - test
6 - nightly 6 - docker-nightly
7 7
8before_script: 8#before_script:
9 - 'sed -i -z "s/database:\n hostname: ''localhost''/database:\n hostname: ''postgres''/" config/test.yaml' 9# - 'sed -i -z "s/database:\n hostname: ''localhost''/database:\n hostname: ''postgres''/" config/test.yaml'
10 - 'sed -i -z "s/redis:\n hostname: ''localhost''/redis:\n hostname: ''redis''/" config/test.yaml' 10# - 'sed -i -z "s/redis:\n hostname: ''localhost''/redis:\n hostname: ''redis''/" config/test.yaml'
11 - if [[ $CI_JOB_STAGE == "test" ]]; then psql -c "create user peertube with password 'peertube';"; fi 11# - if [[ $CI_JOB_STAGE == "test" ]]; then psql -c "create user peertube with password 'peertube';"; fi
12 - NOCLIENT=1 yarn install --pure-lockfile --cache-folder .yarn-cache 12# - NOCLIENT=1 yarn install --pure-lockfile --cache-folder .yarn-cache
13 13
14cache: 14cache:
15 key: yarn 15 key: yarn
@@ -85,7 +85,7 @@ cache:
85# - NODE_PENDING_JOB_WAIT=1000 npm run ci -- api-$CI_NODE_INDEX 85# - NODE_PENDING_JOB_WAIT=1000 npm run ci -- api-$CI_NODE_INDEX
86 86
87build-nightly: 87build-nightly:
88 stage: nightly 88 stage: docker-nightly
89 only: 89 only:
90 - schedules 90 - schedules
91 script: 91 script:
@@ -98,3 +98,26 @@ build-nightly:
98 - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}"); fi 98 - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}"); fi
99 - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then scp ./peertube-nightly-* ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/nightly; fi 99 - if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then scp ./peertube-nightly-* ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/nightly; fi
100 100
101.docker: &docker
102 stage: docker-nightly
103 image:
104 name: gcr.io/kaniko-project/executor:debug
105 entrypoint: [""]
106 before_script:
107 - echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$CI_REGISTRY_AUTH\",\"email\":\"$CI_REGISTRY_EMAIL\"}}}" > /kaniko/.docker/config.json
108 script:
109 - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/support/docker/production/Dockerfile.stretch --destination $DOCKER_IMAGE_NAME
110
111build-docker-develop:
112 <<: *docker
113 only:
114 - schedules
115 variables:
116 DOCKER_IMAGE_NAME: chocobozzz/peertube:develop-stretch
117
118build-docker-tag:
119 <<: *docker
120 only:
121 - tags
122 variables:
123 DOCKER_IMAGE_NAME: chocobozzz/peertube:$CI_COMMIT_TAG-stretch
diff --git a/README.md b/README.md
index 29478b085..4a785478c 100644
--- a/README.md
+++ b/README.md
@@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch
182 182
183See our REST API documentation: 183See our REST API documentation:
184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) 184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml)
185 * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html) 185 * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html)
186 186
187See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). 187See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub).
188 188
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index db1f91f8c..50c5f5b9b 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -226,7 +226,7 @@ export class AppComponent implements OnInit {
226 new Hotkey('g o', (event: KeyboardEvent): boolean => { 226 new Hotkey('g o', (event: KeyboardEvent): boolean => {
227 this.router.navigate([ '/videos/overview' ]) 227 this.router.navigate([ '/videos/overview' ])
228 return false 228 return false
229 }, undefined, this.i18n('Go to the videos overview page')), 229 }, undefined, this.i18n('Go to the discover videos page')),
230 new Hotkey('g t', (event: KeyboardEvent): boolean => { 230 new Hotkey('g t', (event: KeyboardEvent): boolean => {
231 this.router.navigate([ '/videos/trending' ]) 231 this.router.navigate([ '/videos/trending' ])
232 return false 232 return false
diff --git a/client/src/app/core/plugins/plugin.service.ts b/client/src/app/core/plugins/plugin.service.ts
index 3bb82e8a9..3af36765a 100644
--- a/client/src/app/core/plugins/plugin.service.ts
+++ b/client/src/app/core/plugins/plugin.service.ts
@@ -18,6 +18,7 @@ import { PublicServerSetting } from '@shared/models/plugins/public-server.settin
18import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' 18import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
19import { RegisterClientHelpers } from '../../../types/register-client-option.model' 19import { RegisterClientHelpers } from '../../../types/register-client-option.model'
20import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model' 20import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model'
21import { importModule } from '@app/shared/misc/utils'
21 22
22interface HookStructValue extends RegisterClientHookOptions { 23interface HookStructValue extends RegisterClientHookOptions {
23 plugin: ServerConfigPlugin 24 plugin: ServerConfigPlugin
@@ -222,7 +223,7 @@ export class PluginService implements ClientHook {
222 console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name) 223 console.log('Loading script %s of plugin %s.', clientScript.script, plugin.name)
223 224
224 return this.zone.runOutsideAngular(() => { 225 return this.zone.runOutsideAngular(() => {
225 return import(/* webpackIgnore: true */ clientScript.script) 226 return importModule(clientScript.script)
226 .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers })) 227 .then((script: ClientScriptModule) => script.register({ registerHook, peertubeHelpers }))
227 .then(() => this.sortHooksByPriority()) 228 .then(() => this.sortHooksByPriority())
228 .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err)) 229 .catch(err => console.error('Cannot import or register plugin %s.', pluginInfo.plugin.name, err))
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 85fc1c3a0..f26240d21 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -134,6 +134,41 @@ function scrollToTop () {
134 window.scroll(0, 0) 134 window.scroll(0, 0)
135} 135}
136 136
137// Thanks: https://github.com/uupaa/dynamic-import-polyfill
138function importModule (path: string) {
139 return new Promise((resolve, reject) => {
140 const vector = '$importModule$' + Math.random().toString(32).slice(2)
141 const script = document.createElement('script')
142
143 const destructor = () => {
144 delete window[ vector ]
145 script.onerror = null
146 script.onload = null
147 script.remove()
148 URL.revokeObjectURL(script.src)
149 script.src = ''
150 }
151
152 script.defer = true
153 script.type = 'module'
154
155 script.onerror = () => {
156 reject(new Error(`Failed to import: ${path}`))
157 destructor()
158 }
159 script.onload = () => {
160 resolve(window[ vector ])
161 destructor()
162 }
163 const absURL = (environment.apiUrl || window.location.origin) + path
164 const loader = `import * as m from "${absURL}"; window.${vector} = m;` // export Module
165 const blob = new Blob([ loader ], { type: 'text/javascript' })
166 script.src = URL.createObjectURL(blob)
167
168 document.head.appendChild(script)
169 })
170}
171
137export { 172export {
138 sortBy, 173 sortBy,
139 durationToString, 174 durationToString,
@@ -147,5 +182,6 @@ export {
147 objectToFormData, 182 objectToFormData,
148 objectLineFeedToHtml, 183 objectLineFeedToHtml,
149 removeElementFromArray, 184 removeElementFromArray,
185 importModule,
150 scrollToTop 186 scrollToTop
151} 187}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 4eaae93cb..f0049d8c4 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -19,7 +19,7 @@ const videosRoutes: Routes = [
19 component: VideoOverviewComponent, 19 component: VideoOverviewComponent,
20 data: { 20 data: {
21 meta: { 21 meta: {
22 title: 'Videos overview' 22 title: 'Discover videos'
23 } 23 }
24 } 24 }
25 }, 25 },
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index c64a8ebf8..4fa722327 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -1,5 +1,4 @@
1$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; 1$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
2@import '_bootstrap';
3 2
4@import '_variables'; 3@import '_variables';
5@import '_mixins'; 4@import '_mixins';
diff --git a/client/src/sass/player/_player-variables.scss b/client/src/sass/player/_player-variables.scss
index 4e9e8736c..0c2359ac7 100644
--- a/client/src/sass/player/_player-variables.scss
+++ b/client/src/sass/player/_player-variables.scss
@@ -11,9 +11,3 @@ $slider-bg-color: lighten($primary-background-color, 33%);
11$progress-margin: 10px; 11$progress-margin: 10px;
12 12
13$assets-path: '../../assets/' !default; 13$assets-path: '../../assets/' !default;
14
15body {
16 --embedForegroundColor: #{$primary-foreground-color};
17
18 --embedBigPlayBackgroundColor: #{$primary-background-color};
19}
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index 039cf7e00..4bf48a570 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -2,6 +2,12 @@
2@import '_mixins'; 2@import '_mixins';
3@import './_player-variables'; 3@import './_player-variables';
4 4
5body {
6 --embedForegroundColor: #{$primary-foreground-color};
7
8 --embedBigPlayBackgroundColor: #{$primary-background-color};
9}
10
5@mixin big-play-button-triangle-size($triangle-size) { 11@mixin big-play-button-triangle-size($triangle-size) {
6 width: $triangle-size; 12 width: $triangle-size;
7 height: $triangle-size; 13 height: $triangle-size;
diff --git a/config/default.yaml b/config/default.yaml
index b7a433b99..dfba23f59 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -64,7 +64,7 @@ smtp:
64email: 64email:
65 body: 65 body:
66 signature: "PeerTube" 66 signature: "PeerTube"
67 object: 67 subject:
68 prefix: "[PeerTube]" 68 prefix: "[PeerTube]"
69 69
70# From the project root directory 70# From the project root directory
diff --git a/config/production.yaml.example b/config/production.yaml.example
index 17a1be502..267186e08 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -65,7 +65,7 @@ smtp:
65email: 65email:
66 body: 66 body:
67 signature: "PeerTube" 67 signature: "PeerTube"
68 object: 68 subject:
69 prefix: "[PeerTube]" 69 prefix: "[PeerTube]"
70 70
71# From the project root directory 71# From the project root directory
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts
index b5e32abc2..63af91a44 100644
--- a/server/helpers/custom-validators/plugins.ts
+++ b/server/helpers/custom-validators/plugins.ts
@@ -41,7 +41,11 @@ function isPluginEngineValid (engine: any) {
41} 41}
42 42
43function isPluginHomepage (value: string) { 43function isPluginHomepage (value: string) {
44 return isUrlValid(value) 44 return exists(value) && (!value || isUrlValid(value))
45}
46
47function isPluginBugs (value: string) {
48 return exists(value) && (!value || isUrlValid(value))
45} 49}
46 50
47function areStaticDirectoriesValid (staticDirs: any) { 51function areStaticDirectoriesValid (staticDirs: any) {
@@ -85,7 +89,7 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT
85 isPluginEngineValid(packageJSON.engine) && 89 isPluginEngineValid(packageJSON.engine) &&
86 isPluginHomepage(packageJSON.homepage) && 90 isPluginHomepage(packageJSON.homepage) &&
87 exists(packageJSON.author) && 91 exists(packageJSON.author) &&
88 isUrlValid(packageJSON.bugs) && 92 isPluginBugs(packageJSON.bugs) &&
89 (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) && 93 (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) &&
90 areStaticDirectoriesValid(packageJSON.staticDirs) && 94 areStaticDirectoriesValid(packageJSON.staticDirs) &&
91 areCSSPathsValid(packageJSON.css) && 95 areCSSPathsValid(packageJSON.css) &&
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 55bc820f5..a986c3e0e 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -11,7 +11,7 @@ function checkMissedConfig () {
11 'trust_proxy', 11 'trust_proxy',
12 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', 12 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
13 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', 13 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
14 'email.body.signature', 'email.object.prefix', 14 'email.body.signature', 'email.subject.prefix',
15 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', 15 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
16 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins', 16 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 'storage.plugins',
17 'log.level', 17 'log.level',
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 58241e4ea..510f7d64d 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -48,8 +48,8 @@ const CONFIG = {
48 BODY: { 48 BODY: {
49 SIGNATURE: config.get<string>('email.body.signature') 49 SIGNATURE: config.get<string>('email.body.signature')
50 }, 50 },
51 OBJECT: { 51 SUBJECT: {
52 PREFIX: config.get<string>('email.object.prefix') + ' ' 52 PREFIX: config.get<string>('email.subject.prefix') + ' '
53 } 53 }
54 }, 54 },
55 STORAGE: { 55 STORAGE: {
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index a888b7a72..76349ef8f 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -100,7 +100,7 @@ class Emailer {
100 100
101 const emailPayload: EmailPayload = { 101 const emailPayload: EmailPayload = {
102 to, 102 to,
103 subject: CONFIG.EMAIL.OBJECT.PREFIX + channelName + ' just published a new video', 103 subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video',
104 text 104 text
105 } 105 }
106 106
@@ -119,7 +119,7 @@ class Emailer {
119 119
120 const emailPayload: EmailPayload = { 120 const emailPayload: EmailPayload = {
121 to, 121 to,
122 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New follower on your channel ' + followingName, 122 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName,
123 text 123 text
124 } 124 }
125 125
@@ -137,7 +137,7 @@ class Emailer {
137 137
138 const emailPayload: EmailPayload = { 138 const emailPayload: EmailPayload = {
139 to, 139 to,
140 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New instance follower', 140 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower',
141 text 141 text
142 } 142 }
143 143
@@ -157,7 +157,7 @@ class Emailer {
157 157
158 const emailPayload: EmailPayload = { 158 const emailPayload: EmailPayload = {
159 to, 159 to,
160 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video ${video.name} is published`, 160 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`,
161 text 161 text
162 } 162 }
163 163
@@ -177,7 +177,7 @@ class Emailer {
177 177
178 const emailPayload: EmailPayload = { 178 const emailPayload: EmailPayload = {
179 to, 179 to,
180 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`, 180 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
181 text 181 text
182 } 182 }
183 183
@@ -197,7 +197,7 @@ class Emailer {
197 197
198 const emailPayload: EmailPayload = { 198 const emailPayload: EmailPayload = {
199 to, 199 to,
200 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, 200 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
201 text 201 text
202 } 202 }
203 203
@@ -219,7 +219,7 @@ class Emailer {
219 219
220 const emailPayload: EmailPayload = { 220 const emailPayload: EmailPayload = {
221 to, 221 to,
222 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New comment on your video ' + video.name, 222 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name,
223 text 223 text
224 } 224 }
225 225
@@ -241,7 +241,7 @@ class Emailer {
241 241
242 const emailPayload: EmailPayload = { 242 const emailPayload: EmailPayload = {
243 to, 243 to,
244 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Mention on video ' + video.name, 244 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name,
245 text 245 text
246 } 246 }
247 247
@@ -258,7 +258,7 @@ class Emailer {
258 258
259 const emailPayload: EmailPayload = { 259 const emailPayload: EmailPayload = {
260 to, 260 to,
261 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Received a video abuse', 261 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse',
262 text 262 text
263 } 263 }
264 264
@@ -281,7 +281,7 @@ class Emailer {
281 281
282 const emailPayload: EmailPayload = { 282 const emailPayload: EmailPayload = {
283 to, 283 to,
284 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'An auto-blacklisted video is awaiting review', 284 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
285 text 285 text
286 } 286 }
287 287
@@ -296,7 +296,7 @@ class Emailer {
296 296
297 const emailPayload: EmailPayload = { 297 const emailPayload: EmailPayload = {
298 to, 298 to,
299 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST, 299 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
300 text 300 text
301 } 301 }
302 302
@@ -318,7 +318,7 @@ class Emailer {
318 318
319 const emailPayload: EmailPayload = { 319 const emailPayload: EmailPayload = {
320 to, 320 to,
321 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${videoName} blacklisted`, 321 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`,
322 text 322 text
323 } 323 }
324 324
@@ -336,7 +336,7 @@ class Emailer {
336 336
337 const emailPayload: EmailPayload = { 337 const emailPayload: EmailPayload = {
338 to, 338 to,
339 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${video.name} unblacklisted`, 339 subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`,
340 text 340 text
341 } 341 }
342 342
@@ -353,7 +353,7 @@ class Emailer {
353 353
354 const emailPayload: EmailPayload = { 354 const emailPayload: EmailPayload = {
355 to: [ to ], 355 to: [ to ],
356 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Reset your password', 356 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password',
357 text 357 text
358 } 358 }
359 359
@@ -370,7 +370,7 @@ class Emailer {
370 370
371 const emailPayload: EmailPayload = { 371 const emailPayload: EmailPayload = {
372 to: [ to ], 372 to: [ to ],
373 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Verify your email', 373 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email',
374 text 374 text
375 } 375 }
376 376
@@ -391,7 +391,7 @@ class Emailer {
391 const to = user.email 391 const to = user.email
392 const emailPayload: EmailPayload = { 392 const emailPayload: EmailPayload = {
393 to: [ to ], 393 to: [ to ],
394 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Account ' + blockedWord, 394 subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord,
395 text 395 text
396 } 396 }
397 397
@@ -411,7 +411,7 @@ class Emailer {
411 fromDisplayName: fromEmail, 411 fromDisplayName: fromEmail,
412 replyTo: fromEmail, 412 replyTo: fromEmail,
413 to: [ CONFIG.ADMIN.EMAIL ], 413 to: [ CONFIG.ADMIN.EMAIL ],
414 subject: CONFIG.EMAIL.OBJECT.PREFIX + subject, 414 subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject,
415 text 415 text
416 } 416 }
417 417
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts
index 1c7b09175..ad2bb4845 100644
--- a/server/lib/peertube-socket.ts
+++ b/server/lib/peertube-socket.ts
@@ -8,7 +8,7 @@ class PeerTubeSocket {
8 8
9 private static instance: PeerTubeSocket 9 private static instance: PeerTubeSocket
10 10
11 private userNotificationSockets: { [ userId: number ]: SocketIO.Socket } = {} 11 private userNotificationSockets: { [ userId: number ]: SocketIO.Socket[] } = {}
12 12
13 private constructor () {} 13 private constructor () {}
14 14
@@ -22,22 +22,26 @@ class PeerTubeSocket {
22 22
23 logger.debug('User %d connected on the notification system.', userId) 23 logger.debug('User %d connected on the notification system.', userId)
24 24
25 this.userNotificationSockets[userId] = socket 25 if (!this.userNotificationSockets[userId]) this.userNotificationSockets[userId] = []
26
27 this.userNotificationSockets[userId].push(socket)
26 28
27 socket.on('disconnect', () => { 29 socket.on('disconnect', () => {
28 logger.debug('User %d disconnected from SocketIO notifications.', userId) 30 logger.debug('User %d disconnected from SocketIO notifications.', userId)
29 31
30 delete this.userNotificationSockets[userId] 32 this.userNotificationSockets[userId] = this.userNotificationSockets[userId].filter(s => s !== socket)
31 }) 33 })
32 }) 34 })
33 } 35 }
34 36
35 sendNotification (userId: number, notification: UserNotificationModelForApi) { 37 sendNotification (userId: number, notification: UserNotificationModelForApi) {
36 const socket = this.userNotificationSockets[userId] 38 const sockets = this.userNotificationSockets[userId]
37 39
38 if (!socket) return 40 if (!sockets) return
39 41
40 socket.emit('new-notification', notification.toFormattedJSON()) 42 for (const socket of sockets) {
43 socket.emit('new-notification', notification.toFormattedJSON())
44 }
41 } 45 }
42 46
43 static get Instance () { 47 static get Instance () {
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 78159f89c..89d440748 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -726,8 +726,7 @@ paths:
726 type: string 726 type: string
727 format: binary 727 format: binary
728 encoding: 728 encoding:
729 profileImage: 729 avatarfile:
730 # only accept png/jpeg
731 contentType: image/png, image/jpeg 730 contentType: image/png, image/jpeg
732 /videos: 731 /videos:
733 get: 732 get:
@@ -829,9 +828,11 @@ paths:
829 thumbnailfile: 828 thumbnailfile:
830 description: Video thumbnail file 829 description: Video thumbnail file
831 type: string 830 type: string
831 format: binary
832 previewfile: 832 previewfile:
833 description: Video preview file 833 description: Video preview file
834 type: string 834 type: string
835 format: binary
835 category: 836 category:
836 description: Video category 837 description: Video category
837 type: string 838 type: string
@@ -874,6 +875,11 @@ paths:
874 format: date-time 875 format: date-time
875 scheduleUpdate: 876 scheduleUpdate:
876 $ref: '#/components/schemas/VideoScheduledUpdate' 877 $ref: '#/components/schemas/VideoScheduledUpdate'
878 encoding:
879 thumbnailfile:
880 contentType: image/jpeg
881 previewfile:
882 contentType: image/jpeg
877 get: 883 get:
878 summary: Get a video by its id 884 summary: Get a video by its id
879 tags: 885 tags:
@@ -1029,9 +1035,11 @@ paths:
1029 thumbnailfile: 1035 thumbnailfile:
1030 description: Video thumbnail file 1036 description: Video thumbnail file
1031 type: string 1037 type: string
1038 format: binary
1032 previewfile: 1039 previewfile:
1033 description: Video preview file 1040 description: Video preview file
1034 type: string 1041 type: string
1042 format: binary
1035 privacy: 1043 privacy:
1036 $ref: '#/components/schemas/VideoPrivacySet' 1044 $ref: '#/components/schemas/VideoPrivacySet'
1037 category: 1045 category:
@@ -1080,6 +1088,13 @@ paths:
1080 - videofile 1088 - videofile
1081 - channelId 1089 - channelId
1082 - name 1090 - name
1091 encoding:
1092 videofile:
1093 contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream
1094 thumbnailfile:
1095 contentType: image/jpeg
1096 previewfile:
1097 contentType: image/jpeg
1083 x-code-samples: 1098 x-code-samples:
1084 - lang: Shell 1099 - lang: Shell
1085 source: | 1100 source: |
@@ -1142,9 +1157,11 @@ paths:
1142 thumbnailfile: 1157 thumbnailfile:
1143 description: Video thumbnail file 1158 description: Video thumbnail file
1144 type: string 1159 type: string
1160 format: binary
1145 previewfile: 1161 previewfile:
1146 description: Video preview file 1162 description: Video preview file
1147 type: string 1163 type: string
1164 format: binary
1148 privacy: 1165 privacy:
1149 $ref: '#/components/schemas/VideoPrivacySet' 1166 $ref: '#/components/schemas/VideoPrivacySet'
1150 category: 1167 category:
@@ -1188,6 +1205,13 @@ paths:
1188 required: 1205 required:
1189 - channelId 1206 - channelId
1190 - name 1207 - name
1208 encoding:
1209 torrentfile:
1210 contentType: application/x-bittorrent
1211 thumbnailfile:
1212 contentType: image/jpeg
1213 previewfile:
1214 contentType: image/jpeg
1191 /videos/abuse: 1215 /videos/abuse:
1192 get: 1216 get:
1193 summary: Get list of reported video abuses 1217 summary: Get list of reported video abuses
@@ -1308,6 +1332,9 @@ paths:
1308 description: The file to upload. 1332 description: The file to upload.
1309 type: string 1333 type: string
1310 format: binary 1334 format: binary
1335 encoding:
1336 captionfile:
1337 contentType: text/vtt, application/x-subrip
1311 responses: 1338 responses:
1312 '204': 1339 '204':
1313 $ref: '#/paths/~1users~1me/put/responses/204' 1340 $ref: '#/paths/~1users~1me/put/responses/204'
@@ -1952,7 +1979,7 @@ components:
1952 description: 'Video file size in bytes' 1979 description: 'Video file size in bytes'
1953 torrentUrl: 1980 torrentUrl:
1954 type: string 1981 type: string
1955 torrentDownaloadUrl: 1982 torrentDownloadUrl:
1956 type: string 1983 type: string
1957 fileUrl: 1984 fileUrl:
1958 type: string 1985 type: string
diff --git a/support/doc/tools.md b/support/doc/tools.md
index cf427ec84..88586bfaa 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -11,6 +11,7 @@
11 - [peertube-import-videos.js](#peertube-import-videosjs) 11 - [peertube-import-videos.js](#peertube-import-videosjs)
12 - [peertube-upload.js](#peertube-uploadjs) 12 - [peertube-upload.js](#peertube-uploadjs)
13 - [peertube-watch.js](#peertube-watchjs) 13 - [peertube-watch.js](#peertube-watchjs)
14 - [peertube-plugins.js](#peertube-pluginsjs)
14- [Server tools](#server-tools) 15- [Server tools](#server-tools)
15 - [parse-log](#parse-log) 16 - [parse-log](#parse-log)
16 - [create-transcoding-job.js](#create-transcoding-jobjs) 17 - [create-transcoding-job.js](#create-transcoding-jobjs)
@@ -19,6 +20,7 @@
19 - [optimize-old-videos.js](#optimize-old-videosjs) 20 - [optimize-old-videos.js](#optimize-old-videosjs)
20 - [update-host.js](#update-hostjs) 21 - [update-host.js](#update-hostjs)
21 - [reset-password.js](#reset-passwordjs) 22 - [reset-password.js](#reset-passwordjs)
23 - [plugin install/uninstall](#plugin-installuninstall)
22 - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) 24 - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop)
23 - [.help](#help) 25 - [.help](#help)
24 - [Lodash example](#lodash-example) 26 - [Lodash example](#lodash-example)
@@ -182,6 +184,22 @@ It provides support for different players:
182- chromecast 184- chromecast
183 185
184 186
187#### peertube-plugins.js
188
189Install/update/uninstall or list local or NPM PeerTube plugins:
190
191```
192$ cd ${CLONE}
193$ node dist/server/tools/peertube-plugins.js --help
194$ node dist/server/tools/peertube-plugins.js list --help
195$ node dist/server/tools/peertube-plugins.js install --help
196$ node dist/server/tools/peertube-plugins.js update --help
197$ node dist/server/tools/peertube-plugins.js uninstall --help
198
199$ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path
200$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example
201```
202
185## Server tools 203## Server tools
186 204
187These scripts should be run on the server, in `peertube-latest` directory. 205These scripts should be run on the server, in `peertube-latest` directory.
@@ -262,7 +280,7 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production
262The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. 280The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running.
263If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). 281If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server).
264 282
265To install a plugin or a theme from the disk: 283To install/update a plugin or a theme from the disk:
266 284
267``` 285```
268$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path 286$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path