aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/activitypub/client.ts23
-rw-r--r--server/controllers/activitypub/inbox.ts16
-rw-r--r--server/controllers/activitypub/outbox.ts13
-rw-r--r--server/controllers/api/abuse.ts18
-rw-r--r--server/controllers/api/blocklist.ts108
-rw-r--r--server/controllers/api/config.ts16
-rw-r--r--server/controllers/api/index.ts2
-rw-r--r--server/controllers/api/jobs.ts4
-rw-r--r--server/controllers/api/oauth-clients.ts5
-rw-r--r--server/controllers/api/plugins.ts7
-rw-r--r--server/controllers/api/server/stats.ts4
-rw-r--r--server/controllers/api/users/index.ts9
-rw-r--r--server/controllers/api/users/me.ts14
-rw-r--r--server/controllers/api/users/my-subscriptions.ts21
-rw-r--r--server/controllers/api/users/token.ts2
-rw-r--r--server/controllers/api/video-channel.ts26
-rw-r--r--server/controllers/api/video-playlist.ts2
-rw-r--r--server/controllers/api/videos/blacklist.ts3
-rw-r--r--server/controllers/api/videos/captions.ts5
-rw-r--r--server/controllers/api/videos/import.ts28
-rw-r--r--server/controllers/api/videos/live.ts9
-rw-r--r--server/controllers/api/videos/rate.ts3
-rw-r--r--server/controllers/api/videos/transcoding.ts4
-rw-r--r--server/controllers/api/videos/update.ts35
-rw-r--r--server/controllers/api/videos/upload.ts33
-rw-r--r--server/controllers/api/videos/watching.ts3
-rw-r--r--server/controllers/client.ts2
-rw-r--r--server/controllers/static.ts2
-rw-r--r--server/helpers/actors.ts17
-rw-r--r--server/helpers/audit-logger.ts4
-rw-r--r--server/helpers/core-utils.ts47
-rw-r--r--server/helpers/custom-validators/activitypub/playlist.ts3
-rw-r--r--server/helpers/custom-validators/misc.ts2
-rw-r--r--server/helpers/custom-validators/plugins.ts4
-rw-r--r--server/helpers/custom-validators/users.ts8
-rw-r--r--server/helpers/custom-validators/videos.ts3
-rw-r--r--server/helpers/decache.ts78
-rw-r--r--server/helpers/express-utils.ts6
-rw-r--r--server/helpers/ffmpeg-utils.ts18
-rw-r--r--server/helpers/ffprobe-utils.ts173
-rw-r--r--server/helpers/image-utils.ts4
-rw-r--r--server/helpers/peertube-crypto.ts3
-rw-r--r--server/helpers/register-ts-paths.ts16
-rw-r--r--server/helpers/utils.ts5
-rw-r--r--server/helpers/uuid.ts32
-rw-r--r--server/helpers/webtorrent.ts17
-rw-r--r--server/helpers/youtube-dl/youtube-dl-cli.ts3
-rw-r--r--server/initializers/checker-before-init.ts8
-rw-r--r--server/initializers/config.ts34
-rw-r--r--server/initializers/constants.ts28
-rw-r--r--server/initializers/installer.ts3
-rw-r--r--server/initializers/migrations/0080-video-channels.ts2
-rw-r--r--server/initializers/migrations/0345-video-playlists.ts2
-rw-r--r--server/initializers/migrations/0560-user-feed-token.ts2
-rw-r--r--server/initializers/migrations/0675-p2p-enabled.ts21
-rw-r--r--server/initializers/migrator.ts2
-rw-r--r--server/lib/activitypub/actors/get.ts37
-rw-r--r--server/lib/activitypub/actors/shared/object-to-model-attributes.ts4
-rw-r--r--server/lib/activitypub/actors/updater.ts10
-rw-r--r--server/lib/activitypub/cache-file.ts3
-rw-r--r--server/lib/activitypub/playlists/create-update.ts2
-rw-r--r--server/lib/activitypub/playlists/shared/object-to-model-attributes.ts2
-rw-r--r--server/lib/activitypub/process/process-create.ts4
-rw-r--r--server/lib/activitypub/process/process-dislike.ts3
-rw-r--r--server/lib/activitypub/process/process-flag.ts11
-rw-r--r--server/lib/auth/oauth-model.ts1
-rw-r--r--server/lib/auth/oauth.ts3
-rw-r--r--server/lib/blocklist.ts4
-rw-r--r--server/lib/client-html.ts12
-rw-r--r--server/lib/emailer.ts3
-rw-r--r--server/lib/hls.ts2
-rw-r--r--server/lib/job-queue/handlers/activitypub-cleaner.ts65
-rw-r--r--server/lib/job-queue/handlers/move-to-object-storage.ts35
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts2
-rw-r--r--server/lib/job-queue/handlers/video-import.ts23
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts27
-rw-r--r--server/lib/live/shared/muxing-session.ts2
-rw-r--r--server/lib/local-actor.ts4
-rw-r--r--server/lib/moderation.ts29
-rw-r--r--server/lib/notifier/shared/comment/comment-mention.ts4
-rw-r--r--server/lib/paths.ts2
-rw-r--r--server/lib/plugins/plugin-helpers-builder.ts60
-rw-r--r--server/lib/plugins/plugin-manager.ts24
-rw-r--r--server/lib/redis.ts220
-rw-r--r--server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts35
-rw-r--r--server/lib/server-config-manager.ts25
-rw-r--r--server/lib/transcoding/video-transcoding-profiles.ts27
-rw-r--r--server/lib/transcoding/video-transcoding.ts1
-rw-r--r--server/lib/uploadx.ts14
-rw-r--r--server/lib/user.ts2
-rw-r--r--server/lib/video-path-manager.ts2
-rw-r--r--server/lib/video-state.ts11
-rw-r--r--server/lib/video.ts7
-rw-r--r--server/middlewares/activitypub.ts3
-rw-r--r--server/middlewares/async.ts2
-rw-r--r--server/middlewares/cache/cache.ts4
-rw-r--r--server/middlewares/cache/shared/api-cache.ts50
-rw-r--r--server/middlewares/error.ts10
-rw-r--r--server/middlewares/user-right.ts3
-rw-r--r--server/middlewares/validators/blocklist.ts29
-rw-r--r--server/middlewares/validators/plugins.ts6
-rw-r--r--server/middlewares/validators/shared/video-channels.ts7
-rw-r--r--server/middlewares/validators/users.ts21
-rw-r--r--server/middlewares/validators/videos/video-captions.ts2
-rw-r--r--server/middlewares/validators/videos/video-channels.ts60
-rw-r--r--server/middlewares/validators/videos/video-comments.ts3
-rw-r--r--server/middlewares/validators/videos/video-files.ts2
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts14
-rw-r--r--server/middlewares/validators/videos/videos.ts6
-rw-r--r--server/models/abuse/abuse-message.ts2
-rw-r--r--server/models/abuse/abuse.ts3
-rw-r--r--server/models/abuse/video-abuse.ts2
-rw-r--r--server/models/abuse/video-comment-abuse.ts2
-rw-r--r--server/models/account/account-blocklist.ts42
-rw-r--r--server/models/account/account-video-rate.ts7
-rw-r--r--server/models/account/account.ts2
-rw-r--r--server/models/actor/actor-follow.ts2
-rw-r--r--server/models/actor/actor-image.ts2
-rw-r--r--server/models/actor/actor.ts4
-rw-r--r--server/models/application/application.ts2
-rw-r--r--server/models/oauth/oauth-client.ts2
-rw-r--r--server/models/oauth/oauth-token.ts2
-rw-r--r--server/models/redundancy/video-redundancy.ts14
-rw-r--r--server/models/server/plugin.ts14
-rw-r--r--server/models/server/server-blocklist.ts21
-rw-r--r--server/models/server/server.ts2
-rw-r--r--server/models/server/tracker.ts2
-rw-r--r--server/models/server/video-tracker.ts2
-rw-r--r--server/models/user/user-notification-setting.ts2
-rw-r--r--server/models/user/user-notification.ts6
-rw-r--r--server/models/user/user-video-history.ts2
-rw-r--r--server/models/user/user.ts15
-rw-r--r--server/models/video/formatter/video-format-utils.ts2
-rw-r--r--server/models/video/schedule-video-update.ts2
-rw-r--r--server/models/video/sql/videos-id-list-query-builder.ts5
-rw-r--r--server/models/video/tag.ts2
-rw-r--r--server/models/video/thumbnail.ts2
-rw-r--r--server/models/video/video-blacklist.ts2
-rw-r--r--server/models/video/video-caption.ts4
-rw-r--r--server/models/video/video-change-ownership.ts2
-rw-r--r--server/models/video/video-channel.ts5
-rw-r--r--server/models/video/video-comment.ts2
-rw-r--r--server/models/video/video-file.ts7
-rw-r--r--server/models/video/video-import.ts4
-rw-r--r--server/models/video/video-job-info.ts17
-rw-r--r--server/models/video/video-live.ts2
-rw-r--r--server/models/video/video-playlist-element.ts4
-rw-r--r--server/models/video/video-playlist.ts5
-rw-r--r--server/models/video/video-share.ts2
-rw-r--r--server/models/video/video-streaming-playlist.ts13
-rw-r--r--server/models/video/video-tag.ts2
-rw-r--r--server/models/video/video-view.ts2
-rw-r--r--server/models/video/video.ts40
-rw-r--r--server/tests/api/activitypub/cleaner.ts64
-rw-r--r--server/tests/api/activitypub/client.ts2
-rw-r--r--server/tests/api/activitypub/fetch.ts9
-rw-r--r--server/tests/api/activitypub/helpers.ts3
-rw-r--r--server/tests/api/activitypub/refresher.ts6
-rw-r--r--server/tests/api/activitypub/security.ts5
-rw-r--r--server/tests/api/check-params/abuses.ts8
-rw-r--r--server/tests/api/check-params/accounts.ts10
-rw-r--r--server/tests/api/check-params/blocklist.ts80
-rw-r--r--server/tests/api/check-params/bulk.ts2
-rw-r--r--server/tests/api/check-params/config.ts14
-rw-r--r--server/tests/api/check-params/contact-form.ts4
-rw-r--r--server/tests/api/check-params/custom-pages.ts2
-rw-r--r--server/tests/api/check-params/debug.ts2
-rw-r--r--server/tests/api/check-params/follows.ts8
-rw-r--r--server/tests/api/check-params/jobs.ts12
-rw-r--r--server/tests/api/check-params/live.ts6
-rw-r--r--server/tests/api/check-params/logs.ts2
-rw-r--r--server/tests/api/check-params/my-user.ts11
-rw-r--r--server/tests/api/check-params/plugins.ts10
-rw-r--r--server/tests/api/check-params/redundancy.ts8
-rw-r--r--server/tests/api/check-params/search.ts12
-rw-r--r--server/tests/api/check-params/services.ts2
-rw-r--r--server/tests/api/check-params/transcoding.ts11
-rw-r--r--server/tests/api/check-params/upload-quota.ts6
-rw-r--r--server/tests/api/check-params/user-notifications.ts12
-rw-r--r--server/tests/api/check-params/user-subscriptions.ts6
-rw-r--r--server/tests/api/check-params/users-admin.ts9
-rw-r--r--server/tests/api/check-params/users.ts10
-rw-r--r--server/tests/api/check-params/video-blacklist.ts8
-rw-r--r--server/tests/api/check-params/video-captions.ts6
-rw-r--r--server/tests/api/check-params/video-channels.ts10
-rw-r--r--server/tests/api/check-params/video-comments.ts8
-rw-r--r--server/tests/api/check-params/video-files.ts9
-rw-r--r--server/tests/api/check-params/video-imports.ts11
-rw-r--r--server/tests/api/check-params/video-playlists.ts22
-rw-r--r--server/tests/api/check-params/videos-common-filters.ts2
-rw-r--r--server/tests/api/check-params/videos-history.ts7
-rw-r--r--server/tests/api/check-params/videos-overviews.ts2
-rw-r--r--server/tests/api/check-params/videos.ts12
-rw-r--r--server/tests/api/live/live-constraints.ts6
-rw-r--r--server/tests/api/live/live-permanent.ts6
-rw-r--r--server/tests/api/live/live-rtmps.ts4
-rw-r--r--server/tests/api/live/live-save-replay.ts8
-rw-r--r--server/tests/api/live/live-socket-messages.ts4
-rw-r--r--server/tests/api/live/live-views.ts4
-rw-r--r--server/tests/api/live/live.ts27
-rw-r--r--server/tests/api/moderation/abuses.ts4
-rw-r--r--server/tests/api/moderation/blocklist-notification.ts9
-rw-r--r--server/tests/api/moderation/blocklist.ts109
-rw-r--r--server/tests/api/moderation/video-blacklist.ts6
-rw-r--r--server/tests/api/notifications/admin-notifications.ts9
-rw-r--r--server/tests/api/notifications/comments-notifications.ts8
-rw-r--r--server/tests/api/notifications/moderation-notifications.ts96
-rw-r--r--server/tests/api/notifications/notifications-api.ts8
-rw-r--r--server/tests/api/notifications/user-notifications.ts16
-rw-r--r--server/tests/api/object-storage/live.ts8
-rw-r--r--server/tests/api/object-storage/video-imports.ts9
-rw-r--r--server/tests/api/object-storage/videos.ts11
-rw-r--r--server/tests/api/redundancy/manage-redundancy.ts2
-rw-r--r--server/tests/api/redundancy/redundancy-constraints.ts9
-rw-r--r--server/tests/api/redundancy/redundancy.ts41
-rw-r--r--server/tests/api/search/search-activitypub-video-channels.ts6
-rw-r--r--server/tests/api/search/search-activitypub-video-playlists.ts6
-rw-r--r--server/tests/api/search/search-activitypub-videos.ts6
-rw-r--r--server/tests/api/search/search-channels.ts2
-rw-r--r--server/tests/api/search/search-index.ts2
-rw-r--r--server/tests/api/search/search-playlists.ts2
-rw-r--r--server/tests/api/search/search-videos.ts6
-rw-r--r--server/tests/api/server/auto-follows.ts12
-rw-r--r--server/tests/api/server/bulk.ts2
-rw-r--r--server/tests/api/server/config-defaults.ts213
-rw-r--r--server/tests/api/server/config.ts24
-rw-r--r--server/tests/api/server/contact-form.ts8
-rw-r--r--server/tests/api/server/email.ts5
-rw-r--r--server/tests/api/server/follow-constraints.ts4
-rw-r--r--server/tests/api/server/follows-moderation.ts2
-rw-r--r--server/tests/api/server/follows.ts18
-rw-r--r--server/tests/api/server/handle-down.ts10
-rw-r--r--server/tests/api/server/homepage.ts2
-rw-r--r--server/tests/api/server/index.ts2
-rw-r--r--server/tests/api/server/jobs.ts4
-rw-r--r--server/tests/api/server/logs.ts2
-rw-r--r--server/tests/api/server/no-client.ts2
-rw-r--r--server/tests/api/server/plugins.ts12
-rw-r--r--server/tests/api/server/proxy.ts11
-rw-r--r--server/tests/api/server/reverse-proxy.ts3
-rw-r--r--server/tests/api/server/services.ts2
-rw-r--r--server/tests/api/server/slow-follows.ts9
-rw-r--r--server/tests/api/server/stats.ts6
-rw-r--r--server/tests/api/server/tracker.ts2
-rw-r--r--server/tests/api/users/user-subscriptions.ts57
-rw-r--r--server/tests/api/users/users-multiple-servers.ts10
-rw-r--r--server/tests/api/users/users-verification.ts3
-rw-r--r--server/tests/api/users/users.ts30
-rw-r--r--server/tests/api/videos/audio-only.ts9
-rw-r--r--server/tests/api/videos/multiple-servers.ts37
-rw-r--r--server/tests/api/videos/resumable-upload.ts10
-rw-r--r--server/tests/api/videos/single-server.ts13
-rw-r--r--server/tests/api/videos/video-captions.ts7
-rw-r--r--server/tests/api/videos/video-change-ownership.ts2
-rw-r--r--server/tests/api/videos/video-channels.ts46
-rw-r--r--server/tests/api/videos/video-comments.ts11
-rw-r--r--server/tests/api/videos/video-create-transcoding.ts8
-rw-r--r--server/tests/api/videos/video-description.ts9
-rw-r--r--server/tests/api/videos/video-files.ts9
-rw-r--r--server/tests/api/videos/video-hls.ts13
-rw-r--r--server/tests/api/videos/video-imports.ts10
-rw-r--r--server/tests/api/videos/video-nsfw.ts2
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts6
-rw-r--r--server/tests/api/videos/video-playlists.ts25
-rw-r--r--server/tests/api/videos/video-privacy.ts6
-rw-r--r--server/tests/api/videos/video-schedule-update.ts6
-rw-r--r--server/tests/api/videos/video-transcoder.ts20
-rw-r--r--server/tests/api/videos/videos-common-filters.ts2
-rw-r--r--server/tests/api/videos/videos-history.ts8
-rw-r--r--server/tests/api/videos/videos-overview.ts3
-rw-r--r--server/tests/api/videos/videos-views-cleaner.ts4
-rw-r--r--server/tests/cli/create-import-video-file-job.ts8
-rw-r--r--server/tests/cli/create-move-video-storage-job.ts9
-rw-r--r--server/tests/cli/create-transcoding-job.ts6
-rw-r--r--server/tests/cli/peertube.ts27
-rw-r--r--server/tests/cli/plugins.ts2
-rw-r--r--server/tests/cli/print-transcode-command.ts3
-rw-r--r--server/tests/cli/prune-storage.ts57
-rw-r--r--server/tests/cli/regenerate-thumbnails.ts2
-rw-r--r--server/tests/cli/reset-password.ts2
-rw-r--r--server/tests/cli/update-host.ts2
-rw-r--r--server/tests/client.ts2
-rw-r--r--server/tests/external-plugins/auth-ldap.ts2
-rw-r--r--server/tests/external-plugins/auto-block-videos.ts10
-rw-r--r--server/tests/external-plugins/auto-mute.ts10
-rw-r--r--server/tests/feeds/feeds.ts9
-rw-r--r--server/tests/fixtures/peertube-plugin-test-four/main.js14
-rw-r--r--server/tests/fixtures/peertube-plugin-test-six/main.js5
-rw-r--r--server/tests/fixtures/peertube-plugin-test/main.js25
-rw-r--r--server/tests/helpers/image.ts2
-rw-r--r--server/tests/helpers/request.ts4
-rw-r--r--server/tests/misc-endpoints.ts2
-rw-r--r--server/tests/plugins/action-hooks.ts16
-rw-r--r--server/tests/plugins/external-auth.ts10
-rw-r--r--server/tests/plugins/filter-hooks.ts75
-rw-r--r--server/tests/plugins/html-injection.ts2
-rw-r--r--server/tests/plugins/id-and-pass-auth.ts3
-rw-r--r--server/tests/plugins/plugin-helpers.ts73
-rw-r--r--server/tests/plugins/plugin-router.ts2
-rw-r--r--server/tests/plugins/plugin-storage.ts8
-rw-r--r--server/tests/plugins/plugin-transcoding.ts2
-rw-r--r--server/tests/plugins/plugin-unloading.ts2
-rw-r--r--server/tests/plugins/translations.ts2
-rw-r--r--server/tests/plugins/video-constants.ts2
-rw-r--r--server/tests/register.ts3
-rw-r--r--server/tests/shared/actors.ts73
-rw-r--r--server/tests/shared/captions.ts21
-rw-r--r--server/tests/shared/checks.ts98
-rw-r--r--server/tests/shared/directories.ts34
-rw-r--r--server/tests/shared/generate.ts74
-rw-r--r--server/tests/shared/index.ts15
-rw-r--r--server/tests/shared/live.ts41
-rw-r--r--server/tests/shared/mock-servers/index.ts7
-rw-r--r--server/tests/shared/mock-servers/mock-429.ts33
-rw-r--r--server/tests/shared/mock-servers/mock-email.ts62
-rw-r--r--server/tests/shared/mock-servers/mock-instances-index.ts46
-rw-r--r--server/tests/shared/mock-servers/mock-joinpeertube-versions.ts34
-rw-r--r--server/tests/shared/mock-servers/mock-object-storage.ts41
-rw-r--r--server/tests/shared/mock-servers/mock-plugin-blocklist.ts36
-rw-r--r--server/tests/shared/mock-servers/mock-proxy.ts24
-rw-r--r--server/tests/shared/mock-servers/shared.ts33
-rw-r--r--server/tests/shared/notifications.ts798
-rw-r--r--server/tests/shared/playlists.ts25
-rw-r--r--server/tests/shared/plugins.ts18
-rw-r--r--server/tests/shared/requests.ts41
-rw-r--r--server/tests/shared/streaming-playlists.ts77
-rw-r--r--server/tests/shared/tests.ts37
-rw-r--r--server/tests/shared/tracker.ts27
-rw-r--r--server/tests/shared/videos.ts251
-rw-r--r--server/tools/cli.ts9
-rw-r--r--server/tools/peertube-auth.ts9
-rw-r--r--server/tools/peertube-get-access-token.ts3
-rw-r--r--server/tools/peertube-import-videos.ts10
-rw-r--r--server/tools/peertube-plugins.ts16
-rw-r--r--server/tools/peertube-redundancy.ts5
-rw-r--r--server/tools/peertube-upload.ts3
-rw-r--r--server/tools/peertube.ts3
-rw-r--r--server/tools/tsconfig.json12
-rw-r--r--server/tsconfig.json12
-rw-r--r--server/tsconfig.types.json16
-rw-r--r--server/types/express-handler.ts (renamed from server/types/express.ts)0
-rw-r--r--server/types/express.d.ts (renamed from server/typings/express/index.d.ts)0
-rw-r--r--server/types/models/abuse/abuse-message.ts2
-rw-r--r--server/types/models/abuse/abuse.ts2
-rw-r--r--server/types/models/account/account-blocklist.ts2
-rw-r--r--server/types/models/account/account.ts2
-rw-r--r--server/types/models/account/actor-custom-page.ts1
-rw-r--r--server/types/models/actor/actor-follow.ts2
-rw-r--r--server/types/models/actor/actor-image.ts2
-rw-r--r--server/types/models/actor/actor.ts2
-rw-r--r--server/types/models/oauth/oauth-token.ts2
-rw-r--r--server/types/models/server/server-blocklist.ts2
-rw-r--r--server/types/models/server/server.ts2
-rw-r--r--server/types/models/user/user-notification.ts2
-rw-r--r--server/types/models/user/user.ts2
-rw-r--r--server/types/models/video/thumbnail.ts2
-rw-r--r--server/types/models/video/video-blacklist.ts2
-rw-r--r--server/types/models/video/video-caption.ts2
-rw-r--r--server/types/models/video/video-change-ownership.ts4
-rw-r--r--server/types/models/video/video-channels.ts2
-rw-r--r--server/types/models/video/video-comment.ts2
-rw-r--r--server/types/models/video/video-file.ts4
-rw-r--r--server/types/models/video/video-import.ts4
-rw-r--r--server/types/models/video/video-live.ts2
-rw-r--r--server/types/models/video/video-playlist-element.ts2
-rw-r--r--server/types/models/video/video-playlist.ts4
-rw-r--r--server/types/models/video/video-rate.ts2
-rw-r--r--server/types/models/video/video-redundancy.ts8
-rw-r--r--server/types/models/video/video-share.ts2
-rw-r--r--server/types/models/video/video-streaming-playlist.ts4
-rw-r--r--server/types/models/video/video.ts2
-rw-r--r--server/types/plugins/register-server-option.model.ts30
-rw-r--r--server/types/sequelize.ts2
373 files changed, 4659 insertions, 1667 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index c4e3cec6b..4e6bd5e25 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -20,7 +20,8 @@ import {
20 asyncMiddleware, 20 asyncMiddleware,
21 executeIfActivityPub, 21 executeIfActivityPub,
22 localAccountValidator, 22 localAccountValidator,
23 localVideoChannelValidator, 23 videoChannelsNameWithHostValidator,
24 ensureIsLocalChannel,
24 videosCustomGetValidator, 25 videosCustomGetValidator,
25 videosShareValidator 26 videosShareValidator
26} from '../../middlewares' 27} from '../../middlewares'
@@ -123,24 +124,28 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
123) 124)
124 125
125activityPubClientRouter.get( 126activityPubClientRouter.get(
126 [ '/video-channels/:name', '/video-channels/:name/videos', '/c/:name', '/c/:name/videos' ], 127 [ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ],
127 executeIfActivityPub, 128 executeIfActivityPub,
128 asyncMiddleware(localVideoChannelValidator), 129 asyncMiddleware(videoChannelsNameWithHostValidator),
130 ensureIsLocalChannel,
129 videoChannelController 131 videoChannelController
130) 132)
131activityPubClientRouter.get('/video-channels/:name/followers', 133activityPubClientRouter.get('/video-channels/:nameWithHost/followers',
132 executeIfActivityPub, 134 executeIfActivityPub,
133 asyncMiddleware(localVideoChannelValidator), 135 asyncMiddleware(videoChannelsNameWithHostValidator),
136 ensureIsLocalChannel,
134 asyncMiddleware(videoChannelFollowersController) 137 asyncMiddleware(videoChannelFollowersController)
135) 138)
136activityPubClientRouter.get('/video-channels/:name/following', 139activityPubClientRouter.get('/video-channels/:nameWithHost/following',
137 executeIfActivityPub, 140 executeIfActivityPub,
138 asyncMiddleware(localVideoChannelValidator), 141 asyncMiddleware(videoChannelsNameWithHostValidator),
142 ensureIsLocalChannel,
139 asyncMiddleware(videoChannelFollowingController) 143 asyncMiddleware(videoChannelFollowingController)
140) 144)
141activityPubClientRouter.get('/video-channels/:name/playlists', 145activityPubClientRouter.get('/video-channels/:nameWithHost/playlists',
142 executeIfActivityPub, 146 executeIfActivityPub,
143 asyncMiddleware(localVideoChannelValidator), 147 asyncMiddleware(videoChannelsNameWithHostValidator),
148 ensureIsLocalChannel,
144 asyncMiddleware(videoChannelPlaylistsController) 149 asyncMiddleware(videoChannelPlaylistsController)
145) 150)
146 151
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index ece4edff0..66a38e055 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -1,10 +1,17 @@
1import express from 'express' 1import express from 'express'
2import { InboxManager } from '@server/lib/activitypub/inbox-manager' 2import { InboxManager } from '@server/lib/activitypub/inbox-manager'
3import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, RootActivity } from '../../../shared' 3import { Activity, ActivityPubCollection, ActivityPubOrderedCollection, RootActivity } from '@shared/models'
4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
5import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity' 5import { isActivityValid } from '../../helpers/custom-validators/activitypub/activity'
6import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
7import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares' 7import {
8 asyncMiddleware,
9 checkSignature,
10 ensureIsLocalChannel,
11 localAccountValidator,
12 signatureValidator,
13 videoChannelsNameWithHostValidator
14} from '../../middlewares'
8import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' 15import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
9 16
10const inboxRouter = express.Router() 17const inboxRouter = express.Router()
@@ -23,10 +30,11 @@ inboxRouter.post('/accounts/:name/inbox',
23 asyncMiddleware(activityPubValidator), 30 asyncMiddleware(activityPubValidator),
24 inboxController 31 inboxController
25) 32)
26inboxRouter.post('/video-channels/:name/inbox', 33inboxRouter.post('/video-channels/:nameWithHost/inbox',
27 signatureValidator, 34 signatureValidator,
28 asyncMiddleware(checkSignature), 35 asyncMiddleware(checkSignature),
29 asyncMiddleware(localVideoChannelValidator), 36 asyncMiddleware(videoChannelsNameWithHostValidator),
37 ensureIsLocalChannel,
30 asyncMiddleware(activityPubValidator), 38 asyncMiddleware(activityPubValidator),
31 inboxController 39 inboxController
32) 40)
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index bdf9d138b..cdef8e969 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -1,15 +1,15 @@
1import express from 'express' 1import express from 'express'
2import { MActorLight } from '@server/types/models'
2import { Activity } from '../../../shared/models/activitypub/activity' 3import { Activity } from '../../../shared/models/activitypub/activity'
3import { VideoPrivacy } from '../../../shared/models/videos' 4import { VideoPrivacy } from '../../../shared/models/videos'
4import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' 5import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
5import { logger } from '../../helpers/logger' 6import { logger } from '../../helpers/logger'
6import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
7import { buildAudience } from '../../lib/activitypub/audience' 7import { buildAudience } from '../../lib/activitypub/audience'
8import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' 8import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send'
9import { asyncMiddleware, ensureIsLocalChannel, localAccountValidator, videoChannelsNameWithHostValidator } from '../../middlewares'
10import { apPaginationValidator } from '../../middlewares/validators/activitypub'
9import { VideoModel } from '../../models/video/video' 11import { VideoModel } from '../../models/video/video'
10import { activityPubResponse } from './utils' 12import { activityPubResponse } from './utils'
11import { MActorLight } from '@server/types/models'
12import { apPaginationValidator } from '../../middlewares/validators/activitypub'
13 13
14const outboxRouter = express.Router() 14const outboxRouter = express.Router()
15 15
@@ -19,9 +19,10 @@ outboxRouter.get('/accounts/:name/outbox',
19 asyncMiddleware(outboxController) 19 asyncMiddleware(outboxController)
20) 20)
21 21
22outboxRouter.get('/video-channels/:name/outbox', 22outboxRouter.get('/video-channels/:nameWithHost/outbox',
23 apPaginationValidator, 23 apPaginationValidator,
24 localVideoChannelValidator, 24 asyncMiddleware(videoChannelsNameWithHostValidator),
25 ensureIsLocalChannel,
25 asyncMiddleware(outboxController) 26 asyncMiddleware(outboxController)
26) 27)
27 28
diff --git a/server/controllers/api/abuse.ts b/server/controllers/api/abuse.ts
index 72c418e74..d6211cc83 100644
--- a/server/controllers/api/abuse.ts
+++ b/server/controllers/api/abuse.ts
@@ -6,8 +6,7 @@ import { AbuseModel } from '@server/models/abuse/abuse'
6import { AbuseMessageModel } from '@server/models/abuse/abuse-message' 6import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
7import { getServerActor } from '@server/models/application/application' 7import { getServerActor } from '@server/models/application/application'
8import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 8import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
9import { HttpStatusCode } from '@shared/models' 9import { AbuseCreate, AbuseState, HttpStatusCode, UserRight } from '@shared/models'
10import { AbuseCreate, AbuseState, UserRight } from '../../../shared'
11import { getFormattedObjects } from '../../helpers/utils' 10import { getFormattedObjects } from '../../helpers/utils'
12import { sequelizeTypescript } from '../../initializers/database' 11import { sequelizeTypescript } from '../../initializers/database'
13import { 12import {
@@ -167,7 +166,11 @@ async function reportAbuse (req: express.Request, res: express.Response) {
167 const body: AbuseCreate = req.body 166 const body: AbuseCreate = req.body
168 167
169 const { id } = await sequelizeTypescript.transaction(async t => { 168 const { id } = await sequelizeTypescript.transaction(async t => {
170 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) 169 const user = res.locals.oauth.token.User
170 // Don't send abuse notification if reporter is an admin/moderator
171 const skipNotification = user.hasRight(UserRight.MANAGE_ABUSES)
172
173 const reporterAccount = await AccountModel.load(user.Account.id, t)
171 const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r]) 174 const predefinedReasons = body.predefinedReasons?.map(r => abusePredefinedReasonsMap[r])
172 175
173 const baseAbuse = { 176 const baseAbuse = {
@@ -184,7 +187,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
184 reporterAccount, 187 reporterAccount,
185 transaction: t, 188 transaction: t,
186 startAt: body.video.startAt, 189 startAt: body.video.startAt,
187 endAt: body.video.endAt 190 endAt: body.video.endAt,
191 skipNotification
188 }) 192 })
189 } 193 }
190 194
@@ -193,7 +197,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
193 baseAbuse, 197 baseAbuse,
194 commentInstance, 198 commentInstance,
195 reporterAccount, 199 reporterAccount,
196 transaction: t 200 transaction: t,
201 skipNotification
197 }) 202 })
198 } 203 }
199 204
@@ -202,7 +207,8 @@ async function reportAbuse (req: express.Request, res: express.Response) {
202 baseAbuse, 207 baseAbuse,
203 accountInstance, 208 accountInstance,
204 reporterAccount, 209 reporterAccount,
205 transaction: t 210 transaction: t,
211 skipNotification
206 }) 212 })
207 }) 213 })
208 214
diff --git a/server/controllers/api/blocklist.ts b/server/controllers/api/blocklist.ts
new file mode 100644
index 000000000..1e936ad10
--- /dev/null
+++ b/server/controllers/api/blocklist.ts
@@ -0,0 +1,108 @@
1import express from 'express'
2import { handleToNameAndHost } from '@server/helpers/actors'
3import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
4import { getServerActor } from '@server/models/application/application'
5import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
6import { MActorAccountId, MUserAccountId } from '@server/types/models'
7import { BlockStatus } from '@shared/models'
8import { asyncMiddleware, blocklistStatusValidator, optionalAuthenticate } from '../../middlewares'
9import { logger } from '@server/helpers/logger'
10
11const blocklistRouter = express.Router()
12
13blocklistRouter.get('/status',
14 optionalAuthenticate,
15 blocklistStatusValidator,
16 asyncMiddleware(getBlocklistStatus)
17)
18
19// ---------------------------------------------------------------------------
20
21export {
22 blocklistRouter
23}
24
25// ---------------------------------------------------------------------------
26
27async function getBlocklistStatus (req: express.Request, res: express.Response) {
28 const hosts = req.query.hosts as string[]
29 const accounts = req.query.accounts as string[]
30 const user = res.locals.oauth?.token.User
31
32 const serverActor = await getServerActor()
33
34 const byAccountIds = [ serverActor.Account.id ]
35 if (user) byAccountIds.push(user.Account.id)
36
37 const status: BlockStatus = {
38 accounts: {},
39 hosts: {}
40 }
41
42 const baseOptions = {
43 byAccountIds,
44 user,
45 serverActor,
46 status
47 }
48
49 await Promise.all([
50 populateServerBlocklistStatus({ ...baseOptions, hosts }),
51 populateAccountBlocklistStatus({ ...baseOptions, accounts })
52 ])
53
54 return res.json(status)
55}
56
57async function populateServerBlocklistStatus (options: {
58 byAccountIds: number[]
59 user?: MUserAccountId
60 serverActor: MActorAccountId
61 hosts: string[]
62 status: BlockStatus
63}) {
64 const { byAccountIds, user, serverActor, hosts, status } = options
65
66 if (!hosts || hosts.length === 0) return
67
68 const serverBlocklistStatus = await ServerBlocklistModel.getBlockStatus(byAccountIds, hosts)
69
70 logger.debug('Got server blocklist status.', { serverBlocklistStatus, byAccountIds, hosts })
71
72 for (const host of hosts) {
73 const block = serverBlocklistStatus.find(b => b.host === host)
74
75 status.hosts[host] = getStatus(block, serverActor, user)
76 }
77}
78
79async function populateAccountBlocklistStatus (options: {
80 byAccountIds: number[]
81 user?: MUserAccountId
82 serverActor: MActorAccountId
83 accounts: string[]
84 status: BlockStatus
85}) {
86 const { byAccountIds, user, serverActor, accounts, status } = options
87
88 if (!accounts || accounts.length === 0) return
89
90 const accountBlocklistStatus = await AccountBlocklistModel.getBlockStatus(byAccountIds, accounts)
91
92 logger.debug('Got account blocklist status.', { accountBlocklistStatus, byAccountIds, accounts })
93
94 for (const account of accounts) {
95 const sanitizedHandle = handleToNameAndHost(account)
96
97 const block = accountBlocklistStatus.find(b => b.name === sanitizedHandle.name && b.host === sanitizedHandle.host)
98
99 status.accounts[sanitizedHandle.handle] = getStatus(block, serverActor, user)
100 }
101}
102
103function getStatus (block: { accountId: number }, serverActor: MActorAccountId, user?: MUserAccountId) {
104 return {
105 blockedByServer: !!(block && block.accountId === serverActor.Account.id),
106 blockedByUser: !!(block && user && block.accountId === user.Account.id)
107 }
108}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 805ad99c7..4e3dd4d80 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -3,9 +3,7 @@ import { remove, writeJSON } from 'fs-extra'
3import { snakeCase } from 'lodash' 3import { snakeCase } from 'lodash'
4import validator from 'validator' 4import validator from 'validator'
5import { ServerConfigManager } from '@server/lib/server-config-manager' 5import { ServerConfigManager } from '@server/lib/server-config-manager'
6import { UserRight } from '../../../shared' 6import { About, CustomConfig, UserRight } from '@shared/models'
7import { About } from '../../../shared/models/server/about.model'
8import { CustomConfig } from '../../../shared/models/server/custom-config.model'
9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' 7import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
10import { objectConverter } from '../../helpers/core-utils' 8import { objectConverter } from '../../helpers/core-utils'
11import { CONFIG, reloadConfig } from '../../initializers/config' 9import { CONFIG, reloadConfig } from '../../initializers/config'
@@ -169,6 +167,18 @@ function customConfig (): CustomConfig {
169 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED 167 whitelisted: CONFIG.SERVICES.TWITTER.WHITELISTED
170 } 168 }
171 }, 169 },
170 client: {
171 videos: {
172 miniature: {
173 preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
174 }
175 },
176 menu: {
177 login: {
178 redirectOnSingleExternalAuth: CONFIG.CLIENT.MENU.LOGIN.REDIRECT_ON_SINGLE_EXTERNAL_AUTH
179 }
180 }
181 },
172 cache: { 182 cache: {
173 previews: { 183 previews: {
174 size: CONFIG.CACHE.PREVIEWS.SIZE 184 size: CONFIG.CACHE.PREVIEWS.SIZE
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 9949b378a..5f49336b1 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -6,6 +6,7 @@ import { badRequest } from '../../helpers/express-utils'
6import { CONFIG } from '../../initializers/config' 6import { CONFIG } from '../../initializers/config'
7import { abuseRouter } from './abuse' 7import { abuseRouter } from './abuse'
8import { accountsRouter } from './accounts' 8import { accountsRouter } from './accounts'
9import { blocklistRouter } from './blocklist'
9import { bulkRouter } from './bulk' 10import { bulkRouter } from './bulk'
10import { configRouter } from './config' 11import { configRouter } from './config'
11import { customPageRouter } from './custom-page' 12import { customPageRouter } from './custom-page'
@@ -49,6 +50,7 @@ apiRouter.use('/search', searchRouter)
49apiRouter.use('/overviews', overviewsRouter) 50apiRouter.use('/overviews', overviewsRouter)
50apiRouter.use('/plugins', pluginRouter) 51apiRouter.use('/plugins', pluginRouter)
51apiRouter.use('/custom-pages', customPageRouter) 52apiRouter.use('/custom-pages', customPageRouter)
53apiRouter.use('/blocklist', blocklistRouter)
52apiRouter.use('/ping', pong) 54apiRouter.use('/ping', pong)
53apiRouter.use('/*', badRequest) 55apiRouter.use('/*', badRequest)
54 56
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index 7001674bb..eebd195b0 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -1,7 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { ResultList } from '../../../shared' 2import { Job, JobState, JobType, ResultList, UserRight } from '@shared/models'
3import { Job, JobState, JobType } from '../../../shared/models'
4import { UserRight } from '../../../shared/models/users'
5import { isArray } from '../../helpers/custom-validators/misc' 3import { isArray } from '../../helpers/custom-validators/misc'
6import { JobQueue } from '../../lib/job-queue' 4import { JobQueue } from '../../lib/job-queue'
7import { 5import {
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index 4990fb0df..2d847bdc1 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -1,10 +1,9 @@
1import express from 'express' 1import express from 'express'
2import { OAuthClientLocal } from '../../../shared' 2import { OAuthClientModel } from '@server/models/oauth/oauth-client'
3import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 3import { HttpStatusCode, OAuthClientLocal } from '@shared/models'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { CONFIG } from '../../initializers/config' 5import { CONFIG } from '../../initializers/config'
6import { asyncMiddleware, openapiOperationDoc } from '../../middlewares' 6import { asyncMiddleware, openapiOperationDoc } from '../../middlewares'
7import { OAuthClientModel } from '../../models/oauth/oauth-client'
8 7
9const oauthClientsRouter = express.Router() 8const oauthClientsRouter = express.Router()
10 9
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts
index 2de7fe41f..de9e055dc 100644
--- a/server/controllers/api/plugins.ts
+++ b/server/controllers/api/plugins.ts
@@ -144,8 +144,13 @@ async function installPlugin (req: express.Request, res: express.Response) {
144 144
145 const fromDisk = !!body.path 145 const fromDisk = !!body.path
146 const toInstall = body.npmName || body.path 146 const toInstall = body.npmName || body.path
147
148 const pluginVersion = body.pluginVersion && body.npmName
149 ? body.pluginVersion
150 : undefined
151
147 try { 152 try {
148 const plugin = await PluginManager.Instance.install(toInstall, undefined, fromDisk) 153 const plugin = await PluginManager.Instance.install(toInstall, pluginVersion, fromDisk)
149 154
150 return res.json(plugin.toFormattedJSON()) 155 return res.json(plugin.toFormattedJSON())
151 } catch (err) { 156 } catch (err) {
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index d661144ca..2ab398f4d 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -3,6 +3,7 @@ import { StatsManager } from '@server/lib/stat-manager'
3import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' 3import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
4import { asyncMiddleware } from '../../../middlewares' 4import { asyncMiddleware } from '../../../middlewares'
5import { cacheRoute } from '../../../middlewares/cache/cache' 5import { cacheRoute } from '../../../middlewares/cache/cache'
6import { Hooks } from '@server/lib/plugins/hooks'
6 7
7const statsRouter = express.Router() 8const statsRouter = express.Router()
8 9
@@ -12,7 +13,8 @@ statsRouter.get('/stats',
12) 13)
13 14
14async function getStats (_req: express.Request, res: express.Response) { 15async function getStats (_req: express.Request, res: express.Response) {
15 const data = await StatsManager.Instance.getStats() 16 let data = await StatsManager.Instance.getStats()
17 data = await Hooks.wrapObject(data, 'filter:api.server.stats.get.result')
16 18
17 return res.json(data) 19 return res.json(data)
18} 20}
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 11d3525e4..7efc3a137 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -4,10 +4,7 @@ import { tokensRouter } from '@server/controllers/api/users/token'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { OAuthTokenModel } from '@server/models/oauth/oauth-token' 5import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
6import { MUser, MUserAccountDefault } from '@server/types/models' 6import { MUser, MUserAccountDefault } from '@server/types/models'
7import { UserCreate, UserCreateResult, UserRight, UserRole, UserUpdate } from '../../../../shared' 7import { HttpStatusCode, UserAdminFlag, UserCreate, UserCreateResult, UserRegister, UserRight, UserRole, UserUpdate } from '@shared/models'
8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
9import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
10import { UserRegister } from '../../../../shared/models/users/user-register.model'
11import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 8import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
12import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
13import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' 10import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
@@ -183,6 +180,7 @@ async function createUser (req: express.Request, res: express.Response) {
183 password: body.password, 180 password: body.password,
184 email: body.email, 181 email: body.email,
185 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 182 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
183 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
186 autoPlayVideo: true, 184 autoPlayVideo: true,
187 role: body.role, 185 role: body.role,
188 videoQuota: body.videoQuota, 186 videoQuota: body.videoQuota,
@@ -209,7 +207,7 @@ async function createUser (req: express.Request, res: express.Response) {
209 logger.info('Sending to user %s a create password email', body.username) 207 logger.info('Sending to user %s a create password email', body.username)
210 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id) 208 const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id)
211 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString 209 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
212 await Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url) 210 Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url)
213 } 211 }
214 212
215 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res }) 213 Hooks.runAction('action:api.user.created', { body, user, account, videoChannel, req, res })
@@ -232,6 +230,7 @@ async function registerUser (req: express.Request, res: express.Response) {
232 password: body.password, 230 password: body.password,
233 email: body.email, 231 email: body.email,
234 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 232 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
233 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
235 autoPlayVideo: true, 234 autoPlayVideo: true,
236 role: UserRole.USER, 235 role: UserRole.USER,
237 videoQuota: CONFIG.USER.VIDEO_QUOTA, 236 videoQuota: CONFIG.USER.VIDEO_QUOTA,
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 6bacdbbb6..878dd5a84 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,10 +2,8 @@ import 'multer'
2import express from 'express' 2import express from 'express'
3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger' 3import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '@server/helpers/audit-logger'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { AttributesOnly } from '@shared/core-utils' 5import { ActorImageType, HttpStatusCode, UserUpdateMe, UserVideoQuota, UserVideoRate as FormattedUserVideoRate } from '@shared/models'
6import { ActorImageType, UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' 6import { AttributesOnly } from '@shared/typescript-utils'
7import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
8import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
9import { createReqFiles } from '../../../helpers/express-utils' 7import { createReqFiles } from '../../../helpers/express-utils'
10import { getFormattedObjects } from '../../../helpers/utils' 8import { getFormattedObjects } from '../../../helpers/utils'
11import { CONFIG } from '../../../initializers/config' 9import { CONFIG } from '../../../initializers/config'
@@ -197,7 +195,7 @@ async function updateMe (req: express.Request, res: express.Response) {
197 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [ 195 const keysToUpdate: (keyof UserUpdateMe & keyof AttributesOnly<UserModel>)[] = [
198 'password', 196 'password',
199 'nsfwPolicy', 197 'nsfwPolicy',
200 'webTorrentEnabled', 198 'p2pEnabled',
201 'autoPlayVideo', 199 'autoPlayVideo',
202 'autoPlayNextVideo', 200 'autoPlayNextVideo',
203 'autoPlayNextVideoPlaylist', 201 'autoPlayNextVideoPlaylist',
@@ -213,6 +211,12 @@ async function updateMe (req: express.Request, res: express.Response) {
213 if (body[key] !== undefined) user.set(key, body[key]) 211 if (body[key] !== undefined) user.set(key, body[key])
214 } 212 }
215 213
214 if (body.p2pEnabled !== undefined) {
215 user.set('p2pEnabled', body.p2pEnabled)
216 } else if (body.webTorrentEnabled !== undefined) { // FIXME: deprecated in 4.1
217 user.set('p2pEnabled', body.webTorrentEnabled)
218 }
219
216 if (body.email !== undefined) { 220 if (body.email !== undefined) {
217 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { 221 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
218 user.pendingEmail = body.email 222 user.pendingEmail = body.email
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 6799ca8c5..fb1f68635 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,5 +1,6 @@
1import 'multer' 1import 'multer'
2import express from 'express' 2import express from 'express'
3import { handlesToNameAndHost } from '@server/helpers/actors'
3import { pickCommonVideoQuery } from '@server/helpers/query' 4import { pickCommonVideoQuery } from '@server/helpers/query'
4import { sendUndoFollow } from '@server/lib/activitypub/send' 5import { sendUndoFollow } from '@server/lib/activitypub/send'
5import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
@@ -7,7 +8,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel'
7import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 8import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
8import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' 9import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
9import { getFormattedObjects } from '../../../helpers/utils' 10import { getFormattedObjects } from '../../../helpers/utils'
10import { WEBSERVER } from '../../../initializers/constants'
11import { sequelizeTypescript } from '../../../initializers/database' 11import { sequelizeTypescript } from '../../../initializers/database'
12import { JobQueue } from '../../../lib/job-queue' 12import { JobQueue } from '../../../lib/job-queue'
13import { 13import {
@@ -89,28 +89,23 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
89 const uris = req.query.uris as string[] 89 const uris = req.query.uris as string[]
90 const user = res.locals.oauth.token.User 90 const user = res.locals.oauth.token.User
91 91
92 const handles = uris.map(u => { 92 const sanitizedHandles = handlesToNameAndHost(uris)
93 let [ name, host ] = u.split('@')
94 if (host === WEBSERVER.HOST) host = null
95 93
96 return { name, host, uri: u } 94 const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, sanitizedHandles)
97 })
98
99 const results = await ActorFollowModel.listSubscriptionsOf(user.Account.Actor.id, handles)
100 95
101 const existObject: { [id: string ]: boolean } = {} 96 const existObject: { [id: string ]: boolean } = {}
102 for (const handle of handles) { 97 for (const sanitizedHandle of sanitizedHandles) {
103 const obj = results.find(r => { 98 const obj = results.find(r => {
104 const server = r.ActorFollowing.Server 99 const server = r.ActorFollowing.Server
105 100
106 return r.ActorFollowing.preferredUsername === handle.name && 101 return r.ActorFollowing.preferredUsername === sanitizedHandle.name &&
107 ( 102 (
108 (!server && !handle.host) || 103 (!server && !sanitizedHandle.host) ||
109 (server.host === handle.host) 104 (server.host === sanitizedHandle.host)
110 ) 105 )
111 }) 106 })
112 107
113 existObject[handle.uri] = obj !== undefined 108 existObject[sanitizedHandle.handle] = obj !== undefined
114 } 109 }
115 110
116 return res.json(existObject) 111 return res.json(existObject)
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts
index 1d4004ce0..258b50fe9 100644
--- a/server/controllers/api/users/token.ts
+++ b/server/controllers/api/users/token.ts
@@ -1,13 +1,13 @@
1import express from 'express' 1import express from 'express'
2import RateLimit from 'express-rate-limit' 2import RateLimit from 'express-rate-limit'
3import { logger } from '@server/helpers/logger' 3import { logger } from '@server/helpers/logger'
4import { buildUUID } from '@server/helpers/uuid'
5import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
6import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth' 5import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
7import { handleOAuthToken } from '@server/lib/auth/oauth' 6import { handleOAuthToken } from '@server/lib/auth/oauth'
8import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model' 7import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
9import { Hooks } from '@server/lib/plugins/hooks' 8import { Hooks } from '@server/lib/plugins/hooks'
10import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares' 9import { asyncMiddleware, authenticate, openapiOperationDoc } from '@server/middlewares'
10import { buildUUID } from '@shared/extra-utils'
11import { ScopedToken } from '@shared/models/users/user-scoped-token' 11import { ScopedToken } from '@shared/models/users/user-scoped-token'
12 12
13const tokensRouter = express.Router() 13const tokensRouter = express.Router()
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index d1a1e6473..e65550a22 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -5,8 +5,7 @@ import { ActorFollowModel } from '@server/models/actor/actor-follow'
5import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils' 6import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
7import { MChannelBannerAccountDefault } from '@server/types/models' 7import { MChannelBannerAccountDefault } from '@server/types/models'
8import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 8import { ActorImageType, HttpStatusCode, VideoChannelCreate, VideoChannelUpdate } from '@shared/models'
9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
10import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 9import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
11import { resetSequelizeInstance } from '../../helpers/database-utils' 10import { resetSequelizeInstance } from '../../helpers/database-utils'
12import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 11import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
@@ -24,6 +23,7 @@ import {
24 asyncRetryTransactionMiddleware, 23 asyncRetryTransactionMiddleware,
25 authenticate, 24 authenticate,
26 commonVideosFiltersValidator, 25 commonVideosFiltersValidator,
26 ensureCanManageChannel,
27 optionalAuthenticate, 27 optionalAuthenticate,
28 paginationValidator, 28 paginationValidator,
29 setDefaultPagination, 29 setDefaultPagination,
@@ -36,7 +36,7 @@ import {
36 videoPlaylistsSortValidator 36 videoPlaylistsSortValidator
37} from '../../middlewares' 37} from '../../middlewares'
38import { 38import {
39 ensureAuthUserOwnsChannelValidator, 39 ensureIsLocalChannel,
40 videoChannelsFollowersSortValidator, 40 videoChannelsFollowersSortValidator,
41 videoChannelsListValidator, 41 videoChannelsListValidator,
42 videoChannelsNameWithHostValidator, 42 videoChannelsNameWithHostValidator,
@@ -74,7 +74,8 @@ videoChannelRouter.post('/:nameWithHost/avatar/pick',
74 authenticate, 74 authenticate,
75 reqAvatarFile, 75 reqAvatarFile,
76 asyncMiddleware(videoChannelsNameWithHostValidator), 76 asyncMiddleware(videoChannelsNameWithHostValidator),
77 ensureAuthUserOwnsChannelValidator, 77 ensureIsLocalChannel,
78 ensureCanManageChannel,
78 updateAvatarValidator, 79 updateAvatarValidator,
79 asyncMiddleware(updateVideoChannelAvatar) 80 asyncMiddleware(updateVideoChannelAvatar)
80) 81)
@@ -83,7 +84,8 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
83 authenticate, 84 authenticate,
84 reqBannerFile, 85 reqBannerFile,
85 asyncMiddleware(videoChannelsNameWithHostValidator), 86 asyncMiddleware(videoChannelsNameWithHostValidator),
86 ensureAuthUserOwnsChannelValidator, 87 ensureIsLocalChannel,
88 ensureCanManageChannel,
87 updateBannerValidator, 89 updateBannerValidator,
88 asyncMiddleware(updateVideoChannelBanner) 90 asyncMiddleware(updateVideoChannelBanner)
89) 91)
@@ -91,27 +93,33 @@ videoChannelRouter.post('/:nameWithHost/banner/pick',
91videoChannelRouter.delete('/:nameWithHost/avatar', 93videoChannelRouter.delete('/:nameWithHost/avatar',
92 authenticate, 94 authenticate,
93 asyncMiddleware(videoChannelsNameWithHostValidator), 95 asyncMiddleware(videoChannelsNameWithHostValidator),
94 ensureAuthUserOwnsChannelValidator, 96 ensureIsLocalChannel,
97 ensureCanManageChannel,
95 asyncMiddleware(deleteVideoChannelAvatar) 98 asyncMiddleware(deleteVideoChannelAvatar)
96) 99)
97 100
98videoChannelRouter.delete('/:nameWithHost/banner', 101videoChannelRouter.delete('/:nameWithHost/banner',
99 authenticate, 102 authenticate,
100 asyncMiddleware(videoChannelsNameWithHostValidator), 103 asyncMiddleware(videoChannelsNameWithHostValidator),
101 ensureAuthUserOwnsChannelValidator, 104 ensureIsLocalChannel,
105 ensureCanManageChannel,
102 asyncMiddleware(deleteVideoChannelBanner) 106 asyncMiddleware(deleteVideoChannelBanner)
103) 107)
104 108
105videoChannelRouter.put('/:nameWithHost', 109videoChannelRouter.put('/:nameWithHost',
106 authenticate, 110 authenticate,
107 asyncMiddleware(videoChannelsNameWithHostValidator), 111 asyncMiddleware(videoChannelsNameWithHostValidator),
108 ensureAuthUserOwnsChannelValidator, 112 ensureIsLocalChannel,
113 ensureCanManageChannel,
109 videoChannelsUpdateValidator, 114 videoChannelsUpdateValidator,
110 asyncRetryTransactionMiddleware(updateVideoChannel) 115 asyncRetryTransactionMiddleware(updateVideoChannel)
111) 116)
112 117
113videoChannelRouter.delete('/:nameWithHost', 118videoChannelRouter.delete('/:nameWithHost',
114 authenticate, 119 authenticate,
120 asyncMiddleware(videoChannelsNameWithHostValidator),
121 ensureIsLocalChannel,
122 ensureCanManageChannel,
115 asyncMiddleware(videoChannelsRemoveValidator), 123 asyncMiddleware(videoChannelsRemoveValidator),
116 asyncRetryTransactionMiddleware(removeVideoChannel) 124 asyncRetryTransactionMiddleware(removeVideoChannel)
117) 125)
@@ -145,7 +153,7 @@ videoChannelRouter.get('/:nameWithHost/videos',
145videoChannelRouter.get('/:nameWithHost/followers', 153videoChannelRouter.get('/:nameWithHost/followers',
146 authenticate, 154 authenticate,
147 asyncMiddleware(videoChannelsNameWithHostValidator), 155 asyncMiddleware(videoChannelsNameWithHostValidator),
148 ensureAuthUserOwnsChannelValidator, 156 ensureCanManageChannel,
149 paginationValidator, 157 paginationValidator,
150 videoChannelsFollowersSortValidator, 158 videoChannelsFollowersSortValidator,
151 setDefaultSort, 159 setDefaultSort,
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index 8b7a76718..795e14e73 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -1,10 +1,10 @@
1import express from 'express' 1import express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { uuidToShort } from '@server/helpers/uuid'
4import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists' 3import { scheduleRefreshIfNeeded } from '@server/lib/activitypub/playlists'
5import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
6import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
7import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models' 6import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/types/models'
7import { uuidToShort } from '@shared/extra-utils'
8import { VideoPlaylistCreateResult, VideoPlaylistElementCreateResult } from '@shared/models' 8import { VideoPlaylistCreateResult, VideoPlaylistElementCreateResult } from '@shared/models'
9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 9import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
10import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model' 10import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model'
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index de65c74f1..4103bb063 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,7 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist' 2import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist'
3import { UserRight, VideoBlacklistCreate } from '../../../../shared' 3import { HttpStatusCode, UserRight, VideoBlacklistCreate } from '@shared/models'
4import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
5import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
6import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers/database' 6import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index aa7259ee9..2a9a9d233 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -12,6 +12,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
12import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' 12import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
13import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' 13import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
14import { VideoCaptionModel } from '../../../models/video/video-caption' 14import { VideoCaptionModel } from '../../../models/video/video-caption'
15import { Hooks } from '@server/lib/plugins/hooks'
15 16
16const reqVideoCaptionAdd = createReqFiles( 17const reqVideoCaptionAdd = createReqFiles(
17 [ 'captionfile' ], 18 [ 'captionfile' ],
@@ -75,6 +76,8 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
75 await federateVideoIfNeeded(video, false, t) 76 await federateVideoIfNeeded(video, false, t)
76 }) 77 })
77 78
79 Hooks.runAction('action:api.video-caption.created', { caption: videoCaption, req, res })
80
78 return res.status(HttpStatusCode.NO_CONTENT_204).end() 81 return res.status(HttpStatusCode.NO_CONTENT_204).end()
79} 82}
80 83
@@ -91,5 +94,7 @@ async function deleteVideoCaption (req: express.Request, res: express.Response)
91 94
92 logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid) 95 logger.info('Video caption %s of video %s deleted.', videoCaption.language, video.uuid)
93 96
97 Hooks.runAction('action:api.video-caption.deleted', { caption: videoCaption, req, res })
98
94 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end() 99 return res.type('json').status(HttpStatusCode.NO_CONTENT_204).end()
95} 100}
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index eddb9b32d..08d69827b 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -4,6 +4,7 @@ import { decode } from 'magnet-uri'
4import parseTorrent, { Instance } from 'parse-torrent' 4import parseTorrent, { Instance } from 'parse-torrent'
5import { join } from 'path' 5import { join } from 'path'
6import { isVideoFileExtnameValid } from '@server/helpers/custom-validators/videos' 6import { isVideoFileExtnameValid } from '@server/helpers/custom-validators/videos'
7import { Hooks } from '@server/lib/plugins/hooks'
7import { ServerConfigManager } from '@server/lib/server-config-manager' 8import { ServerConfigManager } from '@server/lib/server-config-manager'
8import { setVideoTags } from '@server/lib/video' 9import { setVideoTags } from '@server/lib/video'
9import { FilteredModelAttributes } from '@server/types' 10import { FilteredModelAttributes } from '@server/types'
@@ -18,15 +19,14 @@ import {
18 MVideoWithBlacklistLight 19 MVideoWithBlacklistLight
19} from '@server/types/models' 20} from '@server/types/models'
20import { MVideoImportFormattable } from '@server/types/models/video/video-import' 21import { MVideoImportFormattable } from '@server/types/models/video/video-import'
21import { ServerErrorCode, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' 22import { ServerErrorCode, ThumbnailType, VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '@shared/models'
22import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 23import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 24import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
25import { isArray } from '../../../helpers/custom-validators/misc' 25import { isArray } from '../../../helpers/custom-validators/misc'
26import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils' 26import { cleanUpReqFiles, createReqFiles } from '../../../helpers/express-utils'
27import { logger } from '../../../helpers/logger' 27import { logger } from '../../../helpers/logger'
28import { getSecureTorrentName } from '../../../helpers/utils' 28import { getSecureTorrentName } from '../../../helpers/utils'
29import { YoutubeDLWrapper, YoutubeDLInfo } from '../../../helpers/youtube-dl' 29import { YoutubeDLInfo, YoutubeDLWrapper } from '../../../helpers/youtube-dl'
30import { CONFIG } from '../../../initializers/config' 30import { CONFIG } from '../../../initializers/config'
31import { MIMETYPES } from '../../../initializers/constants' 31import { MIMETYPES } from '../../../initializers/constants'
32import { sequelizeTypescript } from '../../../initializers/database' 32import { sequelizeTypescript } from '../../../initializers/database'
@@ -94,7 +94,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
94 videoName = result.name 94 videoName = result.name
95 } 95 }
96 96
97 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 97 const video = await buildVideo(res.locals.videoChannel.id, body, { name: videoName })
98 98
99 const thumbnailModel = await processThumbnail(req, video) 99 const thumbnailModel = await processThumbnail(req, video)
100 const previewModel = await processPreview(req, video) 100 const previewModel = await processPreview(req, video)
@@ -151,7 +151,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
151 }) 151 })
152 } 152 }
153 153
154 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) 154 const video = await buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
155 155
156 // Process video thumbnail from request.files 156 // Process video thumbnail from request.files
157 let thumbnailModel = await processThumbnail(req, video) 157 let thumbnailModel = await processThumbnail(req, video)
@@ -210,15 +210,15 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
210 return res.json(videoImport.toFormattedJSON()).end() 210 return res.json(videoImport.toFormattedJSON()).end()
211} 211}
212 212
213function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): MVideoThumbnail { 213async function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): Promise<MVideoThumbnail> {
214 const videoData = { 214 let videoData = {
215 name: body.name || importData.name || 'Unknown name', 215 name: body.name || importData.name || 'Unknown name',
216 remote: false, 216 remote: false,
217 category: body.category || importData.category, 217 category: body.category || importData.category,
218 licence: body.licence || importData.licence, 218 licence: body.licence ?? importData.licence ?? CONFIG.DEFAULTS.PUBLISH.LICENCE,
219 language: body.language || importData.language, 219 language: body.language || importData.language,
220 commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" 220 commentsEnabled: body.commentsEnabled ?? CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
221 downloadEnabled: body.downloadEnabled !== false, 221 downloadEnabled: body.downloadEnabled ?? CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
222 waitTranscoding: body.waitTranscoding || false, 222 waitTranscoding: body.waitTranscoding || false,
223 state: VideoState.TO_IMPORT, 223 state: VideoState.TO_IMPORT,
224 nsfw: body.nsfw || importData.nsfw || false, 224 nsfw: body.nsfw || importData.nsfw || false,
@@ -231,6 +231,14 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
231 ? new Date(body.originallyPublishedAt) 231 ? new Date(body.originallyPublishedAt)
232 : importData.originallyPublishedAt 232 : importData.originallyPublishedAt
233 } 233 }
234
235 videoData = await Hooks.wrapObject(
236 videoData,
237 body.targetUrl
238 ? 'filter:api.video.import-url.video-attribute.result'
239 : 'filter:api.video.import-torrent.video-attribute.result'
240 )
241
234 const video = new VideoModel(videoData) 242 const video = new VideoModel(videoData)
235 video.url = getLocalVideoActivityPubUrl(video) 243 video.url = getLocalVideoActivityPubUrl(video)
236 244
diff --git a/server/controllers/api/videos/live.ts b/server/controllers/api/videos/live.ts
index e29615ff5..8b8cacff9 100644
--- a/server/controllers/api/videos/live.ts
+++ b/server/controllers/api/videos/live.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { createReqFiles } from '@server/helpers/express-utils' 2import { createReqFiles } from '@server/helpers/express-utils'
3import { buildUUID, uuidToShort } from '@server/helpers/uuid'
4import { CONFIG } from '@server/initializers/config' 3import { CONFIG } from '@server/initializers/config'
5import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants' 4import { ASSETS_PATH, MIMETYPES } from '@server/initializers/constants'
6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 5import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
@@ -10,8 +9,8 @@ import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } fro
10import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live' 9import { videoLiveAddValidator, videoLiveGetValidator, videoLiveUpdateValidator } from '@server/middlewares/validators/videos/video-live'
11import { VideoLiveModel } from '@server/models/video/video-live' 10import { VideoLiveModel } from '@server/models/video/video-live'
12import { MVideoDetails, MVideoFullLight } from '@server/types/models' 11import { MVideoDetails, MVideoFullLight } from '@server/types/models'
13import { LiveVideoCreate, LiveVideoUpdate, VideoState } from '../../../../shared' 12import { buildUUID, uuidToShort } from '@shared/extra-utils'
14import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 13import { HttpStatusCode, LiveVideoCreate, LiveVideoUpdate, VideoState } from '@shared/models'
15import { logger } from '../../../helpers/logger' 14import { logger } from '../../../helpers/logger'
16import { sequelizeTypescript } from '../../../initializers/database' 15import { sequelizeTypescript } from '../../../initializers/database'
17import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail' 16import { updateVideoMiniatureFromExisting } from '../../../lib/thumbnail'
@@ -83,7 +82,9 @@ async function addLiveVideo (req: express.Request, res: express.Response) {
83 const videoInfo: LiveVideoCreate = req.body 82 const videoInfo: LiveVideoCreate = req.body
84 83
85 // Prepare data so we don't block the transaction 84 // Prepare data so we don't block the transaction
86 const videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id) 85 let videoData = buildLocalVideoFromReq(videoInfo, res.locals.videoChannel.id)
86 videoData = await Hooks.wrapObject(videoData, 'filter:api.video.live.video-attribute.result')
87
87 videoData.isLive = true 88 videoData.isLive = true
88 videoData.state = VideoState.WAITING_FOR_LIVE 89 videoData.state = VideoState.WAITING_FOR_LIVE
89 videoData.duration = 0 90 videoData.duration = 0
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index c9cc16644..6b26a8eee 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { HttpStatusCode, UserVideoRateUpdate } from '@shared/models'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
5import { VIDEO_RATE_TYPES } from '../../../initializers/constants' 4import { VIDEO_RATE_TYPES } from '../../../initializers/constants'
6import { sequelizeTypescript } from '../../../initializers/database' 5import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/controllers/api/videos/transcoding.ts b/server/controllers/api/videos/transcoding.ts
index dd6fbd3de..388689c8a 100644
--- a/server/controllers/api/videos/transcoding.ts
+++ b/server/controllers/api/videos/transcoding.ts
@@ -29,7 +29,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
29 29
30 const body: VideoTranscodingCreate = req.body 30 const body: VideoTranscodingCreate = req.body
31 31
32 const { resolution: maxResolution, isPortraitMode } = await video.getMaxQualityResolution() 32 const { resolution: maxResolution, isPortraitMode, audioStream } = await video.getMaxQualityFileInfo()
33 const resolutions = computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ]) 33 const resolutions = computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ])
34 34
35 video.state = VideoState.TO_TRANSCODE 35 video.state = VideoState.TO_TRANSCODE
@@ -42,6 +42,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
42 videoUUID: video.uuid, 42 videoUUID: video.uuid,
43 resolution, 43 resolution,
44 isPortraitMode, 44 isPortraitMode,
45 hasAudio: !!audioStream,
45 copyCodecs: false, 46 copyCodecs: false,
46 isNewVideo: false, 47 isNewVideo: false,
47 autoDeleteWebTorrentIfNeeded: false, 48 autoDeleteWebTorrentIfNeeded: false,
@@ -53,6 +54,7 @@ async function createTranscoding (req: express.Request, res: express.Response) {
53 videoUUID: video.uuid, 54 videoUUID: video.uuid,
54 isNewVideo: false, 55 isNewVideo: false,
55 resolution: resolution, 56 resolution: resolution,
57 hasAudio: !!audioStream,
56 isPortraitMode 58 isPortraitMode
57 }) 59 })
58 } 60 }
diff --git a/server/controllers/api/videos/update.ts b/server/controllers/api/videos/update.ts
index 3fcff3e86..f600847d4 100644
--- a/server/controllers/api/videos/update.ts
+++ b/server/controllers/api/videos/update.ts
@@ -1,12 +1,12 @@
1import express from 'express' 1import express from 'express'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { updateTorrentMetadata } from '@server/helpers/webtorrent'
3import { changeVideoChannelShare } from '@server/lib/activitypub/share' 4import { changeVideoChannelShare } from '@server/lib/activitypub/share'
4import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video' 5import { buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
5import { openapiOperationDoc } from '@server/middlewares/doc' 6import { openapiOperationDoc } from '@server/middlewares/doc'
6import { FilteredModelAttributes } from '@server/types' 7import { FilteredModelAttributes } from '@server/types'
7import { MVideoFullLight } from '@server/types/models' 8import { MVideoFullLight } from '@server/types/models'
8import { VideoUpdate } from '../../../../shared' 9import { HttpStatusCode, VideoUpdate } from '@shared/models'
9import { HttpStatusCode } from '../../../../shared/models'
10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 10import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
11import { resetSequelizeInstance } from '../../../helpers/database-utils' 11import { resetSequelizeInstance } from '../../../helpers/database-utils'
12import { createReqFiles } from '../../../helpers/express-utils' 12import { createReqFiles } from '../../../helpers/express-utils'
@@ -68,7 +68,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
68 }) 68 })
69 69
70 try { 70 try {
71 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { 71 const { videoInstanceUpdated, isNewVideo } = await sequelizeTypescript.transaction(async t => {
72 // Refresh video since thumbnails to prevent concurrent updates 72 // Refresh video since thumbnails to prevent concurrent updates
73 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoFromReq.id, t) 73 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoFromReq.id, t)
74 74
@@ -137,8 +137,6 @@ async function updateVideo (req: express.Request, res: express.Response) {
137 transaction: t 137 transaction: t
138 }) 138 })
139 139
140 await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
141
142 auditLogger.update( 140 auditLogger.update(
143 getAuditIdFromRes(res), 141 getAuditIdFromRes(res),
144 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()), 142 new VideoAuditView(videoInstanceUpdated.toFormattedDetailsJSON()),
@@ -146,12 +144,14 @@ async function updateVideo (req: express.Request, res: express.Response) {
146 ) 144 )
147 logger.info('Video with name %s and uuid %s updated.', video.name, video.uuid, lTags(video.uuid)) 145 logger.info('Video with name %s and uuid %s updated.', video.name, video.uuid, lTags(video.uuid))
148 146
149 return videoInstanceUpdated 147 return { videoInstanceUpdated, isNewVideo }
150 }) 148 })
151 149
152 if (wasConfidentialVideo) { 150 if (videoInfoToUpdate.name) await updateTorrentsMetadata(videoInstanceUpdated)
153 Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated) 151
154 } 152 await sequelizeTypescript.transaction(t => federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t))
153
154 if (wasConfidentialVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoInstanceUpdated)
155 155
156 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res }) 156 Hooks.runAction('action:api.video.updated', { video: videoInstanceUpdated, body: req.body, req, res })
157 } catch (err) { 157 } catch (err) {
@@ -199,3 +199,20 @@ function updateSchedule (videoInstance: MVideoFullLight, videoInfoToUpdate: Vide
199 return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction) 199 return ScheduleVideoUpdateModel.deleteByVideoId(videoInstance.id, transaction)
200 } 200 }
201} 201}
202
203async function updateTorrentsMetadata (video: MVideoFullLight) {
204 for (const file of (video.VideoFiles || [])) {
205 await updateTorrentMetadata(video, file)
206
207 await file.save()
208 }
209
210 const hls = video.getHLSPlaylist()
211 if (!hls) return
212
213 for (const file of (hls.VideoFiles || [])) {
214 await updateTorrentMetadata(hls, file)
215
216 await file.save()
217 }
218}
diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts
index 6773b500f..89787f20b 100644
--- a/server/controllers/api/videos/upload.ts
+++ b/server/controllers/api/videos/upload.ts
@@ -1,13 +1,12 @@
1import express from 'express' 1import express from 'express'
2import { move } from 'fs-extra' 2import { move } from 'fs-extra'
3import { basename } from 'path' 3import { basename } from 'path'
4import { getLowercaseExtension } from '@server/helpers/core-utils'
5import { getResumableUploadPath } from '@server/helpers/upload' 4import { getResumableUploadPath } from '@server/helpers/upload'
6import { uuidToShort } from '@server/helpers/uuid'
7import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 5import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
8import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url' 6import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url'
9import { generateWebTorrentVideoFilename } from '@server/lib/paths' 7import { generateWebTorrentVideoFilename } from '@server/lib/paths'
10import { Redis } from '@server/lib/redis' 8import { Redis } from '@server/lib/redis'
9import { uploadx } from '@server/lib/uploadx'
11import { 10import {
12 addMoveToObjectStorageJob, 11 addMoveToObjectStorageJob,
13 addOptimizeOrMergeAudioJob, 12 addOptimizeOrMergeAudioJob,
@@ -19,16 +18,16 @@ import { VideoPathManager } from '@server/lib/video-path-manager'
19import { buildNextVideoState } from '@server/lib/video-state' 18import { buildNextVideoState } from '@server/lib/video-state'
20import { openapiOperationDoc } from '@server/middlewares/doc' 19import { openapiOperationDoc } from '@server/middlewares/doc'
21import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models' 20import { MVideo, MVideoFile, MVideoFullLight } from '@server/types/models'
22import { Uploadx } from '@uploadx/core' 21import { getLowercaseExtension } from '@shared/core-utils'
23import { VideoCreate, VideoState } from '../../../../shared' 22import { isAudioFile, uuidToShort } from '@shared/extra-utils'
24import { HttpStatusCode } from '../../../../shared/models' 23import { HttpStatusCode, VideoCreate, VideoResolution, VideoState } from '@shared/models'
25import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 24import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
26import { retryTransactionWrapper } from '../../../helpers/database-utils' 25import { retryTransactionWrapper } from '../../../helpers/database-utils'
27import { createReqFiles } from '../../../helpers/express-utils' 26import { createReqFiles } from '../../../helpers/express-utils'
28import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils' 27import { ffprobePromise, getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
29import { logger, loggerTagsFactory } from '../../../helpers/logger' 28import { logger, loggerTagsFactory } from '../../../helpers/logger'
30import { CONFIG } from '../../../initializers/config' 29import { CONFIG } from '../../../initializers/config'
31import { DEFAULT_AUDIO_RESOLUTION, MIMETYPES } from '../../../initializers/constants' 30import { MIMETYPES } from '../../../initializers/constants'
32import { sequelizeTypescript } from '../../../initializers/database' 31import { sequelizeTypescript } from '../../../initializers/database'
33import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' 32import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
34import { Notifier } from '../../../lib/notifier' 33import { Notifier } from '../../../lib/notifier'
@@ -41,8 +40,8 @@ import {
41 authenticate, 40 authenticate,
42 videosAddLegacyValidator, 41 videosAddLegacyValidator,
43 videosAddResumableInitValidator, 42 videosAddResumableInitValidator,
44 videosResumableUploadIdValidator, 43 videosAddResumableValidator,
45 videosAddResumableValidator 44 videosResumableUploadIdValidator
46} from '../../../middlewares' 45} from '../../../middlewares'
47import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' 46import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update'
48import { VideoModel } from '../../../models/video/video' 47import { VideoModel } from '../../../models/video/video'
@@ -52,9 +51,6 @@ const lTags = loggerTagsFactory('api', 'video')
52const auditLogger = auditLoggerFactory('videos') 51const auditLogger = auditLoggerFactory('videos')
53const uploadRouter = express.Router() 52const uploadRouter = express.Router()
54 53
55const uploadx = new Uploadx({ directory: getResumableUploadPath() })
56uploadx.getUserId = (_, res: express.Response) => res.locals.oauth?.token.user.id
57
58const reqVideoFileAdd = createReqFiles( 54const reqVideoFileAdd = createReqFiles(
59 [ 'videofile', 'thumbnailfile', 'previewfile' ], 55 [ 'videofile', 'thumbnailfile', 'previewfile' ],
60 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT), 56 Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
@@ -156,7 +152,8 @@ async function addVideo (options: {
156 const videoChannel = res.locals.videoChannel 152 const videoChannel = res.locals.videoChannel
157 const user = res.locals.oauth.token.User 153 const user = res.locals.oauth.token.User
158 154
159 const videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id) 155 let videoData = buildLocalVideoFromReq(videoInfo, videoChannel.id)
156 videoData = await Hooks.wrapObject(videoData, 'filter:api.video.upload.video-attribute.result')
160 157
161 videoData.state = buildNextVideoState() 158 videoData.state = buildNextVideoState()
162 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware 159 videoData.duration = videoPhysicalFile.duration // duration was added by a previous middleware
@@ -255,11 +252,13 @@ async function buildNewFile (videoPhysicalFile: express.VideoUploadFile) {
255 metadata: await getMetadataFromFile(videoPhysicalFile.path) 252 metadata: await getMetadataFromFile(videoPhysicalFile.path)
256 }) 253 })
257 254
258 if (videoFile.isAudio()) { 255 const probe = await ffprobePromise(videoPhysicalFile.path)
259 videoFile.resolution = DEFAULT_AUDIO_RESOLUTION 256
257 if (await isAudioFile(videoPhysicalFile.path, probe)) {
258 videoFile.resolution = VideoResolution.H_NOVIDEO
260 } else { 259 } else {
261 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path) 260 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path, probe)
262 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).resolution 261 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path, probe)).resolution
263 } 262 }
264 263
265 videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname) 264 videoFile.filename = generateWebTorrentVideoFilename(videoFile.resolution, videoFile.extname)
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index e8c28b613..3fd22caac 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { HttpStatusCode, UserWatchingVideo } from '@shared/models'
3import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
4import { 3import {
5 asyncMiddleware, 4 asyncMiddleware,
6 asyncRetryTransactionMiddleware, 5 asyncRetryTransactionMiddleware,
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index 2157ae533..8a56f2f75 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -7,7 +7,7 @@ import { CONFIG } from '@server/initializers/config'
7import { Hooks } from '@server/lib/plugins/hooks' 7import { Hooks } from '@server/lib/plugins/hooks'
8import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n' 8import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '@shared/core-utils/i18n'
9import { HttpStatusCode } from '@shared/models' 9import { HttpStatusCode } from '@shared/models'
10import { root } from '../helpers/core-utils' 10import { root } from '@shared/core-utils'
11import { STATIC_MAX_AGE } from '../initializers/constants' 11import { STATIC_MAX_AGE } from '../initializers/constants'
12import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' 12import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html'
13import { asyncMiddleware, embedCSP } from '../middlewares' 13import { asyncMiddleware, embedCSP } from '../middlewares'
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 0d94cac9b..87bceba7a 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -5,7 +5,7 @@ import { serveIndexHTML } from '@server/lib/client-html'
5import { ServerConfigManager } from '@server/lib/server-config-manager' 5import { ServerConfigManager } from '@server/lib/server-config-manager'
6import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model' 7import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo/nodeinfo.model'
8import { root } from '../helpers/core-utils' 8import { root } from '@shared/core-utils'
9import { CONFIG, isEmailEnabled } from '../initializers/config' 9import { CONFIG, isEmailEnabled } from '../initializers/config'
10import { 10import {
11 CONSTRAINTS_FIELDS, 11 CONSTRAINTS_FIELDS,
diff --git a/server/helpers/actors.ts b/server/helpers/actors.ts
new file mode 100644
index 000000000..c31fe6f8e
--- /dev/null
+++ b/server/helpers/actors.ts
@@ -0,0 +1,17 @@
1import { WEBSERVER } from '@server/initializers/constants'
2
3function handleToNameAndHost (handle: string) {
4 let [ name, host ] = handle.split('@')
5 if (host === WEBSERVER.HOST) host = null
6
7 return { name, host, handle }
8}
9
10function handlesToNameAndHost (handles: string[]) {
11 return handles.map(h => handleToNameAndHost(h))
12}
13
14export {
15 handleToNameAndHost,
16 handlesToNameAndHost
17}
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index 5f2e870e3..79ef44be1 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -5,9 +5,7 @@ import { chain } from 'lodash'
5import { join } from 'path' 5import { join } from 'path'
6import { addColors, config, createLogger, format, transports } from 'winston' 6import { addColors, config, createLogger, format, transports } from 'winston'
7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants' 7import { AUDIT_LOG_FILENAME } from '@server/initializers/constants'
8import { AdminAbuse, User, VideoChannel, VideoDetails, VideoImport } from '../../shared' 8import { AdminAbuse, CustomConfig, User, VideoChannel, VideoComment, VideoDetails, VideoImport } from '@shared/models'
9import { CustomConfig } from '../../shared/models/server/custom-config.model'
10import { VideoComment } from '../../shared/models/videos/comment/video-comment.model'
11import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
12import { jsonLoggerFormat, labelFormatter } from './logger' 10import { jsonLoggerFormat, labelFormatter } from './logger'
13 11
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 2cbf0f8fe..531ccfba9 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -6,9 +6,8 @@
6*/ 6*/
7 7
8import { exec, ExecOptions } from 'child_process' 8import { exec, ExecOptions } from 'child_process'
9import { BinaryToTextEncoding, createHash, randomBytes } from 'crypto' 9import { randomBytes } from 'crypto'
10import { truncate } from 'lodash' 10import { truncate } from 'lodash'
11import { basename, extname, isAbsolute, join, resolve } from 'path'
12import { createPrivateKey as createPrivateKey_1, getPublicKey as getPublicKey_1 } from 'pem' 11import { createPrivateKey as createPrivateKey_1, getPublicKey as getPublicKey_1 } from 'pem'
13import { pipeline } from 'stream' 12import { pipeline } from 'stream'
14import { URL } from 'url' 13import { URL } from 'url'
@@ -159,34 +158,6 @@ function getAppNumber () {
159 158
160// --------------------------------------------------------------------------- 159// ---------------------------------------------------------------------------
161 160
162let rootPath: string
163
164function root () {
165 if (rootPath) return rootPath
166
167 rootPath = __dirname
168
169 if (basename(rootPath) === 'helpers') rootPath = resolve(rootPath, '..')
170 if (basename(rootPath) === 'server') rootPath = resolve(rootPath, '..')
171 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
172
173 return rootPath
174}
175
176function buildPath (path: string) {
177 if (isAbsolute(path)) return path
178
179 return join(root(), path)
180}
181
182function getLowercaseExtension (filename: string) {
183 const ext = extname(filename) || ''
184
185 return ext.toLowerCase()
186}
187
188// ---------------------------------------------------------------------------
189
190// Consistent with .length, lodash truncate function is not 161// Consistent with .length, lodash truncate function is not
191function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) { 162function peertubeTruncate (str: string, options: { length: number, separator?: RegExp, omission?: string }) {
192 const truncatedStr = truncate(str, options) 163 const truncatedStr = truncate(str, options)
@@ -221,16 +192,6 @@ function parseSemVersion (s: string) {
221 192
222// --------------------------------------------------------------------------- 193// ---------------------------------------------------------------------------
223 194
224function sha256 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
225 return createHash('sha256').update(str).digest(encoding)
226}
227
228function sha1 (str: string | Buffer, encoding: BinaryToTextEncoding = 'hex') {
229 return createHash('sha1').update(str).digest(encoding)
230}
231
232// ---------------------------------------------------------------------------
233
234function execShell (command: string, options?: ExecOptions) { 195function execShell (command: string, options?: ExecOptions) {
235 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { 196 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
236 exec(command, options, (err, stdout, stderr) => { 197 exec(command, options, (err, stdout, stderr) => {
@@ -298,9 +259,6 @@ export {
298 objectConverter, 259 objectConverter,
299 mapToJSON, 260 mapToJSON,
300 261
301 root,
302 buildPath,
303 getLowercaseExtension,
304 sanitizeUrl, 262 sanitizeUrl,
305 sanitizeHost, 263 sanitizeHost,
306 264
@@ -309,9 +267,6 @@ export {
309 pageToStartAndCount, 267 pageToStartAndCount,
310 peertubeTruncate, 268 peertubeTruncate,
311 269
312 sha256,
313 sha1,
314
315 promisify0, 270 promisify0,
316 promisify1, 271 promisify1,
317 promisify2, 272 promisify2,
diff --git a/server/helpers/custom-validators/activitypub/playlist.ts b/server/helpers/custom-validators/activitypub/playlist.ts
index 72c5b80e9..49bcadcfd 100644
--- a/server/helpers/custom-validators/activitypub/playlist.ts
+++ b/server/helpers/custom-validators/activitypub/playlist.ts
@@ -1,6 +1,5 @@
1import validator from 'validator' 1import validator from 'validator'
2import { PlaylistElementObject } from '../../../../shared/models/activitypub/objects/playlist-element-object' 2import { PlaylistElementObject, PlaylistObject } from '@shared/models'
3import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
4import { exists, isDateValid, isUUIDValid } from '../misc' 3import { exists, isDateValid, isUUIDValid } from '../misc'
5import { isVideoPlaylistNameValid } from '../video-playlists' 4import { isVideoPlaylistNameValid } from '../video-playlists'
6import { isActivityPubUrlValid } from './misc' 5import { isActivityPubUrlValid } from './misc'
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index c19a3e5eb..81a60ee66 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -2,7 +2,7 @@ import 'multer'
2import { UploadFilesForCheck } from 'express' 2import { UploadFilesForCheck } from 'express'
3import { sep } from 'path' 3import { sep } from 'path'
4import validator from 'validator' 4import validator from 'validator'
5import { isShortUUID, shortToUUID } from '../uuid' 5import { isShortUUID, shortToUUID } from '@shared/extra-utils'
6 6
7function exists (value: any) { 7function exists (value: any) {
8 return value !== undefined && value !== null 8 return value !== undefined && value !== null
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts
index f2d4efb32..60b29dc89 100644
--- a/server/helpers/custom-validators/plugins.ts
+++ b/server/helpers/custom-validators/plugins.ts
@@ -2,7 +2,7 @@ import { exists, isArray, isSafePath } from './misc'
2import validator from 'validator' 2import validator from 'validator'
3import { PluginType } from '../../../shared/models/plugins/plugin.type' 3import { PluginType } from '../../../shared/models/plugins/plugin.type'
4import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 4import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
5import { PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model' 5import { PluginPackageJSON } from '../../../shared/models/plugins/plugin-package-json.model'
6import { isUrlValid } from './activitypub/misc' 6import { isUrlValid } from './activitypub/misc'
7 7
8const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS 8const PLUGINS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.PLUGINS
@@ -84,7 +84,7 @@ function isThemeNameValid (name: string) {
84 return isPluginNameValid(name) 84 return isPluginNameValid(name)
85} 85}
86 86
87function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginType) { 87function isPackageJSONValid (packageJSON: PluginPackageJSON, pluginType: PluginType) {
88 let result = true 88 let result = true
89 const badFields: string[] = [] 89 const badFields: string[] = []
90 90
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts
index f52c60b60..b04970108 100644
--- a/server/helpers/custom-validators/users.ts
+++ b/server/helpers/custom-validators/users.ts
@@ -1,6 +1,6 @@
1import { values } from 'lodash' 1import { values } from 'lodash'
2import validator from 'validator' 2import validator from 'validator'
3import { UserRole } from '../../../shared' 3import { UserRole } from '@shared/models'
4import { isEmailEnabled } from '../../initializers/config' 4import { isEmailEnabled } from '../../initializers/config'
5import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' 5import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants'
6import { exists, isArray, isBooleanValid } from './misc' 6import { exists, isArray, isBooleanValid } from './misc'
@@ -49,7 +49,7 @@ function isUserNSFWPolicyValid (value: any) {
49 return exists(value) && nsfwPolicies.includes(value) 49 return exists(value) && nsfwPolicies.includes(value)
50} 50}
51 51
52function isUserWebTorrentEnabledValid (value: any) { 52function isUserP2PEnabledValid (value: any) {
53 return isBooleanValid(value) 53 return isBooleanValid(value)
54} 54}
55 55
@@ -90,7 +90,7 @@ function isUserBlockedReasonValid (value: any) {
90} 90}
91 91
92function isUserRoleValid (value: any) { 92function isUserRoleValid (value: any) {
93 return exists(value) && validator.isInt('' + value) && UserRole[value] !== undefined 93 return exists(value) && validator.isInt('' + value) && [ UserRole.ADMINISTRATOR, UserRole.MODERATOR, UserRole.USER ].includes(value)
94} 94}
95 95
96// --------------------------------------------------------------------------- 96// ---------------------------------------------------------------------------
@@ -109,7 +109,7 @@ export {
109 isUserAdminFlagsValid, 109 isUserAdminFlagsValid,
110 isUserEmailVerifiedValid, 110 isUserEmailVerifiedValid,
111 isUserNSFWPolicyValid, 111 isUserNSFWPolicyValid,
112 isUserWebTorrentEnabledValid, 112 isUserP2PEnabledValid,
113 isUserAutoPlayVideoValid, 113 isUserAutoPlayVideoValid,
114 isUserAutoPlayNextVideoValid, 114 isUserAutoPlayNextVideoValid,
115 isUserAutoPlayNextVideoPlaylistValid, 115 isUserAutoPlayNextVideoPlaylistValid,
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index 1d56ade6f..e526c4284 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -2,8 +2,7 @@ import { UploadFilesForCheck } from 'express'
2import { values } from 'lodash' 2import { values } from 'lodash'
3import magnetUtil from 'magnet-uri' 3import magnetUtil from 'magnet-uri'
4import validator from 'validator' 4import validator from 'validator'
5import { VideoInclude } from '@shared/models' 5import { VideoFilter, VideoInclude, VideoPrivacy, VideoRateType } from '@shared/models'
6import { VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
7import { 6import {
8 CONSTRAINTS_FIELDS, 7 CONSTRAINTS_FIELDS,
9 MIMETYPES, 8 MIMETYPES,
diff --git a/server/helpers/decache.ts b/server/helpers/decache.ts
new file mode 100644
index 000000000..e31973b7a
--- /dev/null
+++ b/server/helpers/decache.ts
@@ -0,0 +1,78 @@
1// Thanks: https://github.com/dwyl/decache
2// We reuse this file to also uncache plugin base path
3
4import { extname } from 'path'
5
6function decachePlugin (pluginPath: string, libraryPath: string) {
7 const moduleName = find(libraryPath)
8
9 if (!moduleName) return
10
11 searchCache(moduleName, function (mod) {
12 delete require.cache[mod.id]
13 })
14
15 removeCachedPath(pluginPath)
16}
17
18function decacheModule (name: string) {
19 const moduleName = find(name)
20
21 if (!moduleName) return
22
23 searchCache(moduleName, function (mod) {
24 delete require.cache[mod.id]
25 })
26
27 removeCachedPath(moduleName)
28}
29
30// ---------------------------------------------------------------------------
31
32export {
33 decacheModule,
34 decachePlugin
35}
36
37// ---------------------------------------------------------------------------
38
39function find (moduleName: string) {
40 try {
41 return require.resolve(moduleName)
42 } catch {
43 return ''
44 }
45}
46
47function searchCache (moduleName: string, callback: (current: NodeModule) => void) {
48 const resolvedModule = require.resolve(moduleName)
49 let mod: NodeModule
50 const visited = {}
51
52 if (resolvedModule && ((mod = require.cache[resolvedModule]) !== undefined)) {
53 // Recursively go over the results
54 (function run (current) {
55 visited[current.id] = true
56
57 current.children.forEach(function (child) {
58 if (extname(child.filename) !== '.node' && !visited[child.id]) {
59 run(child)
60 }
61 })
62
63 // Call the specified callback providing the
64 // found module
65 callback(current)
66 })(mod)
67 }
68};
69
70function removeCachedPath (pluginPath: string) {
71 const pathCache = (module.constructor as any)._pathCache
72
73 Object.keys(pathCache).forEach(function (cacheKey) {
74 if (cacheKey.includes(pluginPath)) {
75 delete pathCache[cacheKey]
76 }
77 })
78}
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 7b81ed71b..780fd6345 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -1,9 +1,9 @@
1import express from 'express' 1import express, { RequestHandler } from 'express'
2import multer, { diskStorage } from 'multer' 2import multer, { diskStorage } from 'multer'
3import { HttpStatusCode } from '../../shared/models/http/http-error-codes' 3import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
4import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
5import { REMOTE_SCHEME } from '../initializers/constants' 5import { REMOTE_SCHEME } from '../initializers/constants'
6import { getLowercaseExtension } from './core-utils' 6import { getLowercaseExtension } from '@shared/core-utils'
7import { isArray } from './custom-validators/misc' 7import { isArray } from './custom-validators/misc'
8import { logger } from './logger' 8import { logger } from './logger'
9import { deleteFileAndCatch, generateRandomString } from './utils' 9import { deleteFileAndCatch, generateRandomString } from './utils'
@@ -69,7 +69,7 @@ function createReqFiles (
69 fieldNames: string[], 69 fieldNames: string[],
70 mimeTypes: { [id: string]: string | string[] }, 70 mimeTypes: { [id: string]: string | string[] },
71 destinations: { [fieldName: string]: string } 71 destinations: { [fieldName: string]: string }
72) { 72): RequestHandler {
73 const storage = diskStorage({ 73 const storage = diskStorage({
74 destination: (req, file, cb) => { 74 destination: (req, file, cb) => {
75 cb(null, destinations[file.fieldname]) 75 cb(null, destinations[file.fieldname])
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index ab29d4691..78ee5fa7f 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -287,8 +287,8 @@ async function getLiveTranscodingCommand (options: {
287 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) 287 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
288 288
289 logger.debug( 289 logger.debug(
290 'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile, builderResult, 290 'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile,
291 { fps: resolutionFPS, resolution, ...lTags() } 291 { builderResult, fps: resolutionFPS, resolution, ...lTags() }
292 ) 292 )
293 293
294 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) 294 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
@@ -314,8 +314,8 @@ async function getLiveTranscodingCommand (options: {
314 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) 314 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
315 315
316 logger.debug( 316 logger.debug(
317 'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile, builderResult, 317 'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile,
318 { fps: resolutionFPS, resolution, ...lTags() } 318 { builderResult, fps: resolutionFPS, resolution, ...lTags() }
319 ) 319 )
320 320
321 command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`) 321 command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`)
@@ -368,10 +368,6 @@ function addDefaultEncoderGlobalParams (options: {
368 command.outputOption('-max_muxing_queue_size 1024') 368 command.outputOption('-max_muxing_queue_size 1024')
369 // strip all metadata 369 // strip all metadata
370 .outputOption('-map_metadata -1') 370 .outputOption('-map_metadata -1')
371 // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
372 .outputOption('-b_strategy 1')
373 // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
374 .outputOption('-bf 16')
375 // allows import of source material with incompatible pixel formats (e.g. MJPEG video) 371 // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
376 .outputOption('-pix_fmt yuv420p') 372 .outputOption('-pix_fmt yuv420p')
377} 373}
@@ -627,8 +623,8 @@ async function presetVideo (options: {
627 623
628 logger.debug( 624 logger.debug(
629 'Apply ffmpeg params from %s for %s stream of input %s using %s profile.', 625 'Apply ffmpeg params from %s for %s stream of input %s using %s profile.',
630 builderResult.encoder, streamType, input, profile, builderResult, 626 builderResult.encoder, streamType, input, profile,
631 { resolution, fps, ...lTags() } 627 { builderResult, resolution, fps, ...lTags() }
632 ) 628 )
633 629
634 if (streamType === 'video') { 630 if (streamType === 'video') {
@@ -734,7 +730,7 @@ async function runCommand (options: {
734 command.on('start', cmdline => { shellCommand = cmdline }) 730 command.on('start', cmdline => { shellCommand = cmdline })
735 731
736 command.on('error', (err, stdout, stderr) => { 732 command.on('error', (err, stdout, stderr) => {
737 if (silent !== true) logger.error('Error in ffmpeg.', { stdout, stderr, ...lTags() }) 733 if (silent !== true) logger.error('Error in ffmpeg.', { stdout, stderr, shellCommand, ...lTags() })
738 734
739 rej(err) 735 rej(err)
740 }) 736 })
diff --git a/server/helpers/ffprobe-utils.ts b/server/helpers/ffprobe-utils.ts
index e15628e2a..595112bce 100644
--- a/server/helpers/ffprobe-utils.ts
+++ b/server/helpers/ffprobe-utils.ts
@@ -1,9 +1,22 @@
1import { ffprobe, FfprobeData } from 'fluent-ffmpeg' 1import { FfprobeData } from 'fluent-ffmpeg'
2import { getMaxBitrate } from '@shared/core-utils' 2import { getMaxBitrate } from '@shared/core-utils'
3import { VideoFileMetadata, VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos' 3import { VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos'
4import { CONFIG } from '../initializers/config' 4import { CONFIG } from '../initializers/config'
5import { VIDEO_TRANSCODING_FPS } from '../initializers/constants' 5import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
6import { logger } from './logger' 6import { logger } from './logger'
7import {
8 canDoQuickAudioTranscode,
9 ffprobePromise,
10 getDurationFromVideoFile,
11 getAudioStream,
12 getMaxAudioBitrate,
13 getMetadataFromFile,
14 getVideoFileBitrate,
15 getVideoFileFPS,
16 getVideoFileResolution,
17 getVideoStreamFromFile,
18 getVideoStreamSize
19} from '@shared/extra-utils/ffprobe'
7 20
8/** 21/**
9 * 22 *
@@ -11,79 +24,6 @@ import { logger } from './logger'
11 * 24 *
12 */ 25 */
13 26
14function ffprobePromise (path: string) {
15 return new Promise<FfprobeData>((res, rej) => {
16 ffprobe(path, (err, data) => {
17 if (err) return rej(err)
18
19 return res(data)
20 })
21 })
22}
23
24async function getAudioStream (videoPath: string, existingProbe?: FfprobeData) {
25 // without position, ffprobe considers the last input only
26 // we make it consider the first input only
27 // if you pass a file path to pos, then ffprobe acts on that file directly
28 const data = existingProbe || await ffprobePromise(videoPath)
29
30 if (Array.isArray(data.streams)) {
31 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
32
33 if (audioStream) {
34 return {
35 absolutePath: data.format.filename,
36 audioStream,
37 bitrate: parseInt(audioStream['bit_rate'] + '', 10)
38 }
39 }
40 }
41
42 return { absolutePath: data.format.filename }
43}
44
45function getMaxAudioBitrate (type: 'aac' | 'mp3' | string, bitrate: number) {
46 const maxKBitrate = 384
47 const kToBits = (kbits: number) => kbits * 1000
48
49 // If we did not manage to get the bitrate, use an average value
50 if (!bitrate) return 256
51
52 if (type === 'aac') {
53 switch (true) {
54 case bitrate > kToBits(maxKBitrate):
55 return maxKBitrate
56
57 default:
58 return -1 // we interpret it as a signal to copy the audio stream as is
59 }
60 }
61
62 /*
63 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
64 That's why, when using aac, we can go to lower kbit/sec. The equivalences
65 made here are not made to be accurate, especially with good mp3 encoders.
66 */
67 switch (true) {
68 case bitrate <= kToBits(192):
69 return 128
70
71 case bitrate <= kToBits(384):
72 return 256
73
74 default:
75 return maxKBitrate
76 }
77}
78
79async function getVideoStreamSize (path: string, existingProbe?: FfprobeData): Promise<{ width: number, height: number }> {
80 const videoStream = await getVideoStreamFromFile(path, existingProbe)
81
82 return videoStream === null
83 ? { width: 0, height: 0 }
84 : { width: videoStream.width, height: videoStream.height }
85}
86
87async function getVideoStreamCodec (path: string) { 27async function getVideoStreamCodec (path: string) {
88 const videoStream = await getVideoStreamFromFile(path) 28 const videoStream = await getVideoStreamFromFile(path)
89 29
@@ -143,69 +83,6 @@ async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
143 return 'mp4a.40.2' // Fallback 83 return 'mp4a.40.2' // Fallback
144} 84}
145 85
146async function getVideoFileResolution (path: string, existingProbe?: FfprobeData) {
147 const size = await getVideoStreamSize(path, existingProbe)
148
149 return {
150 width: size.width,
151 height: size.height,
152 ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width),
153 resolution: Math.min(size.height, size.width),
154 isPortraitMode: size.height > size.width
155 }
156}
157
158async function getVideoFileFPS (path: string, existingProbe?: FfprobeData) {
159 const videoStream = await getVideoStreamFromFile(path, existingProbe)
160 if (videoStream === null) return 0
161
162 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
163 const valuesText: string = videoStream[key]
164 if (!valuesText) continue
165
166 const [ frames, seconds ] = valuesText.split('/')
167 if (!frames || !seconds) continue
168
169 const result = parseInt(frames, 10) / parseInt(seconds, 10)
170 if (result > 0) return Math.round(result)
171 }
172
173 return 0
174}
175
176async function getMetadataFromFile (path: string, existingProbe?: FfprobeData) {
177 const metadata = existingProbe || await ffprobePromise(path)
178
179 return new VideoFileMetadata(metadata)
180}
181
182async function getVideoFileBitrate (path: string, existingProbe?: FfprobeData): Promise<number> {
183 const metadata = await getMetadataFromFile(path, existingProbe)
184
185 let bitrate = metadata.format.bit_rate as number
186 if (bitrate && !isNaN(bitrate)) return bitrate
187
188 const videoStream = await getVideoStreamFromFile(path, existingProbe)
189 if (!videoStream) return undefined
190
191 bitrate = videoStream?.bit_rate
192 if (bitrate && !isNaN(bitrate)) return bitrate
193
194 return undefined
195}
196
197async function getDurationFromVideoFile (path: string, existingProbe?: FfprobeData) {
198 const metadata = await getMetadataFromFile(path, existingProbe)
199
200 return Math.round(metadata.format.duration)
201}
202
203async function getVideoStreamFromFile (path: string, existingProbe?: FfprobeData) {
204 const metadata = await getMetadataFromFile(path, existingProbe)
205
206 return metadata.streams.find(s => s.codec_type === 'video') || null
207}
208
209function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') { 86function computeLowerResolutionsToTranscode (videoFileResolution: number, type: 'vod' | 'live') {
210 const configResolutions = type === 'vod' 87 const configResolutions = type === 'vod'
211 ? CONFIG.TRANSCODING.RESOLUTIONS 88 ? CONFIG.TRANSCODING.RESOLUTIONS
@@ -263,26 +140,6 @@ async function canDoQuickVideoTranscode (path: string, probe?: FfprobeData): Pro
263 return true 140 return true
264} 141}
265 142
266async function canDoQuickAudioTranscode (path: string, probe?: FfprobeData): Promise<boolean> {
267 const parsedAudio = await getAudioStream(path, probe)
268
269 if (!parsedAudio.audioStream) return true
270
271 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
272
273 const audioBitrate = parsedAudio.bitrate
274 if (!audioBitrate) return false
275
276 const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
277 if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
278
279 const channelLayout = parsedAudio.audioStream['channel_layout']
280 // Causes playback issues with Chrome
281 if (!channelLayout || channelLayout === 'unknown') return false
282
283 return true
284}
285
286function getClosestFramerateStandard <K extends keyof Pick<VideoTranscodingFPS, 'HD_STANDARD' | 'STANDARD'>> (fps: number, type: K) { 143function getClosestFramerateStandard <K extends keyof Pick<VideoTranscodingFPS, 'HD_STANDARD' | 'STANDARD'>> (fps: number, type: K) {
287 return VIDEO_TRANSCODING_FPS[type].slice(0) 144 return VIDEO_TRANSCODING_FPS[type].slice(0)
288 .sort((a, b) => fps % a - fps % b)[0] 145 .sort((a, b) => fps % a - fps % b)[0]
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts
index 4305584d5..b174ae436 100644
--- a/server/helpers/image-utils.ts
+++ b/server/helpers/image-utils.ts
@@ -1,9 +1,9 @@
1import { copy, readFile, remove, rename } from 'fs-extra' 1import { copy, readFile, remove, rename } from 'fs-extra'
2import Jimp, { read } from 'jimp' 2import Jimp, { read } from 'jimp'
3import { getLowercaseExtension } from './core-utils' 3import { getLowercaseExtension } from '@shared/core-utils'
4import { buildUUID } from '@shared/extra-utils'
4import { convertWebPToJPG, processGIF } from './ffmpeg-utils' 5import { convertWebPToJPG, processGIF } from './ffmpeg-utils'
5import { logger } from './logger' 6import { logger } from './logger'
6import { buildUUID } from './uuid'
7 7
8function generateImageFilename (extension = '.jpg') { 8function generateImageFilename (extension = '.jpg') {
9 return buildUUID() + extension 9 return buildUUID() + extension
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 44d90d9f1..b8f7c782a 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -2,9 +2,10 @@ import { compare, genSalt, hash } from 'bcrypt'
2import { createSign, createVerify } from 'crypto' 2import { createSign, createVerify } from 'crypto'
3import { Request } from 'express' 3import { Request } from 'express'
4import { cloneDeep } from 'lodash' 4import { cloneDeep } from 'lodash'
5import { sha256 } from '@shared/extra-utils'
5import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' 6import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants'
6import { MActor } from '../types/models' 7import { MActor } from '../types/models'
7import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' 8import { createPrivateKey, getPublicKey, promisify1, promisify2 } from './core-utils'
8import { jsonld } from './custom-jsonld-signature' 9import { jsonld } from './custom-jsonld-signature'
9import { logger } from './logger' 10import { logger } from './logger'
10 11
diff --git a/server/helpers/register-ts-paths.ts b/server/helpers/register-ts-paths.ts
deleted file mode 100644
index eec7fed3e..000000000
--- a/server/helpers/register-ts-paths.ts
+++ /dev/null
@@ -1,16 +0,0 @@
1import { resolve } from 'path'
2import tsConfigPaths = require('tsconfig-paths')
3
4const tsConfig = require('../../tsconfig.json')
5
6function registerTSPaths () {
7 // Thanks: https://github.com/dividab/tsconfig-paths/issues/75#issuecomment-458936883
8 tsConfigPaths.register({
9 baseUrl: resolve(tsConfig.compilerOptions.baseUrl || '', tsConfig.compilerOptions.outDir || ''),
10 paths: tsConfig.compilerOptions.paths
11 })
12}
13
14export {
15 registerTSPaths
16}
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 6c95a43b6..6b9333b53 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,9 +1,10 @@
1import { remove } from 'fs-extra' 1import { remove } from 'fs-extra'
2import { Instance as ParseTorrent } from 'parse-torrent' 2import { Instance as ParseTorrent } from 'parse-torrent'
3import { join } from 'path' 3import { join } from 'path'
4import { ResultList } from '../../shared' 4import { sha256 } from '@shared/extra-utils'
5import { ResultList } from '@shared/models'
5import { CONFIG } from '../initializers/config' 6import { CONFIG } from '../initializers/config'
6import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils' 7import { execPromise, execPromise2, randomBytesPromise } from './core-utils'
7import { logger } from './logger' 8import { logger } from './logger'
8 9
9function deleteFileAndCatch (path: string) { 10function deleteFileAndCatch (path: string) {
diff --git a/server/helpers/uuid.ts b/server/helpers/uuid.ts
deleted file mode 100644
index f3c80e046..000000000
--- a/server/helpers/uuid.ts
+++ /dev/null
@@ -1,32 +0,0 @@
1import short, { uuid } from 'short-uuid'
2
3const translator = short()
4
5function buildUUID () {
6 return uuid()
7}
8
9function uuidToShort (uuid: string) {
10 if (!uuid) return uuid
11
12 return translator.fromUUID(uuid)
13}
14
15function shortToUUID (shortUUID: string) {
16 if (!shortUUID) return shortUUID
17
18 return translator.toUUID(shortUUID)
19}
20
21function isShortUUID (value: string) {
22 if (!value) return false
23
24 return value.length === translator.maxLength
25}
26
27export {
28 buildUUID,
29 uuidToShort,
30 shortToUUID,
31 isShortUUID
32}
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts
index c75c058e4..68d532c48 100644
--- a/server/helpers/webtorrent.ts
+++ b/server/helpers/webtorrent.ts
@@ -13,6 +13,7 @@ import { VideoPathManager } from '@server/lib/video-path-manager'
13import { MVideo } from '@server/types/models/video/video' 13import { MVideo } from '@server/types/models/video/video'
14import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/types/models/video/video-file' 14import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/types/models/video/video-file'
15import { MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist' 15import { MStreamingPlaylistVideo } from '@server/types/models/video/video-streaming-playlist'
16import { sha1 } from '@shared/extra-utils'
16import { CONFIG } from '../initializers/config' 17import { CONFIG } from '../initializers/config'
17import { promisify2 } from './core-utils' 18import { promisify2 } from './core-utils'
18import { logger } from './logger' 19import { logger } from './logger'
@@ -94,7 +95,7 @@ function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlayli
94 95
95 const options = { 96 const options = {
96 // Keep the extname, it's used by the client to stream the file inside a web browser 97 // Keep the extname, it's used by the client to stream the file inside a web browser
97 name: `${video.name} ${videoFile.resolution}p${videoFile.extname}`, 98 name: buildInfoName(video, videoFile),
98 createdBy: 'PeerTube', 99 createdBy: 'PeerTube',
99 announceList: buildAnnounceList(), 100 announceList: buildAnnounceList(),
100 urlList: buildUrlList(video, videoFile) 101 urlList: buildUrlList(video, videoFile)
@@ -120,7 +121,7 @@ function createTorrentAndSetInfoHash (videoOrPlaylist: MVideo | MStreamingPlayli
120 }) 121 })
121} 122}
122 123
123async function updateTorrentUrls (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) { 124async function updateTorrentMetadata (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
124 const video = extractVideo(videoOrPlaylist) 125 const video = extractVideo(videoOrPlaylist)
125 126
126 const oldTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename) 127 const oldTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename)
@@ -133,15 +134,19 @@ async function updateTorrentUrls (videoOrPlaylist: MVideo | MStreamingPlaylistVi
133 134
134 decoded['url-list'] = buildUrlList(video, videoFile) 135 decoded['url-list'] = buildUrlList(video, videoFile)
135 136
137 decoded.info.name = buildInfoName(video, videoFile)
138 decoded['creation date'] = Math.ceil(Date.now() / 1000)
139
136 const newTorrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution) 140 const newTorrentFilename = generateTorrentFileName(videoOrPlaylist, videoFile.resolution)
137 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, newTorrentFilename) 141 const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, newTorrentFilename)
138 142
139 logger.info('Updating torrent URLs %s -> %s.', oldTorrentPath, newTorrentPath) 143 logger.info('Updating torrent metadata %s -> %s.', oldTorrentPath, newTorrentPath)
140 144
141 await writeFile(newTorrentPath, encode(decoded)) 145 await writeFile(newTorrentPath, encode(decoded))
142 await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename)) 146 await remove(join(CONFIG.STORAGE.TORRENTS_DIR, videoFile.torrentFilename))
143 147
144 videoFile.torrentFilename = newTorrentFilename 148 videoFile.torrentFilename = newTorrentFilename
149 videoFile.infoHash = sha1(encode(decoded.info))
145} 150}
146 151
147function generateMagnetUri ( 152function generateMagnetUri (
@@ -171,7 +176,7 @@ function generateMagnetUri (
171 176
172export { 177export {
173 createTorrentPromise, 178 createTorrentPromise,
174 updateTorrentUrls, 179 updateTorrentMetadata,
175 createTorrentAndSetInfoHash, 180 createTorrentAndSetInfoHash,
176 generateMagnetUri, 181 generateMagnetUri,
177 downloadWebTorrentVideo 182 downloadWebTorrentVideo
@@ -226,3 +231,7 @@ function buildAnnounceList () {
226function buildUrlList (video: MVideo, videoFile: MVideoFile) { 231function buildUrlList (video: MVideo, videoFile: MVideoFile) {
227 return [ videoFile.getFileUrl(video) ] 232 return [ videoFile.getFileUrl(video) ]
228} 233}
234
235function buildInfoName (video: MVideo, videoFile: MVideoFile) {
236 return `${video.name} ${videoFile.resolution}p${videoFile.extname}`
237}
diff --git a/server/helpers/youtube-dl/youtube-dl-cli.ts b/server/helpers/youtube-dl/youtube-dl-cli.ts
index 559f92984..293acff43 100644
--- a/server/helpers/youtube-dl/youtube-dl-cli.ts
+++ b/server/helpers/youtube-dl/youtube-dl-cli.ts
@@ -153,7 +153,8 @@ export class YoutubeDLCLI {
153 completeArgs = this.wrapWithIPOptions(completeArgs) 153 completeArgs = this.wrapWithIPOptions(completeArgs)
154 completeArgs = this.wrapWithFFmpegOptions(completeArgs) 154 completeArgs = this.wrapWithFFmpegOptions(completeArgs)
155 155
156 const output = await execa('python', [ youtubeDLBinaryPath, ...completeArgs, url ], processOptions) 156 const { PYTHON_PATH } = CONFIG.IMPORT.VIDEOS.HTTP.YOUTUBE_DL_RELEASE
157 const output = await execa(PYTHON_PATH, [ youtubeDLBinaryPath, ...completeArgs, url ], processOptions)
157 158
158 logger.debug('Runned youtube-dl command.', { command: output.command, ...lTags() }) 159 logger.debug('Runned youtube-dl command.', { command: output.command, ...lTags() })
159 160
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 51c396548..458005b98 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -33,6 +33,8 @@ function checkMissedConfig () {
33 'transcoding.resolutions.2160p', 33 'transcoding.resolutions.2160p',
34 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'auto_blacklist.videos.of_users.enabled', 34 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'import.videos.concurrency', 'auto_blacklist.videos.of_users.enabled',
35 'trending.videos.interval_days', 35 'trending.videos.interval_days',
36 'client.videos.miniature.prefer_author_display_name', 'client.menu.login.redirect_on_single_external_auth',
37 'defaults.publish.download_enabled', 'defaults.publish.comments_enabled', 'defaults.publish.privacy', 'defaults.publish.licence',
36 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', 38 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
37 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', 39 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt',
38 'services.twitter.username', 'services.twitter.whitelisted', 40 'services.twitter.username', 'services.twitter.whitelisted',
@@ -115,7 +117,11 @@ function checkNodeVersion () {
115 logger.debug('Checking NodeJS version %s.', v) 117 logger.debug('Checking NodeJS version %s.', v)
116 118
117 if (major <= 10) { 119 if (major <= 10) {
118 logger.warn('Your NodeJS version %s is deprecated. Please upgrade.', v) 120 throw new Error('Your NodeJS version ' + v + ' is not supported. Please upgrade.')
121 }
122
123 if (major <= 12) {
124 logger.warn('Your NodeJS version ' + v + ' is deprecated. Please upgrade.')
119 } 125 }
120} 126}
121 127
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index dadda2a77..fb6f7ae62 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -1,12 +1,13 @@
1import bytes from 'bytes' 1import bytes from 'bytes'
2import { IConfig } from 'config' 2import { IConfig } from 'config'
3import decache from 'decache'
4import { dirname, join } from 'path' 3import { dirname, join } from 'path'
4import { decacheModule } from '@server/helpers/decache'
5import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' 5import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type'
6import { BroadcastMessageLevel } from '@shared/models/server' 6import { BroadcastMessageLevel } from '@shared/models/server'
7import { VideosRedundancyStrategy } from '../../shared/models' 7import { VideoPrivacy, VideosRedundancyStrategy } from '../../shared/models'
8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
9import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' 9import { buildPath, root } from '../../shared/core-utils'
10import { parseBytes, parseDurationToMs } from '../helpers/core-utils'
10 11
11// Use a variable to reload the configuration if we need 12// Use a variable to reload the configuration if we need
12let config: IConfig = require('config') 13let config: IConfig = require('config')
@@ -63,6 +64,28 @@ const CONFIG = {
63 MINIATURE: { 64 MINIATURE: {
64 get PREFER_AUTHOR_DISPLAY_NAME () { return config.get<boolean>('client.videos.miniature.prefer_author_display_name') } 65 get PREFER_AUTHOR_DISPLAY_NAME () { return config.get<boolean>('client.videos.miniature.prefer_author_display_name') }
65 } 66 }
67 },
68 MENU: {
69 LOGIN: {
70 get REDIRECT_ON_SINGLE_EXTERNAL_AUTH () { return config.get<boolean>('client.menu.login.redirect_on_single_external_auth') }
71 }
72 }
73 },
74
75 DEFAULTS: {
76 PUBLISH: {
77 DOWNLOAD_ENABLED: config.get<boolean>('defaults.publish.download_enabled'),
78 COMMENTS_ENABLED: config.get<boolean>('defaults.publish.comments_enabled'),
79 PRIVACY: config.get<VideoPrivacy>('defaults.publish.privacy'),
80 LICENCE: config.get<number>('defaults.publish.licence')
81 },
82 P2P: {
83 WEBAPP: {
84 ENABLED: config.get<boolean>('defaults.p2p.webapp.enabled')
85 },
86 EMBED: {
87 ENABLED: config.get<boolean>('defaults.p2p.embed.enabled')
88 }
66 } 89 }
67 }, 90 },
68 91
@@ -310,7 +333,8 @@ const CONFIG = {
310 333
311 YOUTUBE_DL_RELEASE: { 334 YOUTUBE_DL_RELEASE: {
312 get URL () { return config.get<string>('import.videos.http.youtube_dl_release.url') }, 335 get URL () { return config.get<string>('import.videos.http.youtube_dl_release.url') },
313 get NAME () { return config.get<string>('import.videos.http.youtube_dl_release.name') } 336 get NAME () { return config.get<string>('import.videos.http.youtube_dl_release.name') },
337 get PYTHON_PATH () { return config.get<string>('import.videos.http.youtube_dl_release.python_path') }
314 }, 338 },
315 339
316 get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') } 340 get FORCE_IPV4 () { return config.get<boolean>('import.videos.http.force_ipv4') }
@@ -497,7 +521,7 @@ export function reloadConfig () {
497 delete require.cache[fileName] 521 delete require.cache[fileName]
498 } 522 }
499 523
500 decache('config') 524 decacheModule('config')
501 } 525 }
502 526
503 purge() 527 purge()
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index b8633e83e..c899812a6 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -2,7 +2,7 @@ import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
2import { randomBytes } from 'crypto' 2import { randomBytes } from 'crypto'
3import { invert } from 'lodash' 3import { invert } from 'lodash'
4import { join } from 'path' 4import { join } from 'path'
5import { randomInt } from '../../shared/core-utils/common/miscs' 5import { randomInt, root } from '@shared/core-utils'
6import { 6import {
7 AbuseState, 7 AbuseState,
8 JobType, 8 JobType,
@@ -19,12 +19,12 @@ import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
19import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' 19import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model'
20import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' 20import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model'
21// Do not use barrels, remain constants as independent as possible 21// Do not use barrels, remain constants as independent as possible
22import { isTestInstance, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' 22import { isTestInstance, parseDurationToMs, sanitizeHost, sanitizeUrl } from '../helpers/core-utils'
23import { CONFIG, registerConfigChangedHandler } from './config' 23import { CONFIG, registerConfigChangedHandler } from './config'
24 24
25// --------------------------------------------------------------------------- 25// ---------------------------------------------------------------------------
26 26
27const LAST_MIGRATION_VERSION = 670 27const LAST_MIGRATION_VERSION = 675
28 28
29// --------------------------------------------------------------------------- 29// ---------------------------------------------------------------------------
30 30
@@ -200,8 +200,14 @@ const JOB_PRIORITY = {
200} 200}
201 201
202const BROADCAST_CONCURRENCY = 30 // How many requests in parallel we do in activitypub-http-broadcast job 202const BROADCAST_CONCURRENCY = 30 // How many requests in parallel we do in activitypub-http-broadcast job
203const AP_CLEANER_CONCURRENCY = 10 // How many requests in parallel we do in activitypub-cleaner job
204const CRAWL_REQUEST_CONCURRENCY = 1 // How many requests in parallel to fetch remote data (likes, shares...) 203const CRAWL_REQUEST_CONCURRENCY = 1 // How many requests in parallel to fetch remote data (likes, shares...)
204
205const AP_CLEANER = {
206 CONCURRENCY: 10, // How many requests in parallel we do in activitypub-cleaner job
207 UNAVAILABLE_TRESHOLD: 3, // How many attemps we do before removing an unavailable remote resource
208 PERIOD: parseDurationToMs('1 week') // /!\ Has to be sync with REPEAT_JOBS
209}
210
205const REQUEST_TIMEOUTS = { 211const REQUEST_TIMEOUTS = {
206 DEFAULT: 7000, // 7 seconds 212 DEFAULT: 7000, // 7 seconds
207 FILE: 30000, // 30 seconds 213 FILE: 30000, // 30 seconds
@@ -223,7 +229,7 @@ const SCHEDULER_INTERVALS_MS = {
223 REMOVE_OLD_VIEWS: 60000 * 60 * 24, // 1 day 229 REMOVE_OLD_VIEWS: 60000 * 60 * 24, // 1 day
224 REMOVE_OLD_HISTORY: 60000 * 60 * 24, // 1 day 230 REMOVE_OLD_HISTORY: 60000 * 60 * 24, // 1 day
225 UPDATE_INBOX_STATS: 1000 * 60, // 1 minute 231 UPDATE_INBOX_STATS: 1000 * 60, // 1 minute
226 REMOVE_DANGLING_RESUMABLE_UPLOADS: 60000 * 60 * 16 // 16 hours 232 REMOVE_DANGLING_RESUMABLE_UPLOADS: 60000 * 60 // 1 hour
227} 233}
228 234
229// --------------------------------------------------------------------------- 235// ---------------------------------------------------------------------------
@@ -427,7 +433,8 @@ const VIDEO_STATES: { [ id in VideoState ]: string } = {
427 [VideoState.WAITING_FOR_LIVE]: 'Waiting for livestream', 433 [VideoState.WAITING_FOR_LIVE]: 'Waiting for livestream',
428 [VideoState.LIVE_ENDED]: 'Livestream ended', 434 [VideoState.LIVE_ENDED]: 'Livestream ended',
429 [VideoState.TO_MOVE_TO_EXTERNAL_STORAGE]: 'To move to an external storage', 435 [VideoState.TO_MOVE_TO_EXTERNAL_STORAGE]: 'To move to an external storage',
430 [VideoState.TRANSCODING_FAILED]: 'Transcoding failed' 436 [VideoState.TRANSCODING_FAILED]: 'Transcoding failed',
437 [VideoState.TO_MOVE_TO_EXTERNAL_STORAGE_FAILED]: 'External storage move failed'
431} 438}
432 439
433const VIDEO_IMPORT_STATES: { [ id in VideoImportState ]: string } = { 440const VIDEO_IMPORT_STATES: { [ id in VideoImportState ]: string } = {
@@ -795,8 +802,11 @@ if (isTestInstance() === true) {
795 SCHEDULER_INTERVALS_MS.AUTO_FOLLOW_INDEX_INSTANCES = 5000 802 SCHEDULER_INTERVALS_MS.AUTO_FOLLOW_INDEX_INSTANCES = 5000
796 SCHEDULER_INTERVALS_MS.UPDATE_INBOX_STATS = 5000 803 SCHEDULER_INTERVALS_MS.UPDATE_INBOX_STATS = 5000
797 SCHEDULER_INTERVALS_MS.CHECK_PEERTUBE_VERSION = 2000 804 SCHEDULER_INTERVALS_MS.CHECK_PEERTUBE_VERSION = 2000
805
798 REPEAT_JOBS['videos-views-stats'] = { every: 5000 } 806 REPEAT_JOBS['videos-views-stats'] = { every: 5000 }
807
799 REPEAT_JOBS['activitypub-cleaner'] = { every: 5000 } 808 REPEAT_JOBS['activitypub-cleaner'] = { every: 5000 }
809 AP_CLEANER.PERIOD = 5000
800 810
801 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 811 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
802 812
@@ -857,7 +867,7 @@ export {
857 REDUNDANCY, 867 REDUNDANCY,
858 JOB_CONCURRENCY, 868 JOB_CONCURRENCY,
859 JOB_ATTEMPTS, 869 JOB_ATTEMPTS,
860 AP_CLEANER_CONCURRENCY, 870 AP_CLEANER,
861 LAST_MIGRATION_VERSION, 871 LAST_MIGRATION_VERSION,
862 OAUTH_LIFETIME, 872 OAUTH_LIFETIME,
863 CUSTOM_HTML_TAG_COMMENTS, 873 CUSTOM_HTML_TAG_COMMENTS,
@@ -1075,7 +1085,9 @@ function buildLanguages () {
1075 epo: true, // Esperanto 1085 epo: true, // Esperanto
1076 tlh: true, // Klingon 1086 tlh: true, // Klingon
1077 jbo: true, // Lojban 1087 jbo: true, // Lojban
1078 avk: true // Kotava 1088 avk: true, // Kotava
1089
1090 zxx: true // No linguistic content (ISO-639-2)
1079 } 1091 }
1080 1092
1081 // Only add ISO639-1 languages and some sign languages (ISO639-3) 1093 // Only add ISO639-1 languages and some sign languages (ISO639-3)
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 75daeb5d8..7e321fb76 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -1,6 +1,6 @@
1import { ensureDir, remove } from 'fs-extra' 1import { ensureDir, remove } from 'fs-extra'
2import passwordGenerator from 'password-generator' 2import passwordGenerator from 'password-generator'
3import { UserRole } from '../../shared' 3import { UserRole } from '@shared/models'
4import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
5import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' 5import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user'
6import { ApplicationModel } from '../models/application/application' 6import { ApplicationModel } from '../models/application/application'
@@ -144,6 +144,7 @@ async function createOAuthAdminIfNotExist () {
144 role, 144 role,
145 verified: true, 145 verified: true,
146 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 146 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
147 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
147 videoQuota: -1, 148 videoQuota: -1,
148 videoQuotaDaily: -1 149 videoQuotaDaily: -1
149 } 150 }
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts
index 0e6952350..ef3e15968 100644
--- a/server/initializers/migrations/0080-video-channels.ts
+++ b/server/initializers/migrations/0080-video-channels.ts
@@ -1,4 +1,4 @@
1import { buildUUID } from '@server/helpers/uuid' 1import { buildUUID } from '@shared/extra-utils'
2import * as Sequelize from 'sequelize' 2import * as Sequelize from 'sequelize'
3 3
4async function up (utils: { 4async function up (utils: {
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts
index 8dd631dff..4bf3100e4 100644
--- a/server/initializers/migrations/0345-video-playlists.ts
+++ b/server/initializers/migrations/0345-video-playlists.ts
@@ -1,5 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { buildUUID } from '@server/helpers/uuid' 2import { buildUUID } from '@shared/extra-utils'
3import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos' 3import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos'
4import { WEBSERVER } from '../constants' 4import { WEBSERVER } from '../constants'
5 5
diff --git a/server/initializers/migrations/0560-user-feed-token.ts b/server/initializers/migrations/0560-user-feed-token.ts
index 042301352..4c85b04f7 100644
--- a/server/initializers/migrations/0560-user-feed-token.ts
+++ b/server/initializers/migrations/0560-user-feed-token.ts
@@ -1,5 +1,5 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { buildUUID } from '@server/helpers/uuid' 2import { buildUUID } from '@shared/extra-utils'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction 5 transaction: Sequelize.Transaction
diff --git a/server/initializers/migrations/0675-p2p-enabled.ts b/server/initializers/migrations/0675-p2p-enabled.ts
new file mode 100644
index 000000000..b4f53381e
--- /dev/null
+++ b/server/initializers/migrations/0675-p2p-enabled.ts
@@ -0,0 +1,21 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize
7 db: any
8}): Promise<void> {
9 await utils.queryInterface.renameColumn('user', 'webTorrentEnabled', 'p2pEnabled')
10
11 await utils.sequelize.query('ALTER TABLE "user" ALTER COLUMN "p2pEnabled" DROP DEFAULT')
12}
13
14function down (options) {
15 throw new Error('Not implemented.')
16}
17
18export {
19 up,
20 down
21}
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts
index 7d7c9f8cb..7ac20127e 100644
--- a/server/initializers/migrator.ts
+++ b/server/initializers/migrator.ts
@@ -65,7 +65,7 @@ async function getMigrationScripts () {
65 }[] = [] 65 }[] = []
66 66
67 files 67 files
68 .filter(file => file.endsWith('.js.map') === false) 68 .filter(file => file.endsWith('.js'))
69 .forEach(file => { 69 .forEach(file => {
70 // Filename is something like 'version-blabla.js' 70 // Filename is something like 'version-blabla.js'
71 const version = file.split('-')[0] 71 const version = file.split('-')[0]
diff --git a/server/lib/activitypub/actors/get.ts b/server/lib/activitypub/actors/get.ts
index 8681ea02a..4200ddb4d 100644
--- a/server/lib/activitypub/actors/get.ts
+++ b/server/lib/activitypub/actors/get.ts
@@ -68,9 +68,28 @@ async function getOrCreateAPActor (
68 return actorRefreshed 68 return actorRefreshed
69} 69}
70 70
71function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) {
72 const accountAttributedTo = actorObject.attributedTo.find(a => a.type === 'Person')
73 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actorUrl)
74
75 if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) {
76 throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`)
77 }
78
79 try {
80 // Don't recurse another time
81 const recurseIfNeeded = false
82 return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded)
83 } catch (err) {
84 logger.error('Cannot get or create account attributed to video channel ' + actorUrl)
85 throw new Error(err)
86 }
87}
88
71// --------------------------------------------------------------------------- 89// ---------------------------------------------------------------------------
72 90
73export { 91export {
92 getOrCreateAPOwner,
74 getOrCreateAPActor 93 getOrCreateAPActor
75} 94}
76 95
@@ -88,24 +107,6 @@ async function loadActorFromDB (actorUrl: string, fetchType: ActorLoadByUrlType)
88 return actor 107 return actor
89} 108}
90 109
91function getOrCreateAPOwner (actorObject: ActivityPubActor, actorUrl: string) {
92 const accountAttributedTo = actorObject.attributedTo.find(a => a.type === 'Person')
93 if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actorUrl)
94
95 if (checkUrlsSameHost(accountAttributedTo.id, actorUrl) !== true) {
96 throw new Error(`Account attributed to ${accountAttributedTo.id} does not have the same host than actor url ${actorUrl}`)
97 }
98
99 try {
100 // Don't recurse another time
101 const recurseIfNeeded = false
102 return getOrCreateAPActor(accountAttributedTo.id, 'all', recurseIfNeeded)
103 } catch (err) {
104 logger.error('Cannot get or create account attributed to video channel ' + actorUrl)
105 throw new Error(err)
106 }
107}
108
109async function scheduleOutboxFetchIfNeeded (actor: MActor, created: boolean, refreshed: boolean, updateCollections: boolean) { 110async function scheduleOutboxFetchIfNeeded (actor: MActor, created: boolean, refreshed: boolean, updateCollections: boolean) {
110 if ((created === true || refreshed === true) && updateCollections === true) { 111 if ((created === true || refreshed === true) && updateCollections === true) {
111 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } 112 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' }
diff --git a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
index 1612b3ad0..23bc972e5 100644
--- a/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/actors/shared/object-to-model-attributes.ts
@@ -1,9 +1,9 @@
1import { getLowercaseExtension } from '@server/helpers/core-utils'
2import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc' 1import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc'
3import { buildUUID } from '@server/helpers/uuid'
4import { MIMETYPES } from '@server/initializers/constants' 2import { MIMETYPES } from '@server/initializers/constants'
5import { ActorModel } from '@server/models/actor/actor' 3import { ActorModel } from '@server/models/actor/actor'
6import { FilteredModelAttributes } from '@server/types' 4import { FilteredModelAttributes } from '@server/types'
5import { getLowercaseExtension } from '@shared/core-utils'
6import { buildUUID } from '@shared/extra-utils'
7import { ActivityPubActor, ActorImageType } from '@shared/models' 7import { ActivityPubActor, ActorImageType } from '@shared/models'
8 8
9function getActorAttributesFromObject ( 9function getActorAttributesFromObject (
diff --git a/server/lib/activitypub/actors/updater.ts b/server/lib/activitypub/actors/updater.ts
index de5e03eee..042438d9c 100644
--- a/server/lib/activitypub/actors/updater.ts
+++ b/server/lib/activitypub/actors/updater.ts
@@ -1,8 +1,10 @@
1import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils' 1import { resetSequelizeInstance, runInReadCommittedTransaction } from '@server/helpers/database-utils'
2import { logger } from '@server/helpers/logger' 2import { logger } from '@server/helpers/logger'
3import { AccountModel } from '@server/models/account/account'
3import { VideoChannelModel } from '@server/models/video/video-channel' 4import { VideoChannelModel } from '@server/models/video/video-channel'
4import { MAccount, MActor, MActorFull, MChannel } from '@server/types/models' 5import { MAccount, MActor, MActorFull, MChannel } from '@server/types/models'
5import { ActivityPubActor, ActorImageType } from '@shared/models' 6import { ActivityPubActor, ActorImageType } from '@shared/models'
7import { getOrCreateAPOwner } from './get'
6import { updateActorImageInstance } from './image' 8import { updateActorImageInstance } from './image'
7import { fetchActorFollowsCount } from './shared' 9import { fetchActorFollowsCount } from './shared'
8import { getImageInfoFromObject } from './shared/object-to-model-attributes' 10import { getImageInfoFromObject } from './shared/object-to-model-attributes'
@@ -36,7 +38,13 @@ export class APActorUpdater {
36 this.accountOrChannel.name = this.actorObject.name || this.actorObject.preferredUsername 38 this.accountOrChannel.name = this.actorObject.name || this.actorObject.preferredUsername
37 this.accountOrChannel.description = this.actorObject.summary 39 this.accountOrChannel.description = this.actorObject.summary
38 40
39 if (this.accountOrChannel instanceof VideoChannelModel) this.accountOrChannel.support = this.actorObject.support 41 if (this.accountOrChannel instanceof VideoChannelModel) {
42 const owner = await getOrCreateAPOwner(this.actorObject, this.actorObject.url)
43 this.accountOrChannel.accountId = owner.Account.id
44 this.accountOrChannel.Account = owner.Account as AccountModel
45
46 this.accountOrChannel.support = this.actorObject.support
47 }
40 48
41 await runInReadCommittedTransaction(async t => { 49 await runInReadCommittedTransaction(async t => {
42 await updateActorImageInstance(this.actor, ActorImageType.AVATAR, avatarInfo, t) 50 await updateActorImageInstance(this.actor, ActorImageType.AVATAR, avatarInfo, t)
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
index a16d2cd93..c3acd7112 100644
--- a/server/lib/activitypub/cache-file.ts
+++ b/server/lib/activitypub/cache-file.ts
@@ -1,7 +1,6 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models' 2import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models'
3import { CacheFileObject } from '../../../shared/index' 3import { CacheFileObject, VideoStreamingPlaylistType } from '@shared/models'
4import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 4import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6 5
7async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { 6async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
diff --git a/server/lib/activitypub/playlists/create-update.ts b/server/lib/activitypub/playlists/create-update.ts
index b152d709c..ef572c803 100644
--- a/server/lib/activitypub/playlists/create-update.ts
+++ b/server/lib/activitypub/playlists/create-update.ts
@@ -9,7 +9,7 @@ import { VideoPlaylistModel } from '@server/models/video/video-playlist'
9import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element' 9import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element'
10import { FilteredModelAttributes } from '@server/types' 10import { FilteredModelAttributes } from '@server/types'
11import { MThumbnail, MVideoPlaylist, MVideoPlaylistFull, MVideoPlaylistVideosLength } from '@server/types/models' 11import { MThumbnail, MVideoPlaylist, MVideoPlaylistFull, MVideoPlaylistVideosLength } from '@server/types/models'
12import { AttributesOnly } from '@shared/core-utils' 12import { AttributesOnly } from '@shared/typescript-utils'
13import { PlaylistObject } from '@shared/models' 13import { PlaylistObject } from '@shared/models'
14import { getOrCreateAPActor } from '../actors' 14import { getOrCreateAPActor } from '../actors'
15import { crawlCollectionPage } from '../crawl' 15import { crawlCollectionPage } from '../crawl'
diff --git a/server/lib/activitypub/playlists/shared/object-to-model-attributes.ts b/server/lib/activitypub/playlists/shared/object-to-model-attributes.ts
index 70fd335bc..753b5e660 100644
--- a/server/lib/activitypub/playlists/shared/object-to-model-attributes.ts
+++ b/server/lib/activitypub/playlists/shared/object-to-model-attributes.ts
@@ -2,7 +2,7 @@ import { ACTIVITY_PUB } from '@server/initializers/constants'
2import { VideoPlaylistModel } from '@server/models/video/video-playlist' 2import { VideoPlaylistModel } from '@server/models/video/video-playlist'
3import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element' 3import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element'
4import { MVideoId, MVideoPlaylistId } from '@server/types/models' 4import { MVideoId, MVideoPlaylistId } from '@server/types/models'
5import { AttributesOnly } from '@shared/core-utils' 5import { AttributesOnly } from '@shared/typescript-utils'
6import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@shared/models' 6import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@shared/models'
7 7
8function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) { 8function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 93f1d1c59..3e8ad184c 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,8 +1,6 @@
1import { isBlockedByServerOrAccount } from '@server/lib/blocklist' 1import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
2import { isRedundancyAccepted } from '@server/lib/redundancy' 2import { isRedundancyAccepted } from '@server/lib/redundancy'
3import { ActivityCreate, CacheFileObject, VideoObject } from '../../../../shared' 3import { ActivityCreate, CacheFileObject, PlaylistObject, VideoCommentObject, VideoObject } from '@shared/models'
4import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
5import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
6import { retryTransactionWrapper } from '../../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../../helpers/database-utils'
7import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
8import { sequelizeTypescript } from '../../../initializers/database' 6import { sequelizeTypescript } from '../../../initializers/database'
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
index ecc57cd10..2f46b83d1 100644
--- a/server/lib/activitypub/process/process-dislike.ts
+++ b/server/lib/activitypub/process/process-dislike.ts
@@ -1,5 +1,4 @@
1import { ActivityCreate, ActivityDislike } from '../../../../shared' 1import { ActivityCreate, ActivityDislike, DislikeObject } from '@shared/models'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { retryTransactionWrapper } from '../../../helpers/database-utils' 2import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { sequelizeTypescript } from '../../../initializers/database' 3import { sequelizeTypescript } from '../../../initializers/database'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 4import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
index 7ed409d0e..a15d07a62 100644
--- a/server/lib/activitypub/process/process-flag.ts
+++ b/server/lib/activitypub/process/process-flag.ts
@@ -3,7 +3,7 @@ import { AccountModel } from '@server/models/account/account'
3import { VideoModel } from '@server/models/video/video' 3import { VideoModel } from '@server/models/video/video'
4import { VideoCommentModel } from '@server/models/video/video-comment' 4import { VideoCommentModel } from '@server/models/video/video-comment'
5import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' 5import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
6import { AbuseObject, AbuseState, ActivityCreate, ActivityFlag } from '../../../../shared' 6import { AbuseObject, AbuseState, ActivityCreate, ActivityFlag } from '@shared/models'
7import { getAPId } from '../../../helpers/activitypub' 7import { getAPId } from '../../../helpers/activitypub'
8import { retryTransactionWrapper } from '../../../helpers/database-utils' 8import { retryTransactionWrapper } from '../../../helpers/database-utils'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
@@ -75,7 +75,8 @@ async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byAc
75 endAt, 75 endAt,
76 reporterAccount, 76 reporterAccount,
77 transaction: t, 77 transaction: t,
78 videoInstance: video 78 videoInstance: video,
79 skipNotification: false
79 }) 80 })
80 } 81 }
81 82
@@ -84,7 +85,8 @@ async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byAc
84 baseAbuse, 85 baseAbuse,
85 reporterAccount, 86 reporterAccount,
86 transaction: t, 87 transaction: t,
87 commentInstance: videoComment 88 commentInstance: videoComment,
89 skipNotification: false
88 }) 90 })
89 } 91 }
90 92
@@ -92,7 +94,8 @@ async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byAc
92 baseAbuse, 94 baseAbuse,
93 reporterAccount, 95 reporterAccount,
94 transaction: t, 96 transaction: t,
95 accountInstance: flaggedAccount 97 accountInstance: flaggedAccount,
98 skipNotification: false
96 }) 99 })
97 }) 100 })
98 } catch (err) { 101 } catch (err) {
diff --git a/server/lib/auth/oauth-model.ts b/server/lib/auth/oauth-model.ts
index f2ef0a78a..b68cce6d2 100644
--- a/server/lib/auth/oauth-model.ts
+++ b/server/lib/auth/oauth-model.ts
@@ -226,6 +226,7 @@ async function createUserFromExternal (pluginAuth: string, options: {
226 password: null, 226 password: null,
227 email: options.email, 227 email: options.email,
228 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 228 nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
229 p2pEnabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED,
229 autoPlayVideo: true, 230 autoPlayVideo: true,
230 role: options.role, 231 role: options.role,
231 videoQuota: CONFIG.USER.VIDEO_QUOTA, 232 videoQuota: CONFIG.USER.VIDEO_QUOTA,
diff --git a/server/lib/auth/oauth.ts b/server/lib/auth/oauth.ts
index 497773536..2bf7a6361 100644
--- a/server/lib/auth/oauth.ts
+++ b/server/lib/auth/oauth.ts
@@ -8,8 +8,9 @@ import {
8 UnauthorizedClientError, 8 UnauthorizedClientError,
9 UnsupportedGrantTypeError 9 UnsupportedGrantTypeError
10} from 'oauth2-server' 10} from 'oauth2-server'
11import { randomBytesPromise, sha1 } from '@server/helpers/core-utils' 11import { randomBytesPromise } from '@server/helpers/core-utils'
12import { MOAuthClient } from '@server/types/models' 12import { MOAuthClient } from '@server/types/models'
13import { sha1 } from '@shared/extra-utils'
13import { OAUTH_LIFETIME } from '../../initializers/constants' 14import { OAUTH_LIFETIME } from '../../initializers/constants'
14import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model' 15import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
15 16
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts
index d6b684015..98273a6ea 100644
--- a/server/lib/blocklist.ts
+++ b/server/lib/blocklist.ts
@@ -40,12 +40,12 @@ async function isBlockedByServerOrAccount (targetAccount: MAccountServer, userAc
40 40
41 if (userAccount) sourceAccounts.push(userAccount.id) 41 if (userAccount) sourceAccounts.push(userAccount.id)
42 42
43 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, targetAccount.id) 43 const accountMutedHash = await AccountBlocklistModel.isAccountMutedByAccounts(sourceAccounts, targetAccount.id)
44 if (accountMutedHash[serverAccountId] || (userAccount && accountMutedHash[userAccount.id])) { 44 if (accountMutedHash[serverAccountId] || (userAccount && accountMutedHash[userAccount.id])) {
45 return true 45 return true
46 } 46 }
47 47
48 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, targetAccount.Actor.serverId) 48 const instanceMutedHash = await ServerBlocklistModel.isServerMutedByAccounts(sourceAccounts, targetAccount.Actor.serverId)
49 if (instanceMutedHash[serverAccountId] || (userAccount && instanceMutedHash[userAccount.id])) { 49 if (instanceMutedHash[serverAccountId] || (userAccount && instanceMutedHash[userAccount.id])) {
50 return true 50 return true
51 } 51 }
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 360b4667f..74788af52 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -3,12 +3,14 @@ import { readFile } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import validator from 'validator' 4import validator from 'validator'
5import { toCompleteUUID } from '@server/helpers/custom-validators/misc' 5import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
6import { root } from '@shared/core-utils'
6import { escapeHTML } from '@shared/core-utils/renderer' 7import { escapeHTML } from '@shared/core-utils/renderer'
8import { sha256 } from '@shared/extra-utils'
7import { HTMLServerConfig } from '@shared/models' 9import { HTMLServerConfig } from '@shared/models'
8import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n' 10import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/core-utils/i18n/i18n'
9import { HttpStatusCode } from '../../shared/models/http/http-error-codes' 11import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
10import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos' 12import { VideoPlaylistPrivacy, VideoPrivacy } from '../../shared/models/videos'
11import { isTestInstance, sha256 } from '../helpers/core-utils' 13import { isTestInstance } from '../helpers/core-utils'
12import { logger } from '../helpers/logger' 14import { logger } from '../helpers/logger'
13import { mdToPlainText } from '../helpers/markdown' 15import { mdToPlainText } from '../helpers/markdown'
14import { CONFIG } from '../initializers/config' 16import { CONFIG } from '../initializers/config'
@@ -343,15 +345,11 @@ class ClientHtml {
343 { cookie: req.cookies?.clientLanguage, paramLang, acceptLanguage: req.headers['accept-language'] } 345 { cookie: req.cookies?.clientLanguage, paramLang, acceptLanguage: req.headers['accept-language'] }
344 ) 346 )
345 347
346 return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') 348 return join(root(), 'client', 'dist', buildFileLocale(lang), 'index.html')
347 } 349 }
348 350
349 private static getEmbedPath () { 351 private static getEmbedPath () {
350 return join(__dirname, '../../../client/dist/standalone/videos/embed.html') 352 return join(root(), 'client', 'dist', 'standalone', 'videos', 'embed.html')
351 }
352
353 private static addHtmlLang (htmlStringPage: string, paramLang: string) {
354 return htmlStringPage.replace('<html>', `<html lang="${paramLang}">`)
355 } 353 }
356 354
357 private static addManifestContentHash (htmlStringPage: string) { 355 private static addManifestContentHash (htmlStringPage: string) {
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 60284ea28..ebad43650 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -4,7 +4,8 @@ import { createTransport, Transporter } from 'nodemailer'
4import { join } from 'path' 4import { join } from 'path'
5import { EmailPayload } from '@shared/models' 5import { EmailPayload } from '@shared/models'
6import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model' 6import { SendEmailDefaultOptions } from '../../shared/models/server/emailer.model'
7import { isTestInstance, root } from '../helpers/core-utils' 7import { isTestInstance } from '../helpers/core-utils'
8import { root } from '@shared/core-utils'
8import { bunyanLogger, logger } from '../helpers/logger' 9import { bunyanLogger, logger } from '../helpers/logger'
9import { CONFIG, isEmailEnabled } from '../initializers/config' 10import { CONFIG, isEmailEnabled } from '../initializers/config'
10import { WEBSERVER } from '../initializers/constants' 11import { WEBSERVER } from '../initializers/constants'
diff --git a/server/lib/hls.ts b/server/lib/hls.ts
index f2fe893a9..1574ff27b 100644
--- a/server/lib/hls.ts
+++ b/server/lib/hls.ts
@@ -2,7 +2,7 @@ import { close, ensureDir, move, open, outputJSON, read, readFile, remove, stat,
2import { flatten, uniq } from 'lodash' 2import { flatten, uniq } from 'lodash'
3import { basename, dirname, join } from 'path' 3import { basename, dirname, join } from 'path'
4import { MStreamingPlaylistFilesVideo, MVideo, MVideoUUID } from '@server/types/models' 4import { MStreamingPlaylistFilesVideo, MVideo, MVideoUUID } from '@server/types/models'
5import { sha256 } from '../helpers/core-utils' 5import { sha256 } from '@shared/extra-utils'
6import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamSize } from '../helpers/ffprobe-utils' 6import { getAudioStreamCodec, getVideoStreamCodec, getVideoStreamSize } from '../helpers/ffprobe-utils'
7import { logger } from '../helpers/logger' 7import { logger } from '../helpers/logger'
8import { doRequest, doRequestAndSaveToFile } from '../helpers/requests' 8import { doRequest, doRequestAndSaveToFile } from '../helpers/requests'
diff --git a/server/lib/job-queue/handlers/activitypub-cleaner.ts b/server/lib/job-queue/handlers/activitypub-cleaner.ts
index d5e4508fe..509dd1cb5 100644
--- a/server/lib/job-queue/handlers/activitypub-cleaner.ts
+++ b/server/lib/job-queue/handlers/activitypub-cleaner.ts
@@ -8,36 +8,35 @@ import {
8} from '@server/helpers/custom-validators/activitypub/activity' 8} from '@server/helpers/custom-validators/activitypub/activity'
9import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments' 9import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
10import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests' 10import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests'
11import { AP_CLEANER_CONCURRENCY } from '@server/initializers/constants' 11import { AP_CLEANER } from '@server/initializers/constants'
12import { Redis } from '@server/lib/redis'
12import { VideoModel } from '@server/models/video/video' 13import { VideoModel } from '@server/models/video/video'
13import { VideoCommentModel } from '@server/models/video/video-comment' 14import { VideoCommentModel } from '@server/models/video/video-comment'
14import { VideoShareModel } from '@server/models/video/video-share' 15import { VideoShareModel } from '@server/models/video/video-share'
15import { HttpStatusCode } from '@shared/models' 16import { HttpStatusCode } from '@shared/models'
16import { logger } from '../../../helpers/logger' 17import { logger, loggerTagsFactory } from '../../../helpers/logger'
17import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 18import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
18 19
20const lTags = loggerTagsFactory('ap-cleaner')
21
19// Job to clean remote interactions off local videos 22// Job to clean remote interactions off local videos
20 23
21async function processActivityPubCleaner (_job: Job) { 24async function processActivityPubCleaner (_job: Job) {
22 logger.info('Processing ActivityPub cleaner.') 25 logger.info('Processing ActivityPub cleaner.', lTags())
23 26
24 { 27 {
25 const rateUrls = await AccountVideoRateModel.listRemoteRateUrlsOfLocalVideos() 28 const rateUrls = await AccountVideoRateModel.listRemoteRateUrlsOfLocalVideos()
26 const { bodyValidator, deleter, updater } = rateOptionsFactory() 29 const { bodyValidator, deleter, updater } = rateOptionsFactory()
27 30
28 await map(rateUrls, async rateUrl => { 31 await map(rateUrls, async rateUrl => {
29 try { 32 const result = await updateObjectIfNeeded({ url: rateUrl, bodyValidator, updater, deleter })
30 const result = await updateObjectIfNeeded(rateUrl, bodyValidator, updater, deleter)
31 33
32 if (result?.status === 'deleted') { 34 if (result?.status === 'deleted') {
33 const { videoId, type } = result.data 35 const { videoId, type } = result.data
34 36
35 await VideoModel.updateRatesOf(videoId, type, undefined) 37 await VideoModel.updateRatesOf(videoId, type, undefined)
36 }
37 } catch (err) {
38 logger.warn('Cannot update/delete remote AP rate %s.', rateUrl, { err })
39 } 38 }
40 }, { concurrency: AP_CLEANER_CONCURRENCY }) 39 }, { concurrency: AP_CLEANER.CONCURRENCY })
41 } 40 }
42 41
43 { 42 {
@@ -45,12 +44,8 @@ async function processActivityPubCleaner (_job: Job) {
45 const { bodyValidator, deleter, updater } = shareOptionsFactory() 44 const { bodyValidator, deleter, updater } = shareOptionsFactory()
46 45
47 await map(shareUrls, async shareUrl => { 46 await map(shareUrls, async shareUrl => {
48 try { 47 await updateObjectIfNeeded({ url: shareUrl, bodyValidator, updater, deleter })
49 await updateObjectIfNeeded(shareUrl, bodyValidator, updater, deleter) 48 }, { concurrency: AP_CLEANER.CONCURRENCY })
50 } catch (err) {
51 logger.warn('Cannot update/delete remote AP share %s.', shareUrl, { err })
52 }
53 }, { concurrency: AP_CLEANER_CONCURRENCY })
54 } 49 }
55 50
56 { 51 {
@@ -58,12 +53,8 @@ async function processActivityPubCleaner (_job: Job) {
58 const { bodyValidator, deleter, updater } = commentOptionsFactory() 53 const { bodyValidator, deleter, updater } = commentOptionsFactory()
59 54
60 await map(commentUrls, async commentUrl => { 55 await map(commentUrls, async commentUrl => {
61 try { 56 await updateObjectIfNeeded({ url: commentUrl, bodyValidator, updater, deleter })
62 await updateObjectIfNeeded(commentUrl, bodyValidator, updater, deleter) 57 }, { concurrency: AP_CLEANER.CONCURRENCY })
63 } catch (err) {
64 logger.warn('Cannot update/delete remote AP comment %s.', commentUrl, { err })
65 }
66 }, { concurrency: AP_CLEANER_CONCURRENCY })
67 } 58 }
68} 59}
69 60
@@ -75,14 +66,16 @@ export {
75 66
76// --------------------------------------------------------------------------- 67// ---------------------------------------------------------------------------
77 68
78async function updateObjectIfNeeded <T> ( 69async function updateObjectIfNeeded <T> (options: {
79 url: string, 70 url: string
80 bodyValidator: (body: any) => boolean, 71 bodyValidator: (body: any) => boolean
81 updater: (url: string, newUrl: string) => Promise<T>, 72 updater: (url: string, newUrl: string) => Promise<T>
82 deleter: (url: string) => Promise<T> 73 deleter: (url: string) => Promise<T> }
83): Promise<{ data: T, status: 'deleted' | 'updated' } | null> { 74): Promise<{ data: T, status: 'deleted' | 'updated' } | null> {
75 const { url, bodyValidator, updater, deleter } = options
76
84 const on404OrTombstone = async () => { 77 const on404OrTombstone = async () => {
85 logger.info('Removing remote AP object %s.', url) 78 logger.info('Removing remote AP object %s.', url, lTags(url))
86 const data = await deleter(url) 79 const data = await deleter(url)
87 80
88 return { status: 'deleted' as 'deleted', data } 81 return { status: 'deleted' as 'deleted', data }
@@ -104,7 +97,7 @@ async function updateObjectIfNeeded <T> (
104 throw new Error(`New url ${newUrl} has not the same host than old url ${url}`) 97 throw new Error(`New url ${newUrl} has not the same host than old url ${url}`)
105 } 98 }
106 99
107 logger.info('Updating remote AP object %s.', url) 100 logger.info('Updating remote AP object %s.', url, lTags(url))
108 const data = await updater(url, newUrl) 101 const data = await updater(url, newUrl)
109 102
110 return { status: 'updated', data } 103 return { status: 'updated', data }
@@ -117,7 +110,15 @@ async function updateObjectIfNeeded <T> (
117 return on404OrTombstone() 110 return on404OrTombstone()
118 } 111 }
119 112
120 throw err 113 logger.debug('Remote AP object %s is unavailable.', url, lTags(url))
114
115 const unavailability = await Redis.Instance.addAPUnavailability(url)
116 if (unavailability >= AP_CLEANER.UNAVAILABLE_TRESHOLD) {
117 logger.info('Removing unavailable AP resource %s.', url, lTags(url))
118 return on404OrTombstone()
119 }
120
121 return null
121 } 122 }
122} 123}
123 124
diff --git a/server/lib/job-queue/handlers/move-to-object-storage.ts b/server/lib/job-queue/handlers/move-to-object-storage.ts
index 54a7c566b..9e39322a8 100644
--- a/server/lib/job-queue/handlers/move-to-object-storage.ts
+++ b/server/lib/job-queue/handlers/move-to-object-storage.ts
@@ -2,16 +2,16 @@ import { Job } from 'bull'
2import { remove } from 'fs-extra' 2import { remove } from 'fs-extra'
3import { join } from 'path' 3import { join } from 'path'
4import { logger } from '@server/helpers/logger' 4import { logger } from '@server/helpers/logger'
5import { updateTorrentUrls } from '@server/helpers/webtorrent' 5import { updateTorrentMetadata } from '@server/helpers/webtorrent'
6import { CONFIG } from '@server/initializers/config' 6import { CONFIG } from '@server/initializers/config'
7import { P2P_MEDIA_LOADER_PEER_VERSION } from '@server/initializers/constants' 7import { P2P_MEDIA_LOADER_PEER_VERSION } from '@server/initializers/constants'
8import { storeHLSFile, storeWebTorrentFile } from '@server/lib/object-storage' 8import { storeHLSFile, storeWebTorrentFile } from '@server/lib/object-storage'
9import { getHLSDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths' 9import { getHLSDirectory, getHlsResolutionPlaylistFilename } from '@server/lib/paths'
10import { moveToNextState } from '@server/lib/video-state' 10import { moveToFailedMoveToObjectStorageState, moveToNextState } from '@server/lib/video-state'
11import { VideoModel } from '@server/models/video/video' 11import { VideoModel } from '@server/models/video/video'
12import { VideoJobInfoModel } from '@server/models/video/video-job-info' 12import { VideoJobInfoModel } from '@server/models/video/video-job-info'
13import { MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoWithAllFiles } from '@server/types/models' 13import { MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoWithAllFiles } from '@server/types/models'
14import { MoveObjectStoragePayload, VideoStorage } from '../../../../shared' 14import { MoveObjectStoragePayload, VideoStorage } from '@shared/models'
15 15
16export async function processMoveToObjectStorage (job: Job) { 16export async function processMoveToObjectStorage (job: Job) {
17 const payload = job.data as MoveObjectStoragePayload 17 const payload = job.data as MoveObjectStoragePayload
@@ -24,18 +24,25 @@ export async function processMoveToObjectStorage (job: Job) {
24 return undefined 24 return undefined
25 } 25 }
26 26
27 if (video.VideoFiles) { 27 try {
28 await moveWebTorrentFiles(video) 28 if (video.VideoFiles) {
29 } 29 await moveWebTorrentFiles(video)
30 }
30 31
31 if (video.VideoStreamingPlaylists) { 32 if (video.VideoStreamingPlaylists) {
32 await moveHLSFiles(video) 33 await moveHLSFiles(video)
33 } 34 }
35
36 const pendingMove = await VideoJobInfoModel.decrease(video.uuid, 'pendingMove')
37 if (pendingMove === 0) {
38 logger.info('Running cleanup after moving files to object storage (video %s in job %d)', video.uuid, job.id)
39 await doAfterLastJob(video, payload.isNewVideo)
40 }
41 } catch (err) {
42 logger.error('Cannot move video %s to object storage.', video.url, { err })
34 43
35 const pendingMove = await VideoJobInfoModel.decrease(video.uuid, 'pendingMove') 44 await moveToFailedMoveToObjectStorageState(video)
36 if (pendingMove === 0) { 45 await VideoJobInfoModel.abortAllTasks(video.uuid, 'pendingMove')
37 logger.info('Running cleanup after moving files to object storage (video %s in job %d)', video.uuid, job.id)
38 await doAfterLastJob(video, payload.isNewVideo)
39 } 46 }
40 47
41 return payload.videoUUID 48 return payload.videoUUID
@@ -113,7 +120,7 @@ async function onFileMoved (options: {
113 file.fileUrl = fileUrl 120 file.fileUrl = fileUrl
114 file.storage = VideoStorage.OBJECT_STORAGE 121 file.storage = VideoStorage.OBJECT_STORAGE
115 122
116 await updateTorrentUrls(videoOrPlaylist, file) 123 await updateTorrentMetadata(videoOrPlaylist, file)
117 await file.save() 124 await file.save()
118 125
119 logger.debug('Removing %s because it\'s now on object storage', oldPath) 126 logger.debug('Removing %s because it\'s now on object storage', oldPath)
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index a91c2ef80..0d9e80cb8 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -1,6 +1,6 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { copy, stat } from 'fs-extra' 2import { copy, stat } from 'fs-extra'
3import { getLowercaseExtension } from '@server/helpers/core-utils' 3import { getLowercaseExtension } from '@shared/core-utils'
4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 4import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
5import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
6import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' 6import { federateVideoIfNeeded } from '@server/lib/activitypub/videos'
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 4ce1a6c30..2f74e9fbd 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -1,6 +1,5 @@
1import { Job } from 'bull' 1import { Job } from 'bull'
2import { move, remove, stat } from 'fs-extra' 2import { move, remove, stat } from 'fs-extra'
3import { getLowercaseExtension } from '@server/helpers/core-utils'
4import { retryTransactionWrapper } from '@server/helpers/database-utils' 3import { retryTransactionWrapper } from '@server/helpers/database-utils'
5import { YoutubeDLWrapper } from '@server/helpers/youtube-dl' 4import { YoutubeDLWrapper } from '@server/helpers/youtube-dl'
6import { isPostImportVideoAccepted } from '@server/lib/moderation' 5import { isPostImportVideoAccepted } from '@server/lib/moderation'
@@ -13,17 +12,20 @@ import { VideoPathManager } from '@server/lib/video-path-manager'
13import { buildNextVideoState } from '@server/lib/video-state' 12import { buildNextVideoState } from '@server/lib/video-state'
14import { ThumbnailModel } from '@server/models/video/thumbnail' 13import { ThumbnailModel } from '@server/models/video/thumbnail'
15import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import' 14import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import'
15import { getLowercaseExtension } from '@shared/core-utils'
16import { isAudioFile } from '@shared/extra-utils'
16import { 17import {
18 ThumbnailType,
17 VideoImportPayload, 19 VideoImportPayload,
20 VideoImportState,
18 VideoImportTorrentPayload, 21 VideoImportTorrentPayload,
19 VideoImportTorrentPayloadType, 22 VideoImportTorrentPayloadType,
20 VideoImportYoutubeDLPayload, 23 VideoImportYoutubeDLPayload,
21 VideoImportYoutubeDLPayloadType, 24 VideoImportYoutubeDLPayloadType,
25 VideoResolution,
22 VideoState 26 VideoState
23} from '../../../../shared' 27} from '@shared/models'
24import { VideoImportState } from '../../../../shared/models/videos' 28import { ffprobePromise, getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
25import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
26import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffprobe-utils'
27import { logger } from '../../../helpers/logger' 29import { logger } from '../../../helpers/logger'
28import { getSecureTorrentName } from '../../../helpers/utils' 30import { getSecureTorrentName } from '../../../helpers/utils'
29import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' 31import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent'
@@ -114,9 +116,14 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
114 throw new Error('The user video quota is exceeded with this video to import.') 116 throw new Error('The user video quota is exceeded with this video to import.')
115 } 117 }
116 118
117 const { resolution } = await getVideoFileResolution(tempVideoPath) 119 const probe = await ffprobePromise(tempVideoPath)
118 const fps = await getVideoFileFPS(tempVideoPath) 120
119 const duration = await getDurationFromVideoFile(tempVideoPath) 121 const { resolution } = await isAudioFile(tempVideoPath, probe)
122 ? { resolution: VideoResolution.H_NOVIDEO }
123 : await getVideoFileResolution(tempVideoPath)
124
125 const fps = await getVideoFileFPS(tempVideoPath, probe)
126 const duration = await getDurationFromVideoFile(tempVideoPath, probe)
120 127
121 // Prepare video file object for creation in database 128 // Prepare video file object for creation in database
122 const fileExt = getLowercaseExtension(tempVideoPath) 129 const fileExt = getLowercaseExtension(tempVideoPath)
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 0edcdcba3..02902b0b8 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -6,13 +6,15 @@ import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video
6import { UserModel } from '@server/models/user/user' 6import { UserModel } from '@server/models/user/user'
7import { VideoJobInfoModel } from '@server/models/video/video-job-info' 7import { VideoJobInfoModel } from '@server/models/video/video-job-info'
8import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models' 8import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models'
9import { pick } from '@shared/core-utils'
9import { 10import {
10 HLSTranscodingPayload, 11 HLSTranscodingPayload,
11 MergeAudioTranscodingPayload, 12 MergeAudioTranscodingPayload,
12 NewResolutionTranscodingPayload, 13 NewResolutionTranscodingPayload,
13 OptimizeTranscodingPayload, 14 OptimizeTranscodingPayload,
15 VideoResolution,
14 VideoTranscodingPayload 16 VideoTranscodingPayload
15} from '../../../../shared' 17} from '@shared/models'
16import { retryTransactionWrapper } from '../../../helpers/database-utils' 18import { retryTransactionWrapper } from '../../../helpers/database-utils'
17import { computeLowerResolutionsToTranscode } from '../../../helpers/ffprobe-utils' 19import { computeLowerResolutionsToTranscode } from '../../../helpers/ffprobe-utils'
18import { logger, loggerTagsFactory } from '../../../helpers/logger' 20import { logger, loggerTagsFactory } from '../../../helpers/logger'
@@ -159,6 +161,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay
159 user, 161 user,
160 videoFileResolution: payload.resolution, 162 videoFileResolution: payload.resolution,
161 isPortraitMode: payload.isPortraitMode, 163 isPortraitMode: payload.isPortraitMode,
164 hasAudio: payload.hasAudio,
162 isNewVideo: payload.isNewVideo ?? true, 165 isNewVideo: payload.isNewVideo ?? true,
163 type: 'hls' 166 type: 'hls'
164 }) 167 })
@@ -174,7 +177,7 @@ async function onVideoFirstWebTorrentTranscoding (
174 transcodeType: TranscodeOptionsType, 177 transcodeType: TranscodeOptionsType,
175 user: MUserId 178 user: MUserId
176) { 179) {
177 const { resolution, isPortraitMode } = await videoArg.getMaxQualityResolution() 180 const { resolution, isPortraitMode, audioStream } = await videoArg.getMaxQualityFileInfo()
178 181
179 // Maybe the video changed in database, refresh it 182 // Maybe the video changed in database, refresh it
180 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) 183 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid)
@@ -186,6 +189,7 @@ async function onVideoFirstWebTorrentTranscoding (
186 ...payload, 189 ...payload,
187 190
188 isPortraitMode, 191 isPortraitMode,
192 hasAudio: !!audioStream,
189 resolution: videoDatabase.getMaxQualityFile().resolution, 193 resolution: videoDatabase.getMaxQualityFile().resolution,
190 // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues 194 // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues
191 copyCodecs: transcodeType !== 'quick-transcode', 195 copyCodecs: transcodeType !== 'quick-transcode',
@@ -196,6 +200,7 @@ async function onVideoFirstWebTorrentTranscoding (
196 video: videoDatabase, 200 video: videoDatabase,
197 user, 201 user,
198 videoFileResolution: resolution, 202 videoFileResolution: resolution,
203 hasAudio: !!audioStream,
199 isPortraitMode, 204 isPortraitMode,
200 type: 'webtorrent', 205 type: 'webtorrent',
201 isNewVideo: payload.isNewVideo ?? true 206 isNewVideo: payload.isNewVideo ?? true
@@ -214,7 +219,7 @@ async function onNewWebTorrentFileResolution (
214 user: MUserId, 219 user: MUserId,
215 payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload 220 payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload
216) { 221) {
217 await createHlsJobIfEnabled(user, { ...payload, copyCodecs: true, isMaxQuality: false }) 222 await createHlsJobIfEnabled(user, { hasAudio: true, copyCodecs: true, isMaxQuality: false, ...payload })
218 await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') 223 await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode')
219 224
220 await retryTransactionWrapper(moveToNextState, video, payload.isNewVideo) 225 await retryTransactionWrapper(moveToNextState, video, payload.isNewVideo)
@@ -225,6 +230,7 @@ async function onNewWebTorrentFileResolution (
225async function createHlsJobIfEnabled (user: MUserId, payload: { 230async function createHlsJobIfEnabled (user: MUserId, payload: {
226 videoUUID: string 231 videoUUID: string
227 resolution: number 232 resolution: number
233 hasAudio: boolean
228 isPortraitMode?: boolean 234 isPortraitMode?: boolean
229 copyCodecs: boolean 235 copyCodecs: boolean
230 isMaxQuality: boolean 236 isMaxQuality: boolean
@@ -238,13 +244,9 @@ async function createHlsJobIfEnabled (user: MUserId, payload: {
238 244
239 const hlsTranscodingPayload: HLSTranscodingPayload = { 245 const hlsTranscodingPayload: HLSTranscodingPayload = {
240 type: 'new-resolution-to-hls', 246 type: 'new-resolution-to-hls',
241 videoUUID: payload.videoUUID,
242 resolution: payload.resolution,
243 isPortraitMode: payload.isPortraitMode,
244 copyCodecs: payload.copyCodecs,
245 isMaxQuality: payload.isMaxQuality,
246 autoDeleteWebTorrentIfNeeded: true, 247 autoDeleteWebTorrentIfNeeded: true,
247 isNewVideo: payload.isNewVideo 248
249 ...pick(payload, [ 'videoUUID', 'resolution', 'isPortraitMode', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ])
248 } 250 }
249 251
250 await addTranscodingJob(hlsTranscodingPayload, jobOptions) 252 await addTranscodingJob(hlsTranscodingPayload, jobOptions)
@@ -257,16 +259,19 @@ async function createLowerResolutionsJobs (options: {
257 user: MUserId 259 user: MUserId
258 videoFileResolution: number 260 videoFileResolution: number
259 isPortraitMode: boolean 261 isPortraitMode: boolean
262 hasAudio: boolean
260 isNewVideo: boolean 263 isNewVideo: boolean
261 type: 'hls' | 'webtorrent' 264 type: 'hls' | 'webtorrent'
262}) { 265}) {
263 const { video, user, videoFileResolution, isPortraitMode, isNewVideo, type } = options 266 const { video, user, videoFileResolution, isPortraitMode, isNewVideo, hasAudio, type } = options
264 267
265 // Create transcoding jobs if there are enabled resolutions 268 // Create transcoding jobs if there are enabled resolutions
266 const resolutionsEnabled = computeLowerResolutionsToTranscode(videoFileResolution, 'vod') 269 const resolutionsEnabled = computeLowerResolutionsToTranscode(videoFileResolution, 'vod')
267 const resolutionCreated: string[] = [] 270 const resolutionCreated: string[] = []
268 271
269 for (const resolution of resolutionsEnabled) { 272 for (const resolution of resolutionsEnabled) {
273 if (resolution === VideoResolution.H_NOVIDEO && hasAudio === false) continue
274
270 let dataInput: VideoTranscodingPayload 275 let dataInput: VideoTranscodingPayload
271 276
272 if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') { 277 if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') {
@@ -276,6 +281,7 @@ async function createLowerResolutionsJobs (options: {
276 videoUUID: video.uuid, 281 videoUUID: video.uuid,
277 resolution, 282 resolution,
278 isPortraitMode, 283 isPortraitMode,
284 hasAudio,
279 isNewVideo 285 isNewVideo
280 } 286 }
281 287
@@ -288,6 +294,7 @@ async function createLowerResolutionsJobs (options: {
288 videoUUID: video.uuid, 294 videoUUID: video.uuid,
289 resolution, 295 resolution,
290 isPortraitMode, 296 isPortraitMode,
297 hasAudio,
291 copyCodecs: false, 298 copyCodecs: false,
292 isMaxQuality: false, 299 isMaxQuality: false,
293 autoDeleteWebTorrentIfNeeded: true, 300 autoDeleteWebTorrentIfNeeded: true,
diff --git a/server/lib/live/shared/muxing-session.ts b/server/lib/live/shared/muxing-session.ts
index eccaefcfa..22a47942a 100644
--- a/server/lib/live/shared/muxing-session.ts
+++ b/server/lib/live/shared/muxing-session.ts
@@ -229,7 +229,7 @@ class MuxingSession extends EventEmitter {
229 229
230 const playlistIdMatcher = /^([\d+])-/ 230 const playlistIdMatcher = /^([\d+])-/
231 231
232 const addHandler = async segmentPath => { 232 const addHandler = async (segmentPath: string) => {
233 logger.debug('Live add handler of %s.', segmentPath, this.lTags()) 233 logger.debug('Live add handler of %s.', segmentPath, this.lTags())
234 234
235 const playlistId = basename(segmentPath).match(playlistIdMatcher)[0] 235 const playlistId = basename(segmentPath).match(playlistIdMatcher)[0]
diff --git a/server/lib/local-actor.ts b/server/lib/local-actor.ts
index 821a92b91..c6826759b 100644
--- a/server/lib/local-actor.ts
+++ b/server/lib/local-actor.ts
@@ -2,9 +2,9 @@ import 'multer'
2import { queue } from 'async' 2import { queue } from 'async'
3import LRUCache from 'lru-cache' 3import LRUCache from 'lru-cache'
4import { join } from 'path' 4import { join } from 'path'
5import { getLowercaseExtension } from '@server/helpers/core-utils'
6import { buildUUID } from '@server/helpers/uuid'
7import { ActorModel } from '@server/models/actor/actor' 5import { ActorModel } from '@server/models/actor/actor'
6import { getLowercaseExtension } from '@shared/core-utils'
7import { buildUUID } from '@shared/extra-utils'
8import { ActivityPubActorType, ActorImageType } from '@shared/models' 8import { ActivityPubActorType, ActorImageType } from '@shared/models'
9import { retryTransactionWrapper } from '../helpers/database-utils' 9import { retryTransactionWrapper } from '../helpers/database-utils'
10import { processImage } from '../helpers/image-utils' 10import { processImage } from '../helpers/image-utils'
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index 456b615b2..c2565f867 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -107,8 +107,9 @@ async function createVideoAbuse (options: {
107 endAt: number 107 endAt: number
108 transaction: Transaction 108 transaction: Transaction
109 reporterAccount: MAccountDefault 109 reporterAccount: MAccountDefault
110 skipNotification: boolean
110}) { 111}) {
111 const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount } = options 112 const { baseAbuse, videoInstance, startAt, endAt, transaction, reporterAccount, skipNotification } = options
112 113
113 const associateFun = async (abuseInstance: MAbuseFull) => { 114 const associateFun = async (abuseInstance: MAbuseFull) => {
114 const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({ 115 const videoAbuseInstance: MVideoAbuseVideoFull = await VideoAbuseModel.create({
@@ -129,6 +130,7 @@ async function createVideoAbuse (options: {
129 reporterAccount, 130 reporterAccount,
130 flaggedAccount: videoInstance.VideoChannel.Account, 131 flaggedAccount: videoInstance.VideoChannel.Account,
131 transaction, 132 transaction,
133 skipNotification,
132 associateFun 134 associateFun
133 }) 135 })
134} 136}
@@ -138,8 +140,9 @@ function createVideoCommentAbuse (options: {
138 commentInstance: MCommentOwnerVideo 140 commentInstance: MCommentOwnerVideo
139 transaction: Transaction 141 transaction: Transaction
140 reporterAccount: MAccountDefault 142 reporterAccount: MAccountDefault
143 skipNotification: boolean
141}) { 144}) {
142 const { baseAbuse, commentInstance, transaction, reporterAccount } = options 145 const { baseAbuse, commentInstance, transaction, reporterAccount, skipNotification } = options
143 146
144 const associateFun = async (abuseInstance: MAbuseFull) => { 147 const associateFun = async (abuseInstance: MAbuseFull) => {
145 const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({ 148 const commentAbuseInstance: MCommentAbuseAccountVideo = await VideoCommentAbuseModel.create({
@@ -158,6 +161,7 @@ function createVideoCommentAbuse (options: {
158 reporterAccount, 161 reporterAccount,
159 flaggedAccount: commentInstance.Account, 162 flaggedAccount: commentInstance.Account,
160 transaction, 163 transaction,
164 skipNotification,
161 associateFun 165 associateFun
162 }) 166 })
163} 167}
@@ -167,8 +171,9 @@ function createAccountAbuse (options: {
167 accountInstance: MAccountDefault 171 accountInstance: MAccountDefault
168 transaction: Transaction 172 transaction: Transaction
169 reporterAccount: MAccountDefault 173 reporterAccount: MAccountDefault
174 skipNotification: boolean
170}) { 175}) {
171 const { baseAbuse, accountInstance, transaction, reporterAccount } = options 176 const { baseAbuse, accountInstance, transaction, reporterAccount, skipNotification } = options
172 177
173 const associateFun = () => { 178 const associateFun = () => {
174 return Promise.resolve({ isOwned: accountInstance.isOwned() }) 179 return Promise.resolve({ isOwned: accountInstance.isOwned() })
@@ -179,6 +184,7 @@ function createAccountAbuse (options: {
179 reporterAccount, 184 reporterAccount,
180 flaggedAccount: accountInstance, 185 flaggedAccount: accountInstance,
181 transaction, 186 transaction,
187 skipNotification,
182 associateFun 188 associateFun
183 }) 189 })
184} 190}
@@ -207,9 +213,10 @@ async function createAbuse (options: {
207 reporterAccount: MAccountDefault 213 reporterAccount: MAccountDefault
208 flaggedAccount: MAccountLight 214 flaggedAccount: MAccountLight
209 associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean} > 215 associateFun: (abuseInstance: MAbuseFull) => Promise<{ isOwned: boolean} >
216 skipNotification: boolean
210 transaction: Transaction 217 transaction: Transaction
211}) { 218}) {
212 const { base, reporterAccount, flaggedAccount, associateFun, transaction } = options 219 const { base, reporterAccount, flaggedAccount, associateFun, transaction, skipNotification } = options
213 const auditLogger = auditLoggerFactory('abuse') 220 const auditLogger = auditLoggerFactory('abuse')
214 221
215 const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id }) 222 const abuseAttributes = Object.assign({}, base, { flaggedAccountId: flaggedAccount.id })
@@ -227,13 +234,15 @@ async function createAbuse (options: {
227 const abuseJSON = abuseInstance.toFormattedAdminJSON() 234 const abuseJSON = abuseInstance.toFormattedAdminJSON()
228 auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON)) 235 auditLogger.create(reporterAccount.Actor.getIdentifier(), new AbuseAuditView(abuseJSON))
229 236
230 afterCommitIfTransaction(transaction, () => { 237 if (!skipNotification) {
231 Notifier.Instance.notifyOnNewAbuse({ 238 afterCommitIfTransaction(transaction, () => {
232 abuse: abuseJSON, 239 Notifier.Instance.notifyOnNewAbuse({
233 abuseInstance, 240 abuse: abuseJSON,
234 reporter: reporterAccount.Actor.getIdentifier() 241 abuseInstance,
242 reporter: reporterAccount.Actor.getIdentifier()
243 })
235 }) 244 })
236 }) 245 }
237 246
238 logger.info('Abuse report %d created.', abuseInstance.id) 247 logger.info('Abuse report %d created.', abuseInstance.id)
239 248
diff --git a/server/lib/notifier/shared/comment/comment-mention.ts b/server/lib/notifier/shared/comment/comment-mention.ts
index 4f84d8dea..765cbaad9 100644
--- a/server/lib/notifier/shared/comment/comment-mention.ts
+++ b/server/lib/notifier/shared/comment/comment-mention.ts
@@ -47,8 +47,8 @@ export class CommentMention extends AbstractNotification <MCommentOwnerVideo, MU
47 47
48 const sourceAccounts = this.users.map(u => u.Account.id).concat([ this.serverAccountId ]) 48 const sourceAccounts = this.users.map(u => u.Account.id).concat([ this.serverAccountId ])
49 49
50 this.accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(sourceAccounts, this.payload.accountId) 50 this.accountMutedHash = await AccountBlocklistModel.isAccountMutedByAccounts(sourceAccounts, this.payload.accountId)
51 this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByMulti(sourceAccounts, this.payload.Account.Actor.serverId) 51 this.instanceMutedHash = await ServerBlocklistModel.isServerMutedByAccounts(sourceAccounts, this.payload.Account.Actor.serverId)
52 } 52 }
53 53
54 log () { 54 log () {
diff --git a/server/lib/paths.ts b/server/lib/paths.ts
index 434e637c6..5a85bea42 100644
--- a/server/lib/paths.ts
+++ b/server/lib/paths.ts
@@ -1,9 +1,9 @@
1import { join } from 'path' 1import { join } from 'path'
2import { buildUUID } from '@server/helpers/uuid'
3import { CONFIG } from '@server/initializers/config' 2import { CONFIG } from '@server/initializers/config'
4import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY } from '@server/initializers/constants' 3import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY } from '@server/initializers/constants'
5import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models' 4import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/types/models'
6import { removeFragmentedMP4Ext } from '@shared/core-utils' 5import { removeFragmentedMP4Ext } from '@shared/core-utils'
6import { buildUUID } from '@shared/extra-utils'
7 7
8// ################## Video file name ################## 8// ################## Video file name ##################
9 9
diff --git a/server/lib/plugins/plugin-helpers-builder.ts b/server/lib/plugins/plugin-helpers-builder.ts
index e26776f45..78e4a28ad 100644
--- a/server/lib/plugins/plugin-helpers-builder.ts
+++ b/server/lib/plugins/plugin-helpers-builder.ts
@@ -1,5 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { ffprobePromise } from '@server/helpers/ffprobe-utils'
3import { buildLogger } from '@server/helpers/logger' 4import { buildLogger } from '@server/helpers/logger'
4import { CONFIG } from '@server/initializers/config' 5import { CONFIG } from '@server/initializers/config'
5import { WEBSERVER } from '@server/initializers/constants' 6import { WEBSERVER } from '@server/initializers/constants'
@@ -9,15 +10,16 @@ import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
9import { getServerActor } from '@server/models/application/application' 10import { getServerActor } from '@server/models/application/application'
10import { ServerModel } from '@server/models/server/server' 11import { ServerModel } from '@server/models/server/server'
11import { ServerBlocklistModel } from '@server/models/server/server-blocklist' 12import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
13import { UserModel } from '@server/models/user/user'
12import { VideoModel } from '@server/models/video/video' 14import { VideoModel } from '@server/models/video/video'
13import { VideoBlacklistModel } from '@server/models/video/video-blacklist' 15import { VideoBlacklistModel } from '@server/models/video/video-blacklist'
14import { MPlugin } from '@server/types/models' 16import { MPlugin } from '@server/types/models'
15import { PeerTubeHelpers } from '@server/types/plugins' 17import { PeerTubeHelpers } from '@server/types/plugins'
16import { VideoBlacklistCreate } from '@shared/models' 18import { VideoBlacklistCreate, VideoStorage } from '@shared/models'
17import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist' 19import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../blocklist'
18import { ServerConfigManager } from '../server-config-manager' 20import { ServerConfigManager } from '../server-config-manager'
19import { blacklistVideo, unblacklistVideo } from '../video-blacklist' 21import { blacklistVideo, unblacklistVideo } from '../video-blacklist'
20import { UserModel } from '@server/models/user/user' 22import { VideoPathManager } from '../video-path-manager'
21 23
22function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers { 24function buildPluginHelpers (pluginModel: MPlugin, npmName: string): PeerTubeHelpers {
23 const logger = buildPluginLogger(npmName) 25 const logger = buildPluginLogger(npmName)
@@ -85,6 +87,60 @@ function buildVideosHelpers () {
85 87
86 await video.destroy({ transaction: t }) 88 await video.destroy({ transaction: t })
87 }) 89 })
90 },
91
92 ffprobe: (path: string) => {
93 return ffprobePromise(path)
94 },
95
96 getFiles: async (id: number | string) => {
97 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id)
98 if (!video) return undefined
99
100 const webtorrentVideoFiles = (video.VideoFiles || []).map(f => ({
101 path: f.storage === VideoStorage.FILE_SYSTEM
102 ? VideoPathManager.Instance.getFSVideoFileOutputPath(video, f)
103 : null,
104 url: f.getFileUrl(video),
105
106 resolution: f.resolution,
107 size: f.size,
108 fps: f.fps
109 }))
110
111 const hls = video.getHLSPlaylist()
112
113 const hlsVideoFiles = hls
114 ? (video.getHLSPlaylist().VideoFiles || []).map(f => {
115 return {
116 path: f.storage === VideoStorage.FILE_SYSTEM
117 ? VideoPathManager.Instance.getFSVideoFileOutputPath(hls, f)
118 : null,
119 url: f.getFileUrl(video),
120 resolution: f.resolution,
121 size: f.size,
122 fps: f.fps
123 }
124 })
125 : []
126
127 const thumbnails = video.Thumbnails.map(t => ({
128 type: t.type,
129 url: t.getFileUrl(video),
130 path: t.getPath()
131 }))
132
133 return {
134 webtorrent: {
135 videoFiles: webtorrentVideoFiles
136 },
137
138 hls: {
139 videoFiles: hlsVideoFiles
140 },
141
142 thumbnails
143 }
88 } 144 }
89 } 145 }
90} 146}
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index d4d2a7edc..39e7f9a5b 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -1,11 +1,17 @@
1import decache from 'decache'
2import express from 'express' 1import express from 'express'
3import { createReadStream, createWriteStream } from 'fs' 2import { createReadStream, createWriteStream } from 'fs'
4import { ensureDir, outputFile, readJSON } from 'fs-extra' 3import { ensureDir, outputFile, readJSON } from 'fs-extra'
5import { basename, join } from 'path' 4import { basename, join } from 'path'
5import { decachePlugin } from '@server/helpers/decache'
6import { MOAuthTokenUser, MUser } from '@server/types/models' 6import { MOAuthTokenUser, MUser } from '@server/types/models'
7import { getCompleteLocale } from '@shared/core-utils' 7import { getCompleteLocale } from '@shared/core-utils'
8import { ClientScript, PluginPackageJson, PluginTranslation, PluginTranslationPaths, RegisterServerHookOptions } from '@shared/models' 8import {
9 ClientScriptJSON,
10 PluginPackageJSON,
11 PluginTranslation,
12 PluginTranslationPathsJSON,
13 RegisterServerHookOptions
14} from '@shared/models'
9import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' 15import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks'
10import { PluginType } from '../../../shared/models/plugins/plugin.type' 16import { PluginType } from '../../../shared/models/plugins/plugin.type'
11import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server/server-hook.model' 17import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server/server-hook.model'
@@ -31,7 +37,7 @@ export interface RegisteredPlugin {
31 path: string 37 path: string
32 38
33 staticDirs: { [name: string]: string } 39 staticDirs: { [name: string]: string }
34 clientScripts: { [name: string]: ClientScript } 40 clientScripts: { [name: string]: ClientScriptJSON }
35 41
36 css: string[] 42 css: string[]
37 43
@@ -392,7 +398,7 @@ export class PluginManager implements ServerHook {
392 registerHelpers = result.registerStore 398 registerHelpers = result.registerStore
393 } 399 }
394 400
395 const clientScripts: { [id: string]: ClientScript } = {} 401 const clientScripts: { [id: string]: ClientScriptJSON } = {}
396 for (const c of packageJSON.clientScripts) { 402 for (const c of packageJSON.clientScripts) {
397 clientScripts[c.script] = c 403 clientScripts[c.script] = c
398 } 404 }
@@ -415,12 +421,12 @@ export class PluginManager implements ServerHook {
415 await this.addTranslations(plugin, npmName, packageJSON.translations) 421 await this.addTranslations(plugin, npmName, packageJSON.translations)
416 } 422 }
417 423
418 private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) { 424 private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJSON) {
419 const npmName = PluginModel.buildNpmName(plugin.name, plugin.type) 425 const npmName = PluginModel.buildNpmName(plugin.name, plugin.type)
420 426
421 // Delete cache if needed 427 // Delete cache if needed
422 const modulePath = join(pluginPath, packageJSON.library) 428 const modulePath = join(pluginPath, packageJSON.library)
423 decache(modulePath) 429 decachePlugin(pluginPath, modulePath)
424 const library: PluginLibrary = require(modulePath) 430 const library: PluginLibrary = require(modulePath)
425 431
426 if (!isLibraryCodeValid(library)) { 432 if (!isLibraryCodeValid(library)) {
@@ -442,7 +448,7 @@ export class PluginManager implements ServerHook {
442 448
443 // ###################### Translations ###################### 449 // ###################### Translations ######################
444 450
445 private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PluginTranslationPaths) { 451 private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PluginTranslationPathsJSON) {
446 for (const locale of Object.keys(translationPaths)) { 452 for (const locale of Object.keys(translationPaths)) {
447 const path = translationPaths[locale] 453 const path = translationPaths[locale]
448 const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path)) 454 const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path))
@@ -513,7 +519,7 @@ export class PluginManager implements ServerHook {
513 private getPackageJSON (pluginName: string, pluginType: PluginType) { 519 private getPackageJSON (pluginName: string, pluginType: PluginType) {
514 const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json') 520 const pluginPath = join(this.getPluginPath(pluginName, pluginType), 'package.json')
515 521
516 return readJSON(pluginPath) as Promise<PluginPackageJson> 522 return readJSON(pluginPath) as Promise<PluginPackageJSON>
517 } 523 }
518 524
519 private getPluginPath (pluginName: string, pluginType: PluginType) { 525 private getPluginPath (pluginName: string, pluginType: PluginType) {
@@ -572,7 +578,7 @@ export class PluginManager implements ServerHook {
572 } 578 }
573 } 579 }
574 580
575 private sanitizeAndCheckPackageJSONOrThrow (packageJSON: PluginPackageJson, pluginType: PluginType) { 581 private sanitizeAndCheckPackageJSONOrThrow (packageJSON: PluginPackageJSON, pluginType: PluginType) {
576 if (!packageJSON.staticDirs) packageJSON.staticDirs = {} 582 if (!packageJSON.staticDirs) packageJSON.staticDirs = {}
577 if (!packageJSON.css) packageJSON.css = [] 583 if (!packageJSON.css) packageJSON.css = []
578 if (!packageJSON.clientScripts) packageJSON.clientScripts = [] 584 if (!packageJSON.clientScripts) packageJSON.clientScripts = []
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index 8aec4b793..52766663a 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -1,31 +1,30 @@
1import express from 'express' 1import { createClient, RedisClientOptions, RedisModules } from 'redis'
2import { createClient, RedisClient } from 'redis' 2import { exists } from '@server/helpers/custom-validators/misc'
3import { sha256 } from '@shared/extra-utils'
3import { logger } from '../helpers/logger' 4import { logger } from '../helpers/logger'
4import { generateRandomString } from '../helpers/utils' 5import { generateRandomString } from '../helpers/utils'
6import { CONFIG } from '../initializers/config'
5import { 7import {
8 AP_CLEANER,
6 CONTACT_FORM_LIFETIME, 9 CONTACT_FORM_LIFETIME,
10 RESUMABLE_UPLOAD_SESSION_LIFETIME,
11 TRACKER_RATE_LIMITS,
7 USER_EMAIL_VERIFY_LIFETIME, 12 USER_EMAIL_VERIFY_LIFETIME,
8 USER_PASSWORD_RESET_LIFETIME,
9 USER_PASSWORD_CREATE_LIFETIME, 13 USER_PASSWORD_CREATE_LIFETIME,
14 USER_PASSWORD_RESET_LIFETIME,
10 VIEW_LIFETIME, 15 VIEW_LIFETIME,
11 WEBSERVER, 16 WEBSERVER
12 TRACKER_RATE_LIMITS,
13 RESUMABLE_UPLOAD_SESSION_LIFETIME
14} from '../initializers/constants' 17} from '../initializers/constants'
15import { CONFIG } from '../initializers/config'
16import { exists } from '@server/helpers/custom-validators/misc'
17 18
18type CachedRoute = { 19// Only used for typings
19 body: string 20const redisClientWrapperForType = () => createClient<{}>()
20 contentType?: string
21 statusCode?: string
22}
23 21
24class Redis { 22class Redis {
25 23
26 private static instance: Redis 24 private static instance: Redis
27 private initialized = false 25 private initialized = false
28 private client: RedisClient 26 private connected = false
27 private client: ReturnType<typeof redisClientWrapperForType>
29 private prefix: string 28 private prefix: string
30 29
31 private constructor () { 30 private constructor () {
@@ -38,26 +37,43 @@ class Redis {
38 37
39 this.client = createClient(Redis.getRedisClientOptions()) 38 this.client = createClient(Redis.getRedisClientOptions())
40 39
41 this.client.on('error', err => { 40 logger.info('Connecting to redis...')
42 logger.error('Error in Redis client.', { err })
43 process.exit(-1)
44 })
45 41
46 if (CONFIG.REDIS.AUTH) { 42 this.client.connect()
47 this.client.auth(CONFIG.REDIS.AUTH) 43 .then(() => {
48 } 44 logger.info('Connected to redis.')
45
46 this.connected = true
47 }).catch(err => {
48 logger.error('Cannot connect to redis', { err })
49 process.exit(-1)
50 })
49 51
50 this.prefix = 'redis-' + WEBSERVER.HOST + '-' 52 this.prefix = 'redis-' + WEBSERVER.HOST + '-'
51 } 53 }
52 54
53 static getRedisClientOptions () { 55 static getRedisClientOptions () {
54 return Object.assign({}, 56 let config: RedisClientOptions<RedisModules, {}> = {
55 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, 57 socket: {
56 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, 58 connectTimeout: 20000 // Could be slow since node use sync call to compile PeerTube
57 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) 59 }
58 ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } 60 }
59 : { path: CONFIG.REDIS.SOCKET } 61
60 ) 62 if (CONFIG.REDIS.AUTH) {
63 config = { ...config, password: CONFIG.REDIS.AUTH }
64 }
65
66 if (CONFIG.REDIS.DB) {
67 config = { ...config, database: CONFIG.REDIS.DB }
68 }
69
70 if (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) {
71 config.socket = { ...config.socket, host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT }
72 } else {
73 config.socket = { ...config.socket, path: CONFIG.REDIS.SOCKET }
74 }
75
76 return config
61 } 77 }
62 78
63 getClient () { 79 getClient () {
@@ -68,6 +84,10 @@ class Redis {
68 return this.prefix 84 return this.prefix
69 } 85 }
70 86
87 isConnected () {
88 return this.connected
89 }
90
71 /* ************ Forgot password ************ */ 91 /* ************ Forgot password ************ */
72 92
73 async setResetPasswordVerificationString (userId: number) { 93 async setResetPasswordVerificationString (userId: number) {
@@ -146,25 +166,6 @@ class Redis {
146 return this.exists(this.generateTrackerBlockIPKey(ip)) 166 return this.exists(this.generateTrackerBlockIPKey(ip))
147 } 167 }
148 168
149 /* ************ API cache ************ */
150
151 async getCachedRoute (req: express.Request) {
152 const cached = await this.getObject(this.generateCachedRouteKey(req))
153
154 return cached as CachedRoute
155 }
156
157 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) {
158 const cached: CachedRoute = Object.assign(
159 {},
160 { body: body.toString() },
161 (contentType) ? { contentType } : null,
162 (statusCode) ? { statusCode: statusCode.toString() } : null
163 )
164
165 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime)
166 }
167
168 /* ************ Video views stats ************ */ 169 /* ************ Video views stats ************ */
169 170
170 addVideoViewStats (videoId: number) { 171 addVideoViewStats (videoId: number) {
@@ -275,12 +276,19 @@ class Redis {
275 return this.deleteKey('resumable-upload-' + uploadId) 276 return this.deleteKey('resumable-upload-' + uploadId)
276 } 277 }
277 278
278 /* ************ Keys generation ************ */ 279 /* ************ AP ressource unavailability ************ */
279 280
280 generateCachedRouteKey (req: express.Request) { 281 async addAPUnavailability (url: string) {
281 return req.method + '-' + req.originalUrl 282 const key = this.generateAPUnavailabilityKey(url)
283
284 const value = await this.increment(key)
285 await this.setExpiration(key, AP_CLEANER.PERIOD * 2)
286
287 return value
282 } 288 }
283 289
290 /* ************ Keys generation ************ */
291
284 private generateLocalVideoViewsKeys (videoId?: Number) { 292 private generateLocalVideoViewsKeys (videoId?: Number) {
285 return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` } 293 return { setKey: `local-video-views-buffer`, videoKey: `local-video-views-buffer-${videoId}` }
286 } 294 }
@@ -317,128 +325,52 @@ class Redis {
317 return 'contact-form-' + ip 325 return 'contact-form-' + ip
318 } 326 }
319 327
328 private generateAPUnavailabilityKey (url: string) {
329 return 'ap-unavailability-' + sha256(url)
330 }
331
320 /* ************ Redis helpers ************ */ 332 /* ************ Redis helpers ************ */
321 333
322 private getValue (key: string) { 334 private getValue (key: string) {
323 return new Promise<string>((res, rej) => { 335 return this.client.get(this.prefix + key)
324 this.client.get(this.prefix + key, (err, value) => {
325 if (err) return rej(err)
326
327 return res(value)
328 })
329 })
330 } 336 }
331 337
332 private getSet (key: string) { 338 private getSet (key: string) {
333 return new Promise<string[]>((res, rej) => { 339 return this.client.sMembers(this.prefix + key)
334 this.client.smembers(this.prefix + key, (err, value) => {
335 if (err) return rej(err)
336
337 return res(value)
338 })
339 })
340 } 340 }
341 341
342 private addToSet (key: string, value: string) { 342 private addToSet (key: string, value: string) {
343 return new Promise<void>((res, rej) => { 343 return this.client.sAdd(this.prefix + key, value)
344 this.client.sadd(this.prefix + key, value, err => err ? rej(err) : res())
345 })
346 } 344 }
347 345
348 private deleteFromSet (key: string, value: string) { 346 private deleteFromSet (key: string, value: string) {
349 return new Promise<void>((res, rej) => { 347 return this.client.sRem(this.prefix + key, value)
350 this.client.srem(this.prefix + key, value, err => err ? rej(err) : res())
351 })
352 } 348 }
353 349
354 private deleteKey (key: string) { 350 private deleteKey (key: string) {
355 return new Promise<void>((res, rej) => { 351 return this.client.del(this.prefix + key)
356 this.client.del(this.prefix + key, err => err ? rej(err) : res())
357 })
358 }
359
360 private deleteFieldInHash (key: string, field: string) {
361 return new Promise<void>((res, rej) => {
362 this.client.hdel(this.prefix + key, field, err => err ? rej(err) : res())
363 })
364 } 352 }
365 353
366 private setValue (key: string, value: string, expirationMilliseconds: number) { 354 private async setValue (key: string, value: string, expirationMilliseconds: number) {
367 return new Promise<void>((res, rej) => { 355 const result = await this.client.set(this.prefix + key, value, { PX: expirationMilliseconds })
368 this.client.set(this.prefix + key, value, 'PX', expirationMilliseconds, (err, ok) => {
369 if (err) return rej(err)
370 356
371 if (ok !== 'OK') return rej(new Error('Redis set result is not OK.')) 357 if (result !== 'OK') throw new Error('Redis set result is not OK.')
372
373 return res()
374 })
375 })
376 } 358 }
377 359
378 private removeValue (key: string) { 360 private removeValue (key: string) {
379 return new Promise<void>((res, rej) => { 361 return this.client.del(this.prefix + key)
380 this.client.del(this.prefix + key, err => {
381 if (err) return rej(err)
382
383 return res()
384 })
385 })
386 }
387
388 private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) {
389 return new Promise<void>((res, rej) => {
390 this.client.hmset(this.prefix + key, obj, (err, ok) => {
391 if (err) return rej(err)
392 if (!ok) return rej(new Error('Redis mset result is not OK.'))
393
394 this.client.pexpire(this.prefix + key, expirationMilliseconds, (err, ok) => {
395 if (err) return rej(err)
396 if (!ok) return rej(new Error('Redis expiration result is not OK.'))
397
398 return res()
399 })
400 })
401 })
402 }
403
404 private getObject (key: string) {
405 return new Promise<{ [id: string]: string }>((res, rej) => {
406 this.client.hgetall(this.prefix + key, (err, value) => {
407 if (err) return rej(err)
408
409 return res(value)
410 })
411 })
412 }
413
414 private setValueInHash (key: string, field: string, value: string) {
415 return new Promise<void>((res, rej) => {
416 this.client.hset(this.prefix + key, field, value, (err) => {
417 if (err) return rej(err)
418
419 return res()
420 })
421 })
422 } 362 }
423 363
424 private increment (key: string) { 364 private increment (key: string) {
425 return new Promise<number>((res, rej) => { 365 return this.client.incr(this.prefix + key)
426 this.client.incr(this.prefix + key, (err, value) => {
427 if (err) return rej(err)
428
429 return res(value)
430 })
431 })
432 } 366 }
433 367
434 private exists (key: string) { 368 private exists (key: string) {
435 return new Promise<boolean>((res, rej) => { 369 return this.client.exists(this.prefix + key)
436 this.client.exists(this.prefix + key, (err, existsNumber) => { 370 }
437 if (err) return rej(err)
438 371
439 return res(existsNumber === 1) 372 private setExpiration (key: string, ms: number) {
440 }) 373 return this.client.expire(this.prefix + key, ms / 1000)
441 })
442 } 374 }
443 375
444 static get Instance () { 376 static get Instance () {
diff --git a/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts b/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts
index d6e561cad..61e93eafa 100644
--- a/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts
+++ b/server/lib/schedulers/remove-dangling-resumable-uploads-scheduler.ts
@@ -1,9 +1,7 @@
1import { map } from 'bluebird' 1
2import { readdir, remove, stat } from 'fs-extra'
3import { logger, loggerTagsFactory } from '@server/helpers/logger' 2import { logger, loggerTagsFactory } from '@server/helpers/logger'
4import { getResumableUploadPath } from '@server/helpers/upload'
5import { SCHEDULER_INTERVALS_MS } from '@server/initializers/constants' 3import { SCHEDULER_INTERVALS_MS } from '@server/initializers/constants'
6import { METAFILE_EXTNAME } from '@uploadx/core' 4import { uploadx } from '../uploadx'
7import { AbstractScheduler } from './abstract-scheduler' 5import { AbstractScheduler } from './abstract-scheduler'
8 6
9const lTags = loggerTagsFactory('scheduler', 'resumable-upload', 'cleaner') 7const lTags = loggerTagsFactory('scheduler', 'resumable-upload', 'cleaner')
@@ -22,36 +20,17 @@ export class RemoveDanglingResumableUploadsScheduler extends AbstractScheduler {
22 } 20 }
23 21
24 protected async internalExecute () { 22 protected async internalExecute () {
25 const path = getResumableUploadPath() 23 logger.debug('Removing dangling resumable uploads', lTags())
26 const files = await readdir(path)
27
28 const metafiles = files.filter(f => f.endsWith(METAFILE_EXTNAME))
29 24
30 if (metafiles.length === 0) return 25 const now = new Date().getTime()
31
32 logger.debug('Reading resumable video upload folder %s with %d files', path, metafiles.length, lTags())
33 26
34 try { 27 try {
35 await map(metafiles, metafile => { 28 // Remove files that were not updated since the last execution
36 return this.deleteIfOlderThan(metafile, this.lastExecutionTimeMs) 29 await uploadx.storage.purge(now - this.lastExecutionTimeMs)
37 }, { concurrency: 5 })
38 } catch (error) { 30 } catch (error) {
39 logger.error('Failed to handle file during resumable video upload folder cleanup', { error, ...lTags() }) 31 logger.error('Failed to handle file during resumable video upload folder cleanup', { error, ...lTags() })
40 } finally { 32 } finally {
41 this.lastExecutionTimeMs = new Date().getTime() 33 this.lastExecutionTimeMs = now
42 }
43 }
44
45 private async deleteIfOlderThan (metafile: string, olderThan: number) {
46 const metafilePath = getResumableUploadPath(metafile)
47 const statResult = await stat(metafilePath)
48
49 // Delete uploads that started since a long time
50 if (statResult.ctimeMs < olderThan) {
51 await remove(metafilePath)
52
53 const datafile = metafilePath.replace(new RegExp(`${METAFILE_EXTNAME}$`), '')
54 await remove(datafile)
55 } 34 }
56 } 35 }
57 36
diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts
index bdf6492f9..d97f21eb7 100644
--- a/server/lib/server-config-manager.ts
+++ b/server/lib/server-config-manager.ts
@@ -3,6 +3,7 @@ import { CONFIG, isEmailEnabled } from '@server/initializers/config'
3import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants' 3import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '@server/initializers/constants'
4import { isSignupAllowed, isSignupAllowedForCurrentIP } from '@server/lib/signup' 4import { isSignupAllowed, isSignupAllowedForCurrentIP } from '@server/lib/signup'
5import { ActorCustomPageModel } from '@server/models/account/actor-custom-page' 5import { ActorCustomPageModel } from '@server/models/account/actor-custom-page'
6import { PluginModel } from '@server/models/server/plugin'
6import { HTMLServerConfig, RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models' 7import { HTMLServerConfig, RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig } from '@shared/models'
7import { Hooks } from './plugins/hooks' 8import { Hooks } from './plugins/hooks'
8import { PluginManager } from './plugins/plugin-manager' 9import { PluginManager } from './plugins/plugin-manager'
@@ -47,6 +48,28 @@ class ServerConfigManager {
47 miniature: { 48 miniature: {
48 preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME 49 preferAuthorDisplayName: CONFIG.CLIENT.VIDEOS.MINIATURE.PREFER_AUTHOR_DISPLAY_NAME
49 } 50 }
51 },
52 menu: {
53 login: {
54 redirectOnSingleExternalAuth: CONFIG.CLIENT.MENU.LOGIN.REDIRECT_ON_SINGLE_EXTERNAL_AUTH
55 }
56 }
57 },
58
59 defaults: {
60 publish: {
61 downloadEnabled: CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
62 commentsEnabled: CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
63 privacy: CONFIG.DEFAULTS.PUBLISH.PRIVACY,
64 licence: CONFIG.DEFAULTS.PUBLISH.LICENCE
65 },
66 p2p: {
67 webapp: {
68 enabled: CONFIG.DEFAULTS.P2P.WEBAPP.ENABLED
69 },
70 embed: {
71 enabled: CONFIG.DEFAULTS.P2P.EMBED.ENABLED
72 }
50 } 73 }
51 }, 74 },
52 75
@@ -247,6 +270,7 @@ class ServerConfigManager {
247 getRegisteredThemes () { 270 getRegisteredThemes () {
248 return PluginManager.Instance.getRegisteredThemes() 271 return PluginManager.Instance.getRegisteredThemes()
249 .map(t => ({ 272 .map(t => ({
273 npmName: PluginModel.buildNpmName(t.name, t.type),
250 name: t.name, 274 name: t.name,
251 version: t.version, 275 version: t.version,
252 description: t.description, 276 description: t.description,
@@ -258,6 +282,7 @@ class ServerConfigManager {
258 getRegisteredPlugins () { 282 getRegisteredPlugins () {
259 return PluginManager.Instance.getRegisteredPlugins() 283 return PluginManager.Instance.getRegisteredPlugins()
260 .map(p => ({ 284 .map(p => ({
285 npmName: PluginModel.buildNpmName(p.name, p.type),
261 name: p.name, 286 name: p.name,
262 version: p.version, 287 version: p.version,
263 description: p.description, 288 description: p.description,
diff --git a/server/lib/transcoding/video-transcoding-profiles.ts b/server/lib/transcoding/video-transcoding-profiles.ts
index 34a364415..dcc8d4c5c 100644
--- a/server/lib/transcoding/video-transcoding-profiles.ts
+++ b/server/lib/transcoding/video-transcoding-profiles.ts
@@ -23,10 +23,9 @@ const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOpt
23 23
24 return { 24 return {
25 outputOptions: [ 25 outputOptions: [
26 `-preset veryfast`, 26 ...getCommonOutputOptions(targetBitrate),
27 `-r ${fps}`, 27
28 `-maxrate ${targetBitrate}`, 28 `-r ${fps}`
29 `-bufsize ${targetBitrate * 2}`
30 ] 29 ]
31 } 30 }
32} 31}
@@ -38,11 +37,10 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOp
38 37
39 return { 38 return {
40 outputOptions: [ 39 outputOptions: [
41 `-preset veryfast`, 40 ...getCommonOutputOptions(targetBitrate),
41
42 `${buildStreamSuffix('-r:v', streamNum)} ${fps}`, 42 `${buildStreamSuffix('-r:v', streamNum)} ${fps}`,
43 `${buildStreamSuffix('-b:v', streamNum)} ${targetBitrate}`, 43 `${buildStreamSuffix('-b:v', streamNum)} ${targetBitrate}`
44 `-maxrate ${targetBitrate}`,
45 `-bufsize ${targetBitrate * 2}`
46 ] 44 ]
47 } 45 }
48} 46}
@@ -257,3 +255,16 @@ function capBitrate (inputBitrate: number, targetBitrate: number) {
257 255
258 return Math.min(targetBitrate, inputBitrateWithMargin) 256 return Math.min(targetBitrate, inputBitrateWithMargin)
259} 257}
258
259function getCommonOutputOptions (targetBitrate: number) {
260 return [
261 `-preset veryfast`,
262 `-maxrate ${targetBitrate}`,
263 `-bufsize ${targetBitrate * 2}`,
264
265 // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
266 `-b_strategy 1`,
267 // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
268 `-bf 16`
269 ]
270}
diff --git a/server/lib/transcoding/video-transcoding.ts b/server/lib/transcoding/video-transcoding.ts
index d0db05216..9942a067b 100644
--- a/server/lib/transcoding/video-transcoding.ts
+++ b/server/lib/transcoding/video-transcoding.ts
@@ -169,6 +169,7 @@ function mergeAudioVideofile (video: MVideoFullLight, resolution: VideoResolutio
169 169
170 // Important to do this before getVideoFilename() to take in account the new file extension 170 // Important to do this before getVideoFilename() to take in account the new file extension
171 inputVideoFile.extname = newExtname 171 inputVideoFile.extname = newExtname
172 inputVideoFile.resolution = resolution
172 inputVideoFile.filename = generateWebTorrentVideoFilename(inputVideoFile.resolution, newExtname) 173 inputVideoFile.filename = generateWebTorrentVideoFilename(inputVideoFile.resolution, newExtname)
173 174
174 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile) 175 const videoOutputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, inputVideoFile)
diff --git a/server/lib/uploadx.ts b/server/lib/uploadx.ts
new file mode 100644
index 000000000..36f5a556c
--- /dev/null
+++ b/server/lib/uploadx.ts
@@ -0,0 +1,14 @@
1import express from 'express'
2import { getResumableUploadPath } from '@server/helpers/upload'
3import { Uploadx } from '@uploadx/core'
4
5const uploadx = new Uploadx({
6 directory: getResumableUploadPath(),
7 // Could be big with thumbnails/previews
8 maxMetadataSize: '10MB'
9})
10uploadx.getUserId = (_, res: express.Response) => res.locals.oauth?.token.user.id
11
12export {
13 uploadx
14}
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 936403692..0d292ac90 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,7 +1,7 @@
1import { Transaction } from 'sequelize/types' 1import { Transaction } from 'sequelize/types'
2import { buildUUID } from '@server/helpers/uuid'
3import { UserModel } from '@server/models/user/user' 2import { UserModel } from '@server/models/user/user'
4import { MActorDefault } from '@server/types/models/actor' 3import { MActorDefault } from '@server/types/models/actor'
4import { buildUUID } from '@shared/extra-utils'
5import { ActivityPubActorType } from '../../shared/models/activitypub' 5import { ActivityPubActorType } from '../../shared/models/activitypub'
6import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' 6import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
7import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' 7import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
diff --git a/server/lib/video-path-manager.ts b/server/lib/video-path-manager.ts
index 27058005c..c3f55fd95 100644
--- a/server/lib/video-path-manager.ts
+++ b/server/lib/video-path-manager.ts
@@ -1,6 +1,5 @@
1import { remove } from 'fs-extra' 1import { remove } from 'fs-extra'
2import { extname, join } from 'path' 2import { extname, join } from 'path'
3import { buildUUID } from '@server/helpers/uuid'
4import { extractVideo } from '@server/helpers/video' 3import { extractVideo } from '@server/helpers/video'
5import { CONFIG } from '@server/initializers/config' 4import { CONFIG } from '@server/initializers/config'
6import { 5import {
@@ -11,6 +10,7 @@ import {
11 MVideoFileVideo, 10 MVideoFileVideo,
12 MVideoUUID 11 MVideoUUID
13} from '@server/types/models' 12} from '@server/types/models'
13import { buildUUID } from '@shared/extra-utils'
14import { VideoStorage } from '@shared/models' 14import { VideoStorage } from '@shared/models'
15import { makeHLSFileAvailable, makeWebTorrentFileAvailable } from './object-storage' 15import { makeHLSFileAvailable, makeWebTorrentFileAvailable } from './object-storage'
16import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths' 16import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths'
diff --git a/server/lib/video-state.ts b/server/lib/video-state.ts
index e420991cd..97ff540ed 100644
--- a/server/lib/video-state.ts
+++ b/server/lib/video-state.ts
@@ -4,7 +4,7 @@ import { CONFIG } from '@server/initializers/config'
4import { sequelizeTypescript } from '@server/initializers/database' 4import { sequelizeTypescript } from '@server/initializers/database'
5import { VideoModel } from '@server/models/video/video' 5import { VideoModel } from '@server/models/video/video'
6import { VideoJobInfoModel } from '@server/models/video/video-job-info' 6import { VideoJobInfoModel } from '@server/models/video/video-job-info'
7import { MVideoFullLight, MVideoUUID } from '@server/types/models' 7import { MVideo, MVideoFullLight, MVideoUUID } from '@server/types/models'
8import { VideoState } from '@shared/models' 8import { VideoState } from '@shared/models'
9import { federateVideoIfNeeded } from './activitypub/videos' 9import { federateVideoIfNeeded } from './activitypub/videos'
10import { Notifier } from './notifier' 10import { Notifier } from './notifier'
@@ -79,18 +79,25 @@ async function moveToExternalStorageState (video: MVideoFullLight, isNewVideo: b
79 } 79 }
80} 80}
81 81
82function moveToFailedTranscodingState (video: MVideoFullLight) { 82function moveToFailedTranscodingState (video: MVideo) {
83 if (video.state === VideoState.TRANSCODING_FAILED) return 83 if (video.state === VideoState.TRANSCODING_FAILED) return
84 84
85 return video.setNewState(VideoState.TRANSCODING_FAILED, false, undefined) 85 return video.setNewState(VideoState.TRANSCODING_FAILED, false, undefined)
86} 86}
87 87
88function moveToFailedMoveToObjectStorageState (video: MVideo) {
89 if (video.state === VideoState.TO_MOVE_TO_EXTERNAL_STORAGE_FAILED) return
90
91 return video.setNewState(VideoState.TO_MOVE_TO_EXTERNAL_STORAGE_FAILED, false, undefined)
92}
93
88// --------------------------------------------------------------------------- 94// ---------------------------------------------------------------------------
89 95
90export { 96export {
91 buildNextVideoState, 97 buildNextVideoState,
92 moveToExternalStorageState, 98 moveToExternalStorageState,
93 moveToFailedTranscodingState, 99 moveToFailedTranscodingState,
100 moveToFailedMoveToObjectStorageState,
94 moveToNextState 101 moveToNextState
95} 102}
96 103
diff --git a/server/lib/video.ts b/server/lib/video.ts
index 1cfe4f27c..e5af028ea 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -9,16 +9,17 @@ import { MThumbnail, MUserId, MVideoFile, MVideoTag, MVideoThumbnail, MVideoUUID
9import { ThumbnailType, VideoCreate, VideoPrivacy, VideoTranscodingPayload } from '@shared/models' 9import { ThumbnailType, VideoCreate, VideoPrivacy, VideoTranscodingPayload } from '@shared/models'
10import { CreateJobOptions, JobQueue } from './job-queue/job-queue' 10import { CreateJobOptions, JobQueue } from './job-queue/job-queue'
11import { updateVideoMiniatureFromExisting } from './thumbnail' 11import { updateVideoMiniatureFromExisting } from './thumbnail'
12import { CONFIG } from '@server/initializers/config'
12 13
13function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> { 14function buildLocalVideoFromReq (videoInfo: VideoCreate, channelId: number): FilteredModelAttributes<VideoModel> {
14 return { 15 return {
15 name: videoInfo.name, 16 name: videoInfo.name,
16 remote: false, 17 remote: false,
17 category: videoInfo.category, 18 category: videoInfo.category,
18 licence: videoInfo.licence, 19 licence: videoInfo.licence ?? CONFIG.DEFAULTS.PUBLISH.LICENCE,
19 language: videoInfo.language, 20 language: videoInfo.language,
20 commentsEnabled: videoInfo.commentsEnabled !== false, // If the value is not "false", the default is "true" 21 commentsEnabled: videoInfo.commentsEnabled ?? CONFIG.DEFAULTS.PUBLISH.COMMENTS_ENABLED,
21 downloadEnabled: videoInfo.downloadEnabled !== false, 22 downloadEnabled: videoInfo.downloadEnabled ?? CONFIG.DEFAULTS.PUBLISH.DOWNLOAD_ENABLED,
22 waitTranscoding: videoInfo.waitTranscoding || false, 23 waitTranscoding: videoInfo.waitTranscoding || false,
23 nsfw: videoInfo.nsfw || false, 24 nsfw: videoInfo.nsfw || false,
24 description: videoInfo.description, 25 description: videoInfo.description,
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index 6ef90b275..86d3c1d6c 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -1,8 +1,7 @@
1import { NextFunction, Request, Response } from 'express' 1import { NextFunction, Request, Response } from 'express'
2import { getAPId } from '@server/helpers/activitypub' 2import { getAPId } from '@server/helpers/activitypub'
3import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' 3import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor'
4import { ActivityDelete, ActivityPubSignature } from '../../shared' 4import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@shared/models'
5import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
6import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
7import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' 6import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto'
8import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' 7import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants'
diff --git a/server/middlewares/async.ts b/server/middlewares/async.ts
index 3d6e38809..9d0193536 100644
--- a/server/middlewares/async.ts
+++ b/server/middlewares/async.ts
@@ -1,7 +1,7 @@
1import { eachSeries } from 'async' 1import { eachSeries } from 'async'
2import { NextFunction, Request, RequestHandler, Response } from 'express' 2import { NextFunction, Request, RequestHandler, Response } from 'express'
3import { ValidationChain } from 'express-validator' 3import { ValidationChain } from 'express-validator'
4import { ExpressPromiseHandler } from '@server/types/express' 4import { ExpressPromiseHandler } from '@server/types/express-handler'
5import { retryTransactionWrapper } from '../helpers/database-utils' 5import { retryTransactionWrapper } from '../helpers/database-utils'
6 6
7// Syntactic sugar to avoid try/catch in express controllers 7// Syntactic sugar to avoid try/catch in express controllers
diff --git a/server/middlewares/cache/cache.ts b/server/middlewares/cache/cache.ts
index 48162a0ae..e14160ba8 100644
--- a/server/middlewares/cache/cache.ts
+++ b/server/middlewares/cache/cache.ts
@@ -1,10 +1,6 @@
1import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 1import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
2import { Redis } from '../../lib/redis'
3import { ApiCache, APICacheOptions } from './shared' 2import { ApiCache, APICacheOptions } from './shared'
4 3
5// Ensure Redis is initialized
6Redis.Instance.init()
7
8const defaultOptions: APICacheOptions = { 4const defaultOptions: APICacheOptions = {
9 excludeStatus: [ 5 excludeStatus: [
10 HttpStatusCode.FORBIDDEN_403, 6 HttpStatusCode.FORBIDDEN_403,
diff --git a/server/middlewares/cache/shared/api-cache.ts b/server/middlewares/cache/shared/api-cache.ts
index f8846dcfc..86c5095b5 100644
--- a/server/middlewares/cache/shared/api-cache.ts
+++ b/server/middlewares/cache/shared/api-cache.ts
@@ -7,6 +7,7 @@ import { isTestInstance, parseDurationToMs } from '@server/helpers/core-utils'
7import { logger } from '@server/helpers/logger' 7import { logger } from '@server/helpers/logger'
8import { Redis } from '@server/lib/redis' 8import { Redis } from '@server/lib/redis'
9import { HttpStatusCode } from '@shared/models' 9import { HttpStatusCode } from '@shared/models'
10import { asyncMiddleware } from '@server/middlewares'
10 11
11export interface APICacheOptions { 12export interface APICacheOptions {
12 headerBlacklist?: string[] 13 headerBlacklist?: string[]
@@ -40,24 +41,25 @@ export class ApiCache {
40 buildMiddleware (strDuration: string) { 41 buildMiddleware (strDuration: string) {
41 const duration = parseDurationToMs(strDuration) 42 const duration = parseDurationToMs(strDuration)
42 43
43 return (req: express.Request, res: express.Response, next: express.NextFunction) => { 44 return asyncMiddleware(
44 const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl 45 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
45 const redis = Redis.Instance.getClient() 46 const key = Redis.Instance.getPrefix() + 'api-cache-' + req.originalUrl
47 const redis = Redis.Instance.getClient()
46 48
47 if (!redis.connected) return this.makeResponseCacheable(res, next, key, duration) 49 if (!Redis.Instance.isConnected()) return this.makeResponseCacheable(res, next, key, duration)
48 50
49 try { 51 try {
50 redis.hgetall(key, (err, obj) => { 52 const obj = await redis.hGetAll(key)
51 if (!err && obj && obj.response) { 53 if (obj?.response) {
52 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration) 54 return this.sendCachedResponse(req, res, JSON.parse(obj.response), duration)
53 } 55 }
54 56
55 return this.makeResponseCacheable(res, next, key, duration) 57 return this.makeResponseCacheable(res, next, key, duration)
56 }) 58 } catch (err) {
57 } catch (err) { 59 return this.makeResponseCacheable(res, next, key, duration)
58 return this.makeResponseCacheable(res, next, key, duration) 60 }
59 } 61 }
60 } 62 )
61 } 63 }
62 64
63 private shouldCacheResponse (response: express.Response) { 65 private shouldCacheResponse (response: express.Response) {
@@ -93,21 +95,22 @@ export class ApiCache {
93 } as CacheObject 95 } as CacheObject
94 } 96 }
95 97
96 private cacheResponse (key: string, value: object, duration: number) { 98 private async cacheResponse (key: string, value: object, duration: number) {
97 const redis = Redis.Instance.getClient() 99 const redis = Redis.Instance.getClient()
98 100
99 if (redis.connected) { 101 if (Redis.Instance.isConnected()) {
100 try { 102 await Promise.all([
101 redis.hset(key, 'response', JSON.stringify(value)) 103 redis.hSet(key, 'response', JSON.stringify(value)),
102 redis.hset(key, 'duration', duration + '') 104 redis.hSet(key, 'duration', duration + ''),
103 redis.expire(key, duration / 1000) 105 redis.expire(key, duration / 1000)
104 } catch (err) { 106 ])
105 logger.error('Cannot set cache in redis.', { err })
106 }
107 } 107 }
108 108
109 // add automatic cache clearing from duration, includes max limit on setTimeout 109 // add automatic cache clearing from duration, includes max limit on setTimeout
110 this.timers[key] = setTimeout(() => this.clear(key), Math.min(duration, 2147483647)) 110 this.timers[key] = setTimeout(() => {
111 this.clear(key)
112 .catch(err => logger.error('Cannot clear Redis key %s.', key, { err }))
113 }, Math.min(duration, 2147483647))
111 } 114 }
112 115
113 private accumulateContent (res: express.Response, content: any) { 116 private accumulateContent (res: express.Response, content: any) {
@@ -184,6 +187,7 @@ export class ApiCache {
184 encoding 187 encoding
185 ) 188 )
186 self.cacheResponse(key, cacheObject, duration) 189 self.cacheResponse(key, cacheObject, duration)
190 .catch(err => logger.error('Cannot cache response', { err }))
187 } 191 }
188 } 192 }
189 193
@@ -235,7 +239,7 @@ export class ApiCache {
235 return response.end(data, cacheObject.encoding) 239 return response.end(data, cacheObject.encoding)
236 } 240 }
237 241
238 private clear (target: string) { 242 private async clear (target: string) {
239 const redis = Redis.Instance.getClient() 243 const redis = Redis.Instance.getClient()
240 244
241 if (target) { 245 if (target) {
@@ -243,7 +247,7 @@ export class ApiCache {
243 delete this.timers[target] 247 delete this.timers[target]
244 248
245 try { 249 try {
246 redis.del(target) 250 await redis.del(target)
247 } catch (err) { 251 } catch (err) {
248 logger.error('Cannot delete %s in redis cache.', target, { err }) 252 logger.error('Cannot delete %s in redis cache.', target, { err })
249 } 253 }
@@ -255,7 +259,7 @@ export class ApiCache {
255 delete this.timers[key] 259 delete this.timers[key]
256 260
257 try { 261 try {
258 redis.del(key) 262 await redis.del(key)
259 } catch (err) { 263 } catch (err) {
260 logger.error('Cannot delete %s in redis cache.', key, { err }) 264 logger.error('Cannot delete %s in redis cache.', key, { err })
261 } 265 }
diff --git a/server/middlewares/error.ts b/server/middlewares/error.ts
index 6c52ce7bd..34c87a26d 100644
--- a/server/middlewares/error.ts
+++ b/server/middlewares/error.ts
@@ -1,5 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details' 2import { ProblemDocument, ProblemDocumentExtension } from 'http-problem-details'
3import { logger } from '@server/helpers/logger'
3import { HttpStatusCode } from '@shared/models' 4import { HttpStatusCode } from '@shared/models'
4 5
5function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) { 6function apiFailMiddleware (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -18,7 +19,8 @@ function apiFailMiddleware (req: express.Request, res: express.Response, next: e
18 19
19 res.status(status) 20 res.status(status)
20 res.setHeader('Content-Type', 'application/problem+json') 21 res.setHeader('Content-Type', 'application/problem+json')
21 res.json(new ProblemDocument({ 22
23 const json = new ProblemDocument({
22 status, 24 status,
23 title, 25 title,
24 instance, 26 instance,
@@ -28,7 +30,11 @@ function apiFailMiddleware (req: express.Request, res: express.Response, next: e
28 type: type 30 type: type
29 ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}` 31 ? `https://docs.joinpeertube.org/api-rest-reference.html#section/Errors/${type}`
30 : undefined 32 : undefined
31 }, extension)) 33 }, extension)
34
35 logger.debug('Bad HTTP request.', { json })
36
37 res.json(json)
32 } 38 }
33 39
34 if (next) next() 40 if (next) next()
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts
index ea95b16c2..7d53e8341 100644
--- a/server/middlewares/user-right.ts
+++ b/server/middlewares/user-right.ts
@@ -1,6 +1,5 @@
1import express from 'express' 1import express from 'express'
2import { UserRight } from '../../shared' 2import { HttpStatusCode, UserRight } from '@shared/models'
3import { HttpStatusCode } from '../../shared/models/http/http-error-codes'
4import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
5 4
6function ensureUserHasRight (userRight: UserRight) { 5function ensureUserHasRight (userRight: UserRight) {
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts
index b7749e204..12980ced4 100644
--- a/server/middlewares/validators/blocklist.ts
+++ b/server/middlewares/validators/blocklist.ts
@@ -1,8 +1,10 @@
1import express from 'express' 1import express from 'express'
2import { body, param } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { areValidActorHandles } from '@server/helpers/custom-validators/activitypub/actor'
4import { toArray } from '@server/helpers/custom-validators/misc'
3import { getServerActor } from '@server/models/application/application' 5import { getServerActor } from '@server/models/application/application'
4import { HttpStatusCode } from '../../../shared/models/http/http-error-codes' 6import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
5import { isHostValid } from '../../helpers/custom-validators/servers' 7import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers'
6import { logger } from '../../helpers/logger' 8import { logger } from '../../helpers/logger'
7import { WEBSERVER } from '../../initializers/constants' 9import { WEBSERVER } from '../../initializers/constants'
8import { AccountBlocklistModel } from '../../models/account/account-blocklist' 10import { AccountBlocklistModel } from '../../models/account/account-blocklist'
@@ -123,6 +125,26 @@ const unblockServerByServerValidator = [
123 } 125 }
124] 126]
125 127
128const blocklistStatusValidator = [
129 query('hosts')
130 .optional()
131 .customSanitizer(toArray)
132 .custom(isEachUniqueHostValid).withMessage('Should have a valid hosts array'),
133
134 query('accounts')
135 .optional()
136 .customSanitizer(toArray)
137 .custom(areValidActorHandles).withMessage('Should have a valid accounts array'),
138
139 (req: express.Request, res: express.Response, next: express.NextFunction) => {
140 logger.debug('Checking blocklistStatusValidator parameters', { query: req.query })
141
142 if (areValidationErrors(req, res)) return
143
144 return next()
145 }
146]
147
126// --------------------------------------------------------------------------- 148// ---------------------------------------------------------------------------
127 149
128export { 150export {
@@ -131,7 +153,8 @@ export {
131 unblockAccountByAccountValidator, 153 unblockAccountByAccountValidator,
132 unblockServerByAccountValidator, 154 unblockServerByAccountValidator,
133 unblockAccountByServerValidator, 155 unblockAccountByServerValidator,
134 unblockServerByServerValidator 156 unblockServerByServerValidator,
157 blocklistStatusValidator
135} 158}
136 159
137// --------------------------------------------------------------------------- 160// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts
index 21171af23..c1e9ebefb 100644
--- a/server/middlewares/validators/plugins.ts
+++ b/server/middlewares/validators/plugins.ts
@@ -116,6 +116,9 @@ const installOrUpdatePluginValidator = [
116 body('npmName') 116 body('npmName')
117 .optional() 117 .optional()
118 .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'), 118 .custom(isNpmPluginNameValid).withMessage('Should have a valid npm name'),
119 body('pluginVersion')
120 .optional()
121 .custom(isPluginVersionValid).withMessage('Should have a valid plugin version'),
119 body('path') 122 body('path')
120 .optional() 123 .optional()
121 .custom(isSafePath).withMessage('Should have a valid safe path'), 124 .custom(isSafePath).withMessage('Should have a valid safe path'),
@@ -129,6 +132,9 @@ const installOrUpdatePluginValidator = [
129 if (!body.path && !body.npmName) { 132 if (!body.path && !body.npmName) {
130 return res.fail({ message: 'Should have either a npmName or a path' }) 133 return res.fail({ message: 'Should have either a npmName or a path' })
131 } 134 }
135 if (body.pluginVersion && !body.npmName) {
136 return res.fail({ message: 'Should have a npmName when specifying a pluginVersion' })
137 }
132 138
133 return next() 139 return next()
134 } 140 }
diff --git a/server/middlewares/validators/shared/video-channels.ts b/server/middlewares/validators/shared/video-channels.ts
index 7c0c89267..bed9f5dbe 100644
--- a/server/middlewares/validators/shared/video-channels.ts
+++ b/server/middlewares/validators/shared/video-channels.ts
@@ -3,12 +3,6 @@ import { VideoChannelModel } from '@server/models/video/video-channel'
3import { MChannelBannerAccountDefault } from '@server/types/models' 3import { MChannelBannerAccountDefault } from '@server/types/models'
4import { HttpStatusCode } from '@shared/models' 4import { HttpStatusCode } from '@shared/models'
5 5
6async function doesLocalVideoChannelNameExist (name: string, res: express.Response) {
7 const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name)
8
9 return processVideoChannelExist(videoChannel, res)
10}
11
12async function doesVideoChannelIdExist (id: number, res: express.Response) { 6async function doesVideoChannelIdExist (id: number, res: express.Response) {
13 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) 7 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
14 8
@@ -24,7 +18,6 @@ async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: e
24// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
25 19
26export { 20export {
27 doesLocalVideoChannelNameExist,
28 doesVideoChannelIdExist, 21 doesVideoChannelIdExist,
29 doesVideoChannelNameWithHostExist 22 doesVideoChannelNameWithHostExist
30} 23}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 33b31d54b..bc6007c6d 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -3,7 +3,7 @@ import { body, param, query } from 'express-validator'
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import { Hooks } from '@server/lib/plugins/hooks' 4import { Hooks } from '@server/lib/plugins/hooks'
5import { MUserDefault } from '@server/types/models' 5import { MUserDefault } from '@server/types/models'
6import { HttpStatusCode, UserRegister, UserRole } from '@shared/models' 6import { HttpStatusCode, UserRegister, UserRight, UserRole } from '@shared/models'
7import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 7import { isBooleanValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
8import { isThemeNameValid } from '../../helpers/custom-validators/plugins' 8import { isThemeNameValid } from '../../helpers/custom-validators/plugins'
9import { 9import {
@@ -15,6 +15,7 @@ import {
15 isUserDisplayNameValid, 15 isUserDisplayNameValid,
16 isUserNoModal, 16 isUserNoModal,
17 isUserNSFWPolicyValid, 17 isUserNSFWPolicyValid,
18 isUserP2PEnabledValid,
18 isUserPasswordValid, 19 isUserPasswordValid,
19 isUserPasswordValidOrEmpty, 20 isUserPasswordValidOrEmpty,
20 isUserRoleValid, 21 isUserRoleValid,
@@ -239,6 +240,9 @@ const usersUpdateMeValidator = [
239 body('autoPlayVideo') 240 body('autoPlayVideo')
240 .optional() 241 .optional()
241 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), 242 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
243 body('p2pEnabled')
244 .optional()
245 .custom(isUserP2PEnabledValid).withMessage('Should have a valid p2p enabled boolean'),
242 body('videoLanguages') 246 body('videoLanguages')
243 .optional() 247 .optional()
244 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'), 248 .custom(isUserVideoLanguages).withMessage('Should have a valid video languages attribute'),
@@ -490,14 +494,17 @@ const ensureAuthUserOwnsAccountValidator = [
490 } 494 }
491] 495]
492 496
493const ensureAuthUserOwnsChannelValidator = [ 497const ensureCanManageChannel = [
494 (req: express.Request, res: express.Response, next: express.NextFunction) => { 498 (req: express.Request, res: express.Response, next: express.NextFunction) => {
495 const user = res.locals.oauth.token.User 499 const user = res.locals.oauth.token.user
500 const isUserOwner = res.locals.videoChannel.Account.userId === user.id
501
502 if (!isUserOwner && user.hasRight(UserRight.MANAGE_ANY_VIDEO_CHANNEL) === false) {
503 const message = `User ${user.username} does not have right to manage channel ${req.params.nameWithHost}.`
496 504
497 if (res.locals.videoChannel.Account.userId !== user.id) {
498 return res.fail({ 505 return res.fail({
499 status: HttpStatusCode.FORBIDDEN_403, 506 status: HttpStatusCode.FORBIDDEN_403,
500 message: 'Only owner of this video channel can access this ressource' 507 message
501 }) 508 })
502 } 509 }
503 510
@@ -542,8 +549,8 @@ export {
542 usersVerifyEmailValidator, 549 usersVerifyEmailValidator,
543 userAutocompleteValidator, 550 userAutocompleteValidator,
544 ensureAuthUserOwnsAccountValidator, 551 ensureAuthUserOwnsAccountValidator,
545 ensureAuthUserOwnsChannelValidator, 552 ensureCanManageUser,
546 ensureCanManageUser 553 ensureCanManageChannel
547} 554}
548 555
549// --------------------------------------------------------------------------- 556// ---------------------------------------------------------------------------
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 4fc4c8ec5..a399871e1 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { body, param } from 'express-validator' 2import { body, param } from 'express-validator'
3import { HttpStatusCode, UserRight } from '../../../../shared' 3import { HttpStatusCode, UserRight } from '@shared/models'
4import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' 4import { isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions'
5import { cleanUpReqFiles } from '../../../helpers/express-utils' 5import { cleanUpReqFiles } from '../../../helpers/express-utils'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index edce48c7f..3bfdebbb1 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -1,7 +1,7 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { MChannelAccountDefault, MUser } from '@server/types/models' 3import { CONFIG } from '@server/initializers/config'
4import { UserRight } from '../../../../shared' 4import { MChannelAccountDefault } from '@server/types/models'
5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' 6import { isBooleanValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
7import { 7import {
@@ -13,8 +13,7 @@ import {
13import { logger } from '../../../helpers/logger' 13import { logger } from '../../../helpers/logger'
14import { ActorModel } from '../../../models/actor/actor' 14import { ActorModel } from '../../../models/actor/actor'
15import { VideoChannelModel } from '../../../models/video/video-channel' 15import { VideoChannelModel } from '../../../models/video/video-channel'
16import { areValidationErrors, doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../shared' 16import { areValidationErrors, doesVideoChannelNameWithHostExist } from '../shared'
17import { CONFIG } from '@server/initializers/config'
18 17
19const videoChannelsAddValidator = [ 18const videoChannelsAddValidator = [
20 body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'), 19 body('name').custom(isVideoChannelUsernameValid).withMessage('Should have a valid channel name'),
@@ -71,16 +70,10 @@ const videoChannelsUpdateValidator = [
71] 70]
72 71
73const videoChannelsRemoveValidator = [ 72const videoChannelsRemoveValidator = [
74 param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
75
76 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 73 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
77 logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) 74 logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params })
78 75
79 if (areValidationErrors(req, res)) return 76 if (!await checkVideoChannelIsNotTheLastOne(res.locals.videoChannel, res)) return
80 if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return
81
82 if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return
83 if (!await checkVideoChannelIsNotTheLastOne(res)) return
84 77
85 return next() 78 return next()
86 } 79 }
@@ -100,14 +93,14 @@ const videoChannelsNameWithHostValidator = [
100 } 93 }
101] 94]
102 95
103const localVideoChannelValidator = [ 96const ensureIsLocalChannel = [
104 param('name').custom(isVideoChannelDisplayNameValid).withMessage('Should have a valid video channel name'), 97 (req: express.Request, res: express.Response, next: express.NextFunction) => {
105 98 if (res.locals.videoChannel.Actor.isOwned() === false) {
106 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 99 return res.fail({
107 logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) 100 status: HttpStatusCode.FORBIDDEN_403,
108 101 message: 'This channel is not owned.'
109 if (areValidationErrors(req, res)) return 102 })
110 if (!await doesLocalVideoChannelNameExist(req.params.name, res)) return 103 }
111 104
112 return next() 105 return next()
113 } 106 }
@@ -144,38 +137,15 @@ export {
144 videoChannelsUpdateValidator, 137 videoChannelsUpdateValidator,
145 videoChannelsRemoveValidator, 138 videoChannelsRemoveValidator,
146 videoChannelsNameWithHostValidator, 139 videoChannelsNameWithHostValidator,
140 ensureIsLocalChannel,
147 videoChannelsListValidator, 141 videoChannelsListValidator,
148 localVideoChannelValidator,
149 videoChannelStatsValidator 142 videoChannelStatsValidator
150} 143}
151 144
152// --------------------------------------------------------------------------- 145// ---------------------------------------------------------------------------
153 146
154function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { 147async function checkVideoChannelIsNotTheLastOne (videoChannel: MChannelAccountDefault, res: express.Response) {
155 if (videoChannel.Actor.isOwned() === false) { 148 const count = await VideoChannelModel.countByAccount(videoChannel.Account.id)
156 res.fail({
157 status: HttpStatusCode.FORBIDDEN_403,
158 message: 'Cannot remove video channel of another server.'
159 })
160 return false
161 }
162
163 // Check if the user can delete the video channel
164 // The user can delete it if s/he is an admin
165 // Or if s/he is the video channel's account
166 if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_CHANNEL) === false && videoChannel.Account.userId !== user.id) {
167 res.fail({
168 status: HttpStatusCode.FORBIDDEN_403,
169 message: 'Cannot remove video channel of another user'
170 })
171 return false
172 }
173
174 return true
175}
176
177async function checkVideoChannelIsNotTheLastOne (res: express.Response) {
178 const count = await VideoChannelModel.countByAccount(res.locals.oauth.token.User.Account.id)
179 149
180 if (count <= 1) { 150 if (count <= 1) {
181 res.fail({ 151 res.fail({
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 04e7b6973..91ae31ec2 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -1,8 +1,7 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { MUserAccountUrl } from '@server/types/models' 3import { MUserAccountUrl } from '@server/types/models'
4import { UserRight } from '../../../../shared' 4import { HttpStatusCode, UserRight } from '@shared/models'
5import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
6import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc' 5import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '../../../helpers/custom-validators/misc'
7import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' 6import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments'
8import { logger } from '../../../helpers/logger' 7import { logger } from '../../../helpers/logger'
diff --git a/server/middlewares/validators/videos/video-files.ts b/server/middlewares/validators/videos/video-files.ts
index c1fa77502..35b0ac757 100644
--- a/server/middlewares/validators/videos/video-files.ts
+++ b/server/middlewares/validators/videos/video-files.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { MVideo } from '@server/types/models' 2import { MVideo } from '@server/types/models'
3import { HttpStatusCode } from '../../../../shared' 3import { HttpStatusCode } from '@shared/models'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' 5import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared'
6 6
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 8f5c75fd5..f5fee845e 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -1,11 +1,15 @@
1import express from 'express' 1import express from 'express'
2import { body, param, query, ValidationChain } from 'express-validator' 2import { body, param, query, ValidationChain } from 'express-validator'
3import { ExpressPromiseHandler } from '@server/types/express' 3import { ExpressPromiseHandler } from '@server/types/express-handler'
4import { MUserAccountId } from '@server/types/models' 4import { MUserAccountId } from '@server/types/models'
5import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' 5import {
6import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' 6 HttpStatusCode,
7import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 7 UserRight,
8import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' 8 VideoPlaylistCreate,
9 VideoPlaylistPrivacy,
10 VideoPlaylistType,
11 VideoPlaylistUpdate
12} from '@shared/models'
9import { 13import {
10 isArrayOf, 14 isArrayOf,
11 isIdOrUUIDValid, 15 isIdOrUUIDValid,
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 782f495e8..3a1a905f3 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -5,12 +5,10 @@ import { getResumableUploadPath } from '@server/helpers/upload'
5import { Redis } from '@server/lib/redis' 5import { Redis } from '@server/lib/redis'
6import { isAbleToUploadVideo } from '@server/lib/user' 6import { isAbleToUploadVideo } from '@server/lib/user'
7import { getServerActor } from '@server/models/application/application' 7import { getServerActor } from '@server/models/application/application'
8import { ExpressPromiseHandler } from '@server/types/express' 8import { ExpressPromiseHandler } from '@server/types/express-handler'
9import { MUserAccountId, MVideoFullLight } from '@server/types/models' 9import { MUserAccountId, MVideoFullLight } from '@server/types/models'
10import { getAllPrivacies } from '@shared/core-utils' 10import { getAllPrivacies } from '@shared/core-utils'
11import { VideoInclude } from '@shared/models' 11import { HttpStatusCode, ServerErrorCode, UserRight, VideoInclude, VideoPrivacy } from '@shared/models'
12import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
13import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
14import { 12import {
15 exists, 13 exists,
16 isBooleanValid, 14 isBooleanValid,
diff --git a/server/models/abuse/abuse-message.ts b/server/models/abuse/abuse-message.ts
index 2c5987e96..6a441a210 100644
--- a/server/models/abuse/abuse-message.ts
+++ b/server/models/abuse/abuse-message.ts
@@ -1,7 +1,7 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses' 2import { isAbuseMessageValid } from '@server/helpers/custom-validators/abuses'
3import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models' 3import { MAbuseMessage, MAbuseMessageFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { AbuseMessage } from '@shared/models' 5import { AbuseMessage } from '@shared/models'
6import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' 6import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account'
7import { getSort, throwIfNotValid } from '../utils' 7import { getSort, throwIfNotValid } from '../utils'
diff --git a/server/models/abuse/abuse.ts b/server/models/abuse/abuse.ts
index 3518f5c02..4344df006 100644
--- a/server/models/abuse/abuse.ts
+++ b/server/models/abuse/abuse.ts
@@ -16,7 +16,7 @@ import {
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses' 18import { isAbuseModerationCommentValid, isAbuseReasonValid, isAbuseStateValid } from '@server/helpers/custom-validators/abuses'
19import { abusePredefinedReasonsMap, AttributesOnly } from '@shared/core-utils' 19import { abusePredefinedReasonsMap } from '@shared/core-utils'
20import { 20import {
21 AbuseFilter, 21 AbuseFilter,
22 AbuseObject, 22 AbuseObject,
@@ -30,6 +30,7 @@ import {
30 UserAbuse, 30 UserAbuse,
31 UserVideoAbuse 31 UserVideoAbuse
32} from '@shared/models' 32} from '@shared/models'
33import { AttributesOnly } from '@shared/typescript-utils'
33import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants' 34import { ABUSE_STATES, CONSTRAINTS_FIELDS } from '../../initializers/constants'
34import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models' 35import { MAbuseAdminFormattable, MAbuseAP, MAbuseFull, MAbuseReporter, MAbuseUserFormattable, MUserAccountId } from '../../types/models'
35import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account' 36import { AccountModel, ScopeNames as AccountScopeNames, SummaryOptions as AccountSummaryOptions } from '../account/account'
diff --git a/server/models/abuse/video-abuse.ts b/server/models/abuse/video-abuse.ts
index 95bff50d0..773a9ebba 100644
--- a/server/models/abuse/video-abuse.ts
+++ b/server/models/abuse/video-abuse.ts
@@ -1,5 +1,5 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils' 2import { AttributesOnly } from '@shared/typescript-utils'
3import { VideoDetails } from '@shared/models' 3import { VideoDetails } from '@shared/models'
4import { VideoModel } from '../video/video' 4import { VideoModel } from '../video/video'
5import { AbuseModel } from './abuse' 5import { AbuseModel } from './abuse'
diff --git a/server/models/abuse/video-comment-abuse.ts b/server/models/abuse/video-comment-abuse.ts
index 32cb2ca64..337aaaa58 100644
--- a/server/models/abuse/video-comment-abuse.ts
+++ b/server/models/abuse/video-comment-abuse.ts
@@ -1,5 +1,5 @@
1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils' 2import { AttributesOnly } from '@shared/typescript-utils'
3import { VideoCommentModel } from '../video/video-comment' 3import { VideoCommentModel } from '../video/video-comment'
4import { AbuseModel } from './abuse' 4import { AbuseModel } from './abuse'
5 5
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index b2375b006..1162962bf 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -1,11 +1,12 @@
1import { Op } from 'sequelize' 1import { Op, QueryTypes } from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { handlesToNameAndHost } from '@server/helpers/actors'
3import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models' 4import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 5import { AttributesOnly } from '@shared/typescript-utils'
5import { AccountBlock } from '../../../shared/models' 6import { AccountBlock } from '../../../shared/models'
6import { ActorModel } from '../actor/actor' 7import { ActorModel } from '../actor/actor'
7import { ServerModel } from '../server/server' 8import { ServerModel } from '../server/server'
8import { getSort, searchAttribute } from '../utils' 9import { createSafeIn, getSort, searchAttribute } from '../utils'
9import { AccountModel } from './account' 10import { AccountModel } from './account'
10 11
11enum ScopeNames { 12enum ScopeNames {
@@ -77,7 +78,7 @@ export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountB
77 }) 78 })
78 BlockedAccount: AccountModel 79 BlockedAccount: AccountModel
79 80
80 static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) { 81 static isAccountMutedByAccounts (accountIds: number[], targetAccountId: number) {
81 const query = { 82 const query = {
82 attributes: [ 'accountId', 'id' ], 83 attributes: [ 'accountId', 'id' ],
83 where: { 84 where: {
@@ -187,6 +188,39 @@ export class AccountBlocklistModel extends Model<Partial<AttributesOnly<AccountB
187 .then(entries => entries.map(e => `${e.BlockedAccount.Actor.preferredUsername}@${e.BlockedAccount.Actor.Server.host}`)) 188 .then(entries => entries.map(e => `${e.BlockedAccount.Actor.preferredUsername}@${e.BlockedAccount.Actor.Server.host}`))
188 } 189 }
189 190
191 static getBlockStatus (byAccountIds: number[], handles: string[]): Promise<{ name: string, host: string, accountId: number }[]> {
192 const sanitizedHandles = handlesToNameAndHost(handles)
193
194 const localHandles = sanitizedHandles.filter(h => !h.host)
195 .map(h => h.name)
196
197 const remoteHandles = sanitizedHandles.filter(h => !!h.host)
198 .map(h => ([ h.name, h.host ]))
199
200 const handlesWhere: string[] = []
201
202 if (localHandles.length !== 0) {
203 handlesWhere.push(`("actor"."preferredUsername" IN (:localHandles) AND "server"."id" IS NULL)`)
204 }
205
206 if (remoteHandles.length !== 0) {
207 handlesWhere.push(`(("actor"."preferredUsername", "server"."host") IN (:remoteHandles))`)
208 }
209
210 const rawQuery = `SELECT "accountBlocklist"."accountId", "actor"."preferredUsername" AS "name", "server"."host" ` +
211 `FROM "accountBlocklist" ` +
212 `INNER JOIN "account" ON "account"."id" = "accountBlocklist"."targetAccountId" ` +
213 `INNER JOIN "actor" ON "actor"."id" = "account"."actorId" ` +
214 `LEFT JOIN "server" ON "server"."id" = "actor"."serverId" ` +
215 `WHERE "accountBlocklist"."accountId" IN (${createSafeIn(AccountBlocklistModel.sequelize, byAccountIds)}) ` +
216 `AND (${handlesWhere.join(' OR ')})`
217
218 return AccountBlocklistModel.sequelize.query(rawQuery, {
219 type: QueryTypes.SELECT as QueryTypes.SELECT,
220 replacements: { byAccountIds, localHandles, remoteHandles }
221 })
222 }
223
190 toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { 224 toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock {
191 return { 225 return {
192 byAccount: this.ByAccount.toFormattedJSON(), 226 byAccount: this.ByAccount.toFormattedJSON(),
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index ee6dbc6da..e89d31adf 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -6,10 +6,9 @@ import {
6 MAccountVideoRateAccountUrl, 6 MAccountVideoRateAccountUrl,
7 MAccountVideoRateAccountVideo, 7 MAccountVideoRateAccountVideo,
8 MAccountVideoRateFormattable 8 MAccountVideoRateFormattable
9} from '@server/types/models/video/video-rate' 9} from '@server/types/models'
10import { AttributesOnly } from '@shared/core-utils' 10import { AccountVideoRate, VideoRateType } from '@shared/models'
11import { AccountVideoRate } from '../../../shared' 11import { AttributesOnly } from '@shared/typescript-utils'
12import { VideoRateType } from '../../../shared/models/videos'
13import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 12import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
14import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' 13import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants'
15import { ActorModel } from '../actor/actor' 14import { ActorModel } from '../actor/actor'
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 71a9b8ccb..619a598dd 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -17,7 +17,7 @@ import {
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { ModelCache } from '@server/models/model-cache' 19import { ModelCache } from '@server/models/model-cache'
20import { AttributesOnly } from '@shared/core-utils' 20import { AttributesOnly } from '@shared/typescript-utils'
21import { Account, AccountSummary } from '../../../shared/models/actors' 21import { Account, AccountSummary } from '../../../shared/models/actors'
22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' 22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
23import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' 23import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants'
diff --git a/server/models/actor/actor-follow.ts b/server/models/actor/actor-follow.ts
index d6a2387a5..006282530 100644
--- a/server/models/actor/actor-follow.ts
+++ b/server/models/actor/actor-follow.ts
@@ -30,7 +30,7 @@ import {
30 MActorFollowFormattable, 30 MActorFollowFormattable,
31 MActorFollowSubscriptions 31 MActorFollowSubscriptions
32} from '@server/types/models' 32} from '@server/types/models'
33import { AttributesOnly } from '@shared/core-utils' 33import { AttributesOnly } from '@shared/typescript-utils'
34import { ActivityPubActorType } from '@shared/models' 34import { ActivityPubActorType } from '@shared/models'
35import { FollowState } from '../../../shared/models/actors' 35import { FollowState } from '../../../shared/models/actors'
36import { ActorFollow } from '../../../shared/models/actors/follow.model' 36import { ActorFollow } from '../../../shared/models/actors/follow.model'
diff --git a/server/models/actor/actor-image.ts b/server/models/actor/actor-image.ts
index 98a7f6fba..8edff5ab4 100644
--- a/server/models/actor/actor-image.ts
+++ b/server/models/actor/actor-image.ts
@@ -2,7 +2,7 @@ import { remove } from 'fs-extra'
2import { join } from 'path' 2import { join } from 'path'
3import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 3import { AfterDestroy, AllowNull, Column, CreatedAt, Default, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
4import { MActorImageFormattable } from '@server/types/models' 4import { MActorImageFormattable } from '@server/types/models'
5import { AttributesOnly } from '@shared/core-utils' 5import { AttributesOnly } from '@shared/typescript-utils'
6import { ActorImageType } from '@shared/models' 6import { ActorImageType } from '@shared/models'
7import { ActorImage } from '../../../shared/models/actors/actor-image.model' 7import { ActorImage } from '../../../shared/models/actors/actor-image.model'
8import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 8import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
diff --git a/server/models/actor/actor.ts b/server/models/actor/actor.ts
index 8df49951d..c12dcf634 100644
--- a/server/models/actor/actor.ts
+++ b/server/models/actor/actor.ts
@@ -16,9 +16,9 @@ import {
16 Table, 16 Table,
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { getLowercaseExtension } from '@server/helpers/core-utils'
20import { ModelCache } from '@server/models/model-cache' 19import { ModelCache } from '@server/models/model-cache'
21import { AttributesOnly } from '@shared/core-utils' 20import { getLowercaseExtension } from '@shared/core-utils'
21import { AttributesOnly } from '@shared/typescript-utils'
22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' 22import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
23import { ActorImage } from '../../../shared/models/actors/actor-image.model' 23import { ActorImage } from '../../../shared/models/actors/actor-image.model'
24import { activityPubContextify } from '../../helpers/activitypub' 24import { activityPubContextify } from '../../helpers/activitypub'
diff --git a/server/models/application/application.ts b/server/models/application/application.ts
index e3939383b..a479de5d2 100644
--- a/server/models/application/application.ts
+++ b/server/models/application/application.ts
@@ -1,6 +1,6 @@
1import memoizee from 'memoizee' 1import memoizee from 'memoizee'
2import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' 2import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { AccountModel } from '../account/account' 4import { AccountModel } from '../account/account'
5 5
6export const getServerActor = memoizee(async function () { 6export const getServerActor = memoizee(async function () {
diff --git a/server/models/oauth/oauth-client.ts b/server/models/oauth/oauth-client.ts
index 890954bdb..860fa6f53 100644
--- a/server/models/oauth/oauth-client.ts
+++ b/server/models/oauth/oauth-client.ts
@@ -1,5 +1,5 @@
1import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, Column, CreatedAt, DataType, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils' 2import { AttributesOnly } from '@shared/typescript-utils'
3import { OAuthTokenModel } from './oauth-token' 3import { OAuthTokenModel } from './oauth-token'
4 4
5@Table({ 5@Table({
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index af4b0ec42..f72423190 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -15,7 +15,7 @@ import {
15import { TokensCache } from '@server/lib/auth/tokens-cache' 15import { TokensCache } from '@server/lib/auth/tokens-cache'
16import { MUserAccountId } from '@server/types/models' 16import { MUserAccountId } from '@server/types/models'
17import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token' 17import { MOAuthTokenUser } from '@server/types/models/oauth/oauth-token'
18import { AttributesOnly } from '@shared/core-utils' 18import { AttributesOnly } from '@shared/typescript-utils'
19import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
20import { AccountModel } from '../account/account' 20import { AccountModel } from '../account/account'
21import { ActorModel } from '../actor/actor' 21import { ActorModel } from '../actor/actor'
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index e8d79a3ab..6f84747da 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -16,15 +16,17 @@ import {
16} from 'sequelize-typescript' 16} from 'sequelize-typescript'
17import { getServerActor } from '@server/models/application/application' 17import { getServerActor } from '@server/models/application/application'
18import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models' 18import { MActor, MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/types/models'
19import { AttributesOnly } from '@shared/core-utils'
20import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model'
21import { 19import {
20 CacheFileObject,
22 FileRedundancyInformation, 21 FileRedundancyInformation,
23 StreamingPlaylistRedundancyInformation, 22 StreamingPlaylistRedundancyInformation,
24 VideoRedundancy 23 VideoPrivacy,
25} from '@shared/models/redundancy/video-redundancy.model' 24 VideoRedundanciesTarget,
26import { CacheFileObject, VideoPrivacy } from '../../../shared' 25 VideoRedundancy,
27import { VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../shared/models/redundancy' 26 VideoRedundancyStrategy,
27 VideoRedundancyStrategyWithManual
28} from '@shared/models'
29import { AttributesOnly } from '@shared/typescript-utils'
28import { isTestInstance } from '../../helpers/core-utils' 30import { isTestInstance } from '../../helpers/core-utils'
29import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' 31import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
30import { logger } from '../../helpers/logger' 32import { logger } from '../../helpers/logger'
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index a8de64dd4..84f7a14e4 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -1,7 +1,7 @@
1import { FindAndCountOptions, json, QueryTypes } from 'sequelize' 1import { FindAndCountOptions, json, QueryTypes } from 'sequelize'
2import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MPlugin, MPluginFormattable } from '@server/types/models' 3import { MPlugin, MPluginFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { PeerTubePlugin, PluginType, RegisterServerSettingOptions } from '../../../shared/models' 5import { PeerTubePlugin, PluginType, RegisterServerSettingOptions } from '../../../shared/models'
6import { 6import {
7 isPluginDescriptionValid, 7 isPluginDescriptionValid,
@@ -197,15 +197,11 @@ export class PluginModel extends Model<Partial<AttributesOnly<PluginModel>>> {
197 if (!c) return undefined 197 if (!c) return undefined
198 const value = c.value 198 const value = c.value
199 199
200 if (typeof value === 'string' && value.startsWith('{')) { 200 try {
201 try { 201 return JSON.parse(value)
202 return JSON.parse(value) 202 } catch {
203 } catch { 203 return value
204 return value
205 }
206 } 204 }
207
208 return c.value
209 }) 205 })
210 } 206 }
211 207
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index b3579d589..9f64eeb7f 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -1,10 +1,10 @@
1import { Op } from 'sequelize' 1import { Op, QueryTypes } from 'sequelize'
2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models' 3import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { ServerBlock } from '@shared/models' 5import { ServerBlock } from '@shared/models'
6import { AccountModel } from '../account/account' 6import { AccountModel } from '../account/account'
7import { getSort, searchAttribute } from '../utils' 7import { createSafeIn, getSort, searchAttribute } from '../utils'
8import { ServerModel } from './server' 8import { ServerModel } from './server'
9 9
10enum ScopeNames { 10enum ScopeNames {
@@ -76,7 +76,7 @@ export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlo
76 }) 76 })
77 BlockedServer: ServerModel 77 BlockedServer: ServerModel
78 78
79 static isServerMutedByMulti (accountIds: number[], targetServerId: number) { 79 static isServerMutedByAccounts (accountIds: number[], targetServerId: number) {
80 const query = { 80 const query = {
81 attributes: [ 'accountId', 'id' ], 81 attributes: [ 'accountId', 'id' ],
82 where: { 82 where: {
@@ -141,6 +141,19 @@ export class ServerBlocklistModel extends Model<Partial<AttributesOnly<ServerBlo
141 .then(entries => entries.map(e => e.BlockedServer.host)) 141 .then(entries => entries.map(e => e.BlockedServer.host))
142 } 142 }
143 143
144 static getBlockStatus (byAccountIds: number[], hosts: string[]): Promise<{ host: string, accountId: number }[]> {
145 const rawQuery = `SELECT "server"."host", "serverBlocklist"."accountId" ` +
146 `FROM "serverBlocklist" ` +
147 `INNER JOIN "server" ON "server"."id" = "serverBlocklist"."targetServerId" ` +
148 `WHERE "server"."host" IN (:hosts) ` +
149 `AND "serverBlocklist"."accountId" IN (${createSafeIn(ServerBlocklistModel.sequelize, byAccountIds)})`
150
151 return ServerBlocklistModel.sequelize.query(rawQuery, {
152 type: QueryTypes.SELECT as QueryTypes.SELECT,
153 replacements: { hosts }
154 })
155 }
156
144 static listForApi (parameters: { 157 static listForApi (parameters: {
145 start: number 158 start: number
146 count: number 159 count: number
diff --git a/server/models/server/server.ts b/server/models/server/server.ts
index edbe92f73..ef42de090 100644
--- a/server/models/server/server.ts
+++ b/server/models/server/server.ts
@@ -1,7 +1,7 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MServer, MServerFormattable } from '@server/types/models/server' 3import { MServer, MServerFormattable } from '@server/types/models/server'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { isHostValid } from '../../helpers/custom-validators/servers' 5import { isHostValid } from '../../helpers/custom-validators/servers'
6import { ActorModel } from '../actor/actor' 6import { ActorModel } from '../actor/actor'
7import { throwIfNotValid } from '../utils' 7import { throwIfNotValid } from '../utils'
diff --git a/server/models/server/tracker.ts b/server/models/server/tracker.ts
index c09fdd64b..ee087c4a3 100644
--- a/server/models/server/tracker.ts
+++ b/server/models/server/tracker.ts
@@ -1,7 +1,7 @@
1import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsToMany, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { Transaction } from 'sequelize/types' 2import { Transaction } from 'sequelize/types'
3import { MTracker } from '@server/types/models/server/tracker' 3import { MTracker } from '@server/types/models/server/tracker'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoModel } from '../video/video' 5import { VideoModel } from '../video/video'
6import { VideoTrackerModel } from './video-tracker' 6import { VideoTrackerModel } from './video-tracker'
7 7
diff --git a/server/models/server/video-tracker.ts b/server/models/server/video-tracker.ts
index c49fbd1c6..f14f3bd7d 100644
--- a/server/models/server/video-tracker.ts
+++ b/server/models/server/video-tracker.ts
@@ -1,5 +1,5 @@
1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils' 2import { AttributesOnly } from '@shared/typescript-utils'
3import { VideoModel } from '../video/video' 3import { VideoModel } from '../video/video'
4import { TrackerModel } from './tracker' 4import { TrackerModel } from './tracker'
5 5
diff --git a/server/models/user/user-notification-setting.ts b/server/models/user/user-notification-setting.ts
index bee7d7851..f03b19e41 100644
--- a/server/models/user/user-notification-setting.ts
+++ b/server/models/user/user-notification-setting.ts
@@ -14,7 +14,7 @@ import {
14} from 'sequelize-typescript' 14} from 'sequelize-typescript'
15import { TokensCache } from '@server/lib/auth/tokens-cache' 15import { TokensCache } from '@server/lib/auth/tokens-cache'
16import { MNotificationSettingFormattable } from '@server/types/models' 16import { MNotificationSettingFormattable } from '@server/types/models'
17import { AttributesOnly } from '@shared/core-utils' 17import { AttributesOnly } from '@shared/typescript-utils'
18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' 18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
19import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' 19import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
20import { throwIfNotValid } from '../utils' 20import { throwIfNotValid } from '../utils'
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts
index 04c5513a9..edad10a55 100644
--- a/server/models/user/user-notification.ts
+++ b/server/models/user/user-notification.ts
@@ -1,9 +1,9 @@
1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' 1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { uuidToShort } from '@server/helpers/uuid'
4import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' 3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
5import { AttributesOnly } from '@shared/core-utils' 4import { uuidToShort } from '@shared/extra-utils'
6import { UserNotification, UserNotificationType } from '../../../shared' 5import { UserNotification, UserNotificationType } from '@shared/models'
6import { AttributesOnly } from '@shared/typescript-utils'
7import { isBooleanValid } from '../../helpers/custom-validators/misc' 7import { isBooleanValid } from '../../helpers/custom-validators/misc'
8import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' 8import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
9import { AbuseModel } from '../abuse/abuse' 9import { AbuseModel } from '../abuse/abuse'
diff --git a/server/models/user/user-video-history.ts b/server/models/user/user-video-history.ts
index 1aefdf02b..6d9f2e03f 100644
--- a/server/models/user/user-video-history.ts
+++ b/server/models/user/user-video-history.ts
@@ -1,7 +1,7 @@
1import { DestroyOptions, Op, Transaction } from 'sequelize' 1import { DestroyOptions, Op, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MUserAccountId, MUserId } from '@server/types/models' 3import { MUserAccountId, MUserId } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoModel } from '../video/video' 5import { VideoModel } from '../video/video'
6import { UserModel } from './user' 6import { UserModel } from './user'
7import { getServerActor } from '../application/application' 7import { getServerActor } from '../application/application'
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index b56f37e55..4ad76e5bc 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -31,7 +31,7 @@ import {
31 MUserWithNotificationSetting, 31 MUserWithNotificationSetting,
32 MVideoWithRights 32 MVideoWithRights
33} from '@server/types/models' 33} from '@server/types/models'
34import { AttributesOnly } from '@shared/core-utils' 34import { AttributesOnly } from '@shared/typescript-utils'
35import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users' 35import { hasUserRight, USER_ROLE_LABELS } from '../../../shared/core-utils/users'
36import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models' 36import { AbuseState, MyUser, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared/models'
37import { User, UserRole } from '../../../shared/models/users' 37import { User, UserRole } from '../../../shared/models/users'
@@ -55,7 +55,7 @@ import {
55 isUserVideoQuotaDailyValid, 55 isUserVideoQuotaDailyValid,
56 isUserVideoQuotaValid, 56 isUserVideoQuotaValid,
57 isUserVideosHistoryEnabledValid, 57 isUserVideosHistoryEnabledValid,
58 isUserWebTorrentEnabledValid 58 isUserP2PEnabledValid
59} from '../../helpers/custom-validators/users' 59} from '../../helpers/custom-validators/users'
60import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 60import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
61import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' 61import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants'
@@ -267,10 +267,9 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
267 nsfwPolicy: NSFWPolicyType 267 nsfwPolicy: NSFWPolicyType
268 268
269 @AllowNull(false) 269 @AllowNull(false)
270 @Default(true) 270 @Is('p2pEnabled', value => throwIfNotValid(value, isUserP2PEnabledValid, 'P2P enabled'))
271 @Is('UserWebTorrentEnabled', value => throwIfNotValid(value, isUserWebTorrentEnabledValid, 'WebTorrent enabled'))
272 @Column 271 @Column
273 webTorrentEnabled: boolean 272 p2pEnabled: boolean
274 273
275 @AllowNull(false) 274 @AllowNull(false)
276 @Default(true) 275 @Default(true)
@@ -892,7 +891,11 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
892 emailVerified: this.emailVerified, 891 emailVerified: this.emailVerified,
893 892
894 nsfwPolicy: this.nsfwPolicy, 893 nsfwPolicy: this.nsfwPolicy,
895 webTorrentEnabled: this.webTorrentEnabled, 894
895 // FIXME: deprecated in 4.1
896 webTorrentEnabled: this.p2pEnabled,
897 p2pEnabled: this.p2pEnabled,
898
896 videosHistoryEnabled: this.videosHistoryEnabled, 899 videosHistoryEnabled: this.videosHistoryEnabled,
897 autoPlayVideo: this.autoPlayVideo, 900 autoPlayVideo: this.autoPlayVideo,
898 autoPlayNextVideo: this.autoPlayNextVideo, 901 autoPlayNextVideo: this.autoPlayNextVideo,
diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts
index fd4da68ed..7456f37c5 100644
--- a/server/models/video/formatter/video-format-utils.ts
+++ b/server/models/video/formatter/video-format-utils.ts
@@ -1,7 +1,7 @@
1import { uuidToShort } from '@server/helpers/uuid'
2import { generateMagnetUri } from '@server/helpers/webtorrent' 1import { generateMagnetUri } from '@server/helpers/webtorrent'
3import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls' 2import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
4import { VideoViews } from '@server/lib/video-views' 3import { VideoViews } from '@server/lib/video-views'
4import { uuidToShort } from '@shared/extra-utils'
5import { VideoFile, VideosCommonQueryAfterSanitize } from '@shared/models' 5import { VideoFile, VideosCommonQueryAfterSanitize } from '@shared/models'
6import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects' 6import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects'
7import { Video, VideoDetails, VideoInclude } from '../../../../shared/models/videos' 7import { Video, VideoDetails, VideoInclude } from '../../../../shared/models/videos'
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts
index d462c20c7..b3cf26966 100644
--- a/server/models/video/schedule-video-update.ts
+++ b/server/models/video/schedule-video-update.ts
@@ -1,7 +1,7 @@
1import { Op, Transaction } from 'sequelize' 1import { Op, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdate } from '@server/types/models' 3import { MScheduleVideoUpdateFormattable, MScheduleVideoUpdate } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoPrivacy } from '../../../shared/models/videos' 5import { VideoPrivacy } from '../../../shared/models/videos'
6import { VideoModel } from './video' 6import { VideoModel } from './video'
7 7
diff --git a/server/models/video/sql/videos-id-list-query-builder.ts b/server/models/video/sql/videos-id-list-query-builder.ts
index d825225ab..76aafb883 100644
--- a/server/models/video/sql/videos-id-list-query-builder.ts
+++ b/server/models/video/sql/videos-id-list-query-builder.ts
@@ -367,9 +367,10 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
367 ' WHERE "videoShare"."videoId" = "video"."id"' + 367 ' WHERE "videoShare"."videoId" = "video"."id"' +
368 ' )' + 368 ' )' +
369 ' OR' + 369 ' OR' +
370 ' EXISTS (' + // Videos published by accounts we follow 370 ' EXISTS (' + // Videos published by channels or accounts we follow
371 ' SELECT 1 from "actorFollow" ' + 371 ' SELECT 1 from "actorFollow" ' +
372 ' WHERE "actorFollow"."targetActorId" = "account"."actorId" AND "actorFollow"."actorId" = :followerActorId ' + 372 ' WHERE ("actorFollow"."targetActorId" = "account"."actorId" OR "actorFollow"."targetActorId" = "videoChannel"."actorId") ' +
373 ' AND "actorFollow"."actorId" = :followerActorId ' +
373 ' AND "actorFollow"."state" = \'accepted\'' + 374 ' AND "actorFollow"."state" = \'accepted\'' +
374 ' )' 375 ' )'
375 376
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts
index 61dfb224d..7900e070d 100644
--- a/server/models/video/tag.ts
+++ b/server/models/video/tag.ts
@@ -1,7 +1,7 @@
1import { col, fn, QueryTypes, Transaction } from 'sequelize' 1import { col, fn, QueryTypes, Transaction } from 'sequelize'
2import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MTag } from '@server/types/models' 3import { MTag } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoPrivacy, VideoState } from '../../../shared/models/videos' 5import { VideoPrivacy, VideoState } from '../../../shared/models/videos'
6import { isVideoTagValid } from '../../helpers/custom-validators/videos' 6import { isVideoTagValid } from '../../helpers/custom-validators/videos'
7import { throwIfNotValid } from '../utils' 7import { throwIfNotValid } from '../utils'
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index 3388478d9..05c58cf19 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -17,7 +17,7 @@ import {
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { afterCommitIfTransaction } from '@server/helpers/database-utils' 18import { afterCommitIfTransaction } from '@server/helpers/database-utils'
19import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models' 19import { MThumbnail, MThumbnailVideo, MVideo } from '@server/types/models'
20import { AttributesOnly } from '@shared/core-utils' 20import { AttributesOnly } from '@shared/typescript-utils'
21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
22import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
23import { CONFIG } from '../../initializers/config' 23import { CONFIG } from '../../initializers/config'
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts
index 98f4ec9c5..1cd8224c0 100644
--- a/server/models/video/video-blacklist.ts
+++ b/server/models/video/video-blacklist.ts
@@ -1,7 +1,7 @@
1import { FindOptions } from 'sequelize' 1import { FindOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models' 3import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' 5import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos'
6import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' 6import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist'
7import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index d24be56c3..6b240f116 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -15,9 +15,9 @@ import {
15 Table, 15 Table,
16 UpdatedAt 16 UpdatedAt
17} from 'sequelize-typescript' 17} from 'sequelize-typescript'
18import { buildUUID } from '@server/helpers/uuid'
19import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models' 18import { MVideo, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
20import { AttributesOnly } from '@shared/core-utils' 19import { buildUUID } from '@shared/extra-utils'
20import { AttributesOnly } from '@shared/typescript-utils'
21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 21import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
22import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 22import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
23import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts
index 7d20a954d..1a1b8c88d 100644
--- a/server/models/video/video-change-ownership.ts
+++ b/server/models/video/video-change-ownership.ts
@@ -1,6 +1,6 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership' 2import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/types/models/video/video-change-ownership'
3import { AttributesOnly } from '@shared/core-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' 4import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos'
5import { AccountModel } from '../account/account' 5import { AccountModel } from '../account/account'
6import { getSort } from '../utils' 6import { getSort } from '../utils'
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index b652d8531..2c6669bcb 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -17,8 +17,10 @@ import {
17 Table, 17 Table,
18 UpdatedAt 18 UpdatedAt
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { CONFIG } from '@server/initializers/config'
20import { MAccountActor } from '@server/types/models' 21import { MAccountActor } from '@server/types/models'
21import { AttributesOnly, pick } from '@shared/core-utils' 22import { pick } from '@shared/core-utils'
23import { AttributesOnly } from '@shared/typescript-utils'
22import { ActivityPubActor } from '../../../shared/models/activitypub' 24import { ActivityPubActor } from '../../../shared/models/activitypub'
23import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' 25import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos'
24import { 26import {
@@ -44,7 +46,6 @@ import { setAsUpdated } from '../shared'
44import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' 46import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils'
45import { VideoModel } from './video' 47import { VideoModel } from './video'
46import { VideoPlaylistModel } from './video-playlist' 48import { VideoPlaylistModel } from './video-playlist'
47import { CONFIG } from '@server/initializers/config'
48 49
49export enum ScopeNames { 50export enum ScopeNames {
50 FOR_API = 'FOR_API', 51 FOR_API = 'FOR_API',
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index c89279c65..7f28b86b4 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -16,7 +16,7 @@ import {
16} from 'sequelize-typescript' 16} from 'sequelize-typescript'
17import { getServerActor } from '@server/models/application/application' 17import { getServerActor } from '@server/models/application/application'
18import { MAccount, MAccountId, MUserAccountId } from '@server/types/models' 18import { MAccount, MAccountId, MUserAccountId } from '@server/types/models'
19import { AttributesOnly } from '@shared/core-utils' 19import { AttributesOnly } from '@shared/typescript-utils'
20import { VideoPrivacy } from '@shared/models' 20import { VideoPrivacy } from '@shared/models'
21import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects' 21import { ActivityTagObject, ActivityTombstoneObject } from '../../../shared/models/activitypub/objects/common-objects'
22import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 22import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 87311c0ed..6f03fae3a 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -26,8 +26,8 @@ import { extractVideo } from '@server/helpers/video'
26import { getHLSPublicFileUrl, getWebTorrentPublicFileUrl } from '@server/lib/object-storage' 26import { getHLSPublicFileUrl, getWebTorrentPublicFileUrl } from '@server/lib/object-storage'
27import { getFSTorrentFilePath } from '@server/lib/paths' 27import { getFSTorrentFilePath } from '@server/lib/paths'
28import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models' 28import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoWithHost } from '@server/types/models'
29import { AttributesOnly } from '@shared/core-utils' 29import { VideoResolution, VideoStorage } from '@shared/models'
30import { VideoStorage } from '@shared/models' 30import { AttributesOnly } from '@shared/typescript-utils'
31import { 31import {
32 isVideoFileExtnameValid, 32 isVideoFileExtnameValid,
33 isVideoFileInfoHashValid, 33 isVideoFileInfoHashValid,
@@ -39,7 +39,6 @@ import {
39 LAZY_STATIC_PATHS, 39 LAZY_STATIC_PATHS,
40 MEMOIZE_LENGTH, 40 MEMOIZE_LENGTH,
41 MEMOIZE_TTL, 41 MEMOIZE_TTL,
42 MIMETYPES,
43 STATIC_DOWNLOAD_PATHS, 42 STATIC_DOWNLOAD_PATHS,
44 STATIC_PATHS, 43 STATIC_PATHS,
45 WEBSERVER 44 WEBSERVER
@@ -448,7 +447,7 @@ export class VideoFileModel extends Model<Partial<AttributesOnly<VideoFileModel>
448 } 447 }
449 448
450 isAudio () { 449 isAudio () {
451 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] 450 return this.resolution === VideoResolution.H_NOVIDEO
452 } 451 }
453 452
454 isLive () { 453 isLive () {
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts
index 5c73fb07c..c5c1a10f4 100644
--- a/server/models/video/video-import.ts
+++ b/server/models/video/video-import.ts
@@ -15,8 +15,8 @@ import {
15} from 'sequelize-typescript' 15} from 'sequelize-typescript'
16import { afterCommitIfTransaction } from '@server/helpers/database-utils' 16import { afterCommitIfTransaction } from '@server/helpers/database-utils'
17import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import' 17import { MVideoImportDefault, MVideoImportFormattable } from '@server/types/models/video/video-import'
18import { AttributesOnly } from '@shared/core-utils' 18import { VideoImport, VideoImportState } from '@shared/models'
19import { VideoImport, VideoImportState } from '../../../shared' 19import { AttributesOnly } from '@shared/typescript-utils'
20import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' 20import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports'
21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' 21import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos'
22import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' 22import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants'
diff --git a/server/models/video/video-job-info.ts b/server/models/video/video-job-info.ts
index 7da5128d7..7497addf1 100644
--- a/server/models/video/video-job-info.ts
+++ b/server/models/video/video-job-info.ts
@@ -1,6 +1,6 @@
1import { Op, QueryTypes, Transaction } from 'sequelize' 1import { Op, QueryTypes, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, IsInt, Model, Table, Unique, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, IsInt, Model, Table, Unique, UpdatedAt } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5 5
6export type VideoJobInfoColumnType = 'pendingMove' | 'pendingTranscode' 6export type VideoJobInfoColumnType = 'pendingMove' | 'pendingTranscode'
@@ -99,4 +99,19 @@ export class VideoJobInfoModel extends Model<Partial<AttributesOnly<VideoJobInfo
99 99
100 return pendingMove 100 return pendingMove
101 } 101 }
102
103 static async abortAllTasks (videoUUID: string, column: VideoJobInfoColumnType): Promise<void> {
104 const options = { type: QueryTypes.UPDATE as QueryTypes.UPDATE, bind: { videoUUID } }
105
106 await VideoJobInfoModel.sequelize.query(`
107 UPDATE
108 "videoJobInfo"
109 SET
110 "${column}" = 0,
111 "updatedAt" = NOW()
112 FROM "video"
113 WHERE
114 "video"."id" = "videoJobInfo"."videoId" AND "video"."uuid" = $videoUUID
115 `, options)
116 }
102} 117}
diff --git a/server/models/video/video-live.ts b/server/models/video/video-live.ts
index 0bc8da022..e3fdcc0ba 100644
--- a/server/models/video/video-live.ts
+++ b/server/models/video/video-live.ts
@@ -1,7 +1,7 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { AllowNull, BelongsTo, Column, CreatedAt, DataType, DefaultScope, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { WEBSERVER } from '@server/initializers/constants' 2import { WEBSERVER } from '@server/initializers/constants'
3import { MVideoLive, MVideoLiveVideo } from '@server/types/models' 3import { MVideoLive, MVideoLiveVideo } from '@server/types/models'
4import { AttributesOnly } from '@shared/core-utils' 4import { AttributesOnly } from '@shared/typescript-utils'
5import { LiveVideo, VideoState } from '@shared/models' 5import { LiveVideo, VideoState } from '@shared/models'
6import { VideoModel } from './video' 6import { VideoModel } from './video'
7import { VideoBlacklistModel } from './video-blacklist' 7import { VideoBlacklistModel } from './video-blacklist'
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index 82c832188..e20e32f8b 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -32,7 +32,7 @@ import { AccountModel } from '../account/account'
32import { getSort, throwIfNotValid } from '../utils' 32import { getSort, throwIfNotValid } from '../utils'
33import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video' 33import { ForAPIOptions, ScopeNames as VideoScopeNames, VideoModel } from './video'
34import { VideoPlaylistModel } from './video-playlist' 34import { VideoPlaylistModel } from './video-playlist'
35import { AttributesOnly } from '@shared/core-utils' 35import { AttributesOnly } from '@shared/typescript-utils'
36 36
37@Table({ 37@Table({
38 tableName: 'videoPlaylistElement', 38 tableName: 'videoPlaylistElement',
@@ -276,7 +276,7 @@ export class VideoPlaylistElementModel extends Model<Partial<AttributesOnly<Vide
276 } 276 }
277 277
278 const positionQuery = Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) 278 const positionQuery = Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`)
279 return VideoPlaylistElementModel.update({ position: positionQuery as any }, query) 279 return VideoPlaylistElementModel.update({ position: positionQuery }, query)
280 } 280 }
281 281
282 static increasePositionOf ( 282 static increasePositionOf (
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index 630684a88..0d43c795e 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -17,9 +17,10 @@ import {
17 Table, 17 Table,
18 UpdatedAt 18 UpdatedAt
19} from 'sequelize-typescript' 19} from 'sequelize-typescript'
20import { buildUUID, uuidToShort } from '@server/helpers/uuid'
21import { MAccountId, MChannelId } from '@server/types/models' 20import { MAccountId, MChannelId } from '@server/types/models'
22import { AttributesOnly, buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils' 21import { buildPlaylistEmbedPath, buildPlaylistWatchPath, pick } from '@shared/core-utils'
22import { buildUUID, uuidToShort } from '@shared/extra-utils'
23import { AttributesOnly } from '@shared/typescript-utils'
23import { ActivityIconObject } from '../../../shared/models/activitypub/objects' 24import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
24import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' 25import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
25import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 26import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts
index 505c305e2..f6659b992 100644
--- a/server/models/video/video-share.ts
+++ b/server/models/video/video-share.ts
@@ -1,6 +1,6 @@
1import { literal, Op, QueryTypes, Transaction } from 'sequelize' 1import { literal, Op, QueryTypes, Transaction } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 4import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
5import { CONSTRAINTS_FIELDS } from '../../initializers/constants' 5import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
6import { MActorDefault } from '../../types/models' 6import { MActorDefault } from '../../types/models'
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts
index 4643c5452..9957ffee3 100644
--- a/server/models/video/video-streaming-playlist.ts
+++ b/server/models/video/video-streaming-playlist.ts
@@ -18,10 +18,10 @@ import {
18import { getHLSPublicFileUrl } from '@server/lib/object-storage' 18import { getHLSPublicFileUrl } from '@server/lib/object-storage'
19import { VideoFileModel } from '@server/models/video/video-file' 19import { VideoFileModel } from '@server/models/video/video-file'
20import { MStreamingPlaylist, MVideo } from '@server/types/models' 20import { MStreamingPlaylist, MVideo } from '@server/types/models'
21import { AttributesOnly } from '@shared/core-utils' 21import { sha1 } from '@shared/extra-utils'
22import { VideoStorage } from '@shared/models' 22import { VideoStorage } from '@shared/models'
23import { AttributesOnly } from '@shared/typescript-utils'
23import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 24import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
24import { sha1 } from '../../helpers/core-utils'
25import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 25import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
26import { isArrayOf } from '../../helpers/custom-validators/misc' 26import { isArrayOf } from '../../helpers/custom-validators/misc'
27import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 27import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
@@ -198,6 +198,15 @@ export class VideoStreamingPlaylistModel extends Model<Partial<AttributesOnly<Vi
198 return Object.assign(playlist, { videoId: video.id, Video: video }) 198 return Object.assign(playlist, { videoId: video.id, Video: video })
199 } 199 }
200 200
201 static doesOwnedHLSPlaylistExist (videoUUID: string) {
202 const query = `SELECT 1 FROM "videoStreamingPlaylist" ` +
203 `INNER JOIN "video" ON "video"."id" = "videoStreamingPlaylist"."videoId" ` +
204 `AND "video"."remote" IS FALSE AND "video"."uuid" = $videoUUID ` +
205 `AND "storage" = ${VideoStorage.FILE_SYSTEM} LIMIT 1`
206
207 return doesExist(query, { videoUUID })
208 }
209
201 assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) { 210 assignP2PMediaLoaderInfoHashes (video: MVideo, files: unknown[]) {
202 const masterPlaylistUrl = this.getMasterPlaylistUrl(video) 211 const masterPlaylistUrl = this.getMasterPlaylistUrl(video)
203 212
diff --git a/server/models/video/video-tag.ts b/server/models/video/video-tag.ts
index 1285d375b..7e880c968 100644
--- a/server/models/video/video-tag.ts
+++ b/server/models/video/video-tag.ts
@@ -1,5 +1,5 @@
1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' 1import { Column, CreatedAt, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript'
2import { AttributesOnly } from '@shared/core-utils' 2import { AttributesOnly } from '@shared/typescript-utils'
3import { TagModel } from './tag' 3import { TagModel } from './tag'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5 5
diff --git a/server/models/video/video-view.ts b/server/models/video/video-view.ts
index b51f0f84d..d72df100f 100644
--- a/server/models/video/video-view.ts
+++ b/server/models/video/video-view.ts
@@ -1,6 +1,6 @@
1import { literal, Op } from 'sequelize' 1import { literal, Op } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Model, Table } from 'sequelize-typescript'
3import { AttributesOnly } from '@shared/core-utils' 3import { AttributesOnly } from '@shared/typescript-utils'
4import { VideoModel } from './video' 4import { VideoModel } from './video'
5 5
6@Table({ 6@Table({
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index 1050463d2..12b937574 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -25,7 +25,6 @@ import {
25 UpdatedAt 25 UpdatedAt
26} from 'sequelize-typescript' 26} from 'sequelize-typescript'
27import { buildNSFWFilter } from '@server/helpers/express-utils' 27import { buildNSFWFilter } from '@server/helpers/express-utils'
28import { uuidToShort } from '@server/helpers/uuid'
29import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video' 28import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video'
30import { LiveManager } from '@server/lib/live/live-manager' 29import { LiveManager } from '@server/lib/live/live-manager'
31import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage' 30import { removeHLSObjectStorage, removeWebTorrentObjectStorage } from '@server/lib/object-storage'
@@ -33,13 +32,24 @@ import { getHLSDirectory, getHLSRedundancyDirectory } from '@server/lib/paths'
33import { VideoPathManager } from '@server/lib/video-path-manager' 32import { VideoPathManager } from '@server/lib/video-path-manager'
34import { getServerActor } from '@server/models/application/application' 33import { getServerActor } from '@server/models/application/application'
35import { ModelCache } from '@server/models/model-cache' 34import { ModelCache } from '@server/models/model-cache'
36import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils' 35import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
37import { VideoFile, VideoInclude } from '@shared/models' 36import { ffprobePromise, getAudioStream, uuidToShort } from '@shared/extra-utils'
38import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared' 37import {
39import { VideoObject } from '../../../shared/models/activitypub/objects' 38 ResultList,
40import { Video, VideoDetails, VideoRateType, VideoStorage } from '../../../shared/models/videos' 39 ThumbnailType,
41import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 40 UserRight,
42import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 41 Video,
42 VideoDetails,
43 VideoFile,
44 VideoInclude,
45 VideoObject,
46 VideoPrivacy,
47 VideoRateType,
48 VideoState,
49 VideoStorage,
50 VideoStreamingPlaylistType
51} from '@shared/models'
52import { AttributesOnly } from '@shared/typescript-utils'
43import { peertubeTruncate } from '../../helpers/core-utils' 53import { peertubeTruncate } from '../../helpers/core-utils'
44import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 54import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
45import { exists, isBooleanValid } from '../../helpers/custom-validators/misc' 55import { exists, isBooleanValid } from '../../helpers/custom-validators/misc'
@@ -1668,12 +1678,20 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
1668 return peertubeTruncate(this.description, { length: maxLength }) 1678 return peertubeTruncate(this.description, { length: maxLength })
1669 } 1679 }
1670 1680
1671 getMaxQualityResolution () { 1681 getMaxQualityFileInfo () {
1672 const file = this.getMaxQualityFile() 1682 const file = this.getMaxQualityFile()
1673 const videoOrPlaylist = file.getVideoOrStreamingPlaylist() 1683 const videoOrPlaylist = file.getVideoOrStreamingPlaylist()
1674 1684
1675 return VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(videoOrPlaylist), originalFilePath => { 1685 return VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(videoOrPlaylist), async originalFilePath => {
1676 return getVideoFileResolution(originalFilePath) 1686 const probe = await ffprobePromise(originalFilePath)
1687
1688 const { audioStream } = await getAudioStream(originalFilePath, probe)
1689
1690 return {
1691 audioStream,
1692
1693 ...await getVideoFileResolution(originalFilePath, probe)
1694 }
1677 }) 1695 })
1678 } 1696 }
1679 1697
diff --git a/server/tests/api/activitypub/cleaner.ts b/server/tests/api/activitypub/cleaner.ts
index 51cf6e599..d0a151f5c 100644
--- a/server/tests/api/activitypub/cleaner.ts
+++ b/server/tests/api/activitypub/cleaner.ts
@@ -2,15 +2,15 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
5import { 6import {
6 cleanupTests, 7 cleanupTests,
7 createMultipleServers, 8 createMultipleServers,
8 doubleFollow, 9 doubleFollow,
9 PeerTubeServer, 10 PeerTubeServer,
10 setAccessTokensToServers, 11 setAccessTokensToServers,
11 wait,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14 14
15const expect = chai.expect 15const expect = chai.expect
16 16
@@ -270,6 +270,66 @@ describe('Test AP cleaner', function () {
270 await checkRemote('kyle') 270 await checkRemote('kyle')
271 }) 271 })
272 272
273 it('Should remove unavailable remote resources', async function () {
274 this.timeout(240000)
275
276 async function expectNotDeleted () {
277 {
278 const video = await servers[0].videos.get({ id: uuid })
279
280 expect(video.likes).to.equal(3)
281 expect(video.dislikes).to.equal(0)
282 }
283
284 {
285 const { total } = await servers[0].comments.listThreads({ videoId: uuid })
286 expect(total).to.equal(3)
287 }
288 }
289
290 async function expectDeleted () {
291 {
292 const video = await servers[0].videos.get({ id: uuid })
293
294 expect(video.likes).to.equal(2)
295 expect(video.dislikes).to.equal(0)
296 }
297
298 {
299 const { total } = await servers[0].comments.listThreads({ videoId: uuid })
300 expect(total).to.equal(2)
301 }
302 }
303
304 const uuid = (await servers[0].videos.quickUpload({ name: 'server 1 video 2' })).uuid
305
306 await waitJobs(servers)
307
308 for (const server of servers) {
309 await server.videos.rate({ id: uuid, rating: 'like' })
310 await server.comments.createThread({ videoId: uuid, text: 'comment' })
311 }
312
313 await waitJobs(servers)
314
315 await expectNotDeleted()
316
317 await servers[1].kill()
318
319 await wait(5000)
320 await expectNotDeleted()
321
322 let continueWhile = true
323
324 do {
325 try {
326 await expectDeleted()
327 continueWhile = false
328 } catch {
329 }
330 } while (continueWhile)
331 })
332
273 after(async function () { 333 after(async function () {
274 await cleanupTests(servers) 334 await cleanupTests(servers)
275 }) 335 })
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts
index c3e4b7f74..e69ab3cb9 100644
--- a/server/tests/api/activitypub/client.ts
+++ b/server/tests/api/activitypub/client.ts
@@ -10,7 +10,7 @@ import {
10 PeerTubeServer, 10 PeerTubeServer,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 setDefaultVideoChannel 12 setDefaultVideoChannel
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models' 14import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts
index 422a75d6e..cc71e82ea 100644
--- a/server/tests/api/activitypub/fetch.ts
+++ b/server/tests/api/activitypub/fetch.ts
@@ -2,7 +2,14 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7const expect = chai.expect 14const expect = chai.expect
8 15
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts
index 57b1cab23..25e1d9823 100644
--- a/server/tests/api/activitypub/helpers.ts
+++ b/server/tests/api/activitypub/helpers.ts
@@ -3,7 +3,8 @@
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cloneDeep } from 'lodash' 5import { cloneDeep } from 'lodash'
6import { buildAbsoluteFixturePath, buildRequestStub } from '@shared/extra-utils' 6import { buildRequestStub } from '@server/tests/shared'
7import { buildAbsoluteFixturePath } from '@shared/core-utils'
7import { buildSignedActivity } from '../../../helpers/activitypub' 8import { buildSignedActivity } from '../../../helpers/activitypub'
8import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto' 9import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto'
9 10
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index 81fee0044..71e1c40ba 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -1,6 +1,8 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { wait } from '@shared/core-utils'
5import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
4import { 6import {
5 cleanupTests, 7 cleanupTests,
6 createMultipleServers, 8 createMultipleServers,
@@ -9,10 +11,8 @@ import {
9 PeerTubeServer, 11 PeerTubeServer,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 setDefaultVideoChannel, 13 setDefaultVideoChannel,
12 wait,
13 waitJobs 14 waitJobs
14} from '@shared/extra-utils' 15} from '@shared/server-commands'
15import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
16 16
17describe('Test AP refresher', function () { 17describe('Test AP refresher', function () {
18 let servers: PeerTubeServer[] = [] 18 let servers: PeerTubeServer[] = []
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index 94d946563..c4cb5ea0d 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -6,9 +6,10 @@ import { activityPubContextify, buildSignedActivity } from '@server/helpers/acti
6import { buildDigest } from '@server/helpers/peertube-crypto' 6import { buildDigest } from '@server/helpers/peertube-crypto'
7import { HTTP_SIGNATURE } from '@server/initializers/constants' 7import { HTTP_SIGNATURE } from '@server/initializers/constants'
8import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils' 8import { buildGlobalHeaders } from '@server/lib/job-queue/handlers/utils/activitypub-http-utils'
9import { buildAbsoluteFixturePath, cleanupTests, createMultipleServers, killallServers, PeerTubeServer, wait } from '@shared/extra-utils' 9import { makeFollowRequest, makePOSTAPRequest } from '@server/tests/shared'
10import { makeFollowRequest, makePOSTAPRequest } from '@shared/extra-utils/requests/activitypub' 10import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
11import { HttpStatusCode } from '@shared/models' 11import { HttpStatusCode } from '@shared/models'
12import { cleanupTests, createMultipleServers, killallServers, PeerTubeServer } from '@shared/server-commands'
12 13
13const expect = chai.expect 14const expect = chai.expect
14 15
diff --git a/server/tests/api/check-params/abuses.ts b/server/tests/api/check-params/abuses.ts
index fb9a5fd8b..c4b051723 100644
--- a/server/tests/api/check-params/abuses.ts
+++ b/server/tests/api/check-params/abuses.ts
@@ -1,11 +1,10 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5import { AbuseCreate, AbuseState, HttpStatusCode } from '@shared/models'
4import { 6import {
5 AbusesCommand, 7 AbusesCommand,
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
9 cleanupTests, 8 cleanupTests,
10 createSingleServer, 9 createSingleServer,
11 doubleFollow, 10 doubleFollow,
@@ -14,8 +13,7 @@ import {
14 PeerTubeServer, 13 PeerTubeServer,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
16 waitJobs 15 waitJobs
17} from '@shared/extra-utils' 16} from '@shared/server-commands'
18import { AbuseCreate, AbuseState, HttpStatusCode } from '@shared/models'
19 17
20describe('Test abuses API validators', function () { 18describe('Test abuses API validators', function () {
21 const basePath = '/api/v1/abuses/' 19 const basePath = '/api/v1/abuses/'
diff --git a/server/tests/api/check-params/accounts.ts b/server/tests/api/check-params/accounts.ts
index 141d869b7..07f879e0e 100644
--- a/server/tests/api/check-params/accounts.ts
+++ b/server/tests/api/check-params/accounts.ts
@@ -1,15 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests,
9 createSingleServer,
10 PeerTubeServer
11} from '@shared/extra-utils'
12import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/server-commands'
13 7
14describe('Test accounts API validators', function () { 8describe('Test accounts API validators', function () {
15 const path = '/api/v1/accounts/' 9 const path = '/api/v1/accounts/'
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts
index 7d5fae5cf..36526d494 100644
--- a/server/tests/api/check-params/blocklist.ts
+++ b/server/tests/api/check-params/blocklist.ts
@@ -1,10 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5import { HttpStatusCode } from '@shared/models'
4import { 6import {
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests, 7 cleanupTests,
9 createMultipleServers, 8 createMultipleServers,
10 doubleFollow, 9 doubleFollow,
@@ -13,8 +12,7 @@ import {
13 makePostBodyRequest, 12 makePostBodyRequest,
14 PeerTubeServer, 13 PeerTubeServer,
15 setAccessTokensToServers 14 setAccessTokensToServers
16} from '@shared/extra-utils' 15} from '@shared/server-commands'
17import { HttpStatusCode } from '@shared/models'
18 16
19describe('Test blocklist API validators', function () { 17describe('Test blocklist API validators', function () {
20 let servers: PeerTubeServer[] 18 let servers: PeerTubeServer[]
@@ -481,6 +479,78 @@ describe('Test blocklist API validators', function () {
481 }) 479 })
482 }) 480 })
483 481
482 describe('When getting blocklist status', function () {
483 const path = '/api/v1/blocklist/status'
484
485 it('Should fail with a bad token', async function () {
486 await makeGetRequest({
487 url: server.url,
488 path,
489 token: 'false',
490 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
491 })
492 })
493
494 it('Should fail with a bad accounts field', async function () {
495 await makeGetRequest({
496 url: server.url,
497 path,
498 query: {
499 accounts: 1
500 },
501 expectedStatus: HttpStatusCode.BAD_REQUEST_400
502 })
503
504 await makeGetRequest({
505 url: server.url,
506 path,
507 query: {
508 accounts: [ 1 ]
509 },
510 expectedStatus: HttpStatusCode.BAD_REQUEST_400
511 })
512 })
513
514 it('Should fail with a bad hosts field', async function () {
515 await makeGetRequest({
516 url: server.url,
517 path,
518 query: {
519 hosts: 1
520 },
521 expectedStatus: HttpStatusCode.BAD_REQUEST_400
522 })
523
524 await makeGetRequest({
525 url: server.url,
526 path,
527 query: {
528 hosts: [ 1 ]
529 },
530 expectedStatus: HttpStatusCode.BAD_REQUEST_400
531 })
532 })
533
534 it('Should succeed with the correct parameters', async function () {
535 await makeGetRequest({
536 url: server.url,
537 path,
538 query: {},
539 expectedStatus: HttpStatusCode.OK_200
540 })
541
542 await makeGetRequest({
543 url: server.url,
544 path,
545 query: {
546 hosts: [ 'example.com' ],
547 accounts: [ 'john@example.com' ]
548 },
549 expectedStatus: HttpStatusCode.OK_200
550 })
551 })
552 })
553
484 after(async function () { 554 after(async function () {
485 await cleanupTests(servers) 555 await cleanupTests(servers)
486 }) 556 })
diff --git a/server/tests/api/check-params/bulk.ts b/server/tests/api/check-params/bulk.ts
index bc9d7784d..9bd0016cf 100644
--- a/server/tests/api/check-params/bulk.ts
+++ b/server/tests/api/check-params/bulk.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 4import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6 6
7describe('Test bulk API validators', function () { 7describe('Test bulk API validators', function () {
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index d0cd7722b..3cccb612a 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -10,7 +10,7 @@ import {
10 makePutBodyRequest, 10 makePutBodyRequest,
11 PeerTubeServer, 11 PeerTubeServer,
12 setAccessTokensToServers 12 setAccessTokensToServers
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { CustomConfig, HttpStatusCode } from '@shared/models' 14import { CustomConfig, HttpStatusCode } from '@shared/models'
15 15
16describe('Test config API validators', function () { 16describe('Test config API validators', function () {
@@ -54,6 +54,18 @@ describe('Test config API validators', function () {
54 whitelisted: true 54 whitelisted: true
55 } 55 }
56 }, 56 },
57 client: {
58 videos: {
59 miniature: {
60 preferAuthorDisplayName: false
61 }
62 },
63 menu: {
64 login: {
65 redirectOnSingleExternalAuth: false
66 }
67 }
68 },
57 cache: { 69 cache: {
58 previews: { 70 previews: {
59 size: 2 71 size: 2
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts
index 9f86fecc6..9db442b0b 100644
--- a/server/tests/api/check-params/contact-form.ts
+++ b/server/tests/api/check-params/contact-form.ts
@@ -1,9 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createSingleServer, killallServers, MockSmtpServer, PeerTubeServer } from '@shared/extra-utils' 4import { MockSmtpServer } from '@server/tests/shared'
5import { ContactFormCommand } from '@shared/extra-utils/server'
6import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, ContactFormCommand, createSingleServer, killallServers, PeerTubeServer } from '@shared/server-commands'
7 7
8describe('Test contact form API validators', function () { 8describe('Test contact form API validators', function () {
9 let server: PeerTubeServer 9 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/custom-pages.ts b/server/tests/api/check-params/custom-pages.ts
index 9fbbea315..a102ee437 100644
--- a/server/tests/api/check-params/custom-pages.ts
+++ b/server/tests/api/check-params/custom-pages.ts
@@ -8,7 +8,7 @@ import {
8 makePutBodyRequest, 8 makePutBodyRequest,
9 PeerTubeServer, 9 PeerTubeServer,
10 setAccessTokensToServers 10 setAccessTokensToServers
11} from '@shared/extra-utils' 11} from '@shared/server-commands'
12import { HttpStatusCode } from '@shared/models' 12import { HttpStatusCode } from '@shared/models'
13 13
14describe('Test custom pages validators', function () { 14describe('Test custom pages validators', function () {
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts
index a55786359..cfa44deca 100644
--- a/server/tests/api/check-params/debug.ts
+++ b/server/tests/api/check-params/debug.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 4import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6 6
7describe('Test debug API validators', function () { 7describe('Test debug API validators', function () {
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts
index 2bc9f6b96..d4dae5a75 100644
--- a/server/tests/api/check-params/follows.ts
+++ b/server/tests/api/check-params/follows.ts
@@ -1,10 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5import { HttpStatusCode } from '@shared/models'
4import { 6import {
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests, 7 cleanupTests,
9 createSingleServer, 8 createSingleServer,
10 makeDeleteRequest, 9 makeDeleteRequest,
@@ -12,8 +11,7 @@ import {
12 makePostBodyRequest, 11 makePostBodyRequest,
13 PeerTubeServer, 12 PeerTubeServer,
14 setAccessTokensToServers 13 setAccessTokensToServers
15} from '@shared/extra-utils' 14} from '@shared/server-commands'
16import { HttpStatusCode } from '@shared/models'
17 15
18describe('Test server follows API validators', function () { 16describe('Test server follows API validators', function () {
19 let server: PeerTubeServer 17 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts
index 23d95d8e4..d85961d62 100644
--- a/server/tests/api/check-params/jobs.ts
+++ b/server/tests/api/check-params/jobs.ts
@@ -1,17 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests,
9 createSingleServer,
10 makeGetRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@shared/extra-utils'
14import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
15 7
16describe('Test jobs API validators', function () { 8describe('Test jobs API validators', function () {
17 const path = '/api/v1/jobs/failed' 9 const path = '/api/v1/jobs/failed'
diff --git a/server/tests/api/check-params/live.ts b/server/tests/api/check-params/live.ts
index 8e1d655d4..8aee6164c 100644
--- a/server/tests/api/check-params/live.ts
+++ b/server/tests/api/check-params/live.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { buildAbsoluteFixturePath } from '@shared/core-utils'
6import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
5import { 7import {
6 buildAbsoluteFixturePath,
7 cleanupTests, 8 cleanupTests,
8 createSingleServer, 9 createSingleServer,
9 LiveCommand, 10 LiveCommand,
@@ -13,8 +14,7 @@ import {
13 sendRTMPStream, 14 sendRTMPStream,
14 setAccessTokensToServers, 15 setAccessTokensToServers,
15 stopFfmpeg 16 stopFfmpeg
16} from '@shared/extra-utils' 17} from '@shared/server-commands'
17import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
18 18
19describe('Test video lives API validator', function () { 19describe('Test video lives API validator', function () {
20 const path = '/api/v1/videos/live' 20 const path = '/api/v1/videos/live'
diff --git a/server/tests/api/check-params/logs.ts b/server/tests/api/check-params/logs.ts
index 05372257a..970671c15 100644
--- a/server/tests/api/check-params/logs.ts
+++ b/server/tests/api/check-params/logs.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 4import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6 6
7describe('Test logs API validators', function () { 7describe('Test logs API validators', function () {
diff --git a/server/tests/api/check-params/my-user.ts b/server/tests/api/check-params/my-user.ts
index d35284d60..95f2122ae 100644
--- a/server/tests/api/check-params/my-user.ts
+++ b/server/tests/api/check-params/my-user.ts
@@ -1,22 +1,19 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination, MockSmtpServer } from '@server/tests/shared'
5import { buildAbsoluteFixturePath } from '@shared/core-utils'
6import { HttpStatusCode, UserRole, VideoCreateResult } from '@shared/models'
4import { 7import {
5 buildAbsoluteFixturePath,
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
9 cleanupTests, 8 cleanupTests,
10 createSingleServer, 9 createSingleServer,
11 makeGetRequest, 10 makeGetRequest,
12 makePutBodyRequest, 11 makePutBodyRequest,
13 makeUploadRequest, 12 makeUploadRequest,
14 MockSmtpServer,
15 PeerTubeServer, 13 PeerTubeServer,
16 setAccessTokensToServers, 14 setAccessTokensToServers,
17 UsersCommand 15 UsersCommand
18} from '@shared/extra-utils' 16} from '@shared/server-commands'
19import { HttpStatusCode, UserRole, VideoCreateResult } from '@shared/models'
20 17
21describe('Test my user API validators', function () { 18describe('Test my user API validators', function () {
22 const path = '/api/v1/users/' 19 const path = '/api/v1/users/'
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts
index 33f84ecbc..c3f15b86e 100644
--- a/server/tests/api/check-params/plugins.ts
+++ b/server/tests/api/check-params/plugins.ts
@@ -1,10 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5import { HttpStatusCode, PeerTubePlugin, PluginType } from '@shared/models'
4import { 6import {
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests, 7 cleanupTests,
9 createSingleServer, 8 createSingleServer,
10 makeGetRequest, 9 makeGetRequest,
@@ -12,8 +11,7 @@ import {
12 makePutBodyRequest, 11 makePutBodyRequest,
13 PeerTubeServer, 12 PeerTubeServer,
14 setAccessTokensToServers 13 setAccessTokensToServers
15} from '@shared/extra-utils' 14} from '@shared/server-commands'
16import { HttpStatusCode, PeerTubePlugin, PluginType } from '@shared/models'
17 15
18describe('Test server plugins API validators', function () { 16describe('Test server plugins API validators', function () {
19 let server: PeerTubeServer 17 let server: PeerTubeServer
@@ -30,7 +28,7 @@ describe('Test server plugins API validators', function () {
30 // --------------------------------------------------------------- 28 // ---------------------------------------------------------------
31 29
32 before(async function () { 30 before(async function () {
33 this.timeout(30000) 31 this.timeout(60000)
34 32
35 server = await createSingleServer(1) 33 server = await createSingleServer(1)
36 34
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts
index d9f905549..04519cf23 100644
--- a/server/tests/api/check-params/redundancy.ts
+++ b/server/tests/api/check-params/redundancy.ts
@@ -1,10 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5import { HttpStatusCode, VideoCreateResult } from '@shared/models'
4import { 6import {
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests, 7 cleanupTests,
9 createMultipleServers, 8 createMultipleServers,
10 doubleFollow, 9 doubleFollow,
@@ -15,8 +14,7 @@ import {
15 PeerTubeServer, 14 PeerTubeServer,
16 setAccessTokensToServers, 15 setAccessTokensToServers,
17 waitJobs 16 waitJobs
18} from '@shared/extra-utils' 17} from '@shared/server-commands'
19import { HttpStatusCode, VideoCreateResult } from '@shared/models'
20 18
21describe('Test server redundancy API validators', function () { 19describe('Test server redundancy API validators', function () {
22 let servers: PeerTubeServer[] 20 let servers: PeerTubeServer[]
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts
index cc15d2593..ca0fbf31d 100644
--- a/server/tests/api/check-params/search.ts
+++ b/server/tests/api/check-params/search.ts
@@ -1,17 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests,
9 createSingleServer,
10 makeGetRequest,
11 PeerTubeServer,
12 setAccessTokensToServers
13} from '@shared/extra-utils'
14import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
15 7
16function updateSearchIndex (server: PeerTubeServer, enabled: boolean, disableLocalSearch = false) { 8function updateSearchIndex (server: PeerTubeServer, enabled: boolean, disableLocalSearch = false) {
17 return server.config.updateCustomSubConfig({ 9 return server.config.updateCustomSubConfig({
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts
index 8d795fabc..e63f09884 100644
--- a/server/tests/api/check-params/services.ts
+++ b/server/tests/api/check-params/services.ts
@@ -8,7 +8,7 @@ import {
8 PeerTubeServer, 8 PeerTubeServer,
9 setAccessTokensToServers, 9 setAccessTokensToServers,
10 setDefaultVideoChannel 10 setDefaultVideoChannel
11} from '@shared/extra-utils' 11} from '@shared/server-commands'
12import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models' 12import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
13 13
14describe('Test services API validators', function () { 14describe('Test services API validators', function () {
diff --git a/server/tests/api/check-params/transcoding.ts b/server/tests/api/check-params/transcoding.ts
index a8daafe3e..333012940 100644
--- a/server/tests/api/check-params/transcoding.ts
+++ b/server/tests/api/check-params/transcoding.ts
@@ -1,8 +1,15 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils'
5import { HttpStatusCode, UserRole } from '@shared/models' 4import { HttpStatusCode, UserRole } from '@shared/models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7describe('Test transcoding API validators', function () { 14describe('Test transcoding API validators', function () {
8 let servers: PeerTubeServer[] 15 let servers: PeerTubeServer[]
@@ -16,7 +23,7 @@ describe('Test transcoding API validators', function () {
16 // --------------------------------------------------------------- 23 // ---------------------------------------------------------------
17 24
18 before(async function () { 25 before(async function () {
19 this.timeout(60000) 26 this.timeout(120000)
20 27
21 servers = await createMultipleServers(2) 28 servers = await createMultipleServers(2)
22 await setAccessTokensToServers(servers) 29 await setAccessTokensToServers(servers)
diff --git a/server/tests/api/check-params/upload-quota.ts b/server/tests/api/check-params/upload-quota.ts
index 322e93d0d..deb4a7aa3 100644
--- a/server/tests/api/check-params/upload-quota.ts
+++ b/server/tests/api/check-params/upload-quota.ts
@@ -2,18 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { FIXTURE_URLS } from '@server/tests/shared'
5import { randomInt } from '@shared/core-utils' 6import { randomInt } from '@shared/core-utils'
7import { HttpStatusCode, VideoImportState, VideoPrivacy } from '@shared/models'
6import { 8import {
7 cleanupTests, 9 cleanupTests,
8 createSingleServer, 10 createSingleServer,
9 FIXTURE_URLS,
10 PeerTubeServer, 11 PeerTubeServer,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 setDefaultVideoChannel, 13 setDefaultVideoChannel,
13 VideosCommand, 14 VideosCommand,
14 waitJobs 15 waitJobs
15} from '@shared/extra-utils' 16} from '@shared/server-commands'
16import { HttpStatusCode, VideoImportState, VideoPrivacy } from '@shared/models'
17 17
18describe('Test upload quota', function () { 18describe('Test upload quota', function () {
19 let server: PeerTubeServer 19 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
index 17edf5aa1..4bc8084a1 100644
--- a/server/tests/api/check-params/user-notifications.ts
+++ b/server/tests/api/check-params/user-notifications.ts
@@ -2,20 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import { io } from 'socket.io-client' 4import { io } from 'socket.io-client'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
7import { HttpStatusCode, UserNotificationSetting, UserNotificationSettingValue } from '@shared/models'
5import { 8import {
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
9 cleanupTests, 9 cleanupTests,
10 createSingleServer, 10 createSingleServer,
11 makeGetRequest, 11 makeGetRequest,
12 makePostBodyRequest, 12 makePostBodyRequest,
13 makePutBodyRequest, 13 makePutBodyRequest,
14 PeerTubeServer, 14 PeerTubeServer,
15 setAccessTokensToServers, 15 setAccessTokensToServers
16 wait 16} from '@shared/server-commands'
17} from '@shared/extra-utils'
18import { HttpStatusCode, UserNotificationSetting, UserNotificationSettingValue } from '@shared/models'
19 17
20describe('Test user notifications API validators', function () { 18describe('Test user notifications API validators', function () {
21 let server: PeerTubeServer 19 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/user-subscriptions.ts b/server/tests/api/check-params/user-subscriptions.ts
index 624069c80..a13ed5aa3 100644
--- a/server/tests/api/check-params/user-subscriptions.ts
+++ b/server/tests/api/check-params/user-subscriptions.ts
@@ -2,9 +2,6 @@
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests, 5 cleanupTests,
9 createSingleServer, 6 createSingleServer,
10 makeDeleteRequest, 7 makeDeleteRequest,
@@ -13,8 +10,9 @@ import {
13 PeerTubeServer, 10 PeerTubeServer,
14 setAccessTokensToServers, 11 setAccessTokensToServers,
15 waitJobs 12 waitJobs
16} from '@shared/extra-utils' 13} from '@shared/server-commands'
17import { HttpStatusCode } from '@shared/models' 14import { HttpStatusCode } from '@shared/models'
15import { checkBadStartPagination, checkBadCountPagination, checkBadSortPagination } from '@server/tests/shared'
18 16
19describe('Test user subscriptions API validators', function () { 17describe('Test user subscriptions API validators', function () {
20 const path = '/api/v1/users/me/subscriptions' 18 const path = '/api/v1/users/me/subscriptions'
diff --git a/server/tests/api/check-params/users-admin.ts b/server/tests/api/check-params/users-admin.ts
index f71414a6b..d8353f83b 100644
--- a/server/tests/api/check-params/users-admin.ts
+++ b/server/tests/api/check-params/users-admin.ts
@@ -2,21 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination, MockSmtpServer } from '@server/tests/shared'
6import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models'
5import { 7import {
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
9 cleanupTests, 8 cleanupTests,
10 createSingleServer, 9 createSingleServer,
11 killallServers, 10 killallServers,
12 makeGetRequest, 11 makeGetRequest,
13 makePostBodyRequest, 12 makePostBodyRequest,
14 makePutBodyRequest, 13 makePutBodyRequest,
15 MockSmtpServer,
16 PeerTubeServer, 14 PeerTubeServer,
17 setAccessTokensToServers 15 setAccessTokensToServers
18} from '@shared/extra-utils' 16} from '@shared/server-commands'
19import { HttpStatusCode, UserAdminFlag, UserRole } from '@shared/models'
20 17
21describe('Test users admin API validators', function () { 18describe('Test users admin API validators', function () {
22 const path = '/api/v1/users/' 19 const path = '/api/v1/users/'
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5f9cbc5eb..84254945c 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -2,15 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { 5import { MockSmtpServer } from '@server/tests/shared'
6 cleanupTests,
7 createSingleServer,
8 makePostBodyRequest,
9 MockSmtpServer,
10 PeerTubeServer,
11 setAccessTokensToServers
12} from '@shared/extra-utils'
13import { HttpStatusCode, UserRole } from '@shared/models' 6import { HttpStatusCode, UserRole } from '@shared/models'
7import { cleanupTests, createSingleServer, makePostBodyRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
14 8
15describe('Test users API validators', function () { 9describe('Test users API validators', function () {
16 const path = '/api/v1/users/' 10 const path = '/api/v1/users/'
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
index 1f926d227..1aab60826 100644
--- a/server/tests/api/check-params/video-blacklist.ts
+++ b/server/tests/api/check-params/video-blacklist.ts
@@ -2,11 +2,10 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
6import { HttpStatusCode, VideoBlacklistType } from '@shared/models'
5import { 7import {
6 BlacklistCommand, 8 BlacklistCommand,
7 checkBadCountPagination,
8 checkBadSortPagination,
9 checkBadStartPagination,
10 cleanupTests, 9 cleanupTests,
11 createMultipleServers, 10 createMultipleServers,
12 doubleFollow, 11 doubleFollow,
@@ -15,8 +14,7 @@ import {
15 PeerTubeServer, 14 PeerTubeServer,
16 setAccessTokensToServers, 15 setAccessTokensToServers,
17 waitJobs 16 waitJobs
18} from '@shared/extra-utils' 17} from '@shared/server-commands'
19import { HttpStatusCode, VideoBlacklistType } from '@shared/models'
20 18
21describe('Test video blacklist API validators', function () { 19describe('Test video blacklist API validators', function () {
22 let servers: PeerTubeServer[] 20 let servers: PeerTubeServer[]
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts
index 84c6c1355..9881df80c 100644
--- a/server/tests/api/check-params/video-captions.ts
+++ b/server/tests/api/check-params/video-captions.ts
@@ -1,8 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { buildAbsoluteFixturePath } from '@shared/core-utils'
5import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
4import { 6import {
5 buildAbsoluteFixturePath,
6 cleanupTests, 7 cleanupTests,
7 createSingleServer, 8 createSingleServer,
8 makeDeleteRequest, 9 makeDeleteRequest,
@@ -10,8 +11,7 @@ import {
10 makeUploadRequest, 11 makeUploadRequest,
11 PeerTubeServer, 12 PeerTubeServer,
12 setAccessTokensToServers 13 setAccessTokensToServers
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
15 15
16describe('Test video captions API validator', function () { 16describe('Test video captions API validator', function () {
17 const path = '/api/v1/videos/' 17 const path = '/api/v1/videos/'
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index e86c315fa..1e9732fe9 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -3,12 +3,11 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
7import { buildAbsoluteFixturePath } from '@shared/core-utils'
8import { HttpStatusCode, VideoChannelUpdate } from '@shared/models'
6import { 9import {
7 buildAbsoluteFixturePath,
8 ChannelsCommand, 10 ChannelsCommand,
9 checkBadCountPagination,
10 checkBadSortPagination,
11 checkBadStartPagination,
12 cleanupTests, 11 cleanupTests,
13 createSingleServer, 12 createSingleServer,
14 makeGetRequest, 13 makeGetRequest,
@@ -17,8 +16,7 @@ import {
17 makeUploadRequest, 16 makeUploadRequest,
18 PeerTubeServer, 17 PeerTubeServer,
19 setAccessTokensToServers 18 setAccessTokensToServers
20} from '@shared/extra-utils' 19} from '@shared/server-commands'
21import { HttpStatusCode, VideoChannelUpdate } from '@shared/models'
22 20
23const expect = chai.expect 21const expect = chai.expect
24 22
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts
index 8d63fe70c..829f3c8b1 100644
--- a/server/tests/api/check-params/video-comments.ts
+++ b/server/tests/api/check-params/video-comments.ts
@@ -2,10 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
6import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
5import { 7import {
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
9 cleanupTests, 8 cleanupTests,
10 createSingleServer, 9 createSingleServer,
11 makeDeleteRequest, 10 makeDeleteRequest,
@@ -13,8 +12,7 @@ import {
13 makePostBodyRequest, 12 makePostBodyRequest,
14 PeerTubeServer, 13 PeerTubeServer,
15 setAccessTokensToServers 14 setAccessTokensToServers
16} from '@shared/extra-utils' 15} from '@shared/server-commands'
17import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
18 16
19const expect = chai.expect 17const expect = chai.expect
20 18
diff --git a/server/tests/api/check-params/video-files.ts b/server/tests/api/check-params/video-files.ts
index 3ccdf5f49..8c0795092 100644
--- a/server/tests/api/check-params/video-files.ts
+++ b/server/tests/api/check-params/video-files.ts
@@ -1,8 +1,15 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils'
5import { HttpStatusCode, UserRole } from '@shared/models' 4import { HttpStatusCode, UserRole } from '@shared/models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7describe('Test videos files', function () { 14describe('Test videos files', function () {
8 let servers: PeerTubeServer[] 15 let servers: PeerTubeServer[]
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts
index 6c31daa9b..da05793a0 100644
--- a/server/tests/api/check-params/video-imports.ts
+++ b/server/tests/api/check-params/video-imports.ts
@@ -2,21 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
5import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination, FIXTURE_URLS } from '@server/tests/shared'
6import { buildAbsoluteFixturePath } from '@shared/core-utils'
7import { HttpStatusCode, VideoPrivacy } from '@shared/models'
5import { 8import {
6 buildAbsoluteFixturePath,
7 checkBadCountPagination,
8 checkBadSortPagination,
9 checkBadStartPagination,
10 cleanupTests, 9 cleanupTests,
11 createSingleServer, 10 createSingleServer,
12 FIXTURE_URLS,
13 makeGetRequest, 11 makeGetRequest,
14 makePostBodyRequest, 12 makePostBodyRequest,
15 makeUploadRequest, 13 makeUploadRequest,
16 PeerTubeServer, 14 PeerTubeServer,
17 setAccessTokensToServers 15 setAccessTokensToServers
18} from '@shared/extra-utils' 16} from '@shared/server-commands'
19import { HttpStatusCode, VideoPrivacy } from '@shared/models'
20 17
21describe('Test video imports API validator', function () { 18describe('Test video imports API validator', function () {
22 const path = '/api/v1/videos/imports' 19 const path = '/api/v1/videos/imports'
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index e4d541b48..4b17ce7db 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -1,18 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '@server/tests/shared'
5 checkBadCountPagination,
6 checkBadSortPagination,
7 checkBadStartPagination,
8 cleanupTests,
9 createSingleServer,
10 makeGetRequest,
11 PeerTubeServer,
12 PlaylistsCommand,
13 setAccessTokensToServers,
14 setDefaultVideoChannel
15} from '@shared/extra-utils'
16import { 5import {
17 HttpStatusCode, 6 HttpStatusCode,
18 VideoPlaylistCreate, 7 VideoPlaylistCreate,
@@ -23,6 +12,15 @@ import {
23 VideoPlaylistReorder, 12 VideoPlaylistReorder,
24 VideoPlaylistType 13 VideoPlaylistType
25} from '@shared/models' 14} from '@shared/models'
15import {
16 cleanupTests,
17 createSingleServer,
18 makeGetRequest,
19 PeerTubeServer,
20 PlaylistsCommand,
21 setAccessTokensToServers,
22 setDefaultVideoChannel
23} from '@shared/server-commands'
26 24
27describe('Test video playlists API validator', function () { 25describe('Test video playlists API validator', function () {
28 let server: PeerTubeServer 26 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/videos-common-filters.ts b/server/tests/api/check-params/videos-common-filters.ts
index f2b5bee8e..6b3ec917e 100644
--- a/server/tests/api/check-params/videos-common-filters.ts
+++ b/server/tests/api/check-params/videos-common-filters.ts
@@ -8,7 +8,7 @@ import {
8 PeerTubeServer, 8 PeerTubeServer,
9 setAccessTokensToServers, 9 setAccessTokensToServers,
10 setDefaultVideoChannel 10 setDefaultVideoChannel
11} from '@shared/extra-utils' 11} from '@shared/server-commands'
12import { HttpStatusCode, UserRole, VideoInclude, VideoPrivacy } from '@shared/models' 12import { HttpStatusCode, UserRole, VideoInclude, VideoPrivacy } from '@shared/models'
13 13
14describe('Test video filters validators', function () { 14describe('Test video filters validators', function () {
diff --git a/server/tests/api/check-params/videos-history.ts b/server/tests/api/check-params/videos-history.ts
index c3c309ed2..31a0752c7 100644
--- a/server/tests/api/check-params/videos-history.ts
+++ b/server/tests/api/check-params/videos-history.ts
@@ -1,9 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { checkBadCountPagination, checkBadStartPagination } from '@server/tests/shared'
5import { HttpStatusCode } from '@shared/models'
4import { 6import {
5 checkBadCountPagination,
6 checkBadStartPagination,
7 cleanupTests, 7 cleanupTests,
8 createSingleServer, 8 createSingleServer,
9 makeGetRequest, 9 makeGetRequest,
@@ -11,8 +11,7 @@ import {
11 makePutBodyRequest, 11 makePutBodyRequest,
12 PeerTubeServer, 12 PeerTubeServer,
13 setAccessTokensToServers 13 setAccessTokensToServers
14} from '@shared/extra-utils' 14} from '@shared/server-commands'
15import { HttpStatusCode } from '@shared/models'
16 15
17describe('Test videos history API validator', function () { 16describe('Test videos history API validator', function () {
18 const myHistoryPath = '/api/v1/users/me/history/videos' 17 const myHistoryPath = '/api/v1/users/me/history/videos'
diff --git a/server/tests/api/check-params/videos-overviews.ts b/server/tests/api/check-params/videos-overviews.ts
index c2139d74b..1da15dc43 100644
--- a/server/tests/api/check-params/videos-overviews.ts
+++ b/server/tests/api/check-params/videos-overviews.ts
@@ -1,7 +1,7 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/extra-utils' 4import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/server-commands'
5 5
6describe('Test videos overview', function () { 6describe('Test videos overview', function () {
7 let server: PeerTubeServer 7 let server: PeerTubeServer
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index d02b6e156..4cc70f5cc 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -4,12 +4,10 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { join } from 'path' 6import { join } from 'path'
7import { randomInt } from '@shared/core-utils' 7import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination, checkUploadVideoParam } from '@server/tests/shared'
8import { randomInt, root } from '@shared/core-utils'
9import { HttpStatusCode, PeerTubeProblemDocument, VideoCreateResult, VideoPrivacy } from '@shared/models'
8import { 10import {
9 checkBadCountPagination,
10 checkBadSortPagination,
11 checkBadStartPagination,
12 checkUploadVideoParam,
13 cleanupTests, 11 cleanupTests,
14 createSingleServer, 12 createSingleServer,
15 makeDeleteRequest, 13 makeDeleteRequest,
@@ -17,10 +15,8 @@ import {
17 makePutBodyRequest, 15 makePutBodyRequest,
18 makeUploadRequest, 16 makeUploadRequest,
19 PeerTubeServer, 17 PeerTubeServer,
20 root,
21 setAccessTokensToServers 18 setAccessTokensToServers
22} from '@shared/extra-utils' 19} from '@shared/server-commands'
23import { HttpStatusCode, PeerTubeProblemDocument, VideoCreateResult, VideoPrivacy } from '@shared/models'
24 20
25const expect = chai.expect 21const expect = chai.expect
26 22
diff --git a/server/tests/api/live/live-constraints.ts b/server/tests/api/live/live-constraints.ts
index 6a6a11796..909399836 100644
--- a/server/tests/api/live/live-constraints.ts
+++ b/server/tests/api/live/live-constraints.ts
@@ -2,9 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
5import { VideoPrivacy } from '@shared/models' 6import { VideoPrivacy } from '@shared/models'
6import { 7import {
7 checkLiveCleanupAfterSave,
8 cleanupTests, 8 cleanupTests,
9 ConfigCommand, 9 ConfigCommand,
10 createMultipleServers, 10 createMultipleServers,
@@ -12,9 +12,9 @@ import {
12 PeerTubeServer, 12 PeerTubeServer,
13 setAccessTokensToServers, 13 setAccessTokensToServers,
14 setDefaultVideoChannel, 14 setDefaultVideoChannel,
15 wait,
16 waitJobs 15 waitJobs
17} from '../../../../shared/extra-utils' 16} from '@shared/server-commands'
17import { checkLiveCleanupAfterSave } from '../../shared'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/api/live/live-permanent.ts b/server/tests/api/live/live-permanent.ts
index c5f942901..3e6fec453 100644
--- a/server/tests/api/live/live-permanent.ts
+++ b/server/tests/api/live/live-permanent.ts
@@ -2,6 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
5import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models' 6import { LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models'
6import { 7import {
7 cleanupTests, 8 cleanupTests,
@@ -12,9 +13,8 @@ import {
12 setAccessTokensToServers, 13 setAccessTokensToServers,
13 setDefaultVideoChannel, 14 setDefaultVideoChannel,
14 stopFfmpeg, 15 stopFfmpeg,
15 wait,
16 waitJobs 16 waitJobs
17} from '../../../../shared/extra-utils' 17} from '@shared/server-commands'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
@@ -140,7 +140,7 @@ describe('Permanent live', function () {
140 }) 140 })
141 141
142 it('Should be able to stream again in the permanent live', async function () { 142 it('Should be able to stream again in the permanent live', async function () {
143 this.timeout(20000) 143 this.timeout(60000)
144 144
145 await servers[0].config.updateCustomSubConfig({ 145 await servers[0].config.updateCustomSubConfig({
146 newConfig: { 146 newConfig: {
diff --git a/server/tests/api/live/live-rtmps.ts b/server/tests/api/live/live-rtmps.ts
index 378e6df3c..935061971 100644
--- a/server/tests/api/live/live-rtmps.ts
+++ b/server/tests/api/live/live-rtmps.ts
@@ -2,9 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { buildAbsoluteFixturePath } from '@shared/core-utils'
5import { VideoPrivacy } from '@shared/models' 6import { VideoPrivacy } from '@shared/models'
6import { 7import {
7 buildAbsoluteFixturePath,
8 cleanupTests, 8 cleanupTests,
9 createSingleServer, 9 createSingleServer,
10 PeerTubeServer, 10 PeerTubeServer,
@@ -14,7 +14,7 @@ import {
14 stopFfmpeg, 14 stopFfmpeg,
15 testFfmpegStreamError, 15 testFfmpegStreamError,
16 waitUntilLivePublishedOnAllServers 16 waitUntilLivePublishedOnAllServers
17} from '../../../../shared/extra-utils' 17} from '@shared/server-commands'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/api/live/live-save-replay.ts b/server/tests/api/live/live-save-replay.ts
index 6c4ea90ca..95a342b01 100644
--- a/server/tests/api/live/live-save-replay.ts
+++ b/server/tests/api/live/live-save-replay.ts
@@ -3,8 +3,10 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg' 5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { checkLiveCleanupAfterSave } from '@server/tests/shared'
7import { wait } from '@shared/core-utils'
8import { HttpStatusCode, LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models'
6import { 9import {
7 checkLiveCleanupAfterSave,
8 cleanupTests, 10 cleanupTests,
9 ConfigCommand, 11 ConfigCommand,
10 createMultipleServers, 12 createMultipleServers,
@@ -14,12 +16,10 @@ import {
14 setDefaultVideoChannel, 16 setDefaultVideoChannel,
15 stopFfmpeg, 17 stopFfmpeg,
16 testFfmpegStreamError, 18 testFfmpegStreamError,
17 wait,
18 waitJobs, 19 waitJobs,
19 waitUntilLivePublishedOnAllServers, 20 waitUntilLivePublishedOnAllServers,
20 waitUntilLiveSavedOnAllServers 21 waitUntilLiveSavedOnAllServers
21} from '@shared/extra-utils' 22} from '@shared/server-commands'
22import { HttpStatusCode, LiveVideoCreate, VideoPrivacy, VideoState } from '@shared/models'
23 23
24const expect = chai.expect 24const expect = chai.expect
25 25
diff --git a/server/tests/api/live/live-socket-messages.ts b/server/tests/api/live/live-socket-messages.ts
index 33ee2c051..50b16443e 100644
--- a/server/tests/api/live/live-socket-messages.ts
+++ b/server/tests/api/live/live-socket-messages.ts
@@ -2,6 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
5import { VideoPrivacy, VideoState } from '@shared/models' 6import { VideoPrivacy, VideoState } from '@shared/models'
6import { 7import {
7 cleanupTests, 8 cleanupTests,
@@ -11,10 +12,9 @@ import {
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 setDefaultVideoChannel, 13 setDefaultVideoChannel,
13 stopFfmpeg, 14 stopFfmpeg,
14 wait,
15 waitJobs, 15 waitJobs,
16 waitUntilLivePublishedOnAllServers 16 waitUntilLivePublishedOnAllServers
17} from '../../../../shared/extra-utils' 17} from '@shared/server-commands'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/api/live/live-views.ts b/server/tests/api/live/live-views.ts
index 9186af8e7..446d0913c 100644
--- a/server/tests/api/live/live-views.ts
+++ b/server/tests/api/live/live-views.ts
@@ -3,6 +3,7 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg' 5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { wait } from '@shared/core-utils'
6import { VideoPrivacy } from '@shared/models' 7import { VideoPrivacy } from '@shared/models'
7import { 8import {
8 cleanupTests, 9 cleanupTests,
@@ -12,10 +13,9 @@ import {
12 setAccessTokensToServers, 13 setAccessTokensToServers,
13 setDefaultVideoChannel, 14 setDefaultVideoChannel,
14 stopFfmpeg, 15 stopFfmpeg,
15 wait,
16 waitJobs, 16 waitJobs,
17 waitUntilLivePublishedOnAllServers 17 waitUntilLivePublishedOnAllServers
18} from '../../../../shared/extra-utils' 18} from '@shared/server-commands'
19 19
20const expect = chai.expect 20const expect = chai.expect
21 21
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index b96c03cf8..3f9355d2d 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -4,10 +4,18 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' 6import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils'
7import { checkLiveCleanupAfterSave, checkLiveSegmentHash, checkResolutionsInMasterPlaylist, testImage } from '@server/tests/shared'
8import { wait } from '@shared/core-utils'
9import {
10 HttpStatusCode,
11 LiveVideo,
12 LiveVideoCreate,
13 VideoDetails,
14 VideoPrivacy,
15 VideoState,
16 VideoStreamingPlaylistType
17} from '@shared/models'
7import { 18import {
8 checkLiveCleanupAfterSave,
9 checkLiveSegmentHash,
10 checkResolutionsInMasterPlaylist,
11 cleanupTests, 19 cleanupTests,
12 createMultipleServers, 20 createMultipleServers,
13 doubleFollow, 21 doubleFollow,
@@ -20,20 +28,9 @@ import {
20 setDefaultVideoChannel, 28 setDefaultVideoChannel,
21 stopFfmpeg, 29 stopFfmpeg,
22 testFfmpegStreamError, 30 testFfmpegStreamError,
23 testImage,
24 wait,
25 waitJobs, 31 waitJobs,
26 waitUntilLivePublishedOnAllServers 32 waitUntilLivePublishedOnAllServers
27} from '@shared/extra-utils' 33} from '@shared/server-commands'
28import {
29 HttpStatusCode,
30 LiveVideo,
31 LiveVideoCreate,
32 VideoDetails,
33 VideoPrivacy,
34 VideoState,
35 VideoStreamingPlaylistType
36} from '@shared/models'
37 34
38const expect = chai.expect 35const expect = chai.expect
39 36
diff --git a/server/tests/api/moderation/abuses.ts b/server/tests/api/moderation/abuses.ts
index c258414ce..0c3bed3e7 100644
--- a/server/tests/api/moderation/abuses.ts
+++ b/server/tests/api/moderation/abuses.ts
@@ -10,7 +10,7 @@ import {
10 PeerTubeServer, 10 PeerTubeServer,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { AbuseMessage, AbusePredefinedReasonsString, AbuseState, AdminAbuse, UserAbuse } from '@shared/models' 14import { AbuseMessage, AbusePredefinedReasonsString, AbuseState, AdminAbuse, UserAbuse } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
@@ -468,7 +468,7 @@ describe('Test abuses', function () {
468 }) 468 })
469 469
470 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () { 470 it('Should have 2 comment abuses on server 1 and 1 on server 2', async function () {
471 const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.id) 471 const commentServer2 = await getComment(servers[0], servers[1].store.videoCreated.shortUUID)
472 472
473 { 473 {
474 const body = await commands[0].getAdminList({ filter: 'comment' }) 474 const body = await commands[0].getAdminList({ filter: 'comment' })
diff --git a/server/tests/api/moderation/blocklist-notification.ts b/server/tests/api/moderation/blocklist-notification.ts
index 75b15c298..87d147998 100644
--- a/server/tests/api/moderation/blocklist-notification.ts
+++ b/server/tests/api/moderation/blocklist-notification.ts
@@ -2,8 +2,15 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils'
6import { UserNotificationType } from '@shared/models' 5import { UserNotificationType } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
7 14
8const expect = chai.expect 15const expect = chai.expect
9 16
diff --git a/server/tests/api/moderation/blocklist.ts b/server/tests/api/moderation/blocklist.ts
index 089af8b15..b45460bb4 100644
--- a/server/tests/api/moderation/blocklist.ts
+++ b/server/tests/api/moderation/blocklist.ts
@@ -11,7 +11,7 @@ import {
11 PeerTubeServer, 11 PeerTubeServer,
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 waitJobs 13 waitJobs
14} from '@shared/extra-utils' 14} from '@shared/server-commands'
15import { UserNotificationType } from '@shared/models' 15import { UserNotificationType } from '@shared/models'
16 16
17const expect = chai.expect 17const expect = chai.expect
@@ -254,6 +254,45 @@ describe('Test blocklist', function () {
254 } 254 }
255 }) 255 })
256 256
257 it('Should get blocked status', async function () {
258 const remoteHandle = 'user2@' + servers[1].host
259 const localHandle = 'user1@' + servers[0].host
260 const unknownHandle = 'user5@' + servers[0].host
261
262 {
263 const status = await command.getStatus({ accounts: [ remoteHandle ] })
264 expect(Object.keys(status.accounts)).to.have.lengthOf(1)
265 expect(status.accounts[remoteHandle].blockedByUser).to.be.false
266 expect(status.accounts[remoteHandle].blockedByServer).to.be.false
267
268 expect(Object.keys(status.hosts)).to.have.lengthOf(0)
269 }
270
271 {
272 const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ remoteHandle ] })
273 expect(Object.keys(status.accounts)).to.have.lengthOf(1)
274 expect(status.accounts[remoteHandle].blockedByUser).to.be.true
275 expect(status.accounts[remoteHandle].blockedByServer).to.be.false
276
277 expect(Object.keys(status.hosts)).to.have.lengthOf(0)
278 }
279
280 {
281 const status = await command.getStatus({ token: servers[0].accessToken, accounts: [ localHandle, remoteHandle, unknownHandle ] })
282 expect(Object.keys(status.accounts)).to.have.lengthOf(3)
283
284 for (const handle of [ localHandle, remoteHandle ]) {
285 expect(status.accounts[handle].blockedByUser).to.be.true
286 expect(status.accounts[handle].blockedByServer).to.be.false
287 }
288
289 expect(status.accounts[unknownHandle].blockedByUser).to.be.false
290 expect(status.accounts[unknownHandle].blockedByServer).to.be.false
291
292 expect(Object.keys(status.hosts)).to.have.lengthOf(0)
293 }
294 })
295
257 it('Should not allow a remote blocked user to comment my videos', async function () { 296 it('Should not allow a remote blocked user to comment my videos', async function () {
258 this.timeout(60000) 297 this.timeout(60000)
259 298
@@ -434,6 +473,35 @@ describe('Test blocklist', function () {
434 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 473 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
435 }) 474 })
436 475
476 it('Should get blocklist status', async function () {
477 const blockedServer = servers[1].host
478 const notBlockedServer = 'example.com'
479
480 {
481 const status = await command.getStatus({ hosts: [ blockedServer, notBlockedServer ] })
482 expect(Object.keys(status.accounts)).to.have.lengthOf(0)
483
484 expect(Object.keys(status.hosts)).to.have.lengthOf(2)
485 expect(status.hosts[blockedServer].blockedByUser).to.be.false
486 expect(status.hosts[blockedServer].blockedByServer).to.be.false
487
488 expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
489 expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
490 }
491
492 {
493 const status = await command.getStatus({ token: servers[0].accessToken, hosts: [ blockedServer, notBlockedServer ] })
494 expect(Object.keys(status.accounts)).to.have.lengthOf(0)
495
496 expect(Object.keys(status.hosts)).to.have.lengthOf(2)
497 expect(status.hosts[blockedServer].blockedByUser).to.be.true
498 expect(status.hosts[blockedServer].blockedByServer).to.be.false
499
500 expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
501 expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
502 }
503 })
504
437 it('Should unblock the remote server', async function () { 505 it('Should unblock the remote server', async function () {
438 await command.removeFromMyBlocklist({ server: 'localhost:' + servers[1].port }) 506 await command.removeFromMyBlocklist({ server: 'localhost:' + servers[1].port })
439 }) 507 })
@@ -575,6 +643,27 @@ describe('Test blocklist', function () {
575 } 643 }
576 }) 644 })
577 645
646 it('Should get blocked status', async function () {
647 const remoteHandle = 'user2@' + servers[1].host
648 const localHandle = 'user1@' + servers[0].host
649 const unknownHandle = 'user5@' + servers[0].host
650
651 for (const token of [ undefined, servers[0].accessToken ]) {
652 const status = await command.getStatus({ token, accounts: [ localHandle, remoteHandle, unknownHandle ] })
653 expect(Object.keys(status.accounts)).to.have.lengthOf(3)
654
655 for (const handle of [ localHandle, remoteHandle ]) {
656 expect(status.accounts[handle].blockedByUser).to.be.false
657 expect(status.accounts[handle].blockedByServer).to.be.true
658 }
659
660 expect(status.accounts[unknownHandle].blockedByUser).to.be.false
661 expect(status.accounts[unknownHandle].blockedByServer).to.be.false
662
663 expect(Object.keys(status.hosts)).to.have.lengthOf(0)
664 }
665 })
666
578 it('Should unblock the remote account', async function () { 667 it('Should unblock the remote account', async function () {
579 await command.removeFromServerBlocklist({ account: 'user2@localhost:' + servers[1].port }) 668 await command.removeFromServerBlocklist({ account: 'user2@localhost:' + servers[1].port })
580 }) 669 })
@@ -620,6 +709,7 @@ describe('Test blocklist', function () {
620 }) 709 })
621 710
622 describe('When managing server blocklist', function () { 711 describe('When managing server blocklist', function () {
712
623 it('Should list all videos', async function () { 713 it('Should list all videos', async function () {
624 for (const token of [ userModeratorToken, servers[0].accessToken ]) { 714 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
625 await checkAllVideos(servers[0], token) 715 await checkAllVideos(servers[0], token)
@@ -713,6 +803,23 @@ describe('Test blocklist', function () {
713 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 803 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
714 }) 804 })
715 805
806 it('Should get blocklist status', async function () {
807 const blockedServer = servers[1].host
808 const notBlockedServer = 'example.com'
809
810 for (const token of [ undefined, servers[0].accessToken ]) {
811 const status = await command.getStatus({ token, hosts: [ blockedServer, notBlockedServer ] })
812 expect(Object.keys(status.accounts)).to.have.lengthOf(0)
813
814 expect(Object.keys(status.hosts)).to.have.lengthOf(2)
815 expect(status.hosts[blockedServer].blockedByUser).to.be.false
816 expect(status.hosts[blockedServer].blockedByServer).to.be.true
817
818 expect(status.hosts[notBlockedServer].blockedByUser).to.be.false
819 expect(status.hosts[notBlockedServer].blockedByServer).to.be.false
820 }
821 })
822
716 it('Should unblock the remote server', async function () { 823 it('Should unblock the remote server', async function () {
717 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port }) 824 await command.removeFromServerBlocklist({ server: 'localhost:' + servers[1].port })
718 }) 825 })
diff --git a/server/tests/api/moderation/video-blacklist.ts b/server/tests/api/moderation/video-blacklist.ts
index d5838191a..322e93815 100644
--- a/server/tests/api/moderation/video-blacklist.ts
+++ b/server/tests/api/moderation/video-blacklist.ts
@@ -3,18 +3,18 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { orderBy } from 'lodash' 5import { orderBy } from 'lodash'
6import { FIXTURE_URLS } from '@server/tests/shared'
7import { UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@shared/models'
6import { 8import {
7 BlacklistCommand, 9 BlacklistCommand,
8 cleanupTests, 10 cleanupTests,
9 createMultipleServers, 11 createMultipleServers,
10 doubleFollow, 12 doubleFollow,
11 FIXTURE_URLS,
12 killallServers, 13 killallServers,
13 PeerTubeServer, 14 PeerTubeServer,
14 setAccessTokensToServers, 15 setAccessTokensToServers,
15 waitJobs 16 waitJobs
16} from '@shared/extra-utils' 17} from '@shared/server-commands'
17import { UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@shared/models'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/api/notifications/admin-notifications.ts b/server/tests/api/notifications/admin-notifications.ts
index c00d4e257..f037e7aae 100644
--- a/server/tests/api/notifications/admin-notifications.ts
+++ b/server/tests/api/notifications/admin-notifications.ts
@@ -6,14 +6,13 @@ import {
6 CheckerBaseParams, 6 CheckerBaseParams,
7 checkNewPeerTubeVersion, 7 checkNewPeerTubeVersion,
8 checkNewPluginVersion, 8 checkNewPluginVersion,
9 cleanupTests,
10 MockJoinPeerTubeVersions, 9 MockJoinPeerTubeVersions,
11 MockSmtpServer, 10 MockSmtpServer,
12 PeerTubeServer, 11 prepareNotificationsTest
13 prepareNotificationsTest, 12} from '@server/tests/shared'
14 wait 13import { wait } from '@shared/core-utils'
15} from '@shared/extra-utils'
16import { PluginType, UserNotification, UserNotificationType } from '@shared/models' 14import { PluginType, UserNotification, UserNotificationType } from '@shared/models'
15import { cleanupTests, PeerTubeServer } from '@shared/server-commands'
17 16
18describe('Test admin notifications', function () { 17describe('Test admin notifications', function () {
19 let server: PeerTubeServer 18 let server: PeerTubeServer
diff --git a/server/tests/api/notifications/comments-notifications.ts b/server/tests/api/notifications/comments-notifications.ts
index 7cbb21397..b82f1712a 100644
--- a/server/tests/api/notifications/comments-notifications.ts
+++ b/server/tests/api/notifications/comments-notifications.ts
@@ -6,13 +6,11 @@ import {
6 checkCommentMention, 6 checkCommentMention,
7 CheckerBaseParams, 7 CheckerBaseParams,
8 checkNewCommentOnMyVideo, 8 checkNewCommentOnMyVideo,
9 cleanupTests,
10 MockSmtpServer, 9 MockSmtpServer,
11 PeerTubeServer, 10 prepareNotificationsTest
12 prepareNotificationsTest, 11} from '@server/tests/shared'
13 waitJobs
14} from '@shared/extra-utils'
15import { UserNotification } from '@shared/models' 12import { UserNotification } from '@shared/models'
13import { cleanupTests, PeerTubeServer, waitJobs } from '@shared/server-commands'
16 14
17const expect = chai.expect 15const expect = chai.expect
18 16
diff --git a/server/tests/api/notifications/moderation-notifications.ts b/server/tests/api/notifications/moderation-notifications.ts
index f806fed31..9e330bd61 100644
--- a/server/tests/api/notifications/moderation-notifications.ts
+++ b/server/tests/api/notifications/moderation-notifications.ts
@@ -1,7 +1,6 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { buildUUID } from '@server/helpers/uuid'
5import { 4import {
6 checkAbuseStateChange, 5 checkAbuseStateChange,
7 checkAutoInstanceFollowing, 6 checkAutoInstanceFollowing,
@@ -16,19 +15,20 @@ import {
16 checkUserRegistered, 15 checkUserRegistered,
17 checkVideoAutoBlacklistForModerators, 16 checkVideoAutoBlacklistForModerators,
18 checkVideoIsPublished, 17 checkVideoIsPublished,
19 cleanupTests,
20 MockInstancesIndex, 18 MockInstancesIndex,
21 MockSmtpServer, 19 MockSmtpServer,
22 PeerTubeServer, 20 prepareNotificationsTest
23 prepareNotificationsTest, 21} from '@server/tests/shared'
24 wait, 22import { wait } from '@shared/core-utils'
25 waitJobs 23import { buildUUID } from '@shared/extra-utils'
26} from '@shared/extra-utils' 24import { AbuseState, CustomConfig, UserNotification, UserRole, VideoPrivacy } from '@shared/models'
27import { AbuseState, CustomConfig, UserNotification, VideoPrivacy } from '@shared/models' 25import { cleanupTests, PeerTubeServer, waitJobs } from '@shared/server-commands'
28 26
29describe('Test moderation notifications', function () { 27describe('Test moderation notifications', function () {
30 let servers: PeerTubeServer[] = [] 28 let servers: PeerTubeServer[] = []
31 let userAccessToken: string 29 let userToken1: string
30 let userToken2: string
31
32 let userNotifications: UserNotification[] = [] 32 let userNotifications: UserNotification[] = []
33 let adminNotifications: UserNotification[] = [] 33 let adminNotifications: UserNotification[] = []
34 let adminNotificationsServer2: UserNotification[] = [] 34 let adminNotificationsServer2: UserNotification[] = []
@@ -39,11 +39,13 @@ describe('Test moderation notifications', function () {
39 39
40 const res = await prepareNotificationsTest(3) 40 const res = await prepareNotificationsTest(3)
41 emails = res.emails 41 emails = res.emails
42 userAccessToken = res.userAccessToken 42 userToken1 = res.userAccessToken
43 servers = res.servers 43 servers = res.servers
44 userNotifications = res.userNotifications 44 userNotifications = res.userNotifications
45 adminNotifications = res.adminNotifications 45 adminNotifications = res.adminNotifications
46 adminNotificationsServer2 = res.adminNotificationsServer2 46 adminNotificationsServer2 = res.adminNotificationsServer2
47
48 userToken2 = await servers[1].users.generateUserAndToken('user2', UserRole.USER)
47 }) 49 })
48 50
49 describe('Abuse for moderators notification', function () { 51 describe('Abuse for moderators notification', function () {
@@ -58,15 +60,27 @@ describe('Test moderation notifications', function () {
58 } 60 }
59 }) 61 })
60 62
61 it('Should send a notification to moderators on local video abuse', async function () { 63 it('Should not send a notification to moderators on local abuse reported by an admin', async function () {
62 this.timeout(20000) 64 this.timeout(20000)
63 65
64 const name = 'video for abuse ' + buildUUID() 66 const name = 'video for abuse ' + buildUUID()
65 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 67 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
66 68
67 await servers[0].abuses.report({ videoId: video.id, reason: 'super reason' }) 69 await servers[0].abuses.report({ videoId: video.id, reason: 'super reason' })
68 70
69 await waitJobs(servers) 71 await waitJobs(servers)
72 await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'absence' })
73 })
74
75 it('Should send a notification to moderators on local video abuse', async function () {
76 this.timeout(20000)
77
78 const name = 'video for abuse ' + buildUUID()
79 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
80
81 await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
82
83 await waitJobs(servers)
70 await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' }) 84 await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
71 }) 85 })
72 86
@@ -74,12 +88,12 @@ describe('Test moderation notifications', function () {
74 this.timeout(20000) 88 this.timeout(20000)
75 89
76 const name = 'video for abuse ' + buildUUID() 90 const name = 'video for abuse ' + buildUUID()
77 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 91 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
78 92
79 await waitJobs(servers) 93 await waitJobs(servers)
80 94
81 const videoId = await servers[1].videos.getId({ uuid: video.uuid }) 95 const videoId = await servers[1].videos.getId({ uuid: video.uuid })
82 await servers[1].abuses.report({ videoId, reason: 'super reason' }) 96 await servers[1].abuses.report({ token: userToken2, videoId, reason: 'super reason' })
83 97
84 await waitJobs(servers) 98 await waitJobs(servers)
85 await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' }) 99 await checkNewVideoAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
@@ -89,16 +103,16 @@ describe('Test moderation notifications', function () {
89 this.timeout(20000) 103 this.timeout(20000)
90 104
91 const name = 'video for abuse ' + buildUUID() 105 const name = 'video for abuse ' + buildUUID()
92 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 106 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
93 const comment = await servers[0].comments.createThread({ 107 const comment = await servers[0].comments.createThread({
94 token: userAccessToken, 108 token: userToken1,
95 videoId: video.id, 109 videoId: video.id,
96 text: 'comment abuse ' + buildUUID() 110 text: 'comment abuse ' + buildUUID()
97 }) 111 })
98 112
99 await waitJobs(servers) 113 await waitJobs(servers)
100 114
101 await servers[0].abuses.report({ commentId: comment.id, reason: 'super reason' }) 115 await servers[0].abuses.report({ token: userToken1, commentId: comment.id, reason: 'super reason' })
102 116
103 await waitJobs(servers) 117 await waitJobs(servers)
104 await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' }) 118 await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
@@ -108,10 +122,10 @@ describe('Test moderation notifications', function () {
108 this.timeout(20000) 122 this.timeout(20000)
109 123
110 const name = 'video for abuse ' + buildUUID() 124 const name = 'video for abuse ' + buildUUID()
111 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 125 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
112 126
113 await servers[0].comments.createThread({ 127 await servers[0].comments.createThread({
114 token: userAccessToken, 128 token: userToken1,
115 videoId: video.id, 129 videoId: video.id,
116 text: 'comment abuse ' + buildUUID() 130 text: 'comment abuse ' + buildUUID()
117 }) 131 })
@@ -120,7 +134,7 @@ describe('Test moderation notifications', function () {
120 134
121 const { data } = await servers[1].comments.listThreads({ videoId: video.uuid }) 135 const { data } = await servers[1].comments.listThreads({ videoId: video.uuid })
122 const commentId = data[0].id 136 const commentId = data[0].id
123 await servers[1].abuses.report({ commentId, reason: 'super reason' }) 137 await servers[1].abuses.report({ token: userToken2, commentId, reason: 'super reason' })
124 138
125 await waitJobs(servers) 139 await waitJobs(servers)
126 await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' }) 140 await checkNewCommentAbuseForModerators({ ...baseParams, shortUUID: video.shortUUID, videoName: name, checkType: 'presence' })
@@ -133,7 +147,7 @@ describe('Test moderation notifications', function () {
133 const { account } = await servers[0].users.create({ username, password: 'donald' }) 147 const { account } = await servers[0].users.create({ username, password: 'donald' })
134 const accountId = account.id 148 const accountId = account.id
135 149
136 await servers[0].abuses.report({ accountId, reason: 'super reason' }) 150 await servers[0].abuses.report({ token: userToken1, accountId, reason: 'super reason' })
137 151
138 await waitJobs(servers) 152 await waitJobs(servers)
139 await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' }) 153 await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' })
@@ -149,7 +163,7 @@ describe('Test moderation notifications', function () {
149 await waitJobs(servers) 163 await waitJobs(servers)
150 164
151 const account = await servers[1].accounts.get({ accountName: username + '@' + servers[0].host }) 165 const account = await servers[1].accounts.get({ accountName: username + '@' + servers[0].host })
152 await servers[1].abuses.report({ accountId: account.id, reason: 'super reason' }) 166 await servers[1].abuses.report({ token: userToken2, accountId: account.id, reason: 'super reason' })
153 167
154 await waitJobs(servers) 168 await waitJobs(servers)
155 await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' }) 169 await checkNewAccountAbuseForModerators({ ...baseParams, displayName: username, checkType: 'presence' })
@@ -165,13 +179,13 @@ describe('Test moderation notifications', function () {
165 server: servers[0], 179 server: servers[0],
166 emails, 180 emails,
167 socketNotifications: userNotifications, 181 socketNotifications: userNotifications,
168 token: userAccessToken 182 token: userToken1
169 } 183 }
170 184
171 const name = 'abuse ' + buildUUID() 185 const name = 'abuse ' + buildUUID()
172 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 186 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
173 187
174 const body = await servers[0].abuses.report({ token: userAccessToken, videoId: video.id, reason: 'super reason' }) 188 const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
175 abuseId = body.abuse.id 189 abuseId = body.abuse.id
176 }) 190 })
177 191
@@ -205,7 +219,7 @@ describe('Test moderation notifications', function () {
205 server: servers[0], 219 server: servers[0],
206 emails, 220 emails,
207 socketNotifications: userNotifications, 221 socketNotifications: userNotifications,
208 token: userAccessToken 222 token: userToken1
209 } 223 }
210 224
211 baseParamsAdmin = { 225 baseParamsAdmin = {
@@ -216,15 +230,15 @@ describe('Test moderation notifications', function () {
216 } 230 }
217 231
218 const name = 'abuse ' + buildUUID() 232 const name = 'abuse ' + buildUUID()
219 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 233 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
220 234
221 { 235 {
222 const body = await servers[0].abuses.report({ token: userAccessToken, videoId: video.id, reason: 'super reason' }) 236 const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason' })
223 abuseId = body.abuse.id 237 abuseId = body.abuse.id
224 } 238 }
225 239
226 { 240 {
227 const body = await servers[0].abuses.report({ token: userAccessToken, videoId: video.id, reason: 'super reason 2' }) 241 const body = await servers[0].abuses.report({ token: userToken1, videoId: video.id, reason: 'super reason 2' })
228 abuseId2 = body.abuse.id 242 abuseId2 = body.abuse.id
229 } 243 }
230 }) 244 })
@@ -254,7 +268,7 @@ describe('Test moderation notifications', function () {
254 this.timeout(10000) 268 this.timeout(10000)
255 269
256 const message = 'my super message to moderators' 270 const message = 'my super message to moderators'
257 await servers[0].abuses.addMessage({ token: userAccessToken, abuseId: abuseId2, message }) 271 await servers[0].abuses.addMessage({ token: userToken1, abuseId: abuseId2, message })
258 await waitJobs(servers) 272 await waitJobs(servers)
259 273
260 const toEmail = 'admin' + servers[0].internalServerNumber + '@example.com' 274 const toEmail = 'admin' + servers[0].internalServerNumber + '@example.com'
@@ -265,7 +279,7 @@ describe('Test moderation notifications', function () {
265 this.timeout(10000) 279 this.timeout(10000)
266 280
267 const message = 'my super message that should not be sent to reporter' 281 const message = 'my super message that should not be sent to reporter'
268 await servers[0].abuses.addMessage({ token: userAccessToken, abuseId: abuseId2, message }) 282 await servers[0].abuses.addMessage({ token: userToken1, abuseId: abuseId2, message })
269 await waitJobs(servers) 283 await waitJobs(servers)
270 284
271 const toEmail = 'user_1@example.com' 285 const toEmail = 'user_1@example.com'
@@ -281,7 +295,7 @@ describe('Test moderation notifications', function () {
281 server: servers[0], 295 server: servers[0],
282 emails, 296 emails,
283 socketNotifications: userNotifications, 297 socketNotifications: userNotifications,
284 token: userAccessToken 298 token: userToken1
285 } 299 }
286 }) 300 })
287 301
@@ -289,7 +303,7 @@ describe('Test moderation notifications', function () {
289 this.timeout(10000) 303 this.timeout(10000)
290 304
291 const name = 'video for abuse ' + buildUUID() 305 const name = 'video for abuse ' + buildUUID()
292 const { uuid, shortUUID } = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 306 const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
293 307
294 await servers[0].blacklist.add({ videoId: uuid }) 308 await servers[0].blacklist.add({ videoId: uuid })
295 309
@@ -301,7 +315,7 @@ describe('Test moderation notifications', function () {
301 this.timeout(10000) 315 this.timeout(10000)
302 316
303 const name = 'video for abuse ' + buildUUID() 317 const name = 'video for abuse ' + buildUUID()
304 const { uuid, shortUUID } = await servers[0].videos.upload({ token: userAccessToken, attributes: { name } }) 318 const { uuid, shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes: { name } })
305 319
306 await servers[0].blacklist.add({ videoId: uuid }) 320 await servers[0].blacklist.add({ videoId: uuid })
307 321
@@ -335,7 +349,7 @@ describe('Test moderation notifications', function () {
335 349
336 await checkUserRegistered({ ...baseParams, username: 'user_45', checkType: 'presence' }) 350 await checkUserRegistered({ ...baseParams, username: 'user_45', checkType: 'presence' })
337 351
338 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } 352 const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
339 await checkUserRegistered({ ...baseParams, ...userOverride, username: 'user_45', checkType: 'absence' }) 353 await checkUserRegistered({ ...baseParams, ...userOverride, username: 'user_45', checkType: 'absence' })
340 }) 354 })
341 }) 355 })
@@ -377,7 +391,7 @@ describe('Test moderation notifications', function () {
377 391
378 await checkNewInstanceFollower({ ...baseParams, followerHost: 'localhost:' + servers[2].port, checkType: 'presence' }) 392 await checkNewInstanceFollower({ ...baseParams, followerHost: 'localhost:' + servers[2].port, checkType: 'presence' })
379 393
380 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } 394 const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
381 await checkNewInstanceFollower({ ...baseParams, ...userOverride, followerHost: 'localhost:' + servers[2].port, checkType: 'absence' }) 395 await checkNewInstanceFollower({ ...baseParams, ...userOverride, followerHost: 'localhost:' + servers[2].port, checkType: 'absence' })
382 }) 396 })
383 397
@@ -404,7 +418,7 @@ describe('Test moderation notifications', function () {
404 const followingHost = servers[2].host 418 const followingHost = servers[2].host
405 await checkAutoInstanceFollowing({ ...baseParams, followerHost, followingHost, checkType: 'presence' }) 419 await checkAutoInstanceFollowing({ ...baseParams, followerHost, followingHost, checkType: 'presence' })
406 420
407 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } 421 const userOverride = { socketNotifications: userNotifications, token: userToken1, check: { web: true, mail: false } }
408 await checkAutoInstanceFollowing({ ...baseParams, ...userOverride, followerHost, followingHost, checkType: 'absence' }) 422 await checkAutoInstanceFollowing({ ...baseParams, ...userOverride, followerHost, followingHost, checkType: 'absence' })
409 423
410 config.followings.instance.autoFollowBack.enabled = false 424 config.followings.instance.autoFollowBack.enabled = false
@@ -461,7 +475,7 @@ describe('Test moderation notifications', function () {
461 server: servers[0], 475 server: servers[0],
462 emails, 476 emails,
463 socketNotifications: userNotifications, 477 socketNotifications: userNotifications,
464 token: userAccessToken 478 token: userToken1
465 } 479 }
466 480
467 currentCustomConfig = await servers[0].config.getCustomConfig() 481 currentCustomConfig = await servers[0].config.getCustomConfig()
@@ -490,7 +504,7 @@ describe('Test moderation notifications', function () {
490 this.timeout(120000) 504 this.timeout(120000)
491 505
492 videoName = 'video with auto-blacklist ' + buildUUID() 506 videoName = 'video with auto-blacklist ' + buildUUID()
493 const video = await servers[0].videos.upload({ token: userAccessToken, attributes: { name: videoName } }) 507 const video = await servers[0].videos.upload({ token: userToken1, attributes: { name: videoName } })
494 shortUUID = video.shortUUID 508 shortUUID = video.shortUUID
495 uuid = video.uuid 509 uuid = video.uuid
496 510
@@ -547,7 +561,7 @@ describe('Test moderation notifications', function () {
547 } 561 }
548 } 562 }
549 563
550 const { shortUUID, uuid } = await servers[0].videos.upload({ token: userAccessToken, attributes }) 564 const { shortUUID, uuid } = await servers[0].videos.upload({ token: userToken1, attributes })
551 565
552 await servers[0].blacklist.remove({ videoId: uuid }) 566 await servers[0].blacklist.remove({ videoId: uuid })
553 567
@@ -579,7 +593,7 @@ describe('Test moderation notifications', function () {
579 } 593 }
580 } 594 }
581 595
582 const { shortUUID } = await servers[0].videos.upload({ token: userAccessToken, attributes }) 596 const { shortUUID } = await servers[0].videos.upload({ token: userToken1, attributes })
583 597
584 await wait(6000) 598 await wait(6000)
585 await checkVideoIsPublished({ ...userBaseParams, videoName: name, shortUUID, checkType: 'absence' }) 599 await checkVideoIsPublished({ ...userBaseParams, videoName: name, shortUUID, checkType: 'absence' })
diff --git a/server/tests/api/notifications/notifications-api.ts b/server/tests/api/notifications/notifications-api.ts
index a529a9bf7..ac08449f8 100644
--- a/server/tests/api/notifications/notifications-api.ts
+++ b/server/tests/api/notifications/notifications-api.ts
@@ -5,14 +5,12 @@ import * as chai from 'chai'
5import { 5import {
6 CheckerBaseParams, 6 CheckerBaseParams,
7 checkNewVideoFromSubscription, 7 checkNewVideoFromSubscription,
8 cleanupTests,
9 getAllNotificationsSettings, 8 getAllNotificationsSettings,
10 MockSmtpServer, 9 MockSmtpServer,
11 PeerTubeServer, 10 prepareNotificationsTest
12 prepareNotificationsTest, 11} from '@server/tests/shared'
13 waitJobs
14} from '@shared/extra-utils'
15import { UserNotification, UserNotificationSettingValue } from '@shared/models' 12import { UserNotification, UserNotificationSettingValue } from '@shared/models'
13import { cleanupTests, PeerTubeServer, waitJobs } from '@shared/server-commands'
16 14
17const expect = chai.expect 15const expect = chai.expect
18 16
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index 468efdf35..f9f3e0e0e 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -2,23 +2,21 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { buildUUID } from '@server/helpers/uuid'
6import { 5import {
7 CheckerBaseParams, 6 CheckerBaseParams,
8 checkMyVideoImportIsFinished, 7 checkMyVideoImportIsFinished,
9 checkNewActorFollow, 8 checkNewActorFollow,
10 checkNewVideoFromSubscription, 9 checkNewVideoFromSubscription,
11 checkVideoIsPublished, 10 checkVideoIsPublished,
12 cleanupTests,
13 FIXTURE_URLS, 11 FIXTURE_URLS,
14 MockSmtpServer, 12 MockSmtpServer,
15 PeerTubeServer,
16 prepareNotificationsTest, 13 prepareNotificationsTest,
17 uploadRandomVideoOnServers, 14 uploadRandomVideoOnServers
18 wait, 15} from '@server/tests/shared'
19 waitJobs 16import { wait } from '@shared/core-utils'
20} from '@shared/extra-utils' 17import { buildUUID } from '@shared/extra-utils'
21import { UserNotification, UserNotificationType, VideoPrivacy } from '@shared/models' 18import { UserNotification, UserNotificationType, VideoPrivacy } from '@shared/models'
19import { cleanupTests, PeerTubeServer, waitJobs } from '@shared/server-commands'
22 20
23const expect = chai.expect 21const expect = chai.expect
24 22
@@ -128,7 +126,7 @@ describe('Test user notifications', function () {
128 }) 126 })
129 127
130 it('Should not send a notification before the video is published', async function () { 128 it('Should not send a notification before the video is published', async function () {
131 this.timeout(50000) 129 this.timeout(150000)
132 130
133 const updateAt = new Date(new Date().getTime() + 1000000) 131 const updateAt = new Date(new Date().getTime() + 1000000)
134 132
@@ -267,7 +265,7 @@ describe('Test user notifications', function () {
267 }) 265 })
268 266
269 it('Should send a notification when an imported video is transcoded', async function () { 267 it('Should send a notification when an imported video is transcoded', async function () {
270 this.timeout(50000) 268 this.timeout(120000)
271 269
272 const name = 'video import ' + buildUUID() 270 const name = 'video import ' + buildUUID()
273 271
diff --git a/server/tests/api/object-storage/live.ts b/server/tests/api/object-storage/live.ts
index 3726a717b..0cb0a6e34 100644
--- a/server/tests/api/object-storage/live.ts
+++ b/server/tests/api/object-storage/live.ts
@@ -3,11 +3,12 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { FfmpegCommand } from 'fluent-ffmpeg' 5import { FfmpegCommand } from 'fluent-ffmpeg'
6import { expectStartWith } from '@server/tests/shared'
7import { areObjectStorageTestsDisabled } from '@shared/core-utils'
8import { HttpStatusCode, LiveVideoCreate, VideoFile, VideoPrivacy } from '@shared/models'
6import { 9import {
7 areObjectStorageTestsDisabled,
8 createMultipleServers, 10 createMultipleServers,
9 doubleFollow, 11 doubleFollow,
10 expectStartWith,
11 killallServers, 12 killallServers,
12 makeRawRequest, 13 makeRawRequest,
13 ObjectStorageCommand, 14 ObjectStorageCommand,
@@ -18,8 +19,7 @@ import {
18 waitJobs, 19 waitJobs,
19 waitUntilLivePublishedOnAllServers, 20 waitUntilLivePublishedOnAllServers,
20 waitUntilLiveSavedOnAllServers 21 waitUntilLiveSavedOnAllServers
21} from '@shared/extra-utils' 22} from '@shared/server-commands'
22import { HttpStatusCode, LiveVideoCreate, VideoFile, VideoPrivacy } from '@shared/models'
23 23
24const expect = chai.expect 24const expect = chai.expect
25 25
diff --git a/server/tests/api/object-storage/video-imports.ts b/server/tests/api/object-storage/video-imports.ts
index 363fe3b5b..fb81832af 100644
--- a/server/tests/api/object-storage/video-imports.ts
+++ b/server/tests/api/object-storage/video-imports.ts
@@ -2,11 +2,11 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { expectStartWith, FIXTURE_URLS } from '@server/tests/shared'
6import { areObjectStorageTestsDisabled } from '@shared/core-utils'
7import { HttpStatusCode, VideoPrivacy } from '@shared/models'
5import { 8import {
6 areObjectStorageTestsDisabled,
7 createSingleServer, 9 createSingleServer,
8 expectStartWith,
9 FIXTURE_URLS,
10 killallServers, 10 killallServers,
11 makeRawRequest, 11 makeRawRequest,
12 ObjectStorageCommand, 12 ObjectStorageCommand,
@@ -14,8 +14,7 @@ import {
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 setDefaultVideoChannel, 15 setDefaultVideoChannel,
16 waitJobs 16 waitJobs
17} from '@shared/extra-utils' 17} from '@shared/server-commands'
18import { HttpStatusCode, VideoPrivacy } from '@shared/models'
19 18
20const expect = chai.expect 19const expect = chai.expect
21 20
diff --git a/server/tests/api/object-storage/videos.ts b/server/tests/api/object-storage/videos.ts
index 35a5f19ed..498efcb17 100644
--- a/server/tests/api/object-storage/videos.ts
+++ b/server/tests/api/object-storage/videos.ts
@@ -3,25 +3,22 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { merge } from 'lodash' 5import { merge } from 'lodash'
6import { checkTmpIsEmpty, expectLogDoesNotContain, expectStartWith, MockObjectStorage } from '@server/tests/shared'
7import { areObjectStorageTestsDisabled } from '@shared/core-utils'
8import { HttpStatusCode, VideoDetails } from '@shared/models'
6import { 9import {
7 areObjectStorageTestsDisabled,
8 checkTmpIsEmpty,
9 cleanupTests, 10 cleanupTests,
10 createMultipleServers, 11 createMultipleServers,
11 createSingleServer, 12 createSingleServer,
12 doubleFollow, 13 doubleFollow,
13 expectLogDoesNotContain,
14 expectStartWith,
15 killallServers, 14 killallServers,
16 makeRawRequest, 15 makeRawRequest,
17 MockObjectStorage,
18 ObjectStorageCommand, 16 ObjectStorageCommand,
19 PeerTubeServer, 17 PeerTubeServer,
20 setAccessTokensToServers, 18 setAccessTokensToServers,
21 waitJobs, 19 waitJobs,
22 webtorrentAdd 20 webtorrentAdd
23} from '@shared/extra-utils' 21} from '@shared/server-commands'
24import { HttpStatusCode, VideoDetails } from '@shared/models'
25 22
26const expect = chai.expect 23const expect = chai.expect
27 24
diff --git a/server/tests/api/redundancy/manage-redundancy.ts b/server/tests/api/redundancy/manage-redundancy.ts
index 5fd464ded..cbf3106bd 100644
--- a/server/tests/api/redundancy/manage-redundancy.ts
+++ b/server/tests/api/redundancy/manage-redundancy.ts
@@ -10,7 +10,7 @@ import {
10 RedundancyCommand, 10 RedundancyCommand,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { VideoPrivacy, VideoRedundanciesTarget } from '@shared/models' 14import { VideoPrivacy, VideoRedundanciesTarget } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
diff --git a/server/tests/api/redundancy/redundancy-constraints.ts b/server/tests/api/redundancy/redundancy-constraints.ts
index 933a2c776..17c6b25a5 100644
--- a/server/tests/api/redundancy/redundancy-constraints.ts
+++ b/server/tests/api/redundancy/redundancy-constraints.ts
@@ -2,8 +2,15 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils'
6import { VideoPrivacy } from '@shared/models' 5import { VideoPrivacy } from '@shared/models'
6import {
7 cleanupTests,
8 createSingleServer,
9 killallServers,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
7 14
8describe('Test redundancy constraints', function () { 15describe('Test redundancy constraints', function () {
9 let remoteServer: PeerTubeServer 16 let remoteServer: PeerTubeServer
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts
index 86b40cfe6..3f2286278 100644
--- a/server/tests/api/redundancy/redundancy.ts
+++ b/server/tests/api/redundancy/redundancy.ts
@@ -5,29 +5,26 @@ import * as chai from 'chai'
5import { readdir } from 'fs-extra' 5import { readdir } from 'fs-extra'
6import magnetUtil from 'magnet-uri' 6import magnetUtil from 'magnet-uri'
7import { basename, join } from 'path' 7import { basename, join } from 'path'
8import { checkSegmentHash, checkVideoFilesWereRemoved, saveVideoInServers } from '@server/tests/shared'
9import { root, wait } from '@shared/core-utils'
10import {
11 HttpStatusCode,
12 VideoDetails,
13 VideoFile,
14 VideoPrivacy,
15 VideoRedundancyStrategy,
16 VideoRedundancyStrategyWithManual
17} from '@shared/models'
8import { 18import {
9 checkSegmentHash,
10 checkVideoFilesWereRemoved,
11 cleanupTests, 19 cleanupTests,
12 createMultipleServers, 20 createMultipleServers,
13 doubleFollow, 21 doubleFollow,
14 killallServers, 22 killallServers,
15 makeRawRequest, 23 makeRawRequest,
16 PeerTubeServer, 24 PeerTubeServer,
17 root,
18 saveVideoInServers,
19 setAccessTokensToServers, 25 setAccessTokensToServers,
20 wait,
21 waitJobs 26 waitJobs
22} from '@shared/extra-utils' 27} from '@shared/server-commands'
23import {
24 HttpStatusCode,
25 VideoDetails,
26 VideoFile,
27 VideoPrivacy,
28 VideoRedundancyStrategy,
29 VideoRedundancyStrategyWithManual
30} from '@shared/models'
31 28
32const expect = chai.expect 29const expect = chai.expect
33 30
@@ -307,7 +304,7 @@ describe('Test videos redundancy', function () {
307 const strategy = 'most-views' 304 const strategy = 'most-views'
308 305
309 before(function () { 306 before(function () {
310 this.timeout(120000) 307 this.timeout(240000)
311 308
312 return createServers(strategy) 309 return createServers(strategy)
313 }) 310 })
@@ -357,7 +354,7 @@ describe('Test videos redundancy', function () {
357 const strategy = 'trending' 354 const strategy = 'trending'
358 355
359 before(function () { 356 before(function () {
360 this.timeout(120000) 357 this.timeout(240000)
361 358
362 return createServers(strategy) 359 return createServers(strategy)
363 }) 360 })
@@ -420,7 +417,7 @@ describe('Test videos redundancy', function () {
420 const strategy = 'recently-added' 417 const strategy = 'recently-added'
421 418
422 before(function () { 419 before(function () {
423 this.timeout(120000) 420 this.timeout(240000)
424 421
425 return createServers(strategy, { min_views: 3 }) 422 return createServers(strategy, { min_views: 3 })
426 }) 423 })
@@ -491,7 +488,7 @@ describe('Test videos redundancy', function () {
491 const strategy = 'recently-added' 488 const strategy = 'recently-added'
492 489
493 before(async function () { 490 before(async function () {
494 this.timeout(120000) 491 this.timeout(240000)
495 492
496 await createServers(strategy, { min_views: 3 }, false) 493 await createServers(strategy, { min_views: 3 }, false)
497 }) 494 })
@@ -553,7 +550,7 @@ describe('Test videos redundancy', function () {
553 550
554 describe('With manual strategy', function () { 551 describe('With manual strategy', function () {
555 before(function () { 552 before(function () {
556 this.timeout(120000) 553 this.timeout(240000)
557 554
558 return createServers(null) 555 return createServers(null)
559 }) 556 })
@@ -632,7 +629,7 @@ describe('Test videos redundancy', function () {
632 } 629 }
633 630
634 before(async function () { 631 before(async function () {
635 this.timeout(120000) 632 this.timeout(240000)
636 633
637 await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) 634 await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
638 635
@@ -674,7 +671,7 @@ describe('Test videos redundancy', function () {
674 const strategy = 'recently-added' 671 const strategy = 'recently-added'
675 672
676 before(async function () { 673 before(async function () {
677 this.timeout(120000) 674 this.timeout(240000)
678 675
679 await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) 676 await createServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
680 677
@@ -698,7 +695,7 @@ describe('Test videos redundancy', function () {
698 }) 695 })
699 696
700 it('Should cache video 2 webseeds on the first video', async function () { 697 it('Should cache video 2 webseeds on the first video', async function () {
701 this.timeout(120000) 698 this.timeout(240000)
702 699
703 await waitJobs(servers) 700 await waitJobs(servers)
704 701
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts
index efcdb33dc..2e0abc6ba 100644
--- a/server/tests/api/search/search-activitypub-video-channels.ts
+++ b/server/tests/api/search/search-activitypub-video-channels.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { VideoChannel } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 PeerTubeServer, 10 PeerTubeServer,
9 SearchCommand, 11 SearchCommand,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 wait,
12 waitJobs 13 waitJobs
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { VideoChannel } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/api/search/search-activitypub-video-playlists.ts b/server/tests/api/search/search-activitypub-video-playlists.ts
index 34b318268..d9243ac53 100644
--- a/server/tests/api/search/search-activitypub-video-playlists.ts
+++ b/server/tests/api/search/search-activitypub-video-playlists.ts
@@ -2,6 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { VideoPlaylistPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
@@ -9,10 +11,8 @@ import {
9 SearchCommand, 11 SearchCommand,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 setDefaultVideoChannel, 13 setDefaultVideoChannel,
12 wait,
13 waitJobs 14 waitJobs
14} from '@shared/extra-utils' 15} from '@shared/server-commands'
15import { VideoPlaylistPrivacy } from '@shared/models'
16 16
17const expect = chai.expect 17const expect = chai.expect
18 18
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts
index a2e6e70fe..60b95ae4c 100644
--- a/server/tests/api/search/search-activitypub-videos.ts
+++ b/server/tests/api/search/search-activitypub-videos.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { VideoPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 PeerTubeServer, 10 PeerTubeServer,
9 SearchCommand, 11 SearchCommand,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 wait,
12 waitJobs 13 waitJobs
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { VideoPrivacy } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/api/search/search-channels.ts b/server/tests/api/search/search-channels.ts
index 67612537c..8a92def61 100644
--- a/server/tests/api/search/search-channels.ts
+++ b/server/tests/api/search/search-channels.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 SearchCommand, 10 SearchCommand,
11 setAccessTokensToServers 11 setAccessTokensToServers
12} from '@shared/extra-utils' 12} from '@shared/server-commands'
13import { VideoChannel } from '@shared/models' 13import { VideoChannel } from '@shared/models'
14 14
15const expect = chai.expect 15const expect = chai.expect
diff --git a/server/tests/api/search/search-index.ts b/server/tests/api/search/search-index.ts
index 1845c2069..f84d03345 100644
--- a/server/tests/api/search/search-index.ts
+++ b/server/tests/api/search/search-index.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, SearchCommand, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, PeerTubeServer, SearchCommand, setAccessTokensToServers } from '@shared/server-commands'
6import { 6import {
7 BooleanBothQuery, 7 BooleanBothQuery,
8 VideoChannelsSearchQuery, 8 VideoChannelsSearchQuery,
diff --git a/server/tests/api/search/search-playlists.ts b/server/tests/api/search/search-playlists.ts
index 15aac029a..1e9c8d4bb 100644
--- a/server/tests/api/search/search-playlists.ts
+++ b/server/tests/api/search/search-playlists.ts
@@ -10,7 +10,7 @@ import {
10 SearchCommand, 10 SearchCommand,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 setDefaultVideoChannel 12 setDefaultVideoChannel
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { VideoPlaylistPrivacy } from '@shared/models' 14import { VideoPlaylistPrivacy } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index ad2a2fddc..c544705d3 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -10,10 +10,10 @@ import {
10 SearchCommand, 10 SearchCommand,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 setDefaultVideoChannel, 12 setDefaultVideoChannel,
13 stopFfmpeg, 13 stopFfmpeg
14 wait 14} from '@shared/server-commands'
15} from '@shared/extra-utils'
16import { VideoPrivacy } from '@shared/models' 15import { VideoPrivacy } from '@shared/models'
16import { wait } from '@shared/core-utils'
17 17
18const expect = chai.expect 18const expect = chai.expect
19 19
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts
index 90a668edb..6d2333a6b 100644
--- a/server/tests/api/server/auto-follows.ts
+++ b/server/tests/api/server/auto-follows.ts
@@ -2,15 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import { MockInstancesIndex } from '@server/tests/shared'
6 cleanupTests, 6import { wait } from '@shared/core-utils'
7 createMultipleServers, 7import { cleanupTests, createMultipleServers, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
8 MockInstancesIndex,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 wait,
12 waitJobs
13} from '@shared/extra-utils'
14 8
15const expect = chai.expect 9const expect = chai.expect
16 10
diff --git a/server/tests/api/server/bulk.ts b/server/tests/api/server/bulk.ts
index 16cbcd5c3..1b81a6954 100644
--- a/server/tests/api/server/bulk.ts
+++ b/server/tests/api/server/bulk.ts
@@ -10,7 +10,7 @@ import {
10 PeerTubeServer, 10 PeerTubeServer,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14 14
15const expect = chai.expect 15const expect = chai.expect
16 16
diff --git a/server/tests/api/server/config-defaults.ts b/server/tests/api/server/config-defaults.ts
new file mode 100644
index 000000000..3ff09bf7e
--- /dev/null
+++ b/server/tests/api/server/config-defaults.ts
@@ -0,0 +1,213 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import * as chai from 'chai'
5import { FIXTURE_URLS } from '@server/tests/shared'
6import { VideoDetails, VideoPrivacy } from '@shared/models'
7import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands'
8
9const expect = chai.expect
10
11describe('Test config defaults', function () {
12 let server: PeerTubeServer
13 let channelId: number
14
15 before(async function () {
16 this.timeout(30000)
17
18 server = await createSingleServer(1)
19 await setAccessTokensToServers([ server ])
20 await setDefaultVideoChannel([ server ])
21
22 channelId = server.store.channel.id
23 })
24
25 describe('Default publish values', function () {
26
27 before(async function () {
28 const overrideConfig = {
29 defaults: {
30 publish: {
31 comments_enabled: false,
32 download_enabled: false,
33 privacy: VideoPrivacy.INTERNAL,
34 licence: 4
35 }
36 }
37 }
38
39 await server.kill()
40 await server.run(overrideConfig)
41 })
42
43 const attributes = {
44 name: 'video',
45 downloadEnabled: undefined,
46 commentsEnabled: undefined,
47 licence: undefined,
48 privacy: VideoPrivacy.PUBLIC // Privacy is mandatory for server
49 }
50
51 function checkVideo (video: VideoDetails) {
52 expect(video.downloadEnabled).to.be.false
53 expect(video.commentsEnabled).to.be.false
54 expect(video.licence.id).to.equal(4)
55 }
56
57 before(async function () {
58 await server.config.disableTranscoding()
59 await server.config.enableImports()
60 await server.config.enableLive({ allowReplay: false, transcoding: false })
61 })
62
63 it('Should have the correct server configuration', async function () {
64 const config = await server.config.getConfig()
65
66 expect(config.defaults.publish.commentsEnabled).to.be.false
67 expect(config.defaults.publish.downloadEnabled).to.be.false
68 expect(config.defaults.publish.licence).to.equal(4)
69 expect(config.defaults.publish.privacy).to.equal(VideoPrivacy.INTERNAL)
70 })
71
72 it('Should respect default values when uploading a video', async function () {
73 for (const mode of [ 'legacy' as 'legacy', 'resumable' as 'resumable' ]) {
74 const { id } = await server.videos.upload({ attributes, mode })
75
76 const video = await server.videos.get({ id })
77 checkVideo(video)
78 }
79 })
80
81 it('Should respect default values when importing a video using URL', async function () {
82 const { video: { id } } = await server.imports.importVideo({
83 attributes: {
84 ...attributes,
85 channelId,
86 targetUrl: FIXTURE_URLS.goodVideo
87 }
88 })
89
90 const video = await server.videos.get({ id })
91 checkVideo(video)
92 })
93
94 it('Should respect default values when importing a video using magnet URI', async function () {
95 const { video: { id } } = await server.imports.importVideo({
96 attributes: {
97 ...attributes,
98 channelId,
99 magnetUri: FIXTURE_URLS.magnet
100 }
101 })
102
103 const video = await server.videos.get({ id })
104 checkVideo(video)
105 })
106
107 it('Should respect default values when creating a live', async function () {
108 const { id } = await server.live.create({
109 fields: {
110 ...attributes,
111 channelId
112 }
113 })
114
115 const video = await server.videos.get({ id })
116 checkVideo(video)
117 })
118 })
119
120 describe('Default P2P values', function () {
121
122 describe('Webapp default value', function () {
123
124 before(async function () {
125 const overrideConfig = {
126 defaults: {
127 p2p: {
128 webapp: {
129 enabled: false
130 }
131 }
132 }
133 }
134
135 await server.kill()
136 await server.run(overrideConfig)
137 })
138
139 it('Should have appropriate P2P config', async function () {
140 const config = await server.config.getConfig()
141
142 expect(config.defaults.p2p.webapp.enabled).to.be.false
143 expect(config.defaults.p2p.embed.enabled).to.be.true
144 })
145
146 it('Should create a user with this default setting', async function () {
147 await server.users.create({ username: 'user_p2p_1' })
148 const userToken = await server.login.getAccessToken('user_p2p_1')
149
150 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
151 expect(p2pEnabled).to.be.false
152 })
153
154 it('Should register a user with this default setting', async function () {
155 await server.users.register({ username: 'user_p2p_2' })
156
157 const userToken = await server.login.getAccessToken('user_p2p_2')
158
159 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
160 expect(p2pEnabled).to.be.false
161 })
162 })
163
164 describe('Embed default value', function () {
165
166 before(async function () {
167 const overrideConfig = {
168 defaults: {
169 p2p: {
170 embed: {
171 enabled: false
172 }
173 }
174 },
175 signup: {
176 limit: 15
177 }
178 }
179
180 await server.kill()
181 await server.run(overrideConfig)
182 })
183
184 it('Should have appropriate P2P config', async function () {
185 const config = await server.config.getConfig()
186
187 expect(config.defaults.p2p.webapp.enabled).to.be.true
188 expect(config.defaults.p2p.embed.enabled).to.be.false
189 })
190
191 it('Should create a user with this default setting', async function () {
192 await server.users.create({ username: 'user_p2p_3' })
193 const userToken = await server.login.getAccessToken('user_p2p_3')
194
195 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
196 expect(p2pEnabled).to.be.true
197 })
198
199 it('Should register a user with this default setting', async function () {
200 await server.users.register({ username: 'user_p2p_4' })
201
202 const userToken = await server.login.getAccessToken('user_p2p_4')
203
204 const { p2pEnabled } = await server.users.getMyInfo({ token: userToken })
205 expect(p2pEnabled).to.be.true
206 })
207 })
208 })
209
210 after(async function () {
211 await cleanupTests([ server ])
212 })
213})
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index ea524723c..2356f701c 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { parallelTests } from '@shared/core-utils'
6import { CustomConfig, HttpStatusCode } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createSingleServer, 9 createSingleServer,
8 killallServers, 10 killallServers,
9 makeGetRequest, 11 makeGetRequest,
10 parallelTests,
11 PeerTubeServer, 12 PeerTubeServer,
12 setAccessTokensToServers 13 setAccessTokensToServers
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { CustomConfig, HttpStatusCode } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
@@ -43,6 +43,9 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
43 expect(data.services.twitter.username).to.equal('@Chocobozzz') 43 expect(data.services.twitter.username).to.equal('@Chocobozzz')
44 expect(data.services.twitter.whitelisted).to.be.false 44 expect(data.services.twitter.whitelisted).to.be.false
45 45
46 expect(data.client.videos.miniature.preferAuthorDisplayName).to.be.false
47 expect(data.client.menu.login.redirectOnSingleExternalAuth).to.be.false
48
46 expect(data.cache.previews.size).to.equal(1) 49 expect(data.cache.previews.size).to.equal(1)
47 expect(data.cache.captions.size).to.equal(1) 50 expect(data.cache.captions.size).to.equal(1)
48 expect(data.cache.torrents.size).to.equal(1) 51 expect(data.cache.torrents.size).to.equal(1)
@@ -138,6 +141,9 @@ function checkUpdatedConfig (data: CustomConfig) {
138 expect(data.services.twitter.username).to.equal('@Kuja') 141 expect(data.services.twitter.username).to.equal('@Kuja')
139 expect(data.services.twitter.whitelisted).to.be.true 142 expect(data.services.twitter.whitelisted).to.be.true
140 143
144 expect(data.client.videos.miniature.preferAuthorDisplayName).to.be.true
145 expect(data.client.menu.login.redirectOnSingleExternalAuth).to.be.true
146
141 expect(data.cache.previews.size).to.equal(2) 147 expect(data.cache.previews.size).to.equal(2)
142 expect(data.cache.captions.size).to.equal(3) 148 expect(data.cache.captions.size).to.equal(3)
143 expect(data.cache.torrents.size).to.equal(4) 149 expect(data.cache.torrents.size).to.equal(4)
@@ -246,6 +252,18 @@ const newCustomConfig: CustomConfig = {
246 whitelisted: true 252 whitelisted: true
247 } 253 }
248 }, 254 },
255 client: {
256 videos: {
257 miniature: {
258 preferAuthorDisplayName: true
259 }
260 },
261 menu: {
262 login: {
263 redirectOnSingleExternalAuth: true
264 }
265 }
266 },
249 cache: { 267 cache: {
250 previews: { 268 previews: {
251 size: 2 269 size: 2
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts
index c555661ad..f3facb04a 100644
--- a/server/tests/api/server/contact-form.ts
+++ b/server/tests/api/server/contact-form.ts
@@ -2,17 +2,17 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { MockSmtpServer } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
7import { HttpStatusCode } from '@shared/models'
5import { 8import {
6 cleanupTests, 9 cleanupTests,
7 ContactFormCommand, 10 ContactFormCommand,
8 createSingleServer, 11 createSingleServer,
9 MockSmtpServer,
10 PeerTubeServer, 12 PeerTubeServer,
11 setAccessTokensToServers, 13 setAccessTokensToServers,
12 wait,
13 waitJobs 14 waitJobs
14} from '@shared/extra-utils' 15} from '@shared/server-commands'
15import { HttpStatusCode } from '@shared/models'
16 16
17const expect = chai.expect 17const expect = chai.expect
18 18
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts
index 5f97edbc2..20b5e378c 100644
--- a/server/tests/api/server/email.ts
+++ b/server/tests/api/server/email.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, MockSmtpServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import { MockSmtpServer } from '@server/tests/shared'
6import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
7import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
7 8
8const expect = chai.expect 9const expect = chai.expect
9 10
@@ -185,7 +186,7 @@ describe('Test emails', function () {
185 this.timeout(10000) 186 this.timeout(10000)
186 187
187 const reason = 'my super bad reason' 188 const reason = 'my super bad reason'
188 await server.abuses.report({ videoId, reason }) 189 await server.abuses.report({ token: userAccessToken, videoId, reason })
189 190
190 await waitJobs(server) 191 await waitJobs(server)
191 expect(emails).to.have.lengthOf(3) 192 expect(emails).to.have.lengthOf(3)
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts
index 471f5d8d0..455fbc762 100644
--- a/server/tests/api/server/follow-constraints.ts
+++ b/server/tests/api/server/follow-constraints.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
6import { HttpStatusCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models' 6import { HttpStatusCode, PeerTubeProblemDocument, ServerErrorCode } from '@shared/models'
7 7
8const expect = chai.expect 8const expect = chai.expect
@@ -14,7 +14,7 @@ describe('Test follow constraints', function () {
14 let userToken: string 14 let userToken: string
15 15
16 before(async function () { 16 before(async function () {
17 this.timeout(90000) 17 this.timeout(240000)
18 18
19 servers = await createMultipleServers(2) 19 servers = await createMultipleServers(2)
20 20
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts
index 921f51043..120bd7f88 100644
--- a/server/tests/api/server/follows-moderation.ts
+++ b/server/tests/api/server/follows-moderation.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 setAccessTokensToServers, 10 setAccessTokensToServers,
11 waitJobs 11 waitJobs
12} from '@shared/extra-utils' 12} from '@shared/server-commands'
13 13
14const expect = chai.expect 14const expect = chai.expect
15 15
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index 832ba561a..c588cf664 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -2,19 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import { completeVideoCheck, dateIsValid, expectAccountFollows, expectChannelsFollows, testCaptionFile } from '@server/tests/shared'
6 cleanupTests,
7 completeVideoCheck,
8 createMultipleServers,
9 dateIsValid,
10 expectAccountFollows,
11 expectChannelsFollows,
12 PeerTubeServer,
13 setAccessTokensToServers,
14 testCaptionFile,
15 waitJobs
16} from '@shared/extra-utils'
17import { VideoCreateResult, VideoPrivacy } from '@shared/models' 6import { VideoCreateResult, VideoPrivacy } from '@shared/models'
7import { cleanupTests, createMultipleServers, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
18 8
19const expect = chai.expect 9const expect = chai.expect
20 10
@@ -22,7 +12,7 @@ describe('Test follows', function () {
22 let servers: PeerTubeServer[] = [] 12 let servers: PeerTubeServer[] = []
23 13
24 before(async function () { 14 before(async function () {
25 this.timeout(30000) 15 this.timeout(120000)
26 16
27 servers = await createMultipleServers(3) 17 servers = await createMultipleServers(3)
28 18
@@ -292,7 +282,7 @@ describe('Test follows', function () {
292 }) 282 })
293 283
294 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () { 284 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
295 this.timeout(60000) 285 this.timeout(120000)
296 286
297 await servers[1].videos.upload({ attributes: { name: 'server2' } }) 287 await servers[1].videos.upload({ attributes: { name: 'server2' } })
298 await servers[2].videos.upload({ attributes: { name: 'server3' } }) 288 await servers[2].videos.upload({ attributes: { name: 'server3' } })
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index fa1da8fe0..3dcd076f5 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -2,18 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { completeVideoCheck } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
7import { HttpStatusCode, JobState, VideoCreateResult, VideoPrivacy } from '@shared/models'
5import { 8import {
6 cleanupTests, 9 cleanupTests,
7 CommentsCommand, 10 CommentsCommand,
8 completeVideoCheck,
9 createMultipleServers, 11 createMultipleServers,
10 killallServers, 12 killallServers,
11 PeerTubeServer, 13 PeerTubeServer,
12 setAccessTokensToServers, 14 setAccessTokensToServers,
13 wait,
14 waitJobs 15 waitJobs
15} from '@shared/extra-utils' 16} from '@shared/server-commands'
16import { HttpStatusCode, JobState, VideoCreateResult, VideoPrivacy } from '@shared/models'
17 17
18const expect = chai.expect 18const expect = chai.expect
19 19
@@ -50,7 +50,7 @@ describe('Test handle downs', function () {
50 let commentCommands: CommentsCommand[] 50 let commentCommands: CommentsCommand[]
51 51
52 before(async function () { 52 before(async function () {
53 this.timeout(30000) 53 this.timeout(120000)
54 54
55 servers = await createMultipleServers(3) 55 servers = await createMultipleServers(3)
56 commentCommands = servers.map(s => s.comments) 56 commentCommands = servers.map(s => s.comments)
diff --git a/server/tests/api/server/homepage.ts b/server/tests/api/server/homepage.ts
index cb3ba5677..552ee98cf 100644
--- a/server/tests/api/server/homepage.ts
+++ b/server/tests/api/server/homepage.ts
@@ -10,7 +10,7 @@ import {
10 killallServers, 10 killallServers,
11 PeerTubeServer, 11 PeerTubeServer,
12 setAccessTokensToServers 12 setAccessTokensToServers
13} from '../../../../shared/extra-utils/index' 13} from '../../../../shared/server-commands/index'
14 14
15const expect = chai.expect 15const expect = chai.expect
16 16
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts
index 8136fc3c6..45be107ce 100644
--- a/server/tests/api/server/index.ts
+++ b/server/tests/api/server/index.ts
@@ -1,4 +1,6 @@
1import './auto-follows' 1import './auto-follows'
2import './bulk'
3import './config-defaults'
2import './config' 4import './config'
3import './contact-form' 5import './contact-form'
4import './email' 6import './email'
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts
index 5d946f5e8..4294e1fd5 100644
--- a/server/tests/api/server/jobs.ts
+++ b/server/tests/api/server/jobs.ts
@@ -2,15 +2,15 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { dateIsValid } from '@server/tests/shared'
5import { 6import {
6 cleanupTests, 7 cleanupTests,
7 createMultipleServers, 8 createMultipleServers,
8 dateIsValid,
9 doubleFollow, 9 doubleFollow,
10 PeerTubeServer, 10 PeerTubeServer,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14 14
15const expect = chai.expect 15const expect = chai.expect
16 16
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts
index 4fa13886e..697f10337 100644
--- a/server/tests/api/server/logs.ts
+++ b/server/tests/api/server/logs.ts
@@ -10,7 +10,7 @@ import {
10 PeerTubeServer, 10 PeerTubeServer,
11 setAccessTokensToServers, 11 setAccessTokensToServers,
12 waitJobs 12 waitJobs
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14 14
15const expect = chai.expect 15const expect = chai.expect
16 16
diff --git a/server/tests/api/server/no-client.ts b/server/tests/api/server/no-client.ts
index 1e0c95a3b..913907788 100644
--- a/server/tests/api/server/no-client.ts
+++ b/server/tests/api/server/no-client.ts
@@ -1,6 +1,6 @@
1import 'mocha' 1import 'mocha'
2import request from 'supertest' 2import request from 'supertest'
3import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/extra-utils' 3import { cleanupTests, createSingleServer, PeerTubeServer } from '@shared/server-commands'
4import { HttpStatusCode } from '@shared/models' 4import { HttpStatusCode } from '@shared/models'
5 5
6describe('Start and stop server without web client routes', function () { 6describe('Start and stop server without web client routes', function () {
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
index 5f9f4ffdd..76d3e2481 100644
--- a/server/tests/api/server/plugins.ts
+++ b/server/tests/api/server/plugins.ts
@@ -2,17 +2,17 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { testHelloWorldRegisteredSettings } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
7import { HttpStatusCode, PluginType } from '@shared/models'
5import { 8import {
6 cleanupTests, 9 cleanupTests,
7 createSingleServer, 10 createSingleServer,
8 killallServers, 11 killallServers,
9 PeerTubeServer, 12 PeerTubeServer,
10 PluginsCommand, 13 PluginsCommand,
11 setAccessTokensToServers, 14 setAccessTokensToServers
12 testHelloWorldRegisteredSettings, 15} from '@shared/server-commands'
13 wait
14} from '@shared/extra-utils'
15import { HttpStatusCode, PluginType } from '@shared/models'
16 16
17const expect = chai.expect 17const expect = chai.expect
18 18
@@ -99,9 +99,11 @@ describe('Test plugins', function () {
99 99
100 const theme = config.theme.registered.find(r => r.name === 'background-red') 100 const theme = config.theme.registered.find(r => r.name === 'background-red')
101 expect(theme).to.not.be.undefined 101 expect(theme).to.not.be.undefined
102 expect(theme.npmName).to.equal('peertube-theme-background-red')
102 103
103 const plugin = config.plugin.registered.find(r => r.name === 'hello-world') 104 const plugin = config.plugin.registered.find(r => r.name === 'hello-world')
104 expect(plugin).to.not.be.undefined 105 expect(plugin).to.not.be.undefined
106 expect(plugin.npmName).to.equal('peertube-plugin-hello-world')
105 }) 107 })
106 108
107 it('Should update the default theme in the configuration', async function () { 109 it('Should update the default theme in the configuration', async function () {
diff --git a/server/tests/api/server/proxy.ts b/server/tests/api/server/proxy.ts
index 29f3e10d8..2a8ff56d2 100644
--- a/server/tests/api/server/proxy.ts
+++ b/server/tests/api/server/proxy.ts
@@ -2,18 +2,17 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { FIXTURE_URLS, MockProxy } from '@server/tests/shared'
6import { HttpStatusCode, VideoPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 doubleFollow, 10 doubleFollow,
9 FIXTURE_URLS,
10 PeerTubeServer, 11 PeerTubeServer,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 setDefaultVideoChannel, 13 setDefaultVideoChannel,
13 waitJobs 14 waitJobs
14} from '@shared/extra-utils' 15} from '@shared/server-commands'
15import { MockProxy } from '@shared/extra-utils/mock-servers/mock-proxy'
16import { HttpStatusCode, VideoPrivacy } from '@shared/models'
17 16
18const expect = chai.expect 17const expect = chai.expect
19 18
@@ -97,7 +96,7 @@ describe('Test proxy', function () {
97 } 96 }
98 97
99 it('Should succeed import with the appropriate proxy config', async function () { 98 it('Should succeed import with the appropriate proxy config', async function () {
100 this.timeout(40000) 99 this.timeout(120000)
101 100
102 await servers[0].kill() 101 await servers[0].kill()
103 await servers[0].run({}, { env: goodEnv }) 102 await servers[0].run({}, { env: goodEnv })
@@ -112,7 +111,7 @@ describe('Test proxy', function () {
112 }) 111 })
113 112
114 it('Should fail import with a wrong proxy config', async function () { 113 it('Should fail import with a wrong proxy config', async function () {
115 this.timeout(40000) 114 this.timeout(120000)
116 115
117 await servers[0].kill() 116 await servers[0].kill()
118 await servers[0].run({}, { env: badEnv }) 117 await servers[0].run({}, { env: badEnv })
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts
index 484f88d67..968d98e96 100644
--- a/server/tests/api/server/reverse-proxy.ts
+++ b/server/tests/api/server/reverse-proxy.ts
@@ -1,8 +1,9 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, wait } from '@shared/extra-utils' 4import { wait } from '@shared/core-utils'
5import { HttpStatusCode } from '@shared/models' 5import { HttpStatusCode } from '@shared/models'
6import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
6 7
7describe('Test application behind a reverse proxy', function () { 8describe('Test application behind a reverse proxy', function () {
8 let server: PeerTubeServer 9 let server: PeerTubeServer
diff --git a/server/tests/api/server/services.ts b/server/tests/api/server/services.ts
index 823630ae4..5fd2abda4 100644
--- a/server/tests/api/server/services.ts
+++ b/server/tests/api/server/services.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands'
6import { Video, VideoPlaylistPrivacy } from '@shared/models' 6import { Video, VideoPlaylistPrivacy } from '@shared/models'
7 7
8const expect = chai.expect 8const expect = chai.expect
diff --git a/server/tests/api/server/slow-follows.ts b/server/tests/api/server/slow-follows.ts
index 2bef0c9f2..666a7c2e6 100644
--- a/server/tests/api/server/slow-follows.ts
+++ b/server/tests/api/server/slow-follows.ts
@@ -2,8 +2,15 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils'
6import { Job } from '@shared/models' 5import { Job } from '@shared/models'
6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
7 14
8const expect = chai.expect 15const expect = chai.expect
9 16
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
index efc80463c..f0334532b 100644
--- a/server/tests/api/server/stats.ts
+++ b/server/tests/api/server/stats.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { ActivityType, VideoPlaylistPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 doubleFollow, 10 doubleFollow,
9 PeerTubeServer, 11 PeerTubeServer,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 wait,
12 waitJobs 13 waitJobs
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { ActivityType, VideoPlaylistPrivacy } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts
index 30a9618b3..712bb485f 100644
--- a/server/tests/api/server/tracker.ts
+++ b/server/tests/api/server/tracker.ts
@@ -3,7 +3,7 @@
3import 'mocha' 3import 'mocha'
4import magnetUtil from 'magnet-uri' 4import magnetUtil from 'magnet-uri'
5import WebTorrent from 'webtorrent' 5import WebTorrent from 'webtorrent'
6import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 6import { cleanupTests, createSingleServer, killallServers, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7 7
8describe('Test tracker', function () { 8describe('Test tracker', function () {
9 let server: PeerTubeServer 9 let server: PeerTubeServer
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts
index d1d192238..57cca6ad4 100644
--- a/server/tests/api/users/user-subscriptions.ts
+++ b/server/tests/api/users/user-subscriptions.ts
@@ -2,6 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { VideoPrivacy } from '@shared/models'
5import { 6import {
6 cleanupTests, 7 cleanupTests,
7 createMultipleServers, 8 createMultipleServers,
@@ -10,7 +11,7 @@ import {
10 setAccessTokensToServers, 11 setAccessTokensToServers,
11 SubscriptionsCommand, 12 SubscriptionsCommand,
12 waitJobs 13 waitJobs
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14 15
15const expect = chai.expect 16const expect = chai.expect
16 17
@@ -32,20 +33,18 @@ describe('Test users subscriptions', function () {
32 // Server 1 and server 2 follow each other 33 // Server 1 and server 2 follow each other
33 await doubleFollow(servers[0], servers[1]) 34 await doubleFollow(servers[0], servers[1])
34 35
35 { 36 for (const server of servers) {
36 for (const server of servers) { 37 const user = { username: 'user' + server.serverNumber, password: 'password' }
37 const user = { username: 'user' + server.serverNumber, password: 'password' } 38 await server.users.create({ username: user.username, password: user.password })
38 await server.users.create({ username: user.username, password: user.password })
39 39
40 const accessToken = await server.login.getAccessToken(user) 40 const accessToken = await server.login.getAccessToken(user)
41 users.push({ accessToken }) 41 users.push({ accessToken })
42 42
43 const videoName1 = 'video 1-' + server.serverNumber 43 const videoName1 = 'video 1-' + server.serverNumber
44 await server.videos.upload({ token: accessToken, attributes: { name: videoName1 } }) 44 await server.videos.upload({ token: accessToken, attributes: { name: videoName1 } })
45 45
46 const videoName2 = 'video 2-' + server.serverNumber 46 const videoName2 = 'video 2-' + server.serverNumber
47 await server.videos.upload({ token: accessToken, attributes: { name: videoName2 } }) 47 await server.videos.upload({ token: accessToken, attributes: { name: videoName2 } })
48 }
49 } 48 }
50 49
51 await waitJobs(servers) 50 await waitJobs(servers)
@@ -540,6 +539,40 @@ describe('Test users subscriptions', function () {
540 } 539 }
541 }) 540 })
542 541
542 it('Should update video as internal and not see from remote server', async function () {
543 this.timeout(30000)
544
545 await servers[2].videos.update({ id: video3UUID, attributes: { name: 'internal', privacy: VideoPrivacy.INTERNAL } })
546 await waitJobs(servers)
547
548 {
549 const { data } = await command.listVideos({ token: users[0].accessToken })
550 expect(data.find(v => v.name === 'internal')).to.not.exist
551 }
552 })
553
554 it('Should see internal from local user', async function () {
555 const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken })
556 expect(data.find(v => v.name === 'internal')).to.exist
557 })
558
559 it('Should update video as private and not see from anyone server', async function () {
560 this.timeout(30000)
561
562 await servers[2].videos.update({ id: video3UUID, attributes: { name: 'private', privacy: VideoPrivacy.PRIVATE } })
563 await waitJobs(servers)
564
565 {
566 const { data } = await command.listVideos({ token: users[0].accessToken })
567 expect(data.find(v => v.name === 'private')).to.not.exist
568 }
569
570 {
571 const { data } = await servers[2].subscriptions.listVideos({ token: servers[2].accessToken })
572 expect(data.find(v => v.name === 'private')).to.not.exist
573 }
574 })
575
543 after(async function () { 576 after(async function () {
544 await cleanupTests(servers) 577 await cleanupTests(servers)
545 }) 578 })
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts
index d0ca82b07..5b2bbc520 100644
--- a/server/tests/api/users/users-multiple-servers.ts
+++ b/server/tests/api/users/users-multiple-servers.ts
@@ -6,16 +6,18 @@ import {
6 checkActorFilesWereRemoved, 6 checkActorFilesWereRemoved,
7 checkTmpIsEmpty, 7 checkTmpIsEmpty,
8 checkVideoFilesWereRemoved, 8 checkVideoFilesWereRemoved,
9 saveVideoInServers,
10 testImage
11} from '@server/tests/shared'
12import { MyUser } from '@shared/models'
13import {
9 cleanupTests, 14 cleanupTests,
10 createMultipleServers, 15 createMultipleServers,
11 doubleFollow, 16 doubleFollow,
12 PeerTubeServer, 17 PeerTubeServer,
13 saveVideoInServers,
14 setAccessTokensToServers, 18 setAccessTokensToServers,
15 testImage,
16 waitJobs 19 waitJobs
17} from '@shared/extra-utils' 20} from '@shared/server-commands'
18import { MyUser } from '@shared/models'
19 21
20const expect = chai.expect 22const expect = chai.expect
21 23
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts
index f54463359..0f3cc401a 100644
--- a/server/tests/api/users/users-verification.ts
+++ b/server/tests/api/users/users-verification.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, MockSmtpServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import { MockSmtpServer } from '@server/tests/shared'
6import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
7import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
7 8
8const expect = chai.expect 9const expect = chai.expect
9 10
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 6c41e7d56..7023b3f08 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -2,6 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { testImage } from '@server/tests/shared'
6import { AbuseState, HttpStatusCode, OAuth2ErrorCode, UserAdminFlag, UserRole, Video, VideoPlaylistType } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createSingleServer, 9 createSingleServer,
@@ -9,10 +11,8 @@ import {
9 makePutBodyRequest, 11 makePutBodyRequest,
10 PeerTubeServer, 12 PeerTubeServer,
11 setAccessTokensToServers, 13 setAccessTokensToServers,
12 testImage,
13 waitJobs 14 waitJobs
14} from '@shared/extra-utils' 15} from '@shared/server-commands'
15import { AbuseState, HttpStatusCode, OAuth2ErrorCode, UserAdminFlag, UserRole, Video, VideoPlaylistType } from '@shared/models'
16 16
17const expect = chai.expect 17const expect = chai.expect
18 18
@@ -230,7 +230,7 @@ describe('Test users', function () {
230 }) 230 })
231 231
232 it('Should have an expired access token', async function () { 232 it('Should have an expired access token', async function () {
233 this.timeout(15000) 233 this.timeout(60000)
234 234
235 await server.sql.setTokenField(server.accessToken, 'accessTokenExpiresAt', new Date().toISOString()) 235 await server.sql.setTokenField(server.accessToken, 'accessTokenExpiresAt', new Date().toISOString())
236 await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString()) 236 await server.sql.setTokenField(server.accessToken, 'refreshTokenExpiresAt', new Date().toISOString())
@@ -559,6 +559,28 @@ describe('Test users', function () {
559 expect(user.autoPlayNextVideo).to.be.true 559 expect(user.autoPlayNextVideo).to.be.true
560 }) 560 })
561 561
562 it('Should be able to change the p2p attribute', async function () {
563 {
564 await server.users.updateMe({
565 token: userToken,
566 webTorrentEnabled: false
567 })
568
569 const user = await server.users.getMyInfo({ token: userToken })
570 expect(user.p2pEnabled).to.be.false
571 }
572
573 {
574 await server.users.updateMe({
575 token: userToken,
576 p2pEnabled: true
577 })
578
579 const user = await server.users.getMyInfo({ token: userToken })
580 expect(user.p2pEnabled).to.be.true
581 }
582 })
583
562 it('Should be able to change the email attribute', async function () { 584 it('Should be able to change the email attribute', async function () {
563 await server.users.updateMe({ 585 await server.users.updateMe({
564 token: userToken, 586 token: userToken,
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts
index f4b635bd5..e58360ffe 100644
--- a/server/tests/api/videos/audio-only.ts
+++ b/server/tests/api/videos/audio-only.ts
@@ -3,7 +3,14 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { getAudioStream, getVideoStreamSize } from '@server/helpers/ffprobe-utils' 5import { getAudioStream, getVideoStreamSize } from '@server/helpers/ffprobe-utils'
6import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 6import {
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 waitJobs
13} from '@shared/server-commands'
7 14
8const expect = chai.expect 15const expect = chai.expect
9 16
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index c6c279064..ecdd36613 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -4,23 +4,24 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import request from 'supertest' 5import request from 'supertest'
6import { 6import {
7 buildAbsoluteFixturePath,
8 checkTmpIsEmpty, 7 checkTmpIsEmpty,
9 checkVideoFilesWereRemoved, 8 checkVideoFilesWereRemoved,
10 cleanupTests,
11 completeVideoCheck, 9 completeVideoCheck,
12 createMultipleServers,
13 dateIsValid, 10 dateIsValid,
11 saveVideoInServers,
12 testImage
13} from '@server/tests/shared'
14import { buildAbsoluteFixturePath, wait } from '@shared/core-utils'
15import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
16import {
17 cleanupTests,
18 createMultipleServers,
14 doubleFollow, 19 doubleFollow,
15 PeerTubeServer, 20 PeerTubeServer,
16 saveVideoInServers,
17 setAccessTokensToServers, 21 setAccessTokensToServers,
18 testImage,
19 wait,
20 waitJobs, 22 waitJobs,
21 webtorrentAdd 23 webtorrentAdd
22} from '@shared/extra-utils' 24} from '@shared/server-commands'
23import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models'
24 25
25const expect = chai.expect 26const expect = chai.expect
26 27
@@ -379,7 +380,7 @@ describe('Test multiple servers', function () {
379 380
380 describe('Should seed the uploaded video', function () { 381 describe('Should seed the uploaded video', function () {
381 it('Should add the file 1 by asking server 3', async function () { 382 it('Should add the file 1 by asking server 3', async function () {
382 this.timeout(10000) 383 this.timeout(30000)
383 384
384 const { data } = await servers[2].videos.list() 385 const { data } = await servers[2].videos.list()
385 386
@@ -395,7 +396,7 @@ describe('Test multiple servers', function () {
395 }) 396 })
396 397
397 it('Should add the file 2 by asking server 1', async function () { 398 it('Should add the file 2 by asking server 1', async function () {
398 this.timeout(10000) 399 this.timeout(30000)
399 400
400 const { data } = await servers[0].videos.list() 401 const { data } = await servers[0].videos.list()
401 402
@@ -409,7 +410,7 @@ describe('Test multiple servers', function () {
409 }) 410 })
410 411
411 it('Should add the file 3 by asking server 2', async function () { 412 it('Should add the file 3 by asking server 2', async function () {
412 this.timeout(10000) 413 this.timeout(30000)
413 414
414 const { data } = await servers[1].videos.list() 415 const { data } = await servers[1].videos.list()
415 416
@@ -423,7 +424,7 @@ describe('Test multiple servers', function () {
423 }) 424 })
424 425
425 it('Should add the file 3-2 by asking server 1', async function () { 426 it('Should add the file 3-2 by asking server 1', async function () {
426 this.timeout(10000) 427 this.timeout(30000)
427 428
428 const { data } = await servers[0].videos.list() 429 const { data } = await servers[0].videos.list()
429 430
@@ -437,7 +438,7 @@ describe('Test multiple servers', function () {
437 }) 438 })
438 439
439 it('Should add the file 2 in 360p by asking server 1', async function () { 440 it('Should add the file 2 in 360p by asking server 1', async function () {
440 this.timeout(10000) 441 this.timeout(30000)
441 442
442 const { data } = await servers[0].videos.list() 443 const { data } = await servers[0].videos.list()
443 444
@@ -594,7 +595,7 @@ describe('Test multiple servers', function () {
594 let updatedAtMin: Date 595 let updatedAtMin: Date
595 596
596 it('Should update video 3', async function () { 597 it('Should update video 3', async function () {
597 this.timeout(10000) 598 this.timeout(30000)
598 599
599 const attributes = { 600 const attributes = {
600 name: 'my super video updated', 601 name: 'my super video updated',
@@ -617,7 +618,7 @@ describe('Test multiple servers', function () {
617 }) 618 })
618 619
619 it('Should have the video 3 updated on each server', async function () { 620 it('Should have the video 3 updated on each server', async function () {
620 this.timeout(10000) 621 this.timeout(30000)
621 622
622 for (const server of servers) { 623 for (const server of servers) {
623 const { data } = await server.videos.list() 624 const { data } = await server.videos.list()
@@ -668,7 +669,7 @@ describe('Test multiple servers', function () {
668 }) 669 })
669 670
670 it('Should only update thumbnail and update updatedAt attribute', async function () { 671 it('Should only update thumbnail and update updatedAt attribute', async function () {
671 this.timeout(10000) 672 this.timeout(30000)
672 673
673 const attributes = { 674 const attributes = {
674 thumbnailfile: 'thumbnail.jpg' 675 thumbnailfile: 'thumbnail.jpg'
@@ -860,7 +861,7 @@ describe('Test multiple servers', function () {
860 }) 861 })
861 862
862 it('Should delete a reply', async function () { 863 it('Should delete a reply', async function () {
863 this.timeout(10000) 864 this.timeout(30000)
864 865
865 await servers[2].comments.delete({ videoId: videoUUID, commentId: childOfFirstChild.comment.id }) 866 await servers[2].comments.delete({ videoId: videoUUID, commentId: childOfFirstChild.comment.id })
866 867
@@ -891,7 +892,7 @@ describe('Test multiple servers', function () {
891 }) 892 })
892 893
893 it('Should delete the thread comments', async function () { 894 it('Should delete the thread comments', async function () {
894 this.timeout(10000) 895 this.timeout(30000)
895 896
896 const { data } = await servers[0].comments.listThreads({ videoId: videoUUID }) 897 const { data } = await servers[0].comments.listThreads({ videoId: videoUUID })
897 const commentId = data.find(c => c.text === 'my super first comment').id 898 const commentId = data.find(c => c.text === 'my super first comment').id
diff --git a/server/tests/api/videos/resumable-upload.ts b/server/tests/api/videos/resumable-upload.ts
index 1ba7cdbcc..d6f4da630 100644
--- a/server/tests/api/videos/resumable-upload.ts
+++ b/server/tests/api/videos/resumable-upload.ts
@@ -4,15 +4,9 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { pathExists, readdir, stat } from 'fs-extra' 5import { pathExists, readdir, stat } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7import { 7import { buildAbsoluteFixturePath } from '@shared/core-utils'
8 buildAbsoluteFixturePath,
9 cleanupTests,
10 createSingleServer,
11 PeerTubeServer,
12 setAccessTokensToServers,
13 setDefaultVideoChannel
14} from '@shared/extra-utils'
15import { HttpStatusCode, VideoPrivacy } from '@shared/models' 8import { HttpStatusCode, VideoPrivacy } from '@shared/models'
9import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, setDefaultVideoChannel } from '@shared/server-commands'
16 10
17const expect = chai.expect 11const expect = chai.expect
18 12
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index a0e4a156c..28bf018c5 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -2,17 +2,10 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import { checkVideoFilesWereRemoved, completeVideoCheck, testImage } from '@server/tests/shared'
6 checkVideoFilesWereRemoved, 6import { wait } from '@shared/core-utils'
7 cleanupTests,
8 completeVideoCheck,
9 createSingleServer,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 testImage,
13 wait
14} from '@shared/extra-utils'
15import { Video, VideoPrivacy } from '@shared/models' 7import { Video, VideoPrivacy } from '@shared/models'
8import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
16 9
17const expect = chai.expect 10const expect = chai.expect
18 11
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts
index 3bb0d131c..b7f26c35f 100644
--- a/server/tests/api/videos/video-captions.ts
+++ b/server/tests/api/videos/video-captions.ts
@@ -2,17 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { checkVideoFilesWereRemoved, testCaptionFile } from '@server/tests/shared'
6import { wait } from '@shared/core-utils'
5import { 7import {
6 checkVideoFilesWereRemoved,
7 cleanupTests, 8 cleanupTests,
8 createMultipleServers, 9 createMultipleServers,
9 doubleFollow, 10 doubleFollow,
10 PeerTubeServer, 11 PeerTubeServer,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 testCaptionFile,
13 wait,
14 waitJobs 13 waitJobs
15} from '@shared/extra-utils' 14} from '@shared/server-commands'
16 15
17const expect = chai.expect 16const expect = chai.expect
18 17
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
index d6665fe4e..6c229c6cf 100644
--- a/server/tests/api/videos/video-change-ownership.ts
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -12,7 +12,7 @@ import {
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 setDefaultVideoChannel, 13 setDefaultVideoChannel,
14 waitJobs 14 waitJobs
15} from '@shared/extra-utils' 15} from '@shared/server-commands'
16import { HttpStatusCode, VideoPrivacy } from '@shared/models' 16import { HttpStatusCode, VideoPrivacy } from '@shared/models'
17 17
18const expect = chai.expect 18const expect = chai.expect
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index c25754eb6..d435f3682 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -4,6 +4,9 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { basename } from 'path' 5import { basename } from 'path'
6import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants' 6import { ACTOR_IMAGES_SIZE } from '@server/initializers/constants'
7import { testFileExistsOrNot, testImage } from '@server/tests/shared'
8import { wait } from '@shared/core-utils'
9import { User, VideoChannel } from '@shared/models'
7import { 10import {
8 cleanupTests, 11 cleanupTests,
9 createMultipleServers, 12 createMultipleServers,
@@ -11,12 +14,8 @@ import {
11 PeerTubeServer, 14 PeerTubeServer,
12 setAccessTokensToServers, 15 setAccessTokensToServers,
13 setDefaultVideoChannel, 16 setDefaultVideoChannel,
14 testFileExistsOrNot,
15 testImage,
16 wait,
17 waitJobs 17 waitJobs
18} from '@shared/extra-utils' 18} from '@shared/server-commands'
19import { User, VideoChannel } from '@shared/models'
20 19
21const expect = chai.expect 20const expect = chai.expect
22 21
@@ -33,6 +32,7 @@ describe('Test video channels', function () {
33 let totoChannel: number 32 let totoChannel: number
34 let videoUUID: string 33 let videoUUID: string
35 let accountName: string 34 let accountName: string
35 let secondUserChannelName: string
36 36
37 const avatarPaths: { [ port: number ]: string } = {} 37 const avatarPaths: { [ port: number ]: string } = {}
38 const bannerPaths: { [ port: number ]: string } = {} 38 const bannerPaths: { [ port: number ]: string } = {}
@@ -219,6 +219,35 @@ describe('Test video channels', function () {
219 } 219 }
220 }) 220 })
221 221
222 it('Should update another accounts video channel', async function () {
223 this.timeout(15000)
224
225 const result = await servers[0].users.generate('second_user')
226 secondUserChannelName = result.userChannelName
227
228 await servers[0].videos.quickUpload({ name: 'video', token: result.token })
229
230 const videoChannelAttributes = {
231 displayName: 'video channel updated',
232 description: 'video channel description updated',
233 support: 'support updated'
234 }
235
236 await servers[0].channels.update({ channelName: secondUserChannelName, attributes: videoChannelAttributes })
237
238 await waitJobs(servers)
239 })
240
241 it('Should have another accounts video channel updated', async function () {
242 for (const server of servers) {
243 const body = await server.channels.get({ channelName: `${secondUserChannelName}@${servers[0].host}` })
244
245 expect(body.displayName).to.equal('video channel updated')
246 expect(body.description).to.equal('video channel description updated')
247 expect(body.support).to.equal('support updated')
248 }
249 })
250
222 it('Should update the channel support field and update videos too', async function () { 251 it('Should update the channel support field and update videos too', async function () {
223 this.timeout(35000) 252 this.timeout(35000)
224 253
@@ -368,12 +397,13 @@ describe('Test video channels', function () {
368 }) 397 })
369 398
370 it('Should have video channel deleted', async function () { 399 it('Should have video channel deleted', async function () {
371 const body = await servers[0].channels.list({ start: 0, count: 10 }) 400 const body = await servers[0].channels.list({ start: 0, count: 10, sort: 'createdAt' })
372 401
373 expect(body.total).to.equal(1) 402 expect(body.total).to.equal(2)
374 expect(body.data).to.be.an('array') 403 expect(body.data).to.be.an('array')
375 expect(body.data).to.have.lengthOf(1) 404 expect(body.data).to.have.lengthOf(2)
376 expect(body.data[0].displayName).to.equal('Main root channel') 405 expect(body.data[0].displayName).to.equal('Main root channel')
406 expect(body.data[1].displayName).to.equal('video channel updated')
377 }) 407 })
378 408
379 it('Should create the main channel with an uuid if there is a conflict', async function () { 409 it('Should create the main channel with an uuid if there is a conflict', async function () {
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
index 61ee54540..2ae523970 100644
--- a/server/tests/api/videos/video-comments.ts
+++ b/server/tests/api/videos/video-comments.ts
@@ -2,15 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import { dateIsValid, testImage } from '@server/tests/shared'
6 cleanupTests, 6import { cleanupTests, CommentsCommand, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7 CommentsCommand,
8 createSingleServer,
9 dateIsValid,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 testImage
13} from '@shared/extra-utils'
14 7
15const expect = chai.expect 8const expect = chai.expect
16 9
diff --git a/server/tests/api/videos/video-create-transcoding.ts b/server/tests/api/videos/video-create-transcoding.ts
index c4627e0c1..62a6bab0d 100644
--- a/server/tests/api/videos/video-create-transcoding.ts
+++ b/server/tests/api/videos/video-create-transcoding.ts
@@ -2,20 +2,20 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { expectStartWith } from '@server/tests/shared'
6import { areObjectStorageTestsDisabled } from '@shared/core-utils'
7import { HttpStatusCode, VideoDetails } from '@shared/models'
5import { 8import {
6 areObjectStorageTestsDisabled,
7 cleanupTests, 9 cleanupTests,
8 createMultipleServers, 10 createMultipleServers,
9 doubleFollow, 11 doubleFollow,
10 expectNoFailedTranscodingJob, 12 expectNoFailedTranscodingJob,
11 expectStartWith,
12 makeRawRequest, 13 makeRawRequest,
13 ObjectStorageCommand, 14 ObjectStorageCommand,
14 PeerTubeServer, 15 PeerTubeServer,
15 setAccessTokensToServers, 16 setAccessTokensToServers,
16 waitJobs 17 waitJobs
17} from '@shared/extra-utils' 18} from '@shared/server-commands'
18import { HttpStatusCode, VideoDetails } from '@shared/models'
19 19
20const expect = chai.expect 20const expect = chai.expect
21 21
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts
index d22b4ed96..20b20488f 100644
--- a/server/tests/api/videos/video-description.ts
+++ b/server/tests/api/videos/video-description.ts
@@ -2,7 +2,14 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7const expect = chai.expect 14const expect = chai.expect
8 15
diff --git a/server/tests/api/videos/video-files.ts b/server/tests/api/videos/video-files.ts
index fcb2ca2e4..b0ef4a2e9 100644
--- a/server/tests/api/videos/video-files.ts
+++ b/server/tests/api/videos/video-files.ts
@@ -2,7 +2,14 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cleanupTests, createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 PeerTubeServer,
10 setAccessTokensToServers,
11 waitJobs
12} from '@shared/server-commands'
6 13
7describe('Test videos files', function () { 14describe('Test videos files', function () {
8 let servers: PeerTubeServer[] 15 let servers: PeerTubeServer[]
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts
index a18c3d672..218ec08ae 100644
--- a/server/tests/api/videos/video-hls.ts
+++ b/server/tests/api/videos/video-hls.ts
@@ -3,26 +3,27 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
7import { 6import {
8 areObjectStorageTestsDisabled,
9 checkDirectoryIsEmpty, 7 checkDirectoryIsEmpty,
10 checkResolutionsInMasterPlaylist, 8 checkResolutionsInMasterPlaylist,
11 checkSegmentHash, 9 checkSegmentHash,
12 checkTmpIsEmpty, 10 checkTmpIsEmpty,
11 expectStartWith,
12 hlsInfohashExist
13} from '@server/tests/shared'
14import { areObjectStorageTestsDisabled, removeFragmentedMP4Ext, uuidRegex } from '@shared/core-utils'
15import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models'
16import {
13 cleanupTests, 17 cleanupTests,
14 createMultipleServers, 18 createMultipleServers,
15 doubleFollow, 19 doubleFollow,
16 expectStartWith,
17 hlsInfohashExist,
18 makeRawRequest, 20 makeRawRequest,
19 ObjectStorageCommand, 21 ObjectStorageCommand,
20 PeerTubeServer, 22 PeerTubeServer,
21 setAccessTokensToServers, 23 setAccessTokensToServers,
22 waitJobs, 24 waitJobs,
23 webtorrentAdd 25 webtorrentAdd
24} from '@shared/extra-utils' 26} from '@shared/server-commands'
25import { HttpStatusCode, VideoStreamingPlaylistType } from '@shared/models'
26import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' 27import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
27 28
28const expect = chai.expect 29const expect = chai.expect
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index b6168b54e..e8e0f01f1 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -4,21 +4,19 @@ import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { pathExists, readdir, remove } from 'fs-extra' 5import { pathExists, readdir, remove } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared'
8import { areHttpImportTestsDisabled } from '@shared/core-utils'
9import { VideoPrivacy, VideoResolution } from '@shared/models'
7import { 10import {
8 areHttpImportTestsDisabled,
9 cleanupTests, 11 cleanupTests,
10 createMultipleServers, 12 createMultipleServers,
11 createSingleServer, 13 createSingleServer,
12 doubleFollow, 14 doubleFollow,
13 FIXTURE_URLS,
14 PeerTubeServer, 15 PeerTubeServer,
15 setAccessTokensToServers, 16 setAccessTokensToServers,
16 setDefaultVideoChannel, 17 setDefaultVideoChannel,
17 testCaptionFile,
18 testImage,
19 waitJobs 18 waitJobs
20} from '@shared/extra-utils' 19} from '@shared/server-commands'
21import { VideoPrivacy, VideoResolution } from '@shared/models'
22 20
23async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) { 21async function checkVideosServer1 (server: PeerTubeServer, idHttp: string, idMagnet: string, idTorrent: string) {
24 const videoHttp = await server.videos.get({ id: idHttp }) 22 const videoHttp = await server.videos.get({ id: idHttp })
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts
index b5d183d62..99ea67a0f 100644
--- a/server/tests/api/videos/video-nsfw.ts
+++ b/server/tests/api/videos/video-nsfw.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
6import { BooleanBothQuery, CustomConfig, ResultList, Video, VideosOverview } from '@shared/models' 6import { BooleanBothQuery, CustomConfig, ResultList, Video, VideosOverview } from '@shared/models'
7 7
8const expect = chai.expect 8const expect = chai.expect
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
index f0b2ca169..5fdb0fc03 100644
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ b/server/tests/api/videos/video-playlist-thumbnails.ts
@@ -2,6 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { testImage } from '@server/tests/shared'
6import { VideoPlaylistPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
@@ -9,10 +11,8 @@ import {
9 PeerTubeServer, 11 PeerTubeServer,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 setDefaultVideoChannel, 13 setDefaultVideoChannel,
12 testImage,
13 waitJobs 14 waitJobs
14} from '../../../../shared/extra-utils' 15} from '@shared/server-commands'
15import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
16 16
17const expect = chai.expect 17const expect = chai.expect
18 18
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index f42aee2ff..34327334f 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -2,19 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { 5import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared'
6 checkPlaylistFilesWereRemoved, 6import { wait } from '@shared/core-utils'
7 cleanupTests,
8 createMultipleServers,
9 doubleFollow,
10 PeerTubeServer,
11 PlaylistsCommand,
12 setAccessTokensToServers,
13 setDefaultVideoChannel,
14 testImage,
15 wait,
16 waitJobs
17} from '@shared/extra-utils'
18import { 7import {
19 HttpStatusCode, 8 HttpStatusCode,
20 VideoPlaylist, 9 VideoPlaylist,
@@ -24,6 +13,16 @@ import {
24 VideoPlaylistType, 13 VideoPlaylistType,
25 VideoPrivacy 14 VideoPrivacy
26} from '@shared/models' 15} from '@shared/models'
16import {
17 cleanupTests,
18 createMultipleServers,
19 doubleFollow,
20 PeerTubeServer,
21 PlaylistsCommand,
22 setAccessTokensToServers,
23 setDefaultVideoChannel,
24 waitJobs
25} from '@shared/server-commands'
27 26
28const expect = chai.expect 27const expect = chai.expect
29 28
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index b51b3bcdd..3051a443d 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/extra-utils' 5import { wait } from '@shared/core-utils'
6import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models' 6import { HttpStatusCode, VideoCreateResult, VideoPrivacy } from '@shared/models'
7import { cleanupTests, createSingleServer, doubleFollow, PeerTubeServer, setAccessTokensToServers, waitJobs } from '@shared/server-commands'
7 8
8const expect = chai.expect 9const expect = chai.expect
9 10
@@ -209,7 +210,7 @@ describe('Test video privacy', function () {
209 describe('Privacy update', function () { 210 describe('Privacy update', function () {
210 211
211 it('Should update the private and internal videos to public on server 1', async function () { 212 it('Should update the private and internal videos to public on server 1', async function () {
212 this.timeout(10000) 213 this.timeout(100000)
213 214
214 now = Date.now() 215 now = Date.now()
215 216
@@ -230,6 +231,7 @@ describe('Test video privacy', function () {
230 await servers[0].videos.update({ id: internalVideoId, attributes }) 231 await servers[0].videos.update({ id: internalVideoId, attributes })
231 } 232 }
232 233
234 await wait(10000)
233 await waitJobs(servers) 235 await waitJobs(servers)
234 }) 236 })
235 237
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts
index 3f7738784..00b4f6cbc 100644
--- a/server/tests/api/videos/video-schedule-update.ts
+++ b/server/tests/api/videos/video-schedule-update.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { VideoPrivacy } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 doubleFollow, 10 doubleFollow,
9 PeerTubeServer, 11 PeerTubeServer,
10 setAccessTokensToServers, 12 setAccessTokensToServers,
11 wait,
12 waitJobs 13 waitJobs
13} from '@shared/extra-utils' 14} from '@shared/server-commands'
14import { VideoPrivacy } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 7ed55b8e8..d24a8f4e1 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -3,29 +3,21 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils' 6import { canDoQuickTranscode } from '@server/helpers/ffprobe-utils'
7import { generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared'
8import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
9import { getAudioStream, getMetadataFromFile, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '@shared/extra-utils'
10import { HttpStatusCode, VideoState } from '@shared/models'
7import { 11import {
8 buildAbsoluteFixturePath,
9 cleanupTests, 12 cleanupTests,
10 createMultipleServers, 13 createMultipleServers,
11 doubleFollow, 14 doubleFollow,
12 generateHighBitrateVideo,
13 generateVideoWithFramerate,
14 makeGetRequest, 15 makeGetRequest,
15 PeerTubeServer, 16 PeerTubeServer,
16 setAccessTokensToServers, 17 setAccessTokensToServers,
17 waitJobs, 18 waitJobs,
18 webtorrentAdd 19 webtorrentAdd
19} from '@shared/extra-utils' 20} from '@shared/server-commands'
20import { HttpStatusCode, VideoState } from '@shared/models'
21import {
22 canDoQuickTranscode,
23 getAudioStream,
24 getMetadataFromFile,
25 getVideoFileBitrate,
26 getVideoFileFPS,
27 getVideoFileResolution
28} from '../../../helpers/ffprobe-utils'
29 21
30const expect = chai.expect 22const expect = chai.expect
31 23
diff --git a/server/tests/api/videos/videos-common-filters.ts b/server/tests/api/videos/videos-common-filters.ts
index ca5f42173..0254662c5 100644
--- a/server/tests/api/videos/videos-common-filters.ts
+++ b/server/tests/api/videos/videos-common-filters.ts
@@ -12,7 +12,7 @@ import {
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 setDefaultVideoChannel, 13 setDefaultVideoChannel,
14 waitJobs 14 waitJobs
15} from '@shared/extra-utils' 15} from '@shared/server-commands'
16import { HttpStatusCode, UserRole, Video, VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models' 16import { HttpStatusCode, UserRole, Video, VideoDetails, VideoInclude, VideoPrivacy } from '@shared/models'
17 17
18describe('Test videos filter', function () { 18describe('Test videos filter', function () {
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts
index e4bc0bb3a..4e5ba13aa 100644
--- a/server/tests/api/videos/videos-history.ts
+++ b/server/tests/api/videos/videos-history.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
6import { HttpStatusCode, Video } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createSingleServer, 9 createSingleServer,
8 HistoryCommand, 10 HistoryCommand,
9 killallServers, 11 killallServers,
10 PeerTubeServer, 12 PeerTubeServer,
11 setAccessTokensToServers, 13 setAccessTokensToServers
12 wait 14} from '@shared/server-commands'
13} from '@shared/extra-utils'
14import { HttpStatusCode, Video } from '@shared/models'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts
index 70aa66549..61fc0cb20 100644
--- a/server/tests/api/videos/videos-overview.ts
+++ b/server/tests/api/videos/videos-overview.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers, wait } from '@shared/extra-utils' 5import { wait } from '@shared/core-utils'
6import { VideosOverview } from '@shared/models' 6import { VideosOverview } from '@shared/models'
7import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
7 8
8const expect = chai.expect 9const expect = chai.expect
9 10
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts
index 82268b1be..e6815a4a8 100644
--- a/server/tests/api/videos/videos-views-cleaner.ts
+++ b/server/tests/api/videos/videos-views-cleaner.ts
@@ -2,6 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { wait } from '@shared/core-utils'
5import { 6import {
6 cleanupTests, 7 cleanupTests,
7 createMultipleServers, 8 createMultipleServers,
@@ -9,9 +10,8 @@ import {
9 killallServers, 10 killallServers,
10 PeerTubeServer, 11 PeerTubeServer,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 wait,
13 waitJobs 13 waitJobs
14} from '../../../../shared/extra-utils' 14} from '@shared/server-commands'
15 15
16const expect = chai.expect 16const expect = chai.expect
17 17
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts
index c06b9550c..8ef0545d0 100644
--- a/server/tests/cli/create-import-video-file-job.ts
+++ b/server/tests/cli/create-import-video-file-job.ts
@@ -2,19 +2,19 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils'
6import { HttpStatusCode, VideoDetails, VideoFile, VideoInclude } from '@shared/models'
5import { 7import {
6 areObjectStorageTestsDisabled,
7 cleanupTests, 8 cleanupTests,
8 createMultipleServers, 9 createMultipleServers,
9 doubleFollow, 10 doubleFollow,
10 expectStartWith,
11 makeRawRequest, 11 makeRawRequest,
12 ObjectStorageCommand, 12 ObjectStorageCommand,
13 PeerTubeServer, 13 PeerTubeServer,
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 waitJobs 15 waitJobs
16} from '@shared/extra-utils' 16} from '@shared/server-commands'
17import { HttpStatusCode, VideoDetails, VideoFile, VideoInclude } from '@shared/models' 17import { expectStartWith } from '../shared'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/cli/create-move-video-storage-job.ts b/server/tests/cli/create-move-video-storage-job.ts
index b598c8359..c674d28d2 100644
--- a/server/tests/cli/create-move-video-storage-job.ts
+++ b/server/tests/cli/create-move-video-storage-job.ts
@@ -1,20 +1,19 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4import { areObjectStorageTestsDisabled } from '@shared/core-utils'
5import { HttpStatusCode, VideoDetails } from '@shared/models'
5import { 6import {
6 areObjectStorageTestsDisabled,
7 cleanupTests, 7 cleanupTests,
8 createMultipleServers, 8 createMultipleServers,
9 doubleFollow, 9 doubleFollow,
10 expectStartWith,
11 makeRawRequest, 10 makeRawRequest,
12 ObjectStorageCommand, 11 ObjectStorageCommand,
13 PeerTubeServer, 12 PeerTubeServer,
14 setAccessTokensToServers, 13 setAccessTokensToServers,
15 waitJobs 14 waitJobs
16} from '@shared/extra-utils' 15} from '@shared/server-commands'
17import { HttpStatusCode, VideoDetails } from '@shared/models' 16import { expectStartWith } from '../shared'
18 17
19async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) { 18async function checkFiles (origin: PeerTubeServer, video: VideoDetails, inObjectStorage: boolean) {
20 for (const file of video.files) { 19 for (const file of video.files) {
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts
index fb9c2584f..c85130fef 100644
--- a/server/tests/cli/create-transcoding-job.ts
+++ b/server/tests/cli/create-transcoding-job.ts
@@ -2,19 +2,19 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { areObjectStorageTestsDisabled } from '@shared/core-utils'
5import { HttpStatusCode, VideoFile } from '@shared/models' 6import { HttpStatusCode, VideoFile } from '@shared/models'
6import { 7import {
7 areObjectStorageTestsDisabled,
8 cleanupTests, 8 cleanupTests,
9 createMultipleServers, 9 createMultipleServers,
10 doubleFollow, 10 doubleFollow,
11 expectStartWith,
12 makeRawRequest, 11 makeRawRequest,
13 ObjectStorageCommand, 12 ObjectStorageCommand,
14 PeerTubeServer, 13 PeerTubeServer,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
16 waitJobs 15 waitJobs
17} from '../../../shared/extra-utils' 16} from '@shared/server-commands'
17import { expectStartWith } from '../shared'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
index f2a984962..034d216e3 100644
--- a/server/tests/cli/peertube.ts
+++ b/server/tests/cli/peertube.ts
@@ -2,19 +2,17 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { areHttpImportTestsDisabled, buildAbsoluteFixturePath } from '@shared/core-utils'
5import { 6import {
6 areHttpImportTestsDisabled,
7 buildAbsoluteFixturePath,
8 cleanupTests, 7 cleanupTests,
9 CLICommand, 8 CLICommand,
10 createSingleServer, 9 createSingleServer,
11 doubleFollow, 10 doubleFollow,
12 FIXTURE_URLS,
13 PeerTubeServer, 11 PeerTubeServer,
14 setAccessTokensToServers, 12 setAccessTokensToServers,
15 testHelloWorldRegisteredSettings,
16 waitJobs 13 waitJobs
17} from '../../../shared/extra-utils' 14} from '@shared/server-commands'
15import { FIXTURE_URLS, testHelloWorldRegisteredSettings } from '../shared'
18 16
19describe('Test CLI wrapper', function () { 17describe('Test CLI wrapper', function () {
20 let server: PeerTubeServer 18 let server: PeerTubeServer
@@ -207,6 +205,25 @@ describe('Test CLI wrapper', function () {
207 205
208 expect(res).to.not.contain('peertube-plugin-hello-world') 206 expect(res).to.not.contain('peertube-plugin-hello-world')
209 }) 207 })
208
209 it('Should install a plugin in requested version', async function () {
210 this.timeout(60000)
211
212 await cliCommand.execWithEnv(`${cmd} plugins install --npm-name peertube-plugin-hello-world --plugin-version 0.0.17`)
213 })
214
215 it('Should list installed plugins, in correct version', async function () {
216 const res = await cliCommand.execWithEnv(`${cmd} plugins list`)
217
218 expect(res).to.contain('peertube-plugin-hello-world')
219 expect(res).to.contain('0.0.17')
220 })
221
222 it('Should uninstall the plugin again', async function () {
223 const res = await cliCommand.execWithEnv(`${cmd} plugins uninstall --npm-name peertube-plugin-hello-world`)
224
225 expect(res).to.not.contain('peertube-plugin-hello-world')
226 })
210 }) 227 })
211 228
212 describe('Manage video redundancies', function () { 229 describe('Manage video redundancies', function () {
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts
index 07c78cc89..cd9f4e1c3 100644
--- a/server/tests/cli/plugins.ts
+++ b/server/tests/cli/plugins.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 PluginsCommand, 10 PluginsCommand,
11 setAccessTokensToServers 11 setAccessTokensToServers
12} from '../../../shared/extra-utils' 12} from '@shared/server-commands'
13 13
14describe('Test plugin scripts', function () { 14describe('Test plugin scripts', function () {
15 let server: PeerTubeServer 15 let server: PeerTubeServer
diff --git a/server/tests/cli/print-transcode-command.ts b/server/tests/cli/print-transcode-command.ts
index 0b8629251..27896f031 100644
--- a/server/tests/cli/print-transcode-command.ts
+++ b/server/tests/cli/print-transcode-command.ts
@@ -2,7 +2,8 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { buildAbsoluteFixturePath, CLICommand } from '@shared/extra-utils' 5import { buildAbsoluteFixturePath } from '@shared/core-utils'
6import { CLICommand } from '@shared/server-commands'
6import { VideoResolution } from '../../../shared/models/videos' 7import { VideoResolution } from '../../../shared/models/videos'
7 8
8const expect = chai.expect 9const expect = chai.expect
diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts
index 2d4c02da7..a723ed8b4 100644
--- a/server/tests/cli/prune-storage.ts
+++ b/server/tests/cli/prune-storage.ts
@@ -4,7 +4,9 @@ import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { createFile, readdir } from 'fs-extra' 5import { createFile, readdir } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7import { buildUUID } from '@server/helpers/uuid' 7import { wait } from '@shared/core-utils'
8import { buildUUID } from '@shared/extra-utils'
9import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
8import { 10import {
9 cleanupTests, 11 cleanupTests,
10 CLICommand, 12 CLICommand,
@@ -15,10 +17,8 @@ import {
15 PeerTubeServer, 17 PeerTubeServer,
16 setAccessTokensToServers, 18 setAccessTokensToServers,
17 setDefaultVideoChannel, 19 setDefaultVideoChannel,
18 wait,
19 waitJobs 20 waitJobs
20} from '@shared/extra-utils' 21} from '@shared/server-commands'
21import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
22 22
23const expect = chai.expect 23const expect = chai.expect
24 24
@@ -36,7 +36,7 @@ async function assertNotExists (server: PeerTubeServer, directory: string, subst
36 } 36 }
37} 37}
38 38
39async function assertCountAreOkay (servers: PeerTubeServer[], videoServer2UUID: string) { 39async function assertCountAreOkay (servers: PeerTubeServer[]) {
40 for (const server of servers) { 40 for (const server of servers) {
41 const videosCount = await countFiles(server, 'videos') 41 const videosCount = await countFiles(server, 'videos')
42 expect(videosCount).to.equal(8) 42 expect(videosCount).to.equal(8)
@@ -52,22 +52,16 @@ async function assertCountAreOkay (servers: PeerTubeServer[], videoServer2UUID:
52 52
53 const avatarsCount = await countFiles(server, 'avatars') 53 const avatarsCount = await countFiles(server, 'avatars')
54 expect(avatarsCount).to.equal(2) 54 expect(avatarsCount).to.equal(2)
55 }
56
57 // When we'll prune HLS directories too
58 // const hlsRootCount = await countFiles(servers[1], 'streaming-playlists/hls/')
59 // expect(hlsRootCount).to.equal(2)
60 55
61 // const hlsCount = await countFiles(servers[1], 'streaming-playlists/hls/' + videoServer2UUID) 56 const hlsRootCount = await countFiles(server, 'streaming-playlists/hls')
62 // expect(hlsCount).to.equal(10) 57 expect(hlsRootCount).to.equal(2)
58 }
63} 59}
64 60
65describe('Test prune storage scripts', function () { 61describe('Test prune storage scripts', function () {
66 let servers: PeerTubeServer[] 62 let servers: PeerTubeServer[]
67 const badNames: { [directory: string]: string[] } = {} 63 const badNames: { [directory: string]: string[] } = {}
68 64
69 let videoServer2UUID: string
70
71 before(async function () { 65 before(async function () {
72 this.timeout(120000) 66 this.timeout(120000)
73 67
@@ -77,9 +71,7 @@ describe('Test prune storage scripts', function () {
77 71
78 for (const server of servers) { 72 for (const server of servers) {
79 await server.videos.upload({ attributes: { name: 'video 1' } }) 73 await server.videos.upload({ attributes: { name: 'video 1' } })
80 74 await server.videos.upload({ attributes: { name: 'video 2' } })
81 const { uuid } = await server.videos.upload({ attributes: { name: 'video 2' } })
82 if (server.serverNumber === 2) videoServer2UUID = uuid
83 75
84 await server.users.updateMyAvatar({ fixture: 'avatar.png' }) 76 await server.users.updateMyAvatar({ fixture: 'avatar.png' })
85 77
@@ -123,7 +115,7 @@ describe('Test prune storage scripts', function () {
123 }) 115 })
124 116
125 it('Should have the files on the disk', async function () { 117 it('Should have the files on the disk', async function () {
126 await assertCountAreOkay(servers, videoServer2UUID) 118 await assertCountAreOkay(servers)
127 }) 119 })
128 120
129 it('Should create some dirty files', async function () { 121 it('Should create some dirty files', async function () {
@@ -188,27 +180,14 @@ describe('Test prune storage scripts', function () {
188 badNames['avatars'] = [ n1, n2 ] 180 badNames['avatars'] = [ n1, n2 ]
189 } 181 }
190 182
191 // When we'll prune HLS directories too 183 {
192 // { 184 const directory = join('streaming-playlists', 'hls')
193 // const directory = join('streaming-playlists', 'hls') 185 const base = servers[0].servers.buildDirectory(directory)
194 // const base = servers[1].servers.buildDirectory(directory)
195
196 // const n1 = buildUUID()
197 // await createFile(join(base, n1))
198 // badNames[directory] = [ n1 ]
199 // }
200
201 // {
202 // const directory = join('streaming-playlists', 'hls', videoServer2UUID)
203 // const base = servers[1].servers.buildDirectory(directory)
204 // const n1 = buildUUID() + '-240-fragmented-.mp4'
205 // const n2 = buildUUID() + '-master.m3u8'
206
207 // await createFile(join(base, n1))
208 // await createFile(join(base, n2))
209 186
210 // badNames[directory] = [ n1, n2 ] 187 const n1 = buildUUID()
211 // } 188 await createFile(join(base, n1))
189 badNames[directory] = [ n1 ]
190 }
212 } 191 }
213 }) 192 })
214 193
@@ -220,7 +199,7 @@ describe('Test prune storage scripts', function () {
220 }) 199 })
221 200
222 it('Should have removed files', async function () { 201 it('Should have removed files', async function () {
223 await assertCountAreOkay(servers, videoServer2UUID) 202 await assertCountAreOkay(servers)
224 203
225 for (const directory of Object.keys(badNames)) { 204 for (const directory of Object.keys(badNames)) {
226 for (const name of badNames[directory]) { 205 for (const name of badNames[directory]) {
diff --git a/server/tests/cli/regenerate-thumbnails.ts b/server/tests/cli/regenerate-thumbnails.ts
index 780c9b4bd..98d15272a 100644
--- a/server/tests/cli/regenerate-thumbnails.ts
+++ b/server/tests/cli/regenerate-thumbnails.ts
@@ -11,7 +11,7 @@ import {
11 PeerTubeServer, 11 PeerTubeServer,
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 waitJobs 13 waitJobs
14} from '../../../shared/extra-utils' 14} from '../../../shared/server-commands'
15 15
16async function testThumbnail (server: PeerTubeServer, videoId: number | string) { 16async function testThumbnail (server: PeerTubeServer, videoId: number | string) {
17 const video = await server.videos.get({ id: videoId }) 17 const video = await server.videos.get({ id: videoId })
diff --git a/server/tests/cli/reset-password.ts b/server/tests/cli/reset-password.ts
index 4a02db35d..018e9d788 100644
--- a/server/tests/cli/reset-password.ts
+++ b/server/tests/cli/reset-password.ts
@@ -1,5 +1,5 @@
1import 'mocha' 1import 'mocha'
2import { cleanupTests, CLICommand, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '../../../shared/extra-utils' 2import { cleanupTests, CLICommand, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
3 3
4describe('Test reset password scripts', function () { 4describe('Test reset password scripts', function () {
5 let server: PeerTubeServer 5 let server: PeerTubeServer
diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts
index 43fbaec30..da89ff153 100644
--- a/server/tests/cli/update-host.ts
+++ b/server/tests/cli/update-host.ts
@@ -11,7 +11,7 @@ import {
11 PeerTubeServer, 11 PeerTubeServer,
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 waitJobs 13 waitJobs
14} from '@shared/extra-utils' 14} from '@shared/server-commands'
15 15
16describe('Test update host scripts', function () { 16describe('Test update host scripts', function () {
17 let server: PeerTubeServer 17 let server: PeerTubeServer
diff --git a/server/tests/client.ts b/server/tests/client.ts
index a91bec906..fe048d7ff 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -14,7 +14,7 @@ import {
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 setDefaultVideoChannel, 15 setDefaultVideoChannel,
16 waitJobs 16 waitJobs
17} from '../../shared/extra-utils' 17} from '../../shared/server-commands'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
diff --git a/server/tests/external-plugins/auth-ldap.ts b/server/tests/external-plugins/auth-ldap.ts
index acec69df5..326453a5f 100644
--- a/server/tests/external-plugins/auth-ldap.ts
+++ b/server/tests/external-plugins/auth-ldap.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
6import { HttpStatusCode } from '@shared/models' 6import { HttpStatusCode } from '@shared/models'
7 7
8describe('Official plugin auth-ldap', function () { 8describe('Official plugin auth-ldap', function () {
diff --git a/server/tests/external-plugins/auto-block-videos.ts b/server/tests/external-plugins/auto-block-videos.ts
index 0eb4bda9a..bc5c93621 100644
--- a/server/tests/external-plugins/auto-block-videos.ts
+++ b/server/tests/external-plugins/auto-block-videos.ts
@@ -2,17 +2,17 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { wait } from '@shared/core-utils'
6import { Video } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 doubleFollow, 10 doubleFollow,
9 killallServers, 11 killallServers,
10 MockBlocklist,
11 PeerTubeServer, 12 PeerTubeServer,
12 setAccessTokensToServers, 13 setAccessTokensToServers
13 wait 14} from '@shared/server-commands'
14} from '@shared/extra-utils' 15import { MockBlocklist } from '../shared'
15import { Video } from '@shared/models'
16 16
17async function check (server: PeerTubeServer, videoUUID: string, exists = true) { 17async function check (server: PeerTubeServer, videoUUID: string, exists = true) {
18 const { data } = await server.videos.list() 18 const { data } = await server.videos.list()
diff --git a/server/tests/external-plugins/auto-mute.ts b/server/tests/external-plugins/auto-mute.ts
index 271779dd4..375ccf91a 100644
--- a/server/tests/external-plugins/auto-mute.ts
+++ b/server/tests/external-plugins/auto-mute.ts
@@ -2,18 +2,18 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { wait } from '@shared/core-utils'
6import { HttpStatusCode } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createMultipleServers, 9 createMultipleServers,
8 doubleFollow, 10 doubleFollow,
9 killallServers, 11 killallServers,
10 makeGetRequest, 12 makeGetRequest,
11 MockBlocklist,
12 PeerTubeServer, 13 PeerTubeServer,
13 setAccessTokensToServers, 14 setAccessTokensToServers
14 wait 15} from '@shared/server-commands'
15} from '@shared/extra-utils' 16import { MockBlocklist } from '../shared'
16import { HttpStatusCode } from '@shared/models'
17 17
18describe('Official plugin auto-mute', function () { 18describe('Official plugin auto-mute', function () {
19 const autoMuteListPath = '/plugins/auto-mute/router/api/v1/mute-list' 19 const autoMuteListPath = '/plugins/auto-mute/router/api/v1/mute-list'
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index a1c976fd3..24a518342 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { parse, validate } from 'fast-xml-parser' 5import { XMLParser, XMLValidator } from 'fast-xml-parser'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createMultipleServers, 8 createMultipleServers,
@@ -12,7 +12,7 @@ import {
12 PeerTubeServer, 12 PeerTubeServer,
13 setAccessTokensToServers, 13 setAccessTokensToServers,
14 waitJobs 14 waitJobs
15} from '@shared/extra-utils' 15} from '@shared/server-commands'
16import { HttpStatusCode, VideoPrivacy } from '@shared/models' 16import { HttpStatusCode, VideoPrivacy } from '@shared/models'
17 17
18chai.use(require('chai-xml')) 18chai.use(require('chai-xml'))
@@ -149,9 +149,10 @@ describe('Test syndication feeds', () => {
149 it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () { 149 it('Should contain a valid enclosure (covers RSS 2.0 endpoint)', async function () {
150 for (const server of servers) { 150 for (const server of servers) {
151 const rss = await server.feed.getXML({ feed: 'videos' }) 151 const rss = await server.feed.getXML({ feed: 'videos' })
152 expect(validate(rss)).to.be.true 152 expect(XMLValidator.validate(rss)).to.be.true
153 153
154 const xmlDoc = parse(rss, { parseAttributeValue: true, ignoreAttributes: false }) 154 const parser = new XMLParser({ parseAttributeValue: true, ignoreAttributes: false })
155 const xmlDoc = parser.parse(rss)
155 156
156 const enclosure = xmlDoc.rss.channel.item[0].enclosure 157 const enclosure = xmlDoc.rss.channel.item[0].enclosure
157 expect(enclosure).to.exist 158 expect(enclosure).to.exist
diff --git a/server/tests/fixtures/peertube-plugin-test-four/main.js b/server/tests/fixtures/peertube-plugin-test-four/main.js
index b9b207b81..bff42ff40 100644
--- a/server/tests/fixtures/peertube-plugin-test-four/main.js
+++ b/server/tests/fixtures/peertube-plugin-test-four/main.js
@@ -104,6 +104,20 @@ async function register ({
104 isUser 104 isUser
105 }) 105 })
106 }) 106 })
107
108 router.get('/video-files/:id', async (req, res) => {
109 const details = await peertubeHelpers.videos.getFiles(req.params.id)
110 if (!details) return res.sendStatus(404)
111
112 return res.json(details)
113 })
114
115 router.get('/ffprobe', async (req, res) => {
116 const result = await peertubeHelpers.videos.ffprobe(req.query.path)
117 if (!result) return res.sendStatus(404)
118
119 return res.json(result)
120 })
107 } 121 }
108 122
109} 123}
diff --git a/server/tests/fixtures/peertube-plugin-test-six/main.js b/server/tests/fixtures/peertube-plugin-test-six/main.js
index 858bdb2df..243b041e7 100644
--- a/server/tests/fixtures/peertube-plugin-test-six/main.js
+++ b/server/tests/fixtures/peertube-plugin-test-six/main.js
@@ -11,9 +11,14 @@ async function register ({
11 { 11 {
12 await storageManager.storeData('superkey', { value: 'toto' }) 12 await storageManager.storeData('superkey', { value: 'toto' })
13 await storageManager.storeData('anotherkey', { value: 'toto2' }) 13 await storageManager.storeData('anotherkey', { value: 'toto2' })
14 await storageManager.storeData('storedArrayKey', ['toto', 'toto2'])
14 15
15 const result = await storageManager.getData('superkey') 16 const result = await storageManager.getData('superkey')
16 logger.info('superkey stored value is %s', result.value) 17 logger.info('superkey stored value is %s', result.value)
18
19 const storedArrayValue = await storageManager.getData('storedArrayKey')
20 logger.info('storedArrayKey isArray is %s', Array.isArray(storedArrayValue) ? 'true' : 'false')
21 logger.info('storedArrayKey stored value is %s', storedArrayValue.join(', '))
17 } 22 }
18 23
19 { 24 {
diff --git a/server/tests/fixtures/peertube-plugin-test/main.js b/server/tests/fixtures/peertube-plugin-test/main.js
index db405ff31..90951d611 100644
--- a/server/tests/fixtures/peertube-plugin-test/main.js
+++ b/server/tests/fixtures/peertube-plugin-test/main.js
@@ -13,6 +13,9 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
13 'action:api.video-comment-reply.created', 13 'action:api.video-comment-reply.created',
14 'action:api.video-comment.deleted', 14 'action:api.video-comment.deleted',
15 15
16 'action:api.video-caption.created',
17 'action:api.video-caption.deleted',
18
16 'action:api.user.blocked', 19 'action:api.user.blocked',
17 'action:api.user.unblocked', 20 'action:api.user.unblocked',
18 'action:api.user.registered', 21 'action:api.user.registered',
@@ -233,6 +236,28 @@ async function register ({ registerHook, registerSetting, settingsManager, stora
233 } 236 }
234 }) 237 })
235 238
239 registerHook({
240 target: 'filter:api.server.stats.get.result',
241 handler: (result) => {
242 return { ...result, customStats: 14 }
243 }
244 })
245
246 // Upload/import/live attributes
247 for (const target of [
248 'filter:api.video.upload.video-attribute.result',
249 'filter:api.video.import-url.video-attribute.result',
250 'filter:api.video.import-torrent.video-attribute.result',
251 'filter:api.video.live.video-attribute.result'
252 ]) {
253 registerHook({
254 target,
255 handler: (result) => {
256 return { ...result, description: result.description + ' - ' + target }
257 }
258 })
259 }
260
236 { 261 {
237 const filterHooks = [ 262 const filterHooks = [
238 'filter:api.search.videos.local.list.params', 263 'filter:api.search.videos.local.list.params',
diff --git a/server/tests/helpers/image.ts b/server/tests/helpers/image.ts
index 9fe9aa4cb..64bd373cc 100644
--- a/server/tests/helpers/image.ts
+++ b/server/tests/helpers/image.ts
@@ -4,8 +4,8 @@ import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { readFile, remove } from 'fs-extra' 5import { readFile, remove } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7import { buildAbsoluteFixturePath, root } from '@shared/core-utils'
7import { processImage } from '../../../server/helpers/image-utils' 8import { processImage } from '../../../server/helpers/image-utils'
8import { buildAbsoluteFixturePath, root } from '../../../shared/extra-utils'
9 9
10async function checkBuffers (path1: string, path2: string, equals: boolean) { 10async function checkBuffers (path1: string, path2: string, equals: boolean) {
11 const [ buf1, buf2 ] = await Promise.all([ 11 const [ buf1, buf2 ] = await Promise.all([
diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts
index 6edbf2a76..de507ba35 100644
--- a/server/tests/helpers/request.ts
+++ b/server/tests/helpers/request.ts
@@ -4,9 +4,9 @@ import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { pathExists, remove } from 'fs-extra' 5import { pathExists, remove } from 'fs-extra'
6import { join } from 'path' 6import { join } from 'path'
7import { Mock429 } from '@shared/extra-utils/mock-servers/mock-429' 7import { root, wait } from '@shared/core-utils'
8import { FIXTURE_URLS, root, wait } from '../../../shared/extra-utils'
9import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 8import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
9import { FIXTURE_URLS, Mock429 } from '../shared'
10 10
11describe('Request helpers', function () { 11describe('Request helpers', function () {
12 const destPath1 = join(root(), 'test-output-1.txt') 12 const destPath1 = join(root(), 'test-output-1.txt')
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts
index 4968eef08..876546d89 100644
--- a/server/tests/misc-endpoints.ts
+++ b/server/tests/misc-endpoints.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, makeGetRequest, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
6import { HttpStatusCode, VideoPrivacy } from '@shared/models' 6import { HttpStatusCode, VideoPrivacy } from '@shared/models'
7 7
8const expect = chai.expect 8const expect = chai.expect
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index 4c1bc7d06..8788a9644 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -9,7 +9,7 @@ import {
9 PluginsCommand, 9 PluginsCommand,
10 setAccessTokensToServers, 10 setAccessTokensToServers,
11 setDefaultVideoChannel 11 setDefaultVideoChannel
12} from '@shared/extra-utils' 12} from '@shared/server-commands'
13import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' 13import { ServerHookName, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
14 14
15describe('Test plugin action hooks', function () { 15describe('Test plugin action hooks', function () {
@@ -103,6 +103,20 @@ describe('Test plugin action hooks', function () {
103 }) 103 })
104 }) 104 })
105 105
106 describe('Captions hooks', function () {
107 it('Should run action:api.video-caption.created', async function () {
108 await servers[0].captions.add({ videoId: videoUUID, language: 'en', fixture: 'subtitle-good.srt' })
109
110 await checkHook('action:api.video-caption.created')
111 })
112
113 it('Should run action:api.video-caption.deleted', async function () {
114 await servers[0].captions.delete({ videoId: videoUUID, language: 'en' })
115
116 await checkHook('action:api.video-caption.deleted')
117 })
118 })
119
106 describe('Users hooks', function () { 120 describe('Users hooks', function () {
107 let userId: number 121 let userId: number
108 122
diff --git a/server/tests/plugins/external-auth.ts b/server/tests/plugins/external-auth.ts
index f3e018d43..583100671 100644
--- a/server/tests/plugins/external-auth.ts
+++ b/server/tests/plugins/external-auth.ts
@@ -2,16 +2,16 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { wait } from '@shared/core-utils'
6import { HttpStatusCode, UserRole } from '@shared/models'
5import { 7import {
6 cleanupTests, 8 cleanupTests,
7 createSingleServer, 9 createSingleServer,
8 decodeQueryString, 10 decodeQueryString,
9 PeerTubeServer, 11 PeerTubeServer,
10 PluginsCommand, 12 PluginsCommand,
11 setAccessTokensToServers, 13 setAccessTokensToServers
12 wait 14} from '@shared/server-commands'
13} from '@shared/extra-utils'
14import { HttpStatusCode, UserRole } from '@shared/models'
15 15
16async function loginExternal (options: { 16async function loginExternal (options: {
17 server: PeerTubeServer 17 server: PeerTubeServer
@@ -125,7 +125,7 @@ describe('Test external auth plugins', function () {
125 expectedStatus: HttpStatusCode.BAD_REQUEST_400 125 expectedStatus: HttpStatusCode.BAD_REQUEST_400
126 }) 126 })
127 127
128 await server.servers.waitUntilLog('expired external auth token', 2) 128 await server.servers.waitUntilLog('expired external auth token', 4)
129 }) 129 })
130 130
131 it('Should auto login Cyan, create the user and use the token', async function () { 131 it('Should auto login Cyan, create the user and use the token', async function () {
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 02915f08c..52ba396e5 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -2,19 +2,19 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { HttpStatusCode, VideoDetails, VideoImportState, VideoPlaylist, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
5import { 6import {
6 cleanupTests, 7 cleanupTests,
7 createMultipleServers, 8 createMultipleServers,
8 doubleFollow, 9 doubleFollow,
9 FIXTURE_URLS,
10 makeRawRequest, 10 makeRawRequest,
11 PeerTubeServer, 11 PeerTubeServer,
12 PluginsCommand, 12 PluginsCommand,
13 setAccessTokensToServers, 13 setAccessTokensToServers,
14 setDefaultVideoChannel, 14 setDefaultVideoChannel,
15 waitJobs 15 waitJobs
16} from '@shared/extra-utils' 16} from '@shared/server-commands'
17import { HttpStatusCode, VideoDetails, VideoImportState, VideoPlaylist, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' 17import { FIXTURE_URLS } from '../shared'
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
@@ -537,6 +537,75 @@ describe('Test plugin filter hooks', function () {
537 }) 537 })
538 }) 538 })
539 539
540 describe('Upload/import/live attributes filters', function () {
541
542 before(async function () {
543 await servers[0].config.enableLive({ transcoding: false, allowReplay: false })
544 await servers[0].config.enableImports()
545 await servers[0].config.disableTranscoding()
546 })
547
548 it('Should run filter:api.video.upload.video-attribute.result', async function () {
549 for (const mode of [ 'legacy' as 'legacy', 'resumable' as 'resumable' ]) {
550 const { id } = await servers[0].videos.upload({ attributes: { name: 'video', description: 'upload' }, mode })
551
552 const video = await servers[0].videos.get({ id })
553 expect(video.description).to.equal('upload - filter:api.video.upload.video-attribute.result')
554 }
555 })
556
557 it('Should run filter:api.video.import-url.video-attribute.result', async function () {
558 const attributes = {
559 name: 'video',
560 description: 'import url',
561 channelId: servers[0].store.channel.id,
562 targetUrl: FIXTURE_URLS.goodVideo,
563 privacy: VideoPrivacy.PUBLIC
564 }
565 const { video: { id } } = await servers[0].imports.importVideo({ attributes })
566
567 const video = await servers[0].videos.get({ id })
568 expect(video.description).to.equal('import url - filter:api.video.import-url.video-attribute.result')
569 })
570
571 it('Should run filter:api.video.import-torrent.video-attribute.result', async function () {
572 const attributes = {
573 name: 'video',
574 description: 'import torrent',
575 channelId: servers[0].store.channel.id,
576 magnetUri: FIXTURE_URLS.magnet,
577 privacy: VideoPrivacy.PUBLIC
578 }
579 const { video: { id } } = await servers[0].imports.importVideo({ attributes })
580
581 const video = await servers[0].videos.get({ id })
582 expect(video.description).to.equal('import torrent - filter:api.video.import-torrent.video-attribute.result')
583 })
584
585 it('Should run filter:api.video.live.video-attribute.result', async function () {
586 const fields = {
587 name: 'live',
588 description: 'live',
589 channelId: servers[0].store.channel.id,
590 privacy: VideoPrivacy.PUBLIC
591 }
592 const { id } = await servers[0].live.create({ fields })
593
594 const video = await servers[0].videos.get({ id })
595 expect(video.description).to.equal('live - filter:api.video.live.video-attribute.result')
596 })
597 })
598
599 describe('Stats filters', function () {
600
601 it('Should run filter:api.server.stats.get.result', async function () {
602 const data = await servers[0].stats.get()
603
604 expect((data as any).customStats).to.equal(14)
605 })
606
607 })
608
540 after(async function () { 609 after(async function () {
541 await cleanupTests(servers) 610 await cleanupTests(servers)
542 }) 611 })
diff --git a/server/tests/plugins/html-injection.ts b/server/tests/plugins/html-injection.ts
index 95c0cd687..0a3a9c25f 100644
--- a/server/tests/plugins/html-injection.ts
+++ b/server/tests/plugins/html-injection.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 PluginsCommand, 10 PluginsCommand,
11 setAccessTokensToServers 11 setAccessTokensToServers
12} from '../../../shared/extra-utils' 12} from '@shared/server-commands'
13 13
14const expect = chai.expect 14const expect = chai.expect
15 15
diff --git a/server/tests/plugins/id-and-pass-auth.ts b/server/tests/plugins/id-and-pass-auth.ts
index fde0166f9..e72046ce2 100644
--- a/server/tests/plugins/id-and-pass-auth.ts
+++ b/server/tests/plugins/id-and-pass-auth.ts
@@ -2,8 +2,9 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers, wait } from '@shared/extra-utils' 5import { wait } from '@shared/core-utils'
6import { HttpStatusCode, UserRole } from '@shared/models' 6import { HttpStatusCode, UserRole } from '@shared/models'
7import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/server-commands'
7 8
8describe('Test id and pass auth plugins', function () { 9describe('Test id and pass auth plugins', function () {
9 let server: PeerTubeServer 10 let server: PeerTubeServer
diff --git a/server/tests/plugins/plugin-helpers.ts b/server/tests/plugins/plugin-helpers.ts
index 5d16b28a4..167429ef4 100644
--- a/server/tests/plugins/plugin-helpers.ts
+++ b/server/tests/plugins/plugin-helpers.ts
@@ -2,19 +2,21 @@
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
5import { pathExists } from 'fs-extra'
6import { HttpStatusCode, ThumbnailType } from '@shared/models'
5import { 7import {
6 checkVideoFilesWereRemoved,
7 cleanupTests, 8 cleanupTests,
8 createMultipleServers, 9 createMultipleServers,
9 doubleFollow, 10 doubleFollow,
10 makeGetRequest, 11 makeGetRequest,
11 makePostBodyRequest, 12 makePostBodyRequest,
13 makeRawRequest,
12 PeerTubeServer, 14 PeerTubeServer,
13 PluginsCommand, 15 PluginsCommand,
14 setAccessTokensToServers, 16 setAccessTokensToServers,
15 waitJobs 17 waitJobs
16} from '@shared/extra-utils' 18} from '@shared/server-commands'
17import { HttpStatusCode } from '@shared/models' 19import { checkVideoFilesWereRemoved } from '../shared'
18 20
19function postCommand (server: PeerTubeServer, command: string, bodyArg?: object) { 21function postCommand (server: PeerTubeServer, command: string, bodyArg?: object) {
20 const body = { command } 22 const body = { command }
@@ -222,10 +224,75 @@ describe('Test plugin helpers', function () {
222 224
223 describe('Videos', function () { 225 describe('Videos', function () {
224 let videoUUID: string 226 let videoUUID: string
227 let videoPath: string
225 228
226 before(async () => { 229 before(async () => {
230 this.timeout(240000)
231
232 await servers[0].config.enableTranscoding()
233
227 const res = await servers[0].videos.quickUpload({ name: 'video1' }) 234 const res = await servers[0].videos.quickUpload({ name: 'video1' })
228 videoUUID = res.uuid 235 videoUUID = res.uuid
236
237 await waitJobs(servers)
238 })
239
240 it('Should get video files', async function () {
241 const { body } = await makeGetRequest({
242 url: servers[0].url,
243 path: '/plugins/test-four/router/video-files/' + videoUUID,
244 expectedStatus: HttpStatusCode.OK_200
245 })
246
247 // Video files check
248 {
249 expect(body.webtorrent.videoFiles).to.be.an('array')
250 expect(body.hls.videoFiles).to.be.an('array')
251
252 for (const resolution of [ 144, 240, 360, 480, 720 ]) {
253 for (const files of [ body.webtorrent.videoFiles, body.hls.videoFiles ]) {
254 const file = files.find(f => f.resolution === resolution)
255 expect(file).to.exist
256
257 expect(file.size).to.be.a('number')
258 expect(file.fps).to.equal(25)
259
260 expect(await pathExists(file.path)).to.be.true
261 await makeRawRequest(file.url, HttpStatusCode.OK_200)
262 }
263 }
264
265 videoPath = body.webtorrent.videoFiles[0].path
266 }
267
268 // Thumbnails check
269 {
270 expect(body.thumbnails).to.be.an('array')
271
272 const miniature = body.thumbnails.find(t => t.type === ThumbnailType.MINIATURE)
273 expect(miniature).to.exist
274 expect(await pathExists(miniature.path)).to.be.true
275 await makeRawRequest(miniature.url, HttpStatusCode.OK_200)
276
277 const preview = body.thumbnails.find(t => t.type === ThumbnailType.PREVIEW)
278 expect(preview).to.exist
279 expect(await pathExists(preview.path)).to.be.true
280 await makeRawRequest(preview.url, HttpStatusCode.OK_200)
281 }
282 })
283
284 it('Should probe a file', async function () {
285 const { body } = await makeGetRequest({
286 url: servers[0].url,
287 path: '/plugins/test-four/router/ffprobe',
288 query: {
289 path: videoPath
290 },
291 expectedStatus: HttpStatusCode.OK_200
292 })
293
294 expect(body.streams).to.be.an('array')
295 expect(body.streams).to.have.lengthOf(2)
229 }) 296 })
230 297
231 it('Should remove a video after a view', async function () { 298 it('Should remove a video after a view', async function () {
diff --git a/server/tests/plugins/plugin-router.ts b/server/tests/plugins/plugin-router.ts
index b1ac9e2fe..58629adc7 100644
--- a/server/tests/plugins/plugin-router.ts
+++ b/server/tests/plugins/plugin-router.ts
@@ -10,7 +10,7 @@ import {
10 PeerTubeServer, 10 PeerTubeServer,
11 PluginsCommand, 11 PluginsCommand,
12 setAccessTokensToServers 12 setAccessTokensToServers
13} from '@shared/extra-utils' 13} from '@shared/server-commands'
14import { HttpStatusCode } from '@shared/models' 14import { HttpStatusCode } from '@shared/models'
15 15
16describe('Test plugin helpers', function () { 16describe('Test plugin helpers', function () {
diff --git a/server/tests/plugins/plugin-storage.ts b/server/tests/plugins/plugin-storage.ts
index e20c36dba..0bfc4fe28 100644
--- a/server/tests/plugins/plugin-storage.ts
+++ b/server/tests/plugins/plugin-storage.ts
@@ -11,7 +11,7 @@ import {
11 PeerTubeServer, 11 PeerTubeServer,
12 PluginsCommand, 12 PluginsCommand,
13 setAccessTokensToServers 13 setAccessTokensToServers
14} from '@shared/extra-utils' 14} from '@shared/server-commands'
15import { HttpStatusCode } from '@shared/models' 15import { HttpStatusCode } from '@shared/models'
16 16
17describe('Test plugin storage', function () { 17describe('Test plugin storage', function () {
@@ -27,10 +27,14 @@ describe('Test plugin storage', function () {
27 }) 27 })
28 28
29 describe('DB storage', function () { 29 describe('DB storage', function () {
30
31 it('Should correctly store a subkey', async function () { 30 it('Should correctly store a subkey', async function () {
32 await server.servers.waitUntilLog('superkey stored value is toto') 31 await server.servers.waitUntilLog('superkey stored value is toto')
33 }) 32 })
33
34 it('Should correctly retrieve an array as array from the storage.', async function () {
35 await server.servers.waitUntilLog('storedArrayKey isArray is true')
36 await server.servers.waitUntilLog('storedArrayKey stored value is toto, toto2')
37 })
34 }) 38 })
35 39
36 describe('Disk storage', function () { 40 describe('Disk storage', function () {
diff --git a/server/tests/plugins/plugin-transcoding.ts b/server/tests/plugins/plugin-transcoding.ts
index 93637e3ce..5ab686472 100644
--- a/server/tests/plugins/plugin-transcoding.ts
+++ b/server/tests/plugins/plugin-transcoding.ts
@@ -12,7 +12,7 @@ import {
12 setDefaultVideoChannel, 12 setDefaultVideoChannel,
13 testFfmpegStreamError, 13 testFfmpegStreamError,
14 waitJobs 14 waitJobs
15} from '@shared/extra-utils' 15} from '@shared/server-commands'
16import { VideoPrivacy } from '@shared/models' 16import { VideoPrivacy } from '@shared/models'
17 17
18async function createLiveWrapper (server: PeerTubeServer) { 18async function createLiveWrapper (server: PeerTubeServer) {
diff --git a/server/tests/plugins/plugin-unloading.ts b/server/tests/plugins/plugin-unloading.ts
index 6bf2fda9b..a94b83695 100644
--- a/server/tests/plugins/plugin-unloading.ts
+++ b/server/tests/plugins/plugin-unloading.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 PluginsCommand, 10 PluginsCommand,
11 setAccessTokensToServers 11 setAccessTokensToServers
12} from '@shared/extra-utils' 12} from '@shared/server-commands'
13import { HttpStatusCode } from '@shared/models' 13import { HttpStatusCode } from '@shared/models'
14 14
15describe('Test plugins module unloading', function () { 15describe('Test plugins module unloading', function () {
diff --git a/server/tests/plugins/translations.ts b/server/tests/plugins/translations.ts
index 8b25c6b75..0b6e5793d 100644
--- a/server/tests/plugins/translations.ts
+++ b/server/tests/plugins/translations.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/extra-utils' 5import { cleanupTests, createSingleServer, PeerTubeServer, PluginsCommand, setAccessTokensToServers } from '@shared/server-commands'
6 6
7const expect = chai.expect 7const expect = chai.expect
8 8
diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts
index 19cba6c2c..6dce6922f 100644
--- a/server/tests/plugins/video-constants.ts
+++ b/server/tests/plugins/video-constants.ts
@@ -9,7 +9,7 @@ import {
9 PeerTubeServer, 9 PeerTubeServer,
10 PluginsCommand, 10 PluginsCommand,
11 setAccessTokensToServers 11 setAccessTokensToServers
12} from '@shared/extra-utils' 12} from '@shared/server-commands'
13import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models' 13import { HttpStatusCode, VideoPlaylistPrivacy } from '@shared/models'
14 14
15const expect = chai.expect 15const expect = chai.expect
diff --git a/server/tests/register.ts b/server/tests/register.ts
deleted file mode 100644
index af6c8c644..000000000
--- a/server/tests/register.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1import { registerTSPaths } from '../helpers/register-ts-paths'
2
3registerTSPaths()
diff --git a/server/tests/shared/actors.ts b/server/tests/shared/actors.ts
new file mode 100644
index 000000000..f8f4a5137
--- /dev/null
+++ b/server/tests/shared/actors.ts
@@ -0,0 +1,73 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { Account, VideoChannel } from '@shared/models'
8import { PeerTubeServer } from '@shared/server-commands'
9
10async function expectChannelsFollows (options: {
11 server: PeerTubeServer
12 handle: string
13 followers: number
14 following: number
15}) {
16 const { server } = options
17 const { data } = await server.channels.list()
18
19 return expectActorFollow({ ...options, data })
20}
21
22async function expectAccountFollows (options: {
23 server: PeerTubeServer
24 handle: string
25 followers: number
26 following: number
27}) {
28 const { server } = options
29 const { data } = await server.accounts.list()
30
31 return expectActorFollow({ ...options, data })
32}
33
34async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
35 const testDirectory = 'test' + serverNumber
36
37 for (const directory of [ 'avatars' ]) {
38 const directoryPath = join(root(), testDirectory, directory)
39
40 const directoryExists = await pathExists(directoryPath)
41 expect(directoryExists).to.be.true
42
43 const files = await readdir(directoryPath)
44 for (const file of files) {
45 expect(file).to.not.contain(filename)
46 }
47 }
48}
49
50export {
51 expectAccountFollows,
52 expectChannelsFollows,
53 checkActorFilesWereRemoved
54}
55
56// ---------------------------------------------------------------------------
57
58function expectActorFollow (options: {
59 server: PeerTubeServer
60 data: (Account | VideoChannel)[]
61 handle: string
62 followers: number
63 following: number
64}) {
65 const { server, data, handle, followers, following } = options
66
67 const actor = data.find(a => a.name + '@' + a.host === handle)
68 const message = `${handle} on ${server.url}`
69
70 expect(actor, message).to.exist
71 expect(actor.followersCount).to.equal(followers, message)
72 expect(actor.followingCount).to.equal(following, message)
73}
diff --git a/server/tests/shared/captions.ts b/server/tests/shared/captions.ts
new file mode 100644
index 000000000..35e722408
--- /dev/null
+++ b/server/tests/shared/captions.ts
@@ -0,0 +1,21 @@
1import { expect } from 'chai'
2import request from 'supertest'
3import { HttpStatusCode } from '@shared/models'
4
5async function testCaptionFile (url: string, captionPath: string, toTest: RegExp | string) {
6 const res = await request(url)
7 .get(captionPath)
8 .expect(HttpStatusCode.OK_200)
9
10 if (toTest instanceof RegExp) {
11 expect(res.text).to.match(toTest)
12 } else {
13 expect(res.text).to.contain(toTest)
14 }
15}
16
17// ---------------------------------------------------------------------------
18
19export {
20 testCaptionFile
21}
diff --git a/server/tests/shared/checks.ts b/server/tests/shared/checks.ts
new file mode 100644
index 000000000..9ecc84b5d
--- /dev/null
+++ b/server/tests/shared/checks.ts
@@ -0,0 +1,98 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readFile } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { HttpStatusCode } from '@shared/models'
8import { makeGetRequest, PeerTubeServer } from '@shared/server-commands'
9
10// Default interval -> 5 minutes
11function dateIsValid (dateString: string, interval = 300000) {
12 const dateToCheck = new Date(dateString)
13 const now = new Date()
14
15 return Math.abs(now.getTime() - dateToCheck.getTime()) <= interval
16}
17
18function expectStartWith (str: string, start: string) {
19 expect(str.startsWith(start), `${str} does not start with ${start}`).to.be.true
20}
21
22async function expectLogDoesNotContain (server: PeerTubeServer, str: string) {
23 const content = await server.servers.getLogContent()
24
25 expect(content.toString()).to.not.contain(str)
26}
27
28async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
29 const res = await makeGetRequest({
30 url,
31 path: imagePath,
32 expectedStatus: HttpStatusCode.OK_200
33 })
34
35 const body = res.body
36
37 const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension))
38 const minLength = body.length - ((30 * body.length) / 100)
39 const maxLength = body.length + ((30 * body.length) / 100)
40
41 expect(data.length).to.be.above(minLength, 'the generated image is way smaller than the recorded fixture')
42 expect(data.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture')
43}
44
45async function testFileExistsOrNot (server: PeerTubeServer, directory: string, filePath: string, exist: boolean) {
46 const base = server.servers.buildDirectory(directory)
47
48 expect(await pathExists(join(base, filePath))).to.equal(exist)
49}
50
51function checkBadStartPagination (url: string, path: string, token?: string, query = {}) {
52 return makeGetRequest({
53 url,
54 path,
55 token,
56 query: { ...query, start: 'hello' },
57 expectedStatus: HttpStatusCode.BAD_REQUEST_400
58 })
59}
60
61async function checkBadCountPagination (url: string, path: string, token?: string, query = {}) {
62 await makeGetRequest({
63 url,
64 path,
65 token,
66 query: { ...query, count: 'hello' },
67 expectedStatus: HttpStatusCode.BAD_REQUEST_400
68 })
69
70 await makeGetRequest({
71 url,
72 path,
73 token,
74 query: { ...query, count: 2000 },
75 expectedStatus: HttpStatusCode.BAD_REQUEST_400
76 })
77}
78
79function checkBadSortPagination (url: string, path: string, token?: string, query = {}) {
80 return makeGetRequest({
81 url,
82 path,
83 token,
84 query: { ...query, sort: 'hello' },
85 expectedStatus: HttpStatusCode.BAD_REQUEST_400
86 })
87}
88
89export {
90 dateIsValid,
91 testImage,
92 expectLogDoesNotContain,
93 testFileExistsOrNot,
94 expectStartWith,
95 checkBadStartPagination,
96 checkBadCountPagination,
97 checkBadSortPagination
98}
diff --git a/server/tests/shared/directories.ts b/server/tests/shared/directories.ts
new file mode 100644
index 000000000..c7065a767
--- /dev/null
+++ b/server/tests/shared/directories.ts
@@ -0,0 +1,34 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { root } from '@shared/core-utils'
7import { PeerTubeServer } from '@shared/server-commands'
8
9async function checkTmpIsEmpty (server: PeerTubeServer) {
10 await checkDirectoryIsEmpty(server, 'tmp', [ 'plugins-global.css', 'hls', 'resumable-uploads' ])
11
12 if (await pathExists(join('test' + server.internalServerNumber, 'tmp', 'hls'))) {
13 await checkDirectoryIsEmpty(server, 'tmp/hls')
14 }
15}
16
17async function checkDirectoryIsEmpty (server: PeerTubeServer, directory: string, exceptions: string[] = []) {
18 const testDirectory = 'test' + server.internalServerNumber
19
20 const directoryPath = join(root(), testDirectory, directory)
21
22 const directoryExists = await pathExists(directoryPath)
23 expect(directoryExists).to.be.true
24
25 const files = await readdir(directoryPath)
26 const filtered = files.filter(f => exceptions.includes(f) === false)
27
28 expect(filtered).to.have.lengthOf(0)
29}
30
31export {
32 checkTmpIsEmpty,
33 checkDirectoryIsEmpty
34}
diff --git a/server/tests/shared/generate.ts b/server/tests/shared/generate.ts
new file mode 100644
index 000000000..f806df2f5
--- /dev/null
+++ b/server/tests/shared/generate.ts
@@ -0,0 +1,74 @@
1import { expect } from 'chai'
2import ffmpeg from 'fluent-ffmpeg'
3import { ensureDir, pathExists } from 'fs-extra'
4import { dirname } from 'path'
5import { buildAbsoluteFixturePath, getMaxBitrate } from '@shared/core-utils'
6import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '@shared/extra-utils'
7
8async function ensureHasTooBigBitrate (fixturePath: string) {
9 const bitrate = await getVideoFileBitrate(fixturePath)
10 const dataResolution = await getVideoFileResolution(fixturePath)
11 const fps = await getVideoFileFPS(fixturePath)
12
13 const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
14 expect(bitrate).to.be.above(maxBitrate)
15}
16
17async function generateHighBitrateVideo () {
18 const tempFixturePath = buildAbsoluteFixturePath('video_high_bitrate_1080p.mp4', true)
19
20 await ensureDir(dirname(tempFixturePath))
21
22 const exists = await pathExists(tempFixturePath)
23 if (!exists) {
24 console.log('Generating high bitrate video.')
25
26 // Generate a random, high bitrate video on the fly, so we don't have to include
27 // a large file in the repo. The video needs to have a certain minimum length so
28 // that FFmpeg properly applies bitrate limits.
29 // https://stackoverflow.com/a/15795112
30 return new Promise<string>((res, rej) => {
31 ffmpeg()
32 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
33 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
34 .outputOptions([ '-maxrate 10M', '-bufsize 10M' ])
35 .output(tempFixturePath)
36 .on('error', rej)
37 .on('end', () => res(tempFixturePath))
38 .run()
39 })
40 }
41
42 await ensureHasTooBigBitrate(tempFixturePath)
43
44 return tempFixturePath
45}
46
47async function generateVideoWithFramerate (fps = 60) {
48 const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true)
49
50 await ensureDir(dirname(tempFixturePath))
51
52 const exists = await pathExists(tempFixturePath)
53 if (!exists) {
54 console.log('Generating video with framerate %d.', fps)
55
56 return new Promise<string>((res, rej) => {
57 ffmpeg()
58 .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ])
59 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
60 .outputOptions([ `-r ${fps}` ])
61 .output(tempFixturePath)
62 .on('error', rej)
63 .on('end', () => res(tempFixturePath))
64 .run()
65 })
66 }
67
68 return tempFixturePath
69}
70
71export {
72 generateHighBitrateVideo,
73 generateVideoWithFramerate
74}
diff --git a/server/tests/shared/index.ts b/server/tests/shared/index.ts
new file mode 100644
index 000000000..47019d6a8
--- /dev/null
+++ b/server/tests/shared/index.ts
@@ -0,0 +1,15 @@
1export * from './mock-servers'
2export * from './actors'
3export * from './captions'
4export * from './checks'
5export * from './directories'
6export * from './generate'
7export * from './live'
8export * from './notifications'
9export * from './playlists'
10export * from './plugins'
11export * from './requests'
12export * from './streaming-playlists'
13export * from './tests'
14export * from './tracker'
15export * from './videos'
diff --git a/server/tests/shared/live.ts b/server/tests/shared/live.ts
new file mode 100644
index 000000000..72e3e27f6
--- /dev/null
+++ b/server/tests/shared/live.ts
@@ -0,0 +1,41 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { join } from 'path'
6import { PeerTubeServer } from '@shared/server-commands'
7
8async function checkLiveCleanupAfterSave (server: PeerTubeServer, videoUUID: string, resolutions: number[] = []) {
9 const basePath = server.servers.buildDirectory('streaming-playlists')
10 const hlsPath = join(basePath, 'hls', videoUUID)
11
12 if (resolutions.length === 0) {
13 const result = await pathExists(hlsPath)
14 expect(result).to.be.false
15
16 return
17 }
18
19 const files = await readdir(hlsPath)
20
21 // fragmented file and playlist per resolution + master playlist + segments sha256 json file
22 expect(files).to.have.lengthOf(resolutions.length * 2 + 2)
23
24 for (const resolution of resolutions) {
25 const fragmentedFile = files.find(f => f.endsWith(`-${resolution}-fragmented.mp4`))
26 expect(fragmentedFile).to.exist
27
28 const playlistFile = files.find(f => f.endsWith(`${resolution}.m3u8`))
29 expect(playlistFile).to.exist
30 }
31
32 const masterPlaylistFile = files.find(f => f.endsWith('-master.m3u8'))
33 expect(masterPlaylistFile).to.exist
34
35 const shaFile = files.find(f => f.endsWith('-segments-sha256.json'))
36 expect(shaFile).to.exist
37}
38
39export {
40 checkLiveCleanupAfterSave
41}
diff --git a/server/tests/shared/mock-servers/index.ts b/server/tests/shared/mock-servers/index.ts
new file mode 100644
index 000000000..abf4a8203
--- /dev/null
+++ b/server/tests/shared/mock-servers/index.ts
@@ -0,0 +1,7 @@
1export * from './mock-429'
2export * from './mock-email'
3export * from './mock-instances-index'
4export * from './mock-joinpeertube-versions'
5export * from './mock-object-storage'
6export * from './mock-plugin-blocklist'
7export * from './mock-proxy'
diff --git a/server/tests/shared/mock-servers/mock-429.ts b/server/tests/shared/mock-servers/mock-429.ts
new file mode 100644
index 000000000..1fc20b079
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-429.ts
@@ -0,0 +1,33 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './shared'
4
5export class Mock429 {
6 private server: Server
7 private responseSent = false
8
9 async initialize () {
10 const app = express()
11
12 app.get('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13
14 if (!this.responseSent) {
15 this.responseSent = true
16
17 // Retry after 5 seconds
18 res.header('retry-after', '2')
19 return res.sendStatus(429)
20 }
21
22 return res.sendStatus(200)
23 })
24
25 this.server = await randomListen(app)
26
27 return getPort(this.server)
28 }
29
30 terminate () {
31 return terminateServer(this.server)
32 }
33}
diff --git a/server/tests/shared/mock-servers/mock-email.ts b/server/tests/shared/mock-servers/mock-email.ts
new file mode 100644
index 000000000..c518679c9
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-email.ts
@@ -0,0 +1,62 @@
1import { ChildProcess } from 'child_process'
2import MailDev from '@peertube/maildev'
3import { parallelTests, randomInt } from '@shared/core-utils'
4
5class MockSmtpServer {
6
7 private static instance: MockSmtpServer
8 private started = false
9 private emailChildProcess: ChildProcess
10 private emails: object[]
11
12 private constructor () { }
13
14 collectEmails (emailsCollection: object[]) {
15 return new Promise<number>((res, rej) => {
16 const port = parallelTests() ? randomInt(1000, 2000) : 1025
17 this.emails = emailsCollection
18
19 if (this.started) {
20 return res(undefined)
21 }
22
23 const maildev = new MailDev({
24 ip: '127.0.0.1',
25 smtp: port,
26 disableWeb: true,
27 silent: true
28 })
29
30 maildev.on('new', email => {
31 this.emails.push(email)
32 })
33
34 maildev.listen(err => {
35 if (err) return rej(err)
36
37 this.started = true
38
39 return res(port)
40 })
41 })
42 }
43
44 kill () {
45 if (!this.emailChildProcess) return
46
47 process.kill(this.emailChildProcess.pid)
48
49 this.emailChildProcess = null
50 MockSmtpServer.instance = null
51 }
52
53 static get Instance () {
54 return this.instance || (this.instance = new this())
55 }
56}
57
58// ---------------------------------------------------------------------------
59
60export {
61 MockSmtpServer
62}
diff --git a/server/tests/shared/mock-servers/mock-instances-index.ts b/server/tests/shared/mock-servers/mock-instances-index.ts
new file mode 100644
index 000000000..598b007f1
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-instances-index.ts
@@ -0,0 +1,46 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './shared'
4
5export class MockInstancesIndex {
6 private server: Server
7
8 private readonly indexInstances: { host: string, createdAt: string }[] = []
9
10 async initialize () {
11 const app = express()
12
13 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
15
16 return next()
17 })
18
19 app.get('/api/v1/instances/hosts', (req: express.Request, res: express.Response) => {
20 const since = req.query.since
21
22 const filtered = this.indexInstances.filter(i => {
23 if (!since) return true
24
25 return i.createdAt > since
26 })
27
28 return res.json({
29 total: filtered.length,
30 data: filtered
31 })
32 })
33
34 this.server = await randomListen(app)
35
36 return getPort(this.server)
37 }
38
39 addInstance (host: string) {
40 this.indexInstances.push({ host, createdAt: new Date().toISOString() })
41 }
42
43 terminate () {
44 return terminateServer(this.server)
45 }
46}
diff --git a/server/tests/shared/mock-servers/mock-joinpeertube-versions.ts b/server/tests/shared/mock-servers/mock-joinpeertube-versions.ts
new file mode 100644
index 000000000..502f4e2f5
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-joinpeertube-versions.ts
@@ -0,0 +1,34 @@
1import express from 'express'
2import { Server } from 'http'
3import { getPort, randomListen } from './shared'
4
5export class MockJoinPeerTubeVersions {
6 private server: Server
7 private latestVersion: string
8
9 async initialize () {
10 const app = express()
11
12 app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => {
13 if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url)
14
15 return next()
16 })
17
18 app.get('/versions.json', (req: express.Request, res: express.Response) => {
19 return res.json({
20 peertube: {
21 latestVersion: this.latestVersion
22 }
23 })
24 })
25
26 this.server = await randomListen(app)
27
28 return getPort(this.server)
29 }
30
31 setLatestVersion (latestVersion: string) {
32 this.latestVersion = latestVersion
33 }
34}
diff --git a/server/tests/shared/mock-servers/mock-object-storage.ts b/server/tests/shared/mock-servers/mock-object-storage.ts
new file mode 100644
index 000000000..99d68e014
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-object-storage.ts
@@ -0,0 +1,41 @@
1import express from 'express'
2import got, { RequestError } from 'got'
3import { Server } from 'http'
4import { pipeline } from 'stream'
5import { ObjectStorageCommand } from '@shared/server-commands'
6import { getPort, randomListen, terminateServer } from './shared'
7
8export class MockObjectStorage {
9 private server: Server
10
11 async initialize () {
12 const app = express()
13
14 app.get('/:bucketName/:path(*)', (req: express.Request, res: express.Response, next: express.NextFunction) => {
15 const url = `http://${req.params.bucketName}.${ObjectStorageCommand.getEndpointHost()}/${req.params.path}`
16
17 if (process.env.DEBUG) {
18 console.log('Receiving request on mocked server %s.', req.url)
19 console.log('Proxifying request to %s', url)
20 }
21
22 return pipeline(
23 got.stream(url, { throwHttpErrors: false }),
24 res,
25 (err: RequestError) => {
26 if (!err) return
27
28 console.error('Pipeline failed.', err)
29 }
30 )
31 })
32
33 this.server = await randomListen(app)
34
35 return getPort(this.server)
36 }
37
38 terminate () {
39 return terminateServer(this.server)
40 }
41}
diff --git a/server/tests/shared/mock-servers/mock-plugin-blocklist.ts b/server/tests/shared/mock-servers/mock-plugin-blocklist.ts
new file mode 100644
index 000000000..5d6e01816
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-plugin-blocklist.ts
@@ -0,0 +1,36 @@
1import express, { Request, Response } from 'express'
2import { Server } from 'http'
3import { getPort, randomListen, terminateServer } from './shared'
4
5type BlocklistResponse = {
6 data: {
7 value: string
8 action?: 'add' | 'remove'
9 updatedAt?: string
10 }[]
11}
12
13export class MockBlocklist {
14 private body: BlocklistResponse
15 private server: Server
16
17 async initialize () {
18 const app = express()
19
20 app.get('/blocklist', (req: Request, res: Response) => {
21 return res.json(this.body)
22 })
23
24 this.server = await randomListen(app)
25
26 return getPort(this.server)
27 }
28
29 replace (body: BlocklistResponse) {
30 this.body = body
31 }
32
33 terminate () {
34 return terminateServer(this.server)
35 }
36}
diff --git a/server/tests/shared/mock-servers/mock-proxy.ts b/server/tests/shared/mock-servers/mock-proxy.ts
new file mode 100644
index 000000000..cbc7c4466
--- /dev/null
+++ b/server/tests/shared/mock-servers/mock-proxy.ts
@@ -0,0 +1,24 @@
1import { createServer, Server } from 'http'
2import proxy from 'proxy'
3import { getPort, terminateServer } from './shared'
4
5class MockProxy {
6 private server: Server
7
8 initialize () {
9 return new Promise<number>(res => {
10 this.server = proxy(createServer())
11 this.server.listen(0, () => res(getPort(this.server)))
12 })
13 }
14
15 terminate () {
16 return terminateServer(this.server)
17 }
18}
19
20// ---------------------------------------------------------------------------
21
22export {
23 MockProxy
24}
diff --git a/server/tests/shared/mock-servers/shared.ts b/server/tests/shared/mock-servers/shared.ts
new file mode 100644
index 000000000..235642439
--- /dev/null
+++ b/server/tests/shared/mock-servers/shared.ts
@@ -0,0 +1,33 @@
1import { Express } from 'express'
2import { Server } from 'http'
3import { AddressInfo } from 'net'
4
5function randomListen (app: Express) {
6 return new Promise<Server>(res => {
7 const server = app.listen(0, () => res(server))
8 })
9}
10
11function getPort (server: Server) {
12 const address = server.address() as AddressInfo
13
14 return address.port
15}
16
17function terminateServer (server: Server) {
18 if (!server) return Promise.resolve()
19
20 return new Promise<void>((res, rej) => {
21 server.close(err => {
22 if (err) return rej(err)
23
24 return res()
25 })
26 })
27}
28
29export {
30 randomListen,
31 getPort,
32 terminateServer
33}
diff --git a/server/tests/shared/notifications.ts b/server/tests/shared/notifications.ts
new file mode 100644
index 000000000..cdc21fdc8
--- /dev/null
+++ b/server/tests/shared/notifications.ts
@@ -0,0 +1,798 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { inspect } from 'util'
5import {
6 AbuseState,
7 PluginType,
8 UserNotification,
9 UserNotificationSetting,
10 UserNotificationSettingValue,
11 UserNotificationType
12} from '@shared/models'
13import { createMultipleServers, doubleFollow, PeerTubeServer, setAccessTokensToServers } from '@shared/server-commands'
14import { MockSmtpServer } from './mock-servers'
15
16type CheckerBaseParams = {
17 server: PeerTubeServer
18 emails: any[]
19 socketNotifications: UserNotification[]
20 token: string
21 check?: { web: boolean, mail: boolean }
22}
23
24type CheckerType = 'presence' | 'absence'
25
26function getAllNotificationsSettings (): UserNotificationSetting {
27 return {
28 newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
29 newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
30 abuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
31 videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
32 blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
33 myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
34 myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
35 commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
36 newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
37 newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
38 newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
39 abuseNewMessage: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
40 abuseStateChange: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
41 autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
42 newPeerTubeVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
43 newPluginVersion: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
44 }
45}
46
47async function checkNewVideoFromSubscription (options: CheckerBaseParams & {
48 videoName: string
49 shortUUID: string
50 checkType: CheckerType
51}) {
52 const { videoName, shortUUID } = options
53 const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
54
55 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
56 if (checkType === 'presence') {
57 expect(notification).to.not.be.undefined
58 expect(notification.type).to.equal(notificationType)
59
60 checkVideo(notification.video, videoName, shortUUID)
61 checkActor(notification.video.channel)
62 } else {
63 expect(notification).to.satisfy((n: UserNotification) => {
64 return n === undefined || n.type !== UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION || n.video.name !== videoName
65 })
66 }
67 }
68
69 function emailNotificationFinder (email: object) {
70 const text = email['text']
71 return text.indexOf(shortUUID) !== -1 && text.indexOf('Your subscription') !== -1
72 }
73
74 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
75}
76
77async function checkVideoIsPublished (options: CheckerBaseParams & {
78 videoName: string
79 shortUUID: string
80 checkType: CheckerType
81}) {
82 const { videoName, shortUUID } = options
83 const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
84
85 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
86 if (checkType === 'presence') {
87 expect(notification).to.not.be.undefined
88 expect(notification.type).to.equal(notificationType)
89
90 checkVideo(notification.video, videoName, shortUUID)
91 checkActor(notification.video.channel)
92 } else {
93 expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
94 }
95 }
96
97 function emailNotificationFinder (email: object) {
98 const text: string = email['text']
99 return text.includes(shortUUID) && text.includes('Your video')
100 }
101
102 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
103}
104
105async function checkMyVideoImportIsFinished (options: CheckerBaseParams & {
106 videoName: string
107 shortUUID: string
108 url: string
109 success: boolean
110 checkType: CheckerType
111}) {
112 const { videoName, shortUUID, url, success } = options
113
114 const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
115
116 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
117 if (checkType === 'presence') {
118 expect(notification).to.not.be.undefined
119 expect(notification.type).to.equal(notificationType)
120
121 expect(notification.videoImport.targetUrl).to.equal(url)
122
123 if (success) checkVideo(notification.videoImport.video, videoName, shortUUID)
124 } else {
125 expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
126 }
127 }
128
129 function emailNotificationFinder (email: object) {
130 const text: string = email['text']
131 const toFind = success ? ' finished' : ' error'
132
133 return text.includes(url) && text.includes(toFind)
134 }
135
136 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
137}
138
139async function checkUserRegistered (options: CheckerBaseParams & {
140 username: string
141 checkType: CheckerType
142}) {
143 const { username } = options
144 const notificationType = UserNotificationType.NEW_USER_REGISTRATION
145
146 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
147 if (checkType === 'presence') {
148 expect(notification).to.not.be.undefined
149 expect(notification.type).to.equal(notificationType)
150
151 checkActor(notification.account)
152 expect(notification.account.name).to.equal(username)
153 } else {
154 expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
155 }
156 }
157
158 function emailNotificationFinder (email: object) {
159 const text: string = email['text']
160
161 return text.includes(' registered.') && text.includes(username)
162 }
163
164 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
165}
166
167async function checkNewActorFollow (options: CheckerBaseParams & {
168 followType: 'channel' | 'account'
169 followerName: string
170 followerDisplayName: string
171 followingDisplayName: string
172 checkType: CheckerType
173}) {
174 const { followType, followerName, followerDisplayName, followingDisplayName } = options
175 const notificationType = UserNotificationType.NEW_FOLLOW
176
177 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
178 if (checkType === 'presence') {
179 expect(notification).to.not.be.undefined
180 expect(notification.type).to.equal(notificationType)
181
182 checkActor(notification.actorFollow.follower)
183 expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
184 expect(notification.actorFollow.follower.name).to.equal(followerName)
185 expect(notification.actorFollow.follower.host).to.not.be.undefined
186
187 const following = notification.actorFollow.following
188 expect(following.displayName).to.equal(followingDisplayName)
189 expect(following.type).to.equal(followType)
190 } else {
191 expect(notification).to.satisfy(n => {
192 return n.type !== notificationType ||
193 (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
194 })
195 }
196 }
197
198 function emailNotificationFinder (email: object) {
199 const text: string = email['text']
200
201 return text.includes(followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
202 }
203
204 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
205}
206
207async function checkNewInstanceFollower (options: CheckerBaseParams & {
208 followerHost: string
209 checkType: CheckerType
210}) {
211 const { followerHost } = options
212 const notificationType = UserNotificationType.NEW_INSTANCE_FOLLOWER
213
214 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
215 if (checkType === 'presence') {
216 expect(notification).to.not.be.undefined
217 expect(notification.type).to.equal(notificationType)
218
219 checkActor(notification.actorFollow.follower)
220 expect(notification.actorFollow.follower.name).to.equal('peertube')
221 expect(notification.actorFollow.follower.host).to.equal(followerHost)
222
223 expect(notification.actorFollow.following.name).to.equal('peertube')
224 } else {
225 expect(notification).to.satisfy(n => {
226 return n.type !== notificationType || n.actorFollow.follower.host !== followerHost
227 })
228 }
229 }
230
231 function emailNotificationFinder (email: object) {
232 const text: string = email['text']
233
234 return text.includes('instance has a new follower') && text.includes(followerHost)
235 }
236
237 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
238}
239
240async function checkAutoInstanceFollowing (options: CheckerBaseParams & {
241 followerHost: string
242 followingHost: string
243 checkType: CheckerType
244}) {
245 const { followerHost, followingHost } = options
246 const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING
247
248 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
249 if (checkType === 'presence') {
250 expect(notification).to.not.be.undefined
251 expect(notification.type).to.equal(notificationType)
252
253 const following = notification.actorFollow.following
254 checkActor(following)
255 expect(following.name).to.equal('peertube')
256 expect(following.host).to.equal(followingHost)
257
258 expect(notification.actorFollow.follower.name).to.equal('peertube')
259 expect(notification.actorFollow.follower.host).to.equal(followerHost)
260 } else {
261 expect(notification).to.satisfy(n => {
262 return n.type !== notificationType || n.actorFollow.following.host !== followingHost
263 })
264 }
265 }
266
267 function emailNotificationFinder (email: object) {
268 const text: string = email['text']
269
270 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
271 }
272
273 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
274}
275
276async function checkCommentMention (options: CheckerBaseParams & {
277 shortUUID: string
278 commentId: number
279 threadId: number
280 byAccountDisplayName: string
281 checkType: CheckerType
282}) {
283 const { shortUUID, commentId, threadId, byAccountDisplayName } = options
284 const notificationType = UserNotificationType.COMMENT_MENTION
285
286 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
287 if (checkType === 'presence') {
288 expect(notification).to.not.be.undefined
289 expect(notification.type).to.equal(notificationType)
290
291 checkComment(notification.comment, commentId, threadId)
292 checkActor(notification.comment.account)
293 expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
294
295 checkVideo(notification.comment.video, undefined, shortUUID)
296 } else {
297 expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
298 }
299 }
300
301 function emailNotificationFinder (email: object) {
302 const text: string = email['text']
303
304 return text.includes(' mentioned ') && text.includes(shortUUID) && text.includes(byAccountDisplayName)
305 }
306
307 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
308}
309
310let lastEmailCount = 0
311
312async function checkNewCommentOnMyVideo (options: CheckerBaseParams & {
313 shortUUID: string
314 commentId: number
315 threadId: number
316 checkType: CheckerType
317}) {
318 const { server, shortUUID, commentId, threadId, checkType, emails } = options
319 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
320
321 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
322 if (checkType === 'presence') {
323 expect(notification).to.not.be.undefined
324 expect(notification.type).to.equal(notificationType)
325
326 checkComment(notification.comment, commentId, threadId)
327 checkActor(notification.comment.account)
328 checkVideo(notification.comment.video, undefined, shortUUID)
329 } else {
330 expect(notification).to.satisfy((n: UserNotification) => {
331 return n === undefined || n.comment === undefined || n.comment.id !== commentId
332 })
333 }
334 }
335
336 const commentUrl = `http://localhost:${server.port}/w/${shortUUID};threadId=${threadId}`
337
338 function emailNotificationFinder (email: object) {
339 return email['text'].indexOf(commentUrl) !== -1
340 }
341
342 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
343
344 if (checkType === 'presence') {
345 // We cannot detect email duplicates, so check we received another email
346 expect(emails).to.have.length.above(lastEmailCount)
347 lastEmailCount = emails.length
348 }
349}
350
351async function checkNewVideoAbuseForModerators (options: CheckerBaseParams & {
352 shortUUID: string
353 videoName: string
354 checkType: CheckerType
355}) {
356 const { shortUUID, videoName } = options
357 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
358
359 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
360 if (checkType === 'presence') {
361 expect(notification).to.not.be.undefined
362 expect(notification.type).to.equal(notificationType)
363
364 expect(notification.abuse.id).to.be.a('number')
365 checkVideo(notification.abuse.video, videoName, shortUUID)
366 } else {
367 expect(notification).to.satisfy((n: UserNotification) => {
368 return n === undefined || n.abuse === undefined || n.abuse.video.shortUUID !== shortUUID
369 })
370 }
371 }
372
373 function emailNotificationFinder (email: object) {
374 const text = email['text']
375 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
376 }
377
378 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
379}
380
381async function checkNewAbuseMessage (options: CheckerBaseParams & {
382 abuseId: number
383 message: string
384 toEmail: string
385 checkType: CheckerType
386}) {
387 const { abuseId, message, toEmail } = options
388 const notificationType = UserNotificationType.ABUSE_NEW_MESSAGE
389
390 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
391 if (checkType === 'presence') {
392 expect(notification).to.not.be.undefined
393 expect(notification.type).to.equal(notificationType)
394
395 expect(notification.abuse.id).to.equal(abuseId)
396 } else {
397 expect(notification).to.satisfy((n: UserNotification) => {
398 return n === undefined || n.type !== notificationType || n.abuse === undefined || n.abuse.id !== abuseId
399 })
400 }
401 }
402
403 function emailNotificationFinder (email: object) {
404 const text = email['text']
405 const to = email['to'].filter(t => t.address === toEmail)
406
407 return text.indexOf(message) !== -1 && to.length !== 0
408 }
409
410 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
411}
412
413async function checkAbuseStateChange (options: CheckerBaseParams & {
414 abuseId: number
415 state: AbuseState
416 checkType: CheckerType
417}) {
418 const { abuseId, state } = options
419 const notificationType = UserNotificationType.ABUSE_STATE_CHANGE
420
421 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
422 if (checkType === 'presence') {
423 expect(notification).to.not.be.undefined
424 expect(notification.type).to.equal(notificationType)
425
426 expect(notification.abuse.id).to.equal(abuseId)
427 expect(notification.abuse.state).to.equal(state)
428 } else {
429 expect(notification).to.satisfy((n: UserNotification) => {
430 return n === undefined || n.abuse === undefined || n.abuse.id !== abuseId
431 })
432 }
433 }
434
435 function emailNotificationFinder (email: object) {
436 const text = email['text']
437
438 const contains = state === AbuseState.ACCEPTED
439 ? ' accepted'
440 : ' rejected'
441
442 return text.indexOf(contains) !== -1
443 }
444
445 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
446}
447
448async function checkNewCommentAbuseForModerators (options: CheckerBaseParams & {
449 shortUUID: string
450 videoName: string
451 checkType: CheckerType
452}) {
453 const { shortUUID, videoName } = options
454 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
455
456 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
457 if (checkType === 'presence') {
458 expect(notification).to.not.be.undefined
459 expect(notification.type).to.equal(notificationType)
460
461 expect(notification.abuse.id).to.be.a('number')
462 checkVideo(notification.abuse.comment.video, videoName, shortUUID)
463 } else {
464 expect(notification).to.satisfy((n: UserNotification) => {
465 return n === undefined || n.abuse === undefined || n.abuse.comment.video.shortUUID !== shortUUID
466 })
467 }
468 }
469
470 function emailNotificationFinder (email: object) {
471 const text = email['text']
472 return text.indexOf(shortUUID) !== -1 && text.indexOf('abuse') !== -1
473 }
474
475 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
476}
477
478async function checkNewAccountAbuseForModerators (options: CheckerBaseParams & {
479 displayName: string
480 checkType: CheckerType
481}) {
482 const { displayName } = options
483 const notificationType = UserNotificationType.NEW_ABUSE_FOR_MODERATORS
484
485 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
486 if (checkType === 'presence') {
487 expect(notification).to.not.be.undefined
488 expect(notification.type).to.equal(notificationType)
489
490 expect(notification.abuse.id).to.be.a('number')
491 expect(notification.abuse.account.displayName).to.equal(displayName)
492 } else {
493 expect(notification).to.satisfy((n: UserNotification) => {
494 return n === undefined || n.abuse === undefined || n.abuse.account.displayName !== displayName
495 })
496 }
497 }
498
499 function emailNotificationFinder (email: object) {
500 const text = email['text']
501 return text.indexOf(displayName) !== -1 && text.indexOf('abuse') !== -1
502 }
503
504 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
505}
506
507async function checkVideoAutoBlacklistForModerators (options: CheckerBaseParams & {
508 shortUUID: string
509 videoName: string
510 checkType: CheckerType
511}) {
512 const { shortUUID, videoName } = options
513 const notificationType = UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS
514
515 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
516 if (checkType === 'presence') {
517 expect(notification).to.not.be.undefined
518 expect(notification.type).to.equal(notificationType)
519
520 expect(notification.videoBlacklist.video.id).to.be.a('number')
521 checkVideo(notification.videoBlacklist.video, videoName, shortUUID)
522 } else {
523 expect(notification).to.satisfy((n: UserNotification) => {
524 return n === undefined || n.video === undefined || n.video.shortUUID !== shortUUID
525 })
526 }
527 }
528
529 function emailNotificationFinder (email: object) {
530 const text = email['text']
531 return text.indexOf(shortUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
532 }
533
534 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
535}
536
537async function checkNewBlacklistOnMyVideo (options: CheckerBaseParams & {
538 shortUUID: string
539 videoName: string
540 blacklistType: 'blacklist' | 'unblacklist'
541}) {
542 const { videoName, shortUUID, blacklistType } = options
543 const notificationType = blacklistType === 'blacklist'
544 ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
545 : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
546
547 function notificationChecker (notification: UserNotification) {
548 expect(notification).to.not.be.undefined
549 expect(notification.type).to.equal(notificationType)
550
551 const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
552
553 checkVideo(video, videoName, shortUUID)
554 }
555
556 function emailNotificationFinder (email: object) {
557 const text = email['text']
558 const blacklistText = blacklistType === 'blacklist'
559 ? 'blacklisted'
560 : 'unblacklisted'
561
562 return text.includes(shortUUID) && text.includes(blacklistText)
563 }
564
565 await checkNotification({ ...options, notificationChecker, emailNotificationFinder, checkType: 'presence' })
566}
567
568async function checkNewPeerTubeVersion (options: CheckerBaseParams & {
569 latestVersion: string
570 checkType: CheckerType
571}) {
572 const { latestVersion } = options
573 const notificationType = UserNotificationType.NEW_PEERTUBE_VERSION
574
575 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
576 if (checkType === 'presence') {
577 expect(notification).to.not.be.undefined
578 expect(notification.type).to.equal(notificationType)
579
580 expect(notification.peertube).to.exist
581 expect(notification.peertube.latestVersion).to.equal(latestVersion)
582 } else {
583 expect(notification).to.satisfy((n: UserNotification) => {
584 return n === undefined || n.peertube === undefined || n.peertube.latestVersion !== latestVersion
585 })
586 }
587 }
588
589 function emailNotificationFinder (email: object) {
590 const text = email['text']
591
592 return text.includes(latestVersion)
593 }
594
595 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
596}
597
598async function checkNewPluginVersion (options: CheckerBaseParams & {
599 pluginType: PluginType
600 pluginName: string
601 checkType: CheckerType
602}) {
603 const { pluginName, pluginType } = options
604 const notificationType = UserNotificationType.NEW_PLUGIN_VERSION
605
606 function notificationChecker (notification: UserNotification, checkType: CheckerType) {
607 if (checkType === 'presence') {
608 expect(notification).to.not.be.undefined
609 expect(notification.type).to.equal(notificationType)
610
611 expect(notification.plugin.name).to.equal(pluginName)
612 expect(notification.plugin.type).to.equal(pluginType)
613 } else {
614 expect(notification).to.satisfy((n: UserNotification) => {
615 return n === undefined || n.plugin === undefined || n.plugin.name !== pluginName
616 })
617 }
618 }
619
620 function emailNotificationFinder (email: object) {
621 const text = email['text']
622
623 return text.includes(pluginName)
624 }
625
626 await checkNotification({ ...options, notificationChecker, emailNotificationFinder })
627}
628
629async function prepareNotificationsTest (serversCount = 3, overrideConfigArg: any = {}) {
630 const userNotifications: UserNotification[] = []
631 const adminNotifications: UserNotification[] = []
632 const adminNotificationsServer2: UserNotification[] = []
633 const emails: object[] = []
634
635 const port = await MockSmtpServer.Instance.collectEmails(emails)
636
637 const overrideConfig = {
638 smtp: {
639 hostname: 'localhost',
640 port
641 },
642 signup: {
643 limit: 20
644 }
645 }
646 const servers = await createMultipleServers(serversCount, Object.assign(overrideConfig, overrideConfigArg))
647
648 await setAccessTokensToServers(servers)
649
650 if (serversCount > 1) {
651 await doubleFollow(servers[0], servers[1])
652 }
653
654 const user = { username: 'user_1', password: 'super password' }
655 await servers[0].users.create({ ...user, videoQuota: 10 * 1000 * 1000 })
656 const userAccessToken = await servers[0].login.getAccessToken(user)
657
658 await servers[0].notifications.updateMySettings({ token: userAccessToken, settings: getAllNotificationsSettings() })
659 await servers[0].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
660
661 if (serversCount > 1) {
662 await servers[1].notifications.updateMySettings({ settings: getAllNotificationsSettings() })
663 }
664
665 {
666 const socket = servers[0].socketIO.getUserNotificationSocket({ token: userAccessToken })
667 socket.on('new-notification', n => userNotifications.push(n))
668 }
669 {
670 const socket = servers[0].socketIO.getUserNotificationSocket()
671 socket.on('new-notification', n => adminNotifications.push(n))
672 }
673
674 if (serversCount > 1) {
675 const socket = servers[1].socketIO.getUserNotificationSocket()
676 socket.on('new-notification', n => adminNotificationsServer2.push(n))
677 }
678
679 const { videoChannels } = await servers[0].users.getMyInfo()
680 const channelId = videoChannels[0].id
681
682 return {
683 userNotifications,
684 adminNotifications,
685 adminNotificationsServer2,
686 userAccessToken,
687 emails,
688 servers,
689 channelId
690 }
691}
692
693// ---------------------------------------------------------------------------
694
695export {
696 getAllNotificationsSettings,
697
698 CheckerBaseParams,
699 CheckerType,
700 checkMyVideoImportIsFinished,
701 checkUserRegistered,
702 checkAutoInstanceFollowing,
703 checkVideoIsPublished,
704 checkNewVideoFromSubscription,
705 checkNewActorFollow,
706 checkNewCommentOnMyVideo,
707 checkNewBlacklistOnMyVideo,
708 checkCommentMention,
709 checkNewVideoAbuseForModerators,
710 checkVideoAutoBlacklistForModerators,
711 checkNewAbuseMessage,
712 checkAbuseStateChange,
713 checkNewInstanceFollower,
714 prepareNotificationsTest,
715 checkNewCommentAbuseForModerators,
716 checkNewAccountAbuseForModerators,
717 checkNewPeerTubeVersion,
718 checkNewPluginVersion
719}
720
721// ---------------------------------------------------------------------------
722
723async function checkNotification (options: CheckerBaseParams & {
724 notificationChecker: (notification: UserNotification, checkType: CheckerType) => void
725 emailNotificationFinder: (email: object) => boolean
726 checkType: CheckerType
727}) {
728 const { server, token, checkType, notificationChecker, emailNotificationFinder, socketNotifications, emails } = options
729
730 const check = options.check || { web: true, mail: true }
731
732 if (check.web) {
733 const notification = await server.notifications.getLatest({ token: token })
734
735 if (notification || checkType !== 'absence') {
736 notificationChecker(notification, checkType)
737 }
738
739 const socketNotification = socketNotifications.find(n => {
740 try {
741 notificationChecker(n, 'presence')
742 return true
743 } catch {
744 return false
745 }
746 })
747
748 if (checkType === 'presence') {
749 const obj = inspect(socketNotifications, { depth: 5 })
750 expect(socketNotification, 'The socket notification is absent when it should be present. ' + obj).to.not.be.undefined
751 } else {
752 const obj = inspect(socketNotification, { depth: 5 })
753 expect(socketNotification, 'The socket notification is present when it should not be present. ' + obj).to.be.undefined
754 }
755 }
756
757 if (check.mail) {
758 // Last email
759 const email = emails
760 .slice()
761 .reverse()
762 .find(e => emailNotificationFinder(e))
763
764 if (checkType === 'presence') {
765 const texts = emails.map(e => e.text)
766 expect(email, 'The email is absent when is should be present. ' + inspect(texts)).to.not.be.undefined
767 } else {
768 expect(email, 'The email is present when is should not be present. ' + inspect(email)).to.be.undefined
769 }
770 }
771}
772
773function checkVideo (video: any, videoName?: string, shortUUID?: string) {
774 if (videoName) {
775 expect(video.name).to.be.a('string')
776 expect(video.name).to.not.be.empty
777 expect(video.name).to.equal(videoName)
778 }
779
780 if (shortUUID) {
781 expect(video.shortUUID).to.be.a('string')
782 expect(video.shortUUID).to.not.be.empty
783 expect(video.shortUUID).to.equal(shortUUID)
784 }
785
786 expect(video.id).to.be.a('number')
787}
788
789function checkActor (actor: any) {
790 expect(actor.displayName).to.be.a('string')
791 expect(actor.displayName).to.not.be.empty
792 expect(actor.host).to.not.be.undefined
793}
794
795function checkComment (comment: any, commentId: number, threadId: number) {
796 expect(comment.id).to.equal(commentId)
797 expect(comment.threadId).to.equal(threadId)
798}
diff --git a/server/tests/shared/playlists.ts b/server/tests/shared/playlists.ts
new file mode 100644
index 000000000..fdd541d20
--- /dev/null
+++ b/server/tests/shared/playlists.ts
@@ -0,0 +1,25 @@
1import { expect } from 'chai'
2import { readdir } from 'fs-extra'
3import { join } from 'path'
4import { root } from '@shared/core-utils'
5
6async function checkPlaylistFilesWereRemoved (
7 playlistUUID: string,
8 internalServerNumber: number,
9 directories = [ 'thumbnails' ]
10) {
11 const testDirectory = 'test' + internalServerNumber
12
13 for (const directory of directories) {
14 const directoryPath = join(root(), testDirectory, directory)
15
16 const files = await readdir(directoryPath)
17 for (const file of files) {
18 expect(file).to.not.contain(playlistUUID)
19 }
20 }
21}
22
23export {
24 checkPlaylistFilesWereRemoved
25}
diff --git a/server/tests/shared/plugins.ts b/server/tests/shared/plugins.ts
new file mode 100644
index 000000000..036fce2ff
--- /dev/null
+++ b/server/tests/shared/plugins.ts
@@ -0,0 +1,18 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { PeerTubeServer } from '@shared/server-commands'
5
6async function testHelloWorldRegisteredSettings (server: PeerTubeServer) {
7 const body = await server.plugins.getRegisteredSettings({ npmName: 'peertube-plugin-hello-world' })
8
9 const registeredSettings = body.registeredSettings
10 expect(registeredSettings).to.have.length.at.least(1)
11
12 const adminNameSettings = registeredSettings.find(s => s.name === 'admin-name')
13 expect(adminNameSettings).to.not.be.undefined
14}
15
16export {
17 testHelloWorldRegisteredSettings
18}
diff --git a/server/tests/shared/requests.ts b/server/tests/shared/requests.ts
new file mode 100644
index 000000000..7f1acc0e1
--- /dev/null
+++ b/server/tests/shared/requests.ts
@@ -0,0 +1,41 @@
1import { activityPubContextify } from '@server/helpers/activitypub'
2import { buildDigest } from '@server/helpers/peertube-crypto'
3import { doRequest } from '@server/helpers/requests'
4import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
5
6export function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
7 const options = {
8 method: 'POST' as 'POST',
9 json: body,
10 httpSignature,
11 headers
12 }
13
14 return doRequest(url, options)
15}
16
17export async function makeFollowRequest (to: { url: string }, by: { url: string, privateKey }) {
18 const follow = {
19 type: 'Follow',
20 id: by.url + '/' + new Date().getTime(),
21 actor: by.url,
22 object: to.url
23 }
24
25 const body = activityPubContextify(follow)
26
27 const httpSignature = {
28 algorithm: HTTP_SIGNATURE.ALGORITHM,
29 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
30 keyId: by.url,
31 key: by.privateKey,
32 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
33 }
34 const headers = {
35 'digest': buildDigest(body),
36 'content-type': 'application/activity+json',
37 'accept': ACTIVITY_PUB.ACCEPT_HEADER
38 }
39
40 return makePOSTAPRequest(to.url + '/inbox', body, httpSignature, headers)
41}
diff --git a/server/tests/shared/streaming-playlists.ts b/server/tests/shared/streaming-playlists.ts
new file mode 100644
index 000000000..7ca707f2e
--- /dev/null
+++ b/server/tests/shared/streaming-playlists.ts
@@ -0,0 +1,77 @@
1import { expect } from 'chai'
2import { basename } from 'path'
3import { removeFragmentedMP4Ext } from '@shared/core-utils'
4import { sha256 } from '@shared/extra-utils'
5import { HttpStatusCode, VideoStreamingPlaylist } from '@shared/models'
6import { PeerTubeServer } from '@shared/server-commands'
7
8async function checkSegmentHash (options: {
9 server: PeerTubeServer
10 baseUrlPlaylist: string
11 baseUrlSegment: string
12 resolution: number
13 hlsPlaylist: VideoStreamingPlaylist
14}) {
15 const { server, baseUrlPlaylist, baseUrlSegment, resolution, hlsPlaylist } = options
16 const command = server.streamingPlaylists
17
18 const file = hlsPlaylist.files.find(f => f.resolution.id === resolution)
19 const videoName = basename(file.fileUrl)
20
21 const playlist = await command.get({ url: `${baseUrlPlaylist}/${removeFragmentedMP4Ext(videoName)}.m3u8` })
22
23 const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
24
25 const length = parseInt(matches[1], 10)
26 const offset = parseInt(matches[2], 10)
27 const range = `${offset}-${offset + length - 1}`
28
29 const segmentBody = await command.getSegment({
30 url: `${baseUrlSegment}/${videoName}`,
31 expectedStatus: HttpStatusCode.PARTIAL_CONTENT_206,
32 range: `bytes=${range}`
33 })
34
35 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
36 expect(sha256(segmentBody)).to.equal(shaBody[videoName][range])
37}
38
39async function checkLiveSegmentHash (options: {
40 server: PeerTubeServer
41 baseUrlSegment: string
42 videoUUID: string
43 segmentName: string
44 hlsPlaylist: VideoStreamingPlaylist
45}) {
46 const { server, baseUrlSegment, videoUUID, segmentName, hlsPlaylist } = options
47 const command = server.streamingPlaylists
48
49 const segmentBody = await command.getSegment({ url: `${baseUrlSegment}/${videoUUID}/${segmentName}` })
50 const shaBody = await command.getSegmentSha256({ url: hlsPlaylist.segmentsSha256Url })
51
52 expect(sha256(segmentBody)).to.equal(shaBody[segmentName])
53}
54
55async function checkResolutionsInMasterPlaylist (options: {
56 server: PeerTubeServer
57 playlistUrl: string
58 resolutions: number[]
59}) {
60 const { server, playlistUrl, resolutions } = options
61
62 const masterPlaylist = await server.streamingPlaylists.get({ url: playlistUrl })
63
64 for (const resolution of resolutions) {
65 const reg = new RegExp(
66 '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',(FRAME-RATE=\\d+,)?CODECS="avc1.64001f,mp4a.40.2"'
67 )
68
69 expect(masterPlaylist).to.match(reg)
70 }
71}
72
73export {
74 checkSegmentHash,
75 checkLiveSegmentHash,
76 checkResolutionsInMasterPlaylist
77}
diff --git a/server/tests/shared/tests.ts b/server/tests/shared/tests.ts
new file mode 100644
index 000000000..3abaf833d
--- /dev/null
+++ b/server/tests/shared/tests.ts
@@ -0,0 +1,37 @@
1const FIXTURE_URLS = {
2 peertube_long: 'https://peertube2.cpy.re/videos/watch/122d093a-1ede-43bd-bd34-59d2931ffc5e',
3 peertube_short: 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
4
5 youtube: 'https://www.youtube.com/watch?v=msX3jv1XdvM',
6
7 /**
8 * The video is used to check format-selection correctness wrt. HDR,
9 * which brings its own set of oddities outside of a MediaSource.
10 *
11 * The video needs to have the following format_ids:
12 * (which you can check by using `youtube-dl <url> -F`):
13 * - (webm vp9)
14 * - (mp4 avc1)
15 * - (webm vp9.2 HDR)
16 */
17 youtubeHDR: 'https://www.youtube.com/watch?v=RQgnBB9z_N4',
18
19 // eslint-disable-next-line max-len
20 magnet: 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Flazy-static%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4',
21
22 badVideo: 'https://download.cpy.re/peertube/bad_video.mp4',
23 goodVideo: 'https://download.cpy.re/peertube/good_video.mp4',
24 goodVideo720: 'https://download.cpy.re/peertube/good_video_720.mp4',
25
26 file4K: 'https://download.cpy.re/peertube/4k_file.txt'
27}
28
29function buildRequestStub (): any {
30 return { }
31}
32
33export {
34 FIXTURE_URLS,
35
36 buildRequestStub
37}
diff --git a/server/tests/shared/tracker.ts b/server/tests/shared/tracker.ts
new file mode 100644
index 000000000..9c1f0246a
--- /dev/null
+++ b/server/tests/shared/tracker.ts
@@ -0,0 +1,27 @@
1import { expect } from 'chai'
2import { sha1 } from '@shared/extra-utils'
3import { makeGetRequest } from '@shared/server-commands'
4
5async function hlsInfohashExist (serverUrl: string, masterPlaylistUrl: string, fileNumber: number) {
6 const path = '/tracker/announce'
7
8 const infohash = sha1(`2${masterPlaylistUrl}+V${fileNumber}`)
9
10 // From bittorrent-tracker
11 const infohashBinary = escape(Buffer.from(infohash, 'hex').toString('binary')).replace(/[@*/+]/g, function (char) {
12 return '%' + char.charCodeAt(0).toString(16).toUpperCase()
13 })
14
15 const res = await makeGetRequest({
16 url: serverUrl,
17 path,
18 rawQuery: `peer_id=-WW0105-NkvYO/egUAr4&info_hash=${infohashBinary}&port=42100`,
19 expectedStatus: 200
20 })
21
22 expect(res.text).to.not.contain('failure')
23}
24
25export {
26 hlsInfohashExist
27}
diff --git a/server/tests/shared/videos.ts b/server/tests/shared/videos.ts
new file mode 100644
index 000000000..6be094f2b
--- /dev/null
+++ b/server/tests/shared/videos.ts
@@ -0,0 +1,251 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3import { expect } from 'chai'
4import { pathExists, readdir } from 'fs-extra'
5import { basename, join } from 'path'
6import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '@server/initializers/constants'
7import { getLowercaseExtension, uuidRegex } from '@shared/core-utils'
8import { HttpStatusCode, VideoCaption, VideoDetails } from '@shared/models'
9import { makeRawRequest, PeerTubeServer, VideoEdit, waitJobs, webtorrentAdd } from '@shared/server-commands'
10import { dateIsValid, testImage } from './checks'
11
12loadLanguages()
13
14async function completeVideoCheck (
15 server: PeerTubeServer,
16 video: any,
17 attributes: {
18 name: string
19 category: number
20 licence: number
21 language: string
22 nsfw: boolean
23 commentsEnabled: boolean
24 downloadEnabled: boolean
25 description: string
26 publishedAt?: string
27 support: string
28 originallyPublishedAt?: string
29 account: {
30 name: string
31 host: string
32 }
33 isLocal: boolean
34 tags: string[]
35 privacy: number
36 likes?: number
37 dislikes?: number
38 duration: number
39 channel: {
40 displayName: string
41 name: string
42 description: string
43 isLocal: boolean
44 }
45 fixture: string
46 files: {
47 resolution: number
48 size: number
49 }[]
50 thumbnailfile?: string
51 previewfile?: string
52 }
53) {
54 if (!attributes.likes) attributes.likes = 0
55 if (!attributes.dislikes) attributes.dislikes = 0
56
57 const host = new URL(server.url).host
58 const originHost = attributes.account.host
59
60 expect(video.name).to.equal(attributes.name)
61 expect(video.category.id).to.equal(attributes.category)
62 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
63 expect(video.licence.id).to.equal(attributes.licence)
64 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
65 expect(video.language.id).to.equal(attributes.language)
66 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
67 expect(video.privacy.id).to.deep.equal(attributes.privacy)
68 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
69 expect(video.nsfw).to.equal(attributes.nsfw)
70 expect(video.description).to.equal(attributes.description)
71 expect(video.account.id).to.be.a('number')
72 expect(video.account.host).to.equal(attributes.account.host)
73 expect(video.account.name).to.equal(attributes.account.name)
74 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
75 expect(video.channel.name).to.equal(attributes.channel.name)
76 expect(video.likes).to.equal(attributes.likes)
77 expect(video.dislikes).to.equal(attributes.dislikes)
78 expect(video.isLocal).to.equal(attributes.isLocal)
79 expect(video.duration).to.equal(attributes.duration)
80 expect(video.url).to.contain(originHost)
81 expect(dateIsValid(video.createdAt)).to.be.true
82 expect(dateIsValid(video.publishedAt)).to.be.true
83 expect(dateIsValid(video.updatedAt)).to.be.true
84
85 if (attributes.publishedAt) {
86 expect(video.publishedAt).to.equal(attributes.publishedAt)
87 }
88
89 if (attributes.originallyPublishedAt) {
90 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
91 } else {
92 expect(video.originallyPublishedAt).to.be.null
93 }
94
95 const videoDetails = await server.videos.get({ id: video.uuid })
96
97 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
98 expect(videoDetails.tags).to.deep.equal(attributes.tags)
99 expect(videoDetails.account.name).to.equal(attributes.account.name)
100 expect(videoDetails.account.host).to.equal(attributes.account.host)
101 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
102 expect(video.channel.name).to.equal(attributes.channel.name)
103 expect(videoDetails.channel.host).to.equal(attributes.account.host)
104 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
105 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
106 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
107 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
108 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
109
110 for (const attributeFile of attributes.files) {
111 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
112 expect(file).not.to.be.undefined
113
114 let extension = getLowercaseExtension(attributes.fixture)
115 // Transcoding enabled: extension will always be .mp4
116 if (attributes.files.length > 1) extension = '.mp4'
117
118 expect(file.magnetUri).to.have.lengthOf.above(2)
119
120 expect(file.torrentDownloadUrl).to.match(new RegExp(`http://${host}/download/torrents/${uuidRegex}-${file.resolution.id}.torrent`))
121 expect(file.torrentUrl).to.match(new RegExp(`http://${host}/lazy-static/torrents/${uuidRegex}-${file.resolution.id}.torrent`))
122
123 expect(file.fileUrl).to.match(new RegExp(`http://${originHost}/static/webseed/${uuidRegex}-${file.resolution.id}${extension}`))
124 expect(file.fileDownloadUrl).to.match(new RegExp(`http://${originHost}/download/videos/${uuidRegex}-${file.resolution.id}${extension}`))
125
126 await Promise.all([
127 makeRawRequest(file.torrentUrl, 200),
128 makeRawRequest(file.torrentDownloadUrl, 200),
129 makeRawRequest(file.metadataUrl, 200)
130 ])
131
132 expect(file.resolution.id).to.equal(attributeFile.resolution)
133 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
134
135 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
136 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
137 expect(
138 file.size,
139 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
140 ).to.be.above(minSize).and.below(maxSize)
141
142 const torrent = await webtorrentAdd(file.magnetUri, true)
143 expect(torrent.files).to.be.an('array')
144 expect(torrent.files.length).to.equal(1)
145 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
146 }
147
148 expect(videoDetails.thumbnailPath).to.exist
149 await testImage(server.url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
150
151 if (attributes.previewfile) {
152 expect(videoDetails.previewPath).to.exist
153 await testImage(server.url, attributes.previewfile, videoDetails.previewPath)
154 }
155}
156
157async function checkVideoFilesWereRemoved (options: {
158 server: PeerTubeServer
159 video: VideoDetails
160 captions?: VideoCaption[]
161 onlyVideoFiles?: boolean // default false
162}) {
163 const { video, server, captions = [], onlyVideoFiles = false } = options
164
165 const webtorrentFiles = video.files || []
166 const hlsFiles = video.streamingPlaylists[0]?.files || []
167
168 const thumbnailName = basename(video.thumbnailPath)
169 const previewName = basename(video.previewPath)
170
171 const torrentNames = webtorrentFiles.concat(hlsFiles).map(f => basename(f.torrentUrl))
172
173 const captionNames = captions.map(c => basename(c.captionPath))
174
175 const webtorrentFilenames = webtorrentFiles.map(f => basename(f.fileUrl))
176 const hlsFilenames = hlsFiles.map(f => basename(f.fileUrl))
177
178 let directories: { [ directory: string ]: string[] } = {
179 videos: webtorrentFilenames,
180 redundancy: webtorrentFilenames,
181 [join('playlists', 'hls')]: hlsFilenames,
182 [join('redundancy', 'hls')]: hlsFilenames
183 }
184
185 if (onlyVideoFiles !== true) {
186 directories = {
187 ...directories,
188
189 thumbnails: [ thumbnailName ],
190 previews: [ previewName ],
191 torrents: torrentNames,
192 captions: captionNames
193 }
194 }
195
196 for (const directory of Object.keys(directories)) {
197 const directoryPath = server.servers.buildDirectory(directory)
198
199 const directoryExists = await pathExists(directoryPath)
200 if (directoryExists === false) continue
201
202 const existingFiles = await readdir(directoryPath)
203 for (const existingFile of existingFiles) {
204 for (const shouldNotExist of directories[directory]) {
205 expect(existingFile, `File ${existingFile} should not exist in ${directoryPath}`).to.not.contain(shouldNotExist)
206 }
207 }
208 }
209}
210
211async function saveVideoInServers (servers: PeerTubeServer[], uuid: string) {
212 for (const server of servers) {
213 server.store.videoDetails = await server.videos.get({ id: uuid })
214 }
215}
216
217function checkUploadVideoParam (
218 server: PeerTubeServer,
219 token: string,
220 attributes: Partial<VideoEdit>,
221 expectedStatus = HttpStatusCode.OK_200,
222 mode: 'legacy' | 'resumable' = 'legacy'
223) {
224 return mode === 'legacy'
225 ? server.videos.buildLegacyUpload({ token, attributes, expectedStatus })
226 : server.videos.buildResumeUpload({ token, attributes, expectedStatus })
227}
228
229// serverNumber starts from 1
230async function uploadRandomVideoOnServers (
231 servers: PeerTubeServer[],
232 serverNumber: number,
233 additionalParams?: VideoEdit & { prefixName?: string }
234) {
235 const server = servers.find(s => s.serverNumber === serverNumber)
236 const res = await server.videos.randomUpload({ wait: false, additionalParams })
237
238 await waitJobs(servers)
239
240 return res
241}
242
243// ---------------------------------------------------------------------------
244
245export {
246 completeVideoCheck,
247 checkUploadVideoParam,
248 uploadRandomVideoOnServers,
249 checkVideoFilesWereRemoved,
250 saveVideoInServers
251}
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index 52e6ea593..a844b9dcf 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -2,17 +2,19 @@ import { Command } from 'commander'
2import { Netrc } from 'netrc-parser' 2import { Netrc } from 'netrc-parser'
3import { join } from 'path' 3import { join } from 'path'
4import { createLogger, format, transports } from 'winston' 4import { createLogger, format, transports } from 'winston'
5import { PeerTubeServer } from '@shared/extra-utils' 5import { loadLanguages } from '@server/initializers/constants'
6import { root } from '@shared/core-utils'
6import { UserRole } from '@shared/models' 7import { UserRole } from '@shared/models'
8import { PeerTubeServer } from '@shared/server-commands'
7import { VideoPrivacy } from '../../shared/models/videos' 9import { VideoPrivacy } from '../../shared/models/videos'
8import { getAppNumber, isTestInstance, root } from '../helpers/core-utils' 10import { getAppNumber, isTestInstance } from '../helpers/core-utils'
9 11
10let configName = 'PeerTube/CLI' 12let configName = 'PeerTube/CLI'
11if (isTestInstance()) configName += `-${getAppNumber()}` 13if (isTestInstance()) configName += `-${getAppNumber()}`
12 14
13const config = require('application-config')(configName) 15const config = require('application-config')(configName)
14 16
15const version = require('../../../package.json').version 17const version = require(join(root(), 'package.json')).version
16 18
17async function getAdminTokenOrDie (server: PeerTubeServer, username: string, password: string) { 19async function getAdminTokenOrDie (server: PeerTubeServer, username: string, password: string) {
18 const token = await server.login.getAccessToken(username, password) 20 const token = await server.login.getAccessToken(username, password)
@@ -180,6 +182,7 @@ function getServerCredentials (program: Command) {
180} 182}
181 183
182function buildServer (url: string) { 184function buildServer (url: string) {
185 loadLanguages()
183 return new PeerTubeServer({ url }) 186 return new PeerTubeServer({ url })
184} 187}
185 188
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index afa19ee08..f8ac8b2ab 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -1,12 +1,7 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion 1import CliTable3 from 'cli-table3'
2
3import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths()
5
6import { OptionValues, program } from 'commander' 2import { OptionValues, program } from 'commander'
7import { assignToken, buildServer, getNetrc, getSettings, writeSettings } from './cli'
8import { isUserUsernameValid } from '../helpers/custom-validators/users' 3import { isUserUsernameValid } from '../helpers/custom-validators/users'
9import CliTable3 from 'cli-table3' 4import { assignToken, buildServer, getNetrc, getSettings, writeSettings } from './cli'
10 5
11import prompt = require('prompt') 6import prompt = require('prompt')
12 7
diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts
index a67de9180..d59a3632e 100644
--- a/server/tools/peertube-get-access-token.ts
+++ b/server/tools/peertube-get-access-token.ts
@@ -1,6 +1,3 @@
1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import { program } from 'commander' 1import { program } from 'commander'
5import { assignToken, buildServer } from './cli' 2import { assignToken, buildServer } from './cli'
6 3
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index a758beef9..661a4cf35 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -1,11 +1,10 @@
1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import { program } from 'commander' 1import { program } from 'commander'
5import { accessSync, constants } from 'fs' 2import { accessSync, constants } from 'fs'
6import { remove } from 'fs-extra' 3import { remove } from 'fs-extra'
7import { join } from 'path' 4import { join } from 'path'
8import { sha256 } from '../helpers/core-utils' 5import { YoutubeDLCLI, YoutubeDLInfo, YoutubeDLInfoBuilder } from '@server/helpers/youtube-dl'
6import { wait } from '@shared/core-utils'
7import { sha256 } from '@shared/extra-utils'
9import { doRequestAndSaveToFile } from '../helpers/requests' 8import { doRequestAndSaveToFile } from '../helpers/requests'
10import { 9import {
11 assignToken, 10 assignToken,
@@ -15,8 +14,7 @@ import {
15 getLogger, 14 getLogger,
16 getServerCredentials 15 getServerCredentials
17} from './cli' 16} from './cli'
18import { wait } from '@shared/extra-utils' 17
19import { YoutubeDLCLI, YoutubeDLInfo, YoutubeDLInfoBuilder } from '@server/helpers/youtube-dl'
20import prompt = require('prompt') 18import prompt = require('prompt')
21 19
22const processOptions = { 20const processOptions = {
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index ae625114d..47090b3a5 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -1,13 +1,8 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
3import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths()
5
6import { program, Command, OptionValues } from 'commander'
7import { assignToken, buildServer, getServerCredentials } from './cli'
8import { PluginType } from '../../shared/models'
9import { isAbsolute } from 'path'
10import CliTable3 from 'cli-table3' 1import CliTable3 from 'cli-table3'
2import { Command, OptionValues, program } from 'commander'
3import { isAbsolute } from 'path'
4import { PluginType } from '../../shared/models'
5import { assignToken, buildServer, getServerCredentials } from './cli'
11 6
12program 7program
13 .name('plugins') 8 .name('plugins')
@@ -31,6 +26,7 @@ program
31 .option('-p, --password <token>', 'Password') 26 .option('-p, --password <token>', 'Password')
32 .option('-P --path <path>', 'Install from a path') 27 .option('-P --path <path>', 'Install from a path')
33 .option('-n, --npm-name <npmName>', 'Install from npm') 28 .option('-n, --npm-name <npmName>', 'Install from npm')
29 .option('--plugin-version <pluginVersion>', 'Specify the plugin version to install (only available when installing from npm)')
34 .action((options, command) => installPluginCLI(command, options)) 30 .action((options, command) => installPluginCLI(command, options))
35 31
36program 32program
@@ -109,7 +105,7 @@ async function installPluginCLI (command: Command, options: OptionValues) {
109 await assignToken(server, username, password) 105 await assignToken(server, username, password)
110 106
111 try { 107 try {
112 await server.plugins.install({ npmName: options.npmName, path: options.path }) 108 await server.plugins.install({ npmName: options.npmName, path: options.path, pluginVersion: options.pluginVersion })
113 } catch (err) { 109 } catch (err) {
114 console.error('Cannot install plugin.', err) 110 console.error('Cannot install plugin.', err)
115 process.exit(-1) 111 process.exit(-1)
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts
index 7e27ca49e..2c62a3c19 100644
--- a/server/tools/peertube-redundancy.ts
+++ b/server/tools/peertube-redundancy.ts
@@ -1,6 +1,3 @@
1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import CliTable3 from 'cli-table3' 1import CliTable3 from 'cli-table3'
5import { Command, program } from 'commander' 2import { Command, program } from 'commander'
6import { uniq } from 'lodash' 3import { uniq } from 'lodash'
@@ -12,7 +9,7 @@ import { assignToken, buildServer, getServerCredentials } from './cli'
12import bytes = require('bytes') 9import bytes = require('bytes')
13 10
14program 11program
15 .name('plugins') 12 .name('redundancy')
16 .usage('[command] [options]') 13 .usage('[command] [options]')
17 14
18program 15program
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index 01fb1fe8d..08bd5f2bb 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -1,6 +1,3 @@
1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import { program } from 'commander' 1import { program } from 'commander'
5import { access, constants } from 'fs-extra' 2import { access, constants } from 'fs-extra'
6import { isAbsolute } from 'path' 3import { isAbsolute } from 'path'
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index 9e07640f0..1d3158044 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -1,8 +1,5 @@
1#!/usr/bin/env node 1#!/usr/bin/env node
2 2
3import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths()
5
6import { CommandOptions, program } from 'commander' 3import { CommandOptions, program } from 'commander'
7import { getSettings, version } from './cli' 4import { getSettings, version } from './cli'
8 5
diff --git a/server/tools/tsconfig.json b/server/tools/tsconfig.json
index 156a8ed22..8264f5b35 100644
--- a/server/tools/tsconfig.json
+++ b/server/tools/tsconfig.json
@@ -1,5 +1,17 @@
1{ 1{
2 "extends": "../../tsconfig.json", 2 "extends": "../../tsconfig.json",
3 "compilerOptions": {
4 "baseUrl": "./",
5 "outDir": "../../dist/server/tools",
6 "paths": { // FIXME: https://github.com/benyap/resolve-tspaths/issues/10
7 "@server/*": [ "../../server/*" ],
8 "@shared/*": [ "../../shared/*" ]
9 }
10 },
3 "include": [ ".", "../typings" ], 11 "include": [ ".", "../typings" ],
12 "references": [
13 { "path": "../" }
14 ],
15 "files": [],
4 "exclude": [ ] // Overwrite exclude property 16 "exclude": [ ] // Overwrite exclude property
5} 17}
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 000000000..4be7ae2f4
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,12 @@
1{
2 "extends": "../tsconfig.base.json",
3 "compilerOptions": {
4 "outDir": "../dist/server"
5 },
6 "references": [
7 { "path": "../shared" }
8 ],
9 "exclude": [
10 "tools/"
11 ]
12}
diff --git a/server/tsconfig.types.json b/server/tsconfig.types.json
new file mode 100644
index 000000000..da6b572ea
--- /dev/null
+++ b/server/tsconfig.types.json
@@ -0,0 +1,16 @@
1{
2 "extends": "./tsconfig.json",
3 "compilerOptions": {
4 "outDir": "../packages/types/dist/server",
5 "stripInternal": true,
6 "removeComments": false,
7 "emitDeclarationOnly": true
8 },
9 "references": [
10 { "path": "../shared/tsconfig.types.json" }
11 ],
12 "exclude": [
13 "tools/",
14 "tests/"
15 ]
16}
diff --git a/server/types/express.ts b/server/types/express-handler.ts
index e72be36e4..e72be36e4 100644
--- a/server/types/express.ts
+++ b/server/types/express-handler.ts
diff --git a/server/typings/express/index.d.ts b/server/types/express.d.ts
index 1a99b598a..1a99b598a 100644
--- a/server/typings/express/index.d.ts
+++ b/server/types/express.d.ts
diff --git a/server/types/models/abuse/abuse-message.ts b/server/types/models/abuse/abuse-message.ts
index 565eca706..2d7d09316 100644
--- a/server/types/models/abuse/abuse-message.ts
+++ b/server/types/models/abuse/abuse-message.ts
@@ -1,5 +1,5 @@
1import { AbuseMessageModel } from '@server/models/abuse/abuse-message' 1import { AbuseMessageModel } from '@server/models/abuse/abuse-message'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { AbuseModel } from '../../../models/abuse/abuse' 3import { AbuseModel } from '../../../models/abuse/abuse'
4import { MAccountFormattable } from '../account' 4import { MAccountFormattable } from '../account'
5 5
diff --git a/server/types/models/abuse/abuse.ts b/server/types/models/abuse/abuse.ts
index 6fd83684c..1b45b3879 100644
--- a/server/types/models/abuse/abuse.ts
+++ b/server/types/models/abuse/abuse.ts
@@ -1,7 +1,7 @@
1import { VideoAbuseModel } from '@server/models/abuse/video-abuse' 1import { VideoAbuseModel } from '@server/models/abuse/video-abuse'
2import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse' 2import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse'
3import { VideoCommentModel } from '@server/models/video/video-comment' 3import { VideoCommentModel } from '@server/models/video/video-comment'
4import { PickWith } from '@shared/core-utils' 4import { PickWith } from '@shared/typescript-utils'
5import { AbuseModel } from '../../../models/abuse/abuse' 5import { AbuseModel } from '../../../models/abuse/abuse'
6import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account' 6import { MAccountDefault, MAccountFormattable, MAccountLight, MAccountUrl } from '../account'
7import { MComment, MCommentOwner, MCommentUrl, MCommentVideo, MVideoUrl } from '../video' 7import { MComment, MCommentOwner, MCommentUrl, MCommentVideo, MVideoUrl } from '../video'
diff --git a/server/types/models/account/account-blocklist.ts b/server/types/models/account/account-blocklist.ts
index 3126fd0ab..9dae10915 100644
--- a/server/types/models/account/account-blocklist.ts
+++ b/server/types/models/account/account-blocklist.ts
@@ -1,5 +1,5 @@
1import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 1import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MAccountDefault, MAccountFormattable } from './account' 3import { MAccountDefault, MAccountFormattable } from './account'
4 4
5type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M> 5type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M>
diff --git a/server/types/models/account/account.ts b/server/types/models/account/account.ts
index 71f6c79aa..282a2971b 100644
--- a/server/types/models/account/account.ts
+++ b/server/types/models/account/account.ts
@@ -1,4 +1,4 @@
1import { FunctionProperties, PickWith } from '@shared/core-utils' 1import { FunctionProperties, PickWith } from '@shared/typescript-utils'
2import { AccountModel } from '../../../models/account/account' 2import { AccountModel } from '../../../models/account/account'
3import { 3import {
4 MActor, 4 MActor,
diff --git a/server/types/models/account/actor-custom-page.ts b/server/types/models/account/actor-custom-page.ts
index 2cb8aa7e4..fcd8069be 100644
--- a/server/types/models/account/actor-custom-page.ts
+++ b/server/types/models/account/actor-custom-page.ts
@@ -1,4 +1,3 @@
1
2import { ActorCustomPageModel } from '../../../models/account/actor-custom-page' 1import { ActorCustomPageModel } from '../../../models/account/actor-custom-page'
3 2
4export type MActorCustomPage = Omit<ActorCustomPageModel, 'Actor'> 3export type MActorCustomPage = Omit<ActorCustomPageModel, 'Actor'>
diff --git a/server/types/models/actor/actor-follow.ts b/server/types/models/actor/actor-follow.ts
index 98a6ca8a5..338158561 100644
--- a/server/types/models/actor/actor-follow.ts
+++ b/server/types/models/actor/actor-follow.ts
@@ -1,4 +1,4 @@
1import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/typescript-utils'
2import { ActorFollowModel } from '../../../models/actor/actor-follow' 2import { ActorFollowModel } from '../../../models/actor/actor-follow'
3import { 3import {
4 MActor, 4 MActor,
diff --git a/server/types/models/actor/actor-image.ts b/server/types/models/actor/actor-image.ts
index 89adb01ae..521b4cc59 100644
--- a/server/types/models/actor/actor-image.ts
+++ b/server/types/models/actor/actor-image.ts
@@ -1,4 +1,4 @@
1import { FunctionProperties } from '@shared/core-utils' 1import { FunctionProperties } from '@shared/typescript-utils'
2import { ActorImageModel } from '../../../models/actor/actor-image' 2import { ActorImageModel } from '../../../models/actor/actor-image'
3 3
4export type MActorImage = ActorImageModel 4export type MActorImage = ActorImageModel
diff --git a/server/types/models/actor/actor.ts b/server/types/models/actor/actor.ts
index b3a70cbce..9ce97094f 100644
--- a/server/types/models/actor/actor.ts
+++ b/server/types/models/actor/actor.ts
@@ -1,4 +1,4 @@
1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' 1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/typescript-utils'
2import { ActorModel } from '../../../models/actor/actor' 2import { ActorModel } from '../../../models/actor/actor'
3import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from '../account' 3import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from '../account'
4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' 4import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server'
diff --git a/server/types/models/oauth/oauth-token.ts b/server/types/models/oauth/oauth-token.ts
index 8399af8f1..6af087e3c 100644
--- a/server/types/models/oauth/oauth-token.ts
+++ b/server/types/models/oauth/oauth-token.ts
@@ -1,5 +1,5 @@
1import { OAuthTokenModel } from '@server/models/oauth/oauth-token' 1import { OAuthTokenModel } from '@server/models/oauth/oauth-token'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MUserAccountUrl } from '../user/user' 3import { MUserAccountUrl } from '../user/user'
4 4
5type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M> 5type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M>
diff --git a/server/types/models/server/server-blocklist.ts b/server/types/models/server/server-blocklist.ts
index 801f179fd..71a4ea963 100644
--- a/server/types/models/server/server-blocklist.ts
+++ b/server/types/models/server/server-blocklist.ts
@@ -1,5 +1,5 @@
1import { ServerBlocklistModel } from '@server/models/server/server-blocklist' 1import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MAccountDefault, MAccountFormattable } from '../account/account' 3import { MAccountDefault, MAccountFormattable } from '../account/account'
4import { MServer, MServerFormattable } from './server' 4import { MServer, MServerFormattable } from './server'
5 5
diff --git a/server/types/models/server/server.ts b/server/types/models/server/server.ts
index 876186fc0..0b16186cd 100644
--- a/server/types/models/server/server.ts
+++ b/server/types/models/server/server.ts
@@ -1,5 +1,5 @@
1import { FunctionProperties, PickWith } from '@shared/typescript-utils'
1import { ServerModel } from '../../../models/server/server' 2import { ServerModel } from '../../../models/server/server'
2import { FunctionProperties, PickWith } from '@shared/core-utils'
3import { MAccountBlocklistId } from '../account' 3import { MAccountBlocklistId } from '../account'
4 4
5type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M> 5type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M>
diff --git a/server/types/models/user/user-notification.ts b/server/types/models/user/user-notification.ts
index 918614dd1..db9ec0400 100644
--- a/server/types/models/user/user-notification.ts
+++ b/server/types/models/user/user-notification.ts
@@ -3,7 +3,7 @@ import { VideoCommentAbuseModel } from '@server/models/abuse/video-comment-abuse
3import { ApplicationModel } from '@server/models/application/application' 3import { ApplicationModel } from '@server/models/application/application'
4import { PluginModel } from '@server/models/server/plugin' 4import { PluginModel } from '@server/models/server/plugin'
5import { UserNotificationModel } from '@server/models/user/user-notification' 5import { UserNotificationModel } from '@server/models/user/user-notification'
6import { PickWith, PickWithOpt } from '@shared/core-utils' 6import { PickWith, PickWithOpt } from '@shared/typescript-utils'
7import { AbuseModel } from '../../../models/abuse/abuse' 7import { AbuseModel } from '../../../models/abuse/abuse'
8import { AccountModel } from '../../../models/account/account' 8import { AccountModel } from '../../../models/account/account'
9import { ActorModel } from '../../../models/actor/actor' 9import { ActorModel } from '../../../models/actor/actor'
diff --git a/server/types/models/user/user.ts b/server/types/models/user/user.ts
index f79220e11..89092c242 100644
--- a/server/types/models/user/user.ts
+++ b/server/types/models/user/user.ts
@@ -1,7 +1,7 @@
1import { AccountModel } from '@server/models/account/account' 1import { AccountModel } from '@server/models/account/account'
2import { UserModel } from '@server/models/user/user' 2import { UserModel } from '@server/models/user/user'
3import { MVideoPlaylist } from '@server/types/models' 3import { MVideoPlaylist } from '@server/types/models'
4import { PickWith, PickWithOpt } from '@shared/core-utils' 4import { PickWith, PickWithOpt } from '@shared/typescript-utils'
5import { 5import {
6 MAccount, 6 MAccount,
7 MAccountDefault, 7 MAccountDefault,
diff --git a/server/types/models/video/thumbnail.ts b/server/types/models/video/thumbnail.ts
index 81a29e062..c3b27681f 100644
--- a/server/types/models/video/thumbnail.ts
+++ b/server/types/models/video/thumbnail.ts
@@ -1,4 +1,4 @@
1import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/typescript-utils'
2import { ThumbnailModel } from '../../../models/video/thumbnail' 2import { ThumbnailModel } from '../../../models/video/thumbnail'
3import { MVideo } from './video' 3import { MVideo } from './video'
4 4
diff --git a/server/types/models/video/video-blacklist.ts b/server/types/models/video/video-blacklist.ts
index 2ac912405..048b63bd2 100644
--- a/server/types/models/video/video-blacklist.ts
+++ b/server/types/models/video/video-blacklist.ts
@@ -1,5 +1,5 @@
1import { PickWith } from '@shared/typescript-utils'
1import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 2import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
2import { PickWith } from '@shared/core-utils'
3import { MVideo, MVideoFormattable } from './video' 3import { MVideo, MVideoFormattable } from './video'
4 4
5type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M> 5type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M>
diff --git a/server/types/models/video/video-caption.ts b/server/types/models/video/video-caption.ts
index 1f761a866..8cd801064 100644
--- a/server/types/models/video/video-caption.ts
+++ b/server/types/models/video/video-caption.ts
@@ -1,4 +1,4 @@
1import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/typescript-utils'
2import { VideoCaptionModel } from '../../../models/video/video-caption' 2import { VideoCaptionModel } from '../../../models/video/video-caption'
3import { MVideo, MVideoUUID } from './video' 3import { MVideo, MVideoUUID } from './video'
4 4
diff --git a/server/types/models/video/video-change-ownership.ts b/server/types/models/video/video-change-ownership.ts
index 6cad48e4a..d99f25071 100644
--- a/server/types/models/video/video-change-ownership.ts
+++ b/server/types/models/video/video-change-ownership.ts
@@ -1,7 +1,7 @@
1import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' 1import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MAccountDefault, MAccountFormattable } from '../account/account' 3import { MAccountDefault, MAccountFormattable } from '../account/account'
4import { MVideoWithAllFiles, MVideoFormattable } from './video' 4import { MVideoFormattable, MVideoWithAllFiles } from './video'
5 5
6type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M> 6type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M>
7 7
diff --git a/server/types/models/video/video-channels.ts b/server/types/models/video/video-channels.ts
index c147567d9..af8c2ffe4 100644
--- a/server/types/models/video/video-channels.ts
+++ b/server/types/models/video/video-channels.ts
@@ -1,4 +1,4 @@
1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/core-utils' 1import { FunctionProperties, PickWith, PickWithOpt } from '@shared/typescript-utils'
2import { VideoChannelModel } from '../../../models/video/video-channel' 2import { VideoChannelModel } from '../../../models/video/video-channel'
3import { 3import {
4 MAccountActor, 4 MAccountActor,
diff --git a/server/types/models/video/video-comment.ts b/server/types/models/video/video-comment.ts
index 83479e7b2..b66de064f 100644
--- a/server/types/models/video/video-comment.ts
+++ b/server/types/models/video/video-comment.ts
@@ -1,4 +1,4 @@
1import { PickWith, PickWithOpt } from '@shared/core-utils' 1import { PickWith, PickWithOpt } from '@shared/typescript-utils'
2import { VideoCommentModel } from '../../../models/video/video-comment' 2import { VideoCommentModel } from '../../../models/video/video-comment'
3import { MAccountDefault, MAccountFormattable, MAccountUrl } from '../account' 3import { MAccountDefault, MAccountFormattable, MAccountUrl } from '../account'
4import { MVideo, MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video' 4import { MVideo, MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video'
diff --git a/server/types/models/video/video-file.ts b/server/types/models/video/video-file.ts
index 327a148ce..55603e59c 100644
--- a/server/types/models/video/video-file.ts
+++ b/server/types/models/video/video-file.ts
@@ -1,8 +1,8 @@
1import { PickWith, PickWithOpt } from '@shared/typescript-utils'
1import { VideoFileModel } from '../../../models/video/video-file' 2import { VideoFileModel } from '../../../models/video/video-file'
2import { PickWith, PickWithOpt } from '@shared/core-utils'
3import { MVideo, MVideoUUID } from './video' 3import { MVideo, MVideoUUID } from './video'
4import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy' 4import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy'
5import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist' 5import { MStreamingPlaylist, MStreamingPlaylistVideo } from './video-streaming-playlist'
6 6
7type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> 7type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
8 8
diff --git a/server/types/models/video/video-import.ts b/server/types/models/video/video-import.ts
index 759b13c6e..650c293f7 100644
--- a/server/types/models/video/video-import.ts
+++ b/server/types/models/video/video-import.ts
@@ -1,7 +1,7 @@
1import { VideoImportModel } from '@server/models/video/video-import' 1import { VideoImportModel } from '@server/models/video/video-import'
2import { PickWith, PickWithOpt } from '@shared/core-utils' 2import { PickWith, PickWithOpt } from '@shared/typescript-utils'
3import { MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from './video'
4import { MUser } from '../user/user' 3import { MUser } from '../user/user'
4import { MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from './video'
5 5
6type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M> 6type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M>
7 7
diff --git a/server/types/models/video/video-live.ts b/server/types/models/video/video-live.ts
index 346052417..903cea982 100644
--- a/server/types/models/video/video-live.ts
+++ b/server/types/models/video/video-live.ts
@@ -1,5 +1,5 @@
1import { VideoLiveModel } from '@server/models/video/video-live' 1import { VideoLiveModel } from '@server/models/video/video-live'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MVideo } from './video' 3import { MVideo } from './video'
4 4
5type Use<K extends keyof VideoLiveModel, M> = PickWith<VideoLiveModel, K, M> 5type Use<K extends keyof VideoLiveModel, M> = PickWith<VideoLiveModel, K, M>
diff --git a/server/types/models/video/video-playlist-element.ts b/server/types/models/video/video-playlist-element.ts
index f46ff4d49..eae676096 100644
--- a/server/types/models/video/video-playlist-element.ts
+++ b/server/types/models/video/video-playlist-element.ts
@@ -1,5 +1,5 @@
1import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element' 1import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MVideoFormattable, MVideoThumbnail, MVideoUrl } from './video' 3import { MVideoFormattable, MVideoThumbnail, MVideoUrl } from './video'
4import { MVideoPlaylistPrivacy } from './video-playlist' 4import { MVideoPlaylistPrivacy } from './video-playlist'
5 5
diff --git a/server/types/models/video/video-playlist.ts b/server/types/models/video/video-playlist.ts
index 2f9537cf5..33fe5416a 100644
--- a/server/types/models/video/video-playlist.ts
+++ b/server/types/models/video/video-playlist.ts
@@ -1,9 +1,9 @@
1import { MVideoPlaylistElementLight } from '@server/types/models/video/video-playlist-element'
2import { PickWith } from '@shared/typescript-utils'
1import { VideoPlaylistModel } from '../../../models/video/video-playlist' 3import { VideoPlaylistModel } from '../../../models/video/video-playlist'
2import { PickWith } from '@shared/core-utils'
3import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account' 4import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account'
4import { MThumbnail } from './thumbnail' 5import { MThumbnail } from './thumbnail'
5import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels' 6import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels'
6import { MVideoPlaylistElementLight } from '@server/types/models/video/video-playlist-element'
7 7
8type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M> 8type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M>
9 9
diff --git a/server/types/models/video/video-rate.ts b/server/types/models/video/video-rate.ts
index 7bd54f7b0..0dbdf3c41 100644
--- a/server/types/models/video/video-rate.ts
+++ b/server/types/models/video/video-rate.ts
@@ -1,5 +1,5 @@
1import { AccountVideoRateModel } from '@server/models/account/account-video-rate' 1import { AccountVideoRateModel } from '@server/models/account/account-video-rate'
2import { PickWith } from '@shared/core-utils' 2import { PickWith } from '@shared/typescript-utils'
3import { MAccountAudience, MAccountUrl } from '../account/account' 3import { MAccountAudience, MAccountUrl } from '../account/account'
4import { MVideo, MVideoFormattable } from './video' 4import { MVideo, MVideoFormattable } from './video'
5 5
diff --git a/server/types/models/video/video-redundancy.ts b/server/types/models/video/video-redundancy.ts
index 411375c81..e2a9beb93 100644
--- a/server/types/models/video/video-redundancy.ts
+++ b/server/types/models/video/video-redundancy.ts
@@ -1,10 +1,10 @@
1import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
2import { PickWith, PickWithOpt } from '@shared/core-utils'
3import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
4import { VideoFileModel } from '@server/models/video/video-file' 1import { VideoFileModel } from '@server/models/video/video-file'
2import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
3import { PickWith, PickWithOpt } from '@shared/typescript-utils'
4import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
5import { MVideoUrl } from './video'
5import { MVideoFile, MVideoFileVideo } from './video-file' 6import { MVideoFile, MVideoFileVideo } from './video-file'
6import { MStreamingPlaylistVideo } from './video-streaming-playlist' 7import { MStreamingPlaylistVideo } from './video-streaming-playlist'
7import { MVideoUrl } from './video'
8 8
9type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M> 9type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M>
10 10
diff --git a/server/types/models/video/video-share.ts b/server/types/models/video/video-share.ts
index 78f44e58c..ffc0edad6 100644
--- a/server/types/models/video/video-share.ts
+++ b/server/types/models/video/video-share.ts
@@ -1,4 +1,4 @@
1import { PickWith } from '@shared/core-utils' 1import { PickWith } from '@shared/typescript-utils'
2import { VideoShareModel } from '../../../models/video/video-share' 2import { VideoShareModel } from '../../../models/video/video-share'
3import { MActorDefault } from '../actor' 3import { MActorDefault } from '../actor'
4import { MVideo } from './video' 4import { MVideo } from './video'
diff --git a/server/types/models/video/video-streaming-playlist.ts b/server/types/models/video/video-streaming-playlist.ts
index 1e4dccb8e..1c2f83489 100644
--- a/server/types/models/video/video-streaming-playlist.ts
+++ b/server/types/models/video/video-streaming-playlist.ts
@@ -1,8 +1,8 @@
1import { PickWith, PickWithOpt } from '@shared/typescript-utils'
1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' 2import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist'
2import { PickWith, PickWithOpt } from '@shared/core-utils'
3import { MVideoRedundancyFileUrl, MVideoRedundancy } from './video-redundancy'
4import { MVideo } from './video' 3import { MVideo } from './video'
5import { MVideoFile } from './video-file' 4import { MVideoFile } from './video-file'
5import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy'
6 6
7type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M> 7type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M>
8 8
diff --git a/server/types/models/video/video.ts b/server/types/models/video/video.ts
index 9a6b27888..d1af53b92 100644
--- a/server/types/models/video/video.ts
+++ b/server/types/models/video/video.ts
@@ -1,4 +1,4 @@
1import { PickWith, PickWithOpt } from '@shared/core-utils' 1import { PickWith, PickWithOpt } from '@shared/typescript-utils'
2import { VideoModel } from '../../../models/video/video' 2import { VideoModel } from '../../../models/video/video'
3import { MTrackerUrl } from '../server/tracker' 3import { MTrackerUrl } from '../server/tracker'
4import { MUserVideoHistoryTime } from '../user/user-video-history' 4import { MUserVideoHistoryTime } from '../user/user-video-history'
diff --git a/server/types/plugins/register-server-option.model.ts b/server/types/plugins/register-server-option.model.ts
index 8774bcd8c..9f472d900 100644
--- a/server/types/plugins/register-server-option.model.ts
+++ b/server/types/plugins/register-server-option.model.ts
@@ -13,6 +13,7 @@ import {
13 RegisterServerHookOptions, 13 RegisterServerHookOptions,
14 RegisterServerSettingOptions, 14 RegisterServerSettingOptions,
15 ServerConfig, 15 ServerConfig,
16 ThumbnailType,
16 UserRole, 17 UserRole,
17 VideoBlacklistCreate 18 VideoBlacklistCreate
18} from '@shared/models' 19} from '@shared/models'
@@ -35,6 +36,35 @@ export type PeerTubeHelpers = {
35 loadByIdOrUUID: (id: number | string) => Promise<MVideoThumbnail> 36 loadByIdOrUUID: (id: number | string) => Promise<MVideoThumbnail>
36 37
37 removeVideo: (videoId: number) => Promise<void> 38 removeVideo: (videoId: number) => Promise<void>
39
40 ffprobe: (path: string) => Promise<any>
41
42 getFiles: (id: number | string) => Promise<{
43 webtorrent: {
44 videoFiles: {
45 path: string // Could be null if using remote storage
46 url: string
47 resolution: number
48 size: number
49 fps: number
50 }[]
51 }
52
53 hls: {
54 videoFiles: {
55 path: string // Could be null if using remote storage
56 url: string
57 resolution: number
58 size: number
59 fps: number
60 }[]
61 }
62
63 thumbnails: {
64 type: ThumbnailType
65 path: string
66 }[]
67 }>
38 } 68 }
39 69
40 config: { 70 config: {
diff --git a/server/types/sequelize.ts b/server/types/sequelize.ts
index 535113d01..e399c3d5d 100644
--- a/server/types/sequelize.ts
+++ b/server/types/sequelize.ts
@@ -1,4 +1,4 @@
1import { AttributesOnly } from '@shared/core-utils' 1import { AttributesOnly } from '@shared/typescript-utils'
2import { Model } from 'sequelize' 2import { Model } from 'sequelize'
3 3
4// Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript 4// Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript