diff options
-rw-r--r-- | CHANGELOG.md | 15 | ||||
-rw-r--r-- | client/package.json | 2 | ||||
-rw-r--r-- | client/src/app/core/server/server.service.ts | 7 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.scss | 4 | ||||
-rw-r--r-- | client/src/app/shared/video-playlist/video-playlist.service.ts | 6 | ||||
-rw-r--r-- | client/src/standalone/videos/embed.ts | 3 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | server/controllers/client.ts | 8 | ||||
-rw-r--r-- | server/lib/activitypub/actor.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-follow.ts | 4 | ||||
-rw-r--r-- | server/models/video/video.ts | 52 | ||||
-rw-r--r-- | support/doc/api/openapi.yaml | 2 | ||||
-rw-r--r-- | support/docker/production/Dockerfile.buster | 2 |
13 files changed, 69 insertions, 42 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 3149476bb..4887614a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -1,5 +1,20 @@ | |||
1 | # Changelog | 1 | # Changelog |
2 | 2 | ||
3 | ## v2.1.1 | ||
4 | |||
5 | ### Bug fixes | ||
6 | |||
7 | * Fix youtube-dl in docker image | ||
8 | * Fix playlist creation/update | ||
9 | * Fix fetch of instance config in client | ||
10 | * Manual approves followers only for the instance (and not accounts/channels) | ||
11 | * Fix avatar update | ||
12 | * Fix CSP for embeds | ||
13 | * Fix scroll of the menu on mobile | ||
14 | * Fix CPU usage of PostgreSQL | ||
15 | * Fix embed for iOS | ||
16 | |||
17 | |||
3 | ## v2.1.0 | 18 | ## v2.1.0 |
4 | 19 | ||
5 | **Since v2.0.0** | 20 | **Since v2.0.0** |
diff --git a/client/package.json b/client/package.json index b9ddf7042..024e0b1d9 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "name": "peertube-client", | 2 | "name": "peertube-client", |
3 | "version": "2.1.0", | 3 | "version": "2.1.1", |
4 | "private": true, | 4 | "private": true, |
5 | "license": "AGPL-3.0", | 5 | "license": "AGPL-3.0", |
6 | "author": { | 6 | "author": { |
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts index cdcbcb528..3997ce6db 100644 --- a/client/src/app/core/server/server.service.ts +++ b/client/src/app/core/server/server.service.ts | |||
@@ -159,8 +159,11 @@ export class ServerService { | |||
159 | if (!this.configObservable) { | 159 | if (!this.configObservable) { |
160 | this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) | 160 | this.configObservable = this.http.get<ServerConfig>(ServerService.BASE_CONFIG_URL) |
161 | .pipe( | 161 | .pipe( |
162 | tap(this.saveConfigLocally), | 162 | tap(config => this.saveConfigLocally(config)), |
163 | tap(() => this.configLoaded = true), | 163 | tap(config => { |
164 | this.config = config | ||
165 | this.configLoaded = true | ||
166 | }), | ||
164 | tap(() => { | 167 | tap(() => { |
165 | if (this.configReset) { | 168 | if (this.configReset) { |
166 | this.configReloaded.next() | 169 | this.configReloaded.next() |
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index 2963d4d19..b05173751 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -254,6 +254,10 @@ menu { | |||
254 | @media screen and (max-width: $mobile-view) { | 254 | @media screen and (max-width: $mobile-view) { |
255 | .menu-wrapper { | 255 | .menu-wrapper { |
256 | width: 100% !important; | 256 | width: 100% !important; |
257 | |||
258 | menu { | ||
259 | overflow-y: auto; | ||
260 | } | ||
257 | } | 261 | } |
258 | 262 | ||
259 | .top-menu, .footer { | 263 | .top-menu, .footer { |
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts index bae6f9e04..38d915c6b 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts | |||
@@ -141,6 +141,8 @@ export class VideoPlaylistService { | |||
141 | return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) | 141 | return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) |
142 | .pipe( | 142 | .pipe( |
143 | tap(res => { | 143 | tap(res => { |
144 | if (!this.myAccountPlaylistCache) return | ||
145 | |||
144 | this.myAccountPlaylistCache.total++ | 146 | this.myAccountPlaylistCache.total++ |
145 | 147 | ||
146 | this.myAccountPlaylistCache.data.push({ | 148 | this.myAccountPlaylistCache.data.push({ |
@@ -161,6 +163,8 @@ export class VideoPlaylistService { | |||
161 | .pipe( | 163 | .pipe( |
162 | map(this.restExtractor.extractDataBool), | 164 | map(this.restExtractor.extractDataBool), |
163 | tap(() => { | 165 | tap(() => { |
166 | if (!this.myAccountPlaylistCache) return | ||
167 | |||
164 | const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id) | 168 | const playlist = this.myAccountPlaylistCache.data.find(p => p.id === videoPlaylist.id) |
165 | playlist.displayName = body.displayName | 169 | playlist.displayName = body.displayName |
166 | 170 | ||
@@ -175,6 +179,8 @@ export class VideoPlaylistService { | |||
175 | .pipe( | 179 | .pipe( |
176 | map(this.restExtractor.extractDataBool), | 180 | map(this.restExtractor.extractDataBool), |
177 | tap(() => { | 181 | tap(() => { |
182 | if (!this.myAccountPlaylistCache) return | ||
183 | |||
178 | this.myAccountPlaylistCache.total-- | 184 | this.myAccountPlaylistCache.total-- |
179 | this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data | 185 | this.myAccountPlaylistCache.data = this.myAccountPlaylistCache.data |
180 | .filter(p => p.id !== videoPlaylist.id) | 186 | .filter(p => p.id !== videoPlaylist.id) |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index c91ae08b9..5213443fc 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -262,6 +262,9 @@ export class PeerTubeEmbed { | |||
262 | 262 | ||
263 | private async buildDock (videoInfo: VideoDetails, configResponse: Response) { | 263 | private async buildDock (videoInfo: VideoDetails, configResponse: Response) { |
264 | if (this.controls) { | 264 | if (this.controls) { |
265 | // On webtorrent fallback, player may have been disposed | ||
266 | if (!this.player.player_) return | ||
267 | |||
265 | const title = this.title ? videoInfo.name : undefined | 268 | const title = this.title ? videoInfo.name : undefined |
266 | 269 | ||
267 | const config: ServerConfig = await configResponse.json() | 270 | const config: ServerConfig = await configResponse.json() |
diff --git a/package.json b/package.json index dee9840c7..49d9faf97 100644 --- a/package.json +++ b/package.json | |||
@@ -1,7 +1,7 @@ | |||
1 | { | 1 | { |
2 | "name": "peertube", | 2 | "name": "peertube", |
3 | "description": "Federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.", | 3 | "description": "Federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.", |
4 | "version": "2.1.0", | 4 | "version": "2.1.1", |
5 | "private": true, | 5 | "private": true, |
6 | "licence": "AGPL-3.0", | 6 | "licence": "AGPL-3.0", |
7 | "engines": { | 7 | "engines": { |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index dc3ff18fc..56685f102 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -2,10 +2,11 @@ import * as express from 'express' | |||
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { root } from '../helpers/core-utils' | 3 | import { root } from '../helpers/core-utils' |
4 | import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers/constants' | 4 | import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers/constants' |
5 | import { asyncMiddleware } from '../middlewares' | 5 | import { asyncMiddleware, embedCSP } from '../middlewares' |
6 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n' | 6 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n' |
7 | import { ClientHtml } from '../lib/client-html' | 7 | import { ClientHtml } from '../lib/client-html' |
8 | import { logger } from '../helpers/logger' | 8 | import { logger } from '../helpers/logger' |
9 | import { CONFIG } from '@server/initializers/config' | ||
9 | 10 | ||
10 | const clientsRouter = express.Router() | 11 | const clientsRouter = express.Router() |
11 | 12 | ||
@@ -19,8 +20,13 @@ clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) | |||
19 | clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) | 20 | clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) |
20 | clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) | 21 | clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) |
21 | 22 | ||
23 | const embedCSPMiddleware = CONFIG.CSP.ENABLED | ||
24 | ? embedCSP | ||
25 | : (req: express.Request, res: express.Response, next: express.NextFunction) => next() | ||
26 | |||
22 | clientsRouter.use( | 27 | clientsRouter.use( |
23 | '/videos/embed', | 28 | '/videos/embed', |
29 | embedCSPMiddleware, | ||
24 | (req: express.Request, res: express.Response) => { | 30 | (req: express.Request, res: express.Response) => { |
25 | res.removeHeader('X-Frame-Options') | 31 | res.removeHeader('X-Frame-Options') |
26 | res.sendFile(embedPath) | 32 | res.sendFile(embedPath) |
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index f802658cf..0b21de0ca 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -176,8 +176,8 @@ async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo | |||
176 | if (!info.name) return actor | 176 | if (!info.name) return actor |
177 | 177 | ||
178 | if (actor.Avatar) { | 178 | if (actor.Avatar) { |
179 | // Don't update the avatar if the filename did not change | 179 | // Don't update the avatar if the file URL did not change |
180 | if (actor.Avatar.fileUrl === info.fileUrl) return actor | 180 | if (info.fileUrl && actor.Avatar.fileUrl === info.fileUrl) return actor |
181 | 181 | ||
182 | try { | 182 | try { |
183 | await actor.Avatar.destroy({ transaction: t }) | 183 | await actor.Avatar.destroy({ transaction: t }) |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 85f22d654..db7fb8568 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -59,7 +59,9 @@ async function processFollow (byActor: MActorSignature, targetActorURL: string) | |||
59 | transaction: t | 59 | transaction: t |
60 | }) | 60 | }) |
61 | 61 | ||
62 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { | 62 | // Set the follow as accepted if the remote actor follows a channel or account |
63 | // Or if the instance automatically accepts followers | ||
64 | if (actorFollow.state !== 'accepted' && (isFollowingInstance === false || CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false)) { | ||
63 | actorFollow.state = 'accepted' | 65 | actorFollow.state = 'accepted' |
64 | await actorFollow.save({ transaction: t }) | 66 | await actorFollow.save({ transaction: t }) |
65 | } | 67 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index eacffe186..a91a7663d 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -136,8 +136,7 @@ import { | |||
136 | MVideoThumbnailBlacklist, | 136 | MVideoThumbnailBlacklist, |
137 | MVideoWithAllFiles, | 137 | MVideoWithAllFiles, |
138 | MVideoWithFile, | 138 | MVideoWithFile, |
139 | MVideoWithRights, | 139 | MVideoWithRights |
140 | MStreamingPlaylistFiles | ||
141 | } from '../../typings/models' | 140 | } from '../../typings/models' |
142 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' | 141 | import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' |
143 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 142 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
@@ -437,42 +436,31 @@ export type AvailableForListIDsOptions = { | |||
437 | } | 436 | } |
438 | 437 | ||
439 | if (options.followerActorId) { | 438 | if (options.followerActorId) { |
440 | let localVideosReq: WhereOptions = {} | 439 | let localVideosReq = '' |
441 | if (options.includeLocalVideos === true) { | 440 | if (options.includeLocalVideos === true) { |
442 | localVideosReq = { remote: false } | 441 | localVideosReq = ' UNION ALL SELECT "video"."id" FROM "video" WHERE remote IS FALSE' |
443 | } | 442 | } |
444 | 443 | ||
445 | // Force actorId to be a number to avoid SQL injections | 444 | // Force actorId to be a number to avoid SQL injections |
446 | const actorIdNumber = parseInt(options.followerActorId.toString(), 10) | 445 | const actorIdNumber = parseInt(options.followerActorId.toString(), 10) |
447 | whereAnd.push({ | 446 | whereAnd.push({ |
448 | [Op.or]: [ | 447 | id: { |
449 | { | 448 | [Op.in]: Sequelize.literal( |
450 | id: { | 449 | '(' + |
451 | [ Op.in ]: Sequelize.literal( | 450 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + |
452 | '(' + | 451 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
453 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | 452 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + |
454 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 453 | ' UNION ALL ' + |
455 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | 454 | 'SELECT "video"."id" AS "id" FROM "video" ' + |
456 | ')' | 455 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + |
457 | ) | 456 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + |
458 | } | 457 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + |
459 | }, | 458 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + |
460 | { | 459 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + |
461 | id: { | 460 | localVideosReq + |
462 | [ Op.in ]: Sequelize.literal( | 461 | ')' |
463 | '(' + | 462 | ) |
464 | 'SELECT "video"."id" AS "id" FROM "video" ' + | 463 | } |
465 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
466 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
467 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + | ||
468 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + | ||
469 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
470 | ')' | ||
471 | ) | ||
472 | } | ||
473 | }, | ||
474 | localVideosReq | ||
475 | ] | ||
476 | }) | 464 | }) |
477 | } | 465 | } |
478 | 466 | ||
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index f1cfb81a4..85f1102b4 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -1,7 +1,7 @@ | |||
1 | openapi: 3.0.0 | 1 | openapi: 3.0.0 |
2 | info: | 2 | info: |
3 | title: PeerTube | 3 | title: PeerTube |
4 | version: 2.1.0 | 4 | version: 2.1.1 |
5 | contact: | 5 | contact: |
6 | name: PeerTube Community | 6 | name: PeerTube Community |
7 | url: 'https://joinpeertube.org' | 7 | url: 'https://joinpeertube.org' |
diff --git a/support/docker/production/Dockerfile.buster b/support/docker/production/Dockerfile.buster index 515aeb5f1..414bf9aac 100644 --- a/support/docker/production/Dockerfile.buster +++ b/support/docker/production/Dockerfile.buster | |||
@@ -7,7 +7,7 @@ ARG NPM_RUN_BUILD_OPTS | |||
7 | 7 | ||
8 | # Install dependencies | 8 | # Install dependencies |
9 | RUN apt update \ | 9 | RUN apt update \ |
10 | && apt install -y --no-install-recommends openssl ffmpeg gnupg gosu \ | 10 | && apt install -y --no-install-recommends openssl ffmpeg python ca-certificates gnupg gosu \ |
11 | && gosu nobody true \ | 11 | && gosu nobody true \ |
12 | && rm /var/lib/apt/lists/* -fR | 12 | && rm /var/lib/apt/lists/* -fR |
13 | 13 | ||