diff options
-rw-r--r-- | .gitlab-ci.yml | 37 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | client/src/app/app.component.ts | 2 | ||||
-rw-r--r-- | client/src/app/core/plugins/plugin.service.ts | 3 | ||||
-rw-r--r-- | client/src/app/shared/misc/utils.ts | 36 | ||||
-rw-r--r-- | client/src/app/videos/videos-routing.module.ts | 2 | ||||
-rw-r--r-- | client/src/sass/application.scss | 1 | ||||
-rw-r--r-- | client/src/sass/player/_player-variables.scss | 6 | ||||
-rw-r--r-- | client/src/sass/player/peertube-skin.scss | 6 | ||||
-rw-r--r-- | config/default.yaml | 2 | ||||
-rw-r--r-- | config/production.yaml.example | 2 | ||||
-rw-r--r-- | server/helpers/custom-validators/plugins.ts | 8 | ||||
-rw-r--r-- | server/initializers/checker-before-init.ts | 2 | ||||
-rw-r--r-- | server/initializers/config.ts | 4 | ||||
-rw-r--r-- | server/lib/emailer.ts | 34 | ||||
-rw-r--r-- | server/lib/peertube-socket.ts | 16 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 33 | ||||
-rw-r--r-- | support/doc/tools.md | 20 |
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 | |||
3 | stages: | 3 | stages: |
4 | - build-and-lint | 4 | - build-and-lint |
5 | - test | 5 | - test |
6 | - nightly | 6 | - docker-nightly |
7 | 7 | ||
8 | before_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 | ||
14 | cache: | 14 | cache: |
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 | ||
87 | build-nightly: | 87 | build-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 | |||
111 | build-docker-develop: | ||
112 | <<: *docker | ||
113 | only: | ||
114 | - schedules | ||
115 | variables: | ||
116 | DOCKER_IMAGE_NAME: chocobozzz/peertube:develop-stretch | ||
117 | |||
118 | build-docker-tag: | ||
119 | <<: *docker | ||
120 | only: | ||
121 | - tags | ||
122 | variables: | ||
123 | DOCKER_IMAGE_NAME: chocobozzz/peertube:$CI_COMMIT_TAG-stretch | ||
@@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch | |||
182 | 182 | ||
183 | See our REST API documentation: | 183 | See 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 | ||
187 | See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). | 187 | See 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 | |||
18 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 18 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
19 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' | 19 | import { RegisterClientHelpers } from '../../../types/register-client-option.model' |
20 | import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model' | 20 | import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model' |
21 | import { importModule } from '@app/shared/misc/utils' | ||
21 | 22 | ||
22 | interface HookStructValue extends RegisterClientHookOptions { | 23 | interface 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 | ||
138 | function 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 | |||
137 | export { | 172 | export { |
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 | |||
15 | body { | ||
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 | ||
5 | body { | ||
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: | |||
64 | email: | 64 | email: |
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: | |||
65 | email: | 65 | email: |
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 | ||
43 | function isPluginHomepage (value: string) { | 43 | function isPluginHomepage (value: string) { |
44 | return isUrlValid(value) | 44 | return exists(value) && (!value || isUrlValid(value)) |
45 | } | ||
46 | |||
47 | function isPluginBugs (value: string) { | ||
48 | return exists(value) && (!value || isUrlValid(value)) | ||
45 | } | 49 | } |
46 | 50 | ||
47 | function areStaticDirectoriesValid (staticDirs: any) { | 51 | function 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 | |||
189 | Install/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 | ||
187 | These scripts should be run on the server, in `peertube-latest` directory. | 205 | These 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 | |||
262 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. | 280 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. |
263 | If 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). | 281 | If 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 | ||
265 | To install a plugin or a theme from the disk: | 283 | To 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 |