aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.json88
-rw-r--r--.gitignore1
-rw-r--r--client/package.json7
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.ts49
-rw-r--r--client/src/app/+about/about-instance/about-instance.resolver.ts27
-rw-r--r--client/src/app/+about/about-routing.module.ts4
-rw-r--r--client/src/app/+about/about.module.ts2
-rw-r--r--client/src/app/+admin/admin.component.html2
-rw-r--r--client/src/app/+admin/admin.module.ts13
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html8
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss5
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.scss2
-rw-r--r--client/src/app/+admin/follows/follows.component.html6
-rw-r--r--client/src/app/+admin/follows/follows.routes.ts5
-rw-r--r--client/src/app/+admin/follows/index.ts1
-rw-r--r--client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts2
-rw-r--r--client/src/app/+admin/follows/shared/redundancy.service.ts28
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/index.ts1
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html82
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss37
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts178
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html24
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss8
-rw-r--r--client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts11
-rw-r--r--client/src/app/+admin/system/jobs/jobs.component.ts15
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts2
-rw-r--r--client/src/app/+signup/+register/register-step-channel.component.html2
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.html2
-rw-r--r--client/src/app/app.module.ts2
-rw-r--r--client/src/app/core/server/server.service.ts7
-rw-r--r--client/src/app/search/search-filters.component.html6
-rw-r--r--client/src/app/shared/buttons/button.component.scss23
-rw-r--r--client/src/app/shared/forms/form-validators/custom-config-validators.service.ts2
-rw-r--r--client/src/app/shared/images/global-icon.component.ts1
-rw-r--r--client/src/app/shared/instance/instance-statistics.component.ts23
-rw-r--r--client/src/app/shared/menu/top-menu-dropdown.component.ts3
-rw-r--r--client/src/app/shared/renderer/markdown.service.ts4
-rw-r--r--client/src/app/shared/shared.module.ts2
-rw-r--r--client/src/app/shared/video/infinite-scroller.directive.ts4
-rw-r--r--client/src/app/shared/video/redundancy.service.ts73
-rw-r--r--client/src/app/shared/video/video-actions-dropdown.component.ts28
-rw-r--r--client/src/app/shared/video/video-miniature.component.html2
-rw-r--r--client/src/app/shared/video/video-miniature.component.ts3
-rw-r--r--client/src/app/shared/video/video.model.ts19
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html5
-rw-r--r--client/src/assets/player/bezels/bezels-plugin.ts86
-rw-r--r--client/src/assets/player/bezels/pause-bezel.ts72
-rw-r--r--client/src/assets/player/p2p-media-loader/hls-plugin.ts626
-rw-r--r--client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts36
-rw-r--r--client/src/assets/player/peertube-player-manager.ts72
-rw-r--r--client/src/assets/player/peertube-plugin.ts22
-rw-r--r--client/src/assets/player/peertube-videojs-typings.ts82
-rw-r--r--client/src/assets/player/upnext/end-card.ts155
-rw-r--r--client/src/assets/player/upnext/upnext-plugin.ts155
-rw-r--r--client/src/assets/player/videojs-components/next-video-button.ts29
-rw-r--r--client/src/assets/player/videojs-components/p2p-info-button.ts48
-rw-r--r--client/src/assets/player/videojs-components/peertube-link-button.ts20
-rw-r--r--client/src/assets/player/videojs-components/peertube-load-progress-bar.ts17
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-button.ts32
-rw-r--r--client/src/assets/player/videojs-components/resolution-menu-item.ts36
-rw-r--r--client/src/assets/player/videojs-components/settings-dialog.ts37
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-button.ts191
-rw-r--r--client/src/assets/player/videojs-components/settings-menu-item.ts159
-rw-r--r--client/src/assets/player/videojs-components/settings-panel-child.ts22
-rw-r--r--client/src/assets/player/videojs-components/settings-panel.ts22
-rw-r--r--client/src/assets/player/videojs-components/theater-button.ts20
-rw-r--r--client/src/assets/player/webtorrent/webtorrent-plugin.ts39
-rw-r--r--client/src/sass/application.scss24
-rw-r--r--client/src/sass/include/_mixins.scss13
-rw-r--r--client/src/sass/include/_variables.scss2
-rw-r--r--client/src/sass/player/_player-variables.scss2
-rw-r--r--client/src/sass/player/bezels.scss8
-rw-r--r--client/src/sass/player/peertube-skin.scss9
-rw-r--r--client/src/standalone/videos/embed.ts40
-rw-r--r--client/tsconfig.json2
-rw-r--r--client/webpack/webpack.video-embed.js2
-rw-r--r--client/yarn.lock68
-rw-r--r--config/test.yaml8
-rw-r--r--package.json45
-rwxr-xr-xscripts/ci.sh4
-rw-r--r--scripts/create-import-video-file-job.ts2
-rwxr-xr-xscripts/create-transcoding-job.ts2
-rwxr-xr-xscripts/dev/index.sh4
-rw-r--r--server.ts4
-rw-r--r--server/controllers/activitypub/client.ts22
-rw-r--r--server/controllers/activitypub/inbox.ts6
-rw-r--r--server/controllers/api/accounts.ts13
-rw-r--r--server/controllers/api/config.ts22
-rw-r--r--server/controllers/api/jobs.ts2
-rw-r--r--server/controllers/api/overviews.ts2
-rw-r--r--server/controllers/api/server/debug.ts6
-rw-r--r--server/controllers/api/server/follows.ts5
-rw-r--r--server/controllers/api/server/logs.ts10
-rw-r--r--server/controllers/api/server/redundancy.ts84
-rw-r--r--server/controllers/api/server/stats.ts10
-rw-r--r--server/controllers/api/users/me.ts4
-rw-r--r--server/controllers/api/users/my-subscriptions.ts6
-rw-r--r--server/controllers/api/video-channel.ts3
-rw-r--r--server/controllers/api/video-playlist.ts1
-rw-r--r--server/controllers/api/videos/captions.ts2
-rw-r--r--server/controllers/api/videos/import.ts22
-rw-r--r--server/controllers/api/videos/index.ts3
-rw-r--r--server/controllers/client.ts2
-rw-r--r--server/controllers/static.ts16
-rw-r--r--server/controllers/tracker.ts15
-rw-r--r--server/controllers/webfinger.ts4
-rw-r--r--server/helpers/activitypub.ts198
-rw-r--r--server/helpers/audit-logger.ts24
-rw-r--r--server/helpers/core-utils.ts66
-rw-r--r--server/helpers/custom-jsonld-signature.ts90
-rw-r--r--server/helpers/custom-validators/activitypub/actor.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/cache-file.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/video-comments.ts2
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts27
-rw-r--r--server/helpers/custom-validators/misc.ts4
-rw-r--r--server/helpers/custom-validators/plugins.ts6
-rw-r--r--server/helpers/custom-validators/user-notifications.ts3
-rw-r--r--server/helpers/custom-validators/video-abuses.ts4
-rw-r--r--server/helpers/custom-validators/video-captions.ts2
-rw-r--r--server/helpers/custom-validators/video-imports.ts2
-rw-r--r--server/helpers/custom-validators/video-playlists.ts6
-rw-r--r--server/helpers/custom-validators/video-redundancies.ts12
-rw-r--r--server/helpers/custom-validators/videos.ts8
-rw-r--r--server/helpers/express-utils.ts16
-rw-r--r--server/helpers/ffmpeg-utils.ts191
-rw-r--r--server/helpers/logger.ts15
-rw-r--r--server/helpers/regexp.ts2
-rw-r--r--server/helpers/register-ts-paths.ts2
-rw-r--r--server/helpers/signup.ts2
-rw-r--r--server/helpers/utils.ts4
-rw-r--r--server/helpers/webtorrent.ts24
-rw-r--r--server/helpers/youtube-dl.ts61
-rw-r--r--server/initializers/checker-after-init.ts4
-rw-r--r--server/initializers/checker-before-init.ts4
-rw-r--r--server/initializers/config.ts8
-rw-r--r--server/initializers/constants.ts129
-rw-r--r--server/initializers/database.ts2
-rw-r--r--server/initializers/migrations/0005-email-pod.ts4
-rw-r--r--server/initializers/migrations/0010-email-user.ts4
-rw-r--r--server/initializers/migrations/0015-video-views.ts4
-rw-r--r--server/initializers/migrations/0020-video-likes.ts4
-rw-r--r--server/initializers/migrations/0025-video-dislikes.ts4
-rw-r--r--server/initializers/migrations/0030-video-category.ts4
-rw-r--r--server/initializers/migrations/0035-video-licence.ts4
-rw-r--r--server/initializers/migrations/0040-video-nsfw.ts4
-rw-r--r--server/initializers/migrations/0045-user-display-nsfw.ts4
-rw-r--r--server/initializers/migrations/0050-video-language.ts4
-rw-r--r--server/initializers/migrations/0055-video-uuid.ts4
-rw-r--r--server/initializers/migrations/0060-video-file.ts6
-rw-r--r--server/initializers/migrations/0065-video-file-size.ts6
-rw-r--r--server/initializers/migrations/0070-user-video-quota.ts6
-rw-r--r--server/initializers/migrations/0075-video-resolutions.ts6
-rw-r--r--server/initializers/migrations/0080-video-channels.ts6
-rw-r--r--server/initializers/migrations/0085-user-role.ts6
-rw-r--r--server/initializers/migrations/0090-videos-description.ts6
-rw-r--r--server/initializers/migrations/0095-videos-privacy.ts6
-rw-r--r--server/initializers/migrations/0100-activitypub.ts6
-rw-r--r--server/initializers/migrations/0105-server-mail.ts6
-rw-r--r--server/initializers/migrations/0110-server-key.ts6
-rw-r--r--server/initializers/migrations/0115-account-avatar.ts6
-rw-r--r--server/initializers/migrations/0120-video-null.ts6
-rw-r--r--server/initializers/migrations/0125-table-lowercase.ts4
-rw-r--r--server/initializers/migrations/0130-user-autoplay-video.ts4
-rw-r--r--server/initializers/migrations/0135-video-channel-actor.ts42
-rw-r--r--server/initializers/migrations/0140-actor-url.ts4
-rw-r--r--server/initializers/migrations/0145-delete-author.ts4
-rw-r--r--server/initializers/migrations/0150-avatar-cascade.ts4
-rw-r--r--server/initializers/migrations/0155-video-comments-enabled.ts4
-rw-r--r--server/initializers/migrations/0160-account-route.ts4
-rw-r--r--server/initializers/migrations/0165-video-route.ts4
-rw-r--r--server/initializers/migrations/0170-actor-follow-score.ts4
-rw-r--r--server/initializers/migrations/0175-actor-follow-counts.ts4
-rw-r--r--server/initializers/migrations/0180-job-table-delete.ts4
-rw-r--r--server/initializers/migrations/0185-video-share-url.ts4
-rw-r--r--server/initializers/migrations/0190-video-comment-unique-url.ts4
-rw-r--r--server/initializers/migrations/0195-support.ts4
-rw-r--r--server/initializers/migrations/0200-video-published-at.ts4
-rw-r--r--server/initializers/migrations/0205-user-nsfw-policy.ts4
-rw-r--r--server/initializers/migrations/0210-video-language.ts4
-rw-r--r--server/initializers/migrations/0215-video-support-length.ts4
-rw-r--r--server/initializers/migrations/0255-video-blacklist-reason.ts1
-rw-r--r--server/initializers/migrations/0285-description-support.ts6
-rw-r--r--server/initializers/migrations/0290-account-video-rate-url.ts6
-rw-r--r--server/initializers/migrations/0295-video-file-extname.ts6
-rw-r--r--server/initializers/migrations/0300-user-videos-history-enabled.ts6
-rw-r--r--server/initializers/migrations/0305-fix-unfederated-videos.ts6
-rw-r--r--server/initializers/migrations/0310-drop-unused-video-indexes.ts6
-rw-r--r--server/initializers/migrations/0315-user-notifications.ts4
-rw-r--r--server/initializers/migrations/0320-blacklist-unfederate.ts4
-rw-r--r--server/initializers/migrations/0325-video-abuse-fields.ts4
-rw-r--r--server/initializers/migrations/0330-video-streaming-playlist.ts4
-rw-r--r--server/initializers/migrations/0335-video-downloading-enabled.ts4
-rw-r--r--server/initializers/migrations/0340-add-originally-published-at.ts4
-rw-r--r--server/initializers/migrations/0345-video-playlists.ts4
-rw-r--r--server/initializers/migrations/0350-video-blacklist-type.ts6
-rw-r--r--server/initializers/migrations/0355-p2p-peer-version.ts6
-rw-r--r--server/initializers/migrations/0360-notification-instance-follower.ts6
-rw-r--r--server/initializers/migrations/0365-user-admin-flags.ts6
-rw-r--r--server/initializers/migrations/0370-thumbnail.ts6
-rw-r--r--server/initializers/migrations/0375-account-description.ts6
-rw-r--r--server/initializers/migrations/0380-cleanup-timestamps.ts6
-rw-r--r--server/initializers/migrations/0385-remove-actor-uuid.ts6
-rw-r--r--server/initializers/migrations/0390-user-pending-email.ts6
-rw-r--r--server/initializers/migrations/0395-user-video-languages.ts6
-rw-r--r--server/initializers/migrations/0400-user-theme.ts6
-rw-r--r--server/initializers/migrations/0405-plugin.ts6
-rw-r--r--server/initializers/migrations/0410-video-playlist-element.ts6
-rw-r--r--server/initializers/migrations/0415-thumbnail-auto-generated.ts6
-rw-r--r--server/initializers/migrations/0420-avatar-lazy.ts6
-rw-r--r--server/initializers/migrations/0425-nullable-actor-fields.ts6
-rw-r--r--server/initializers/migrations/0430-auto-follow-notification-setting.ts6
-rw-r--r--server/initializers/migrations/0435-user-modals.ts6
-rw-r--r--server/initializers/migrations/0440-user-auto-play-next-video.ts6
-rw-r--r--server/initializers/migrations/0445-shared-inbox-optional.ts6
-rw-r--r--server/initializers/migrations/0450-streaming-playlist-files.ts16
-rw-r--r--server/initializers/migrations/0455-soft-delete-video-comments.ts6
-rw-r--r--server/initializers/migrations/0460-user-playlist-autoplay.ts6
-rw-r--r--server/initializers/migrations/0465-thumbnail-file-url-length.ts6
-rw-r--r--server/initializers/migrations/0470-cleaup-indexes.ts6
-rw-r--r--server/initializers/migrations/0475-redundancy-expires-on.ts27
-rw-r--r--server/initializers/migrations/0480-caption-file-url.ts27
-rw-r--r--server/initializers/migrator.ts4
-rw-r--r--server/lib/activitypub/actor.ts22
-rw-r--r--server/lib/activitypub/cache-file.ts4
-rw-r--r--server/lib/activitypub/crawl.ts6
-rw-r--r--server/lib/activitypub/follow.ts1
-rw-r--r--server/lib/activitypub/send/send-accept.ts2
-rw-r--r--server/lib/activitypub/send/send-announce.ts2
-rw-r--r--server/lib/activitypub/send/send-create.ts8
-rw-r--r--server/lib/activitypub/send/send-dislike.ts2
-rw-r--r--server/lib/activitypub/send/send-flag.ts2
-rw-r--r--server/lib/activitypub/send/send-like.ts2
-rw-r--r--server/lib/activitypub/send/send-reject.ts2
-rw-r--r--server/lib/activitypub/send/send-undo.ts10
-rw-r--r--server/lib/activitypub/send/send-update.ts3
-rw-r--r--server/lib/activitypub/send/send-view.ts2
-rw-r--r--server/lib/activitypub/send/utils.ts32
-rw-r--r--server/lib/activitypub/video-comments.ts14
-rw-r--r--server/lib/activitypub/video-rates.ts2
-rw-r--r--server/lib/activitypub/videos.ts119
-rw-r--r--server/lib/client-html.ts24
-rw-r--r--server/lib/emailer.ts101
-rw-r--r--server/lib/files-cache/videos-caption-cache.ts7
-rw-r--r--server/lib/files-cache/videos-preview-cache.ts13
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-broadcast.ts2
-rw-r--r--server/lib/job-queue/handlers/activitypub-http-unicast.ts2
-rw-r--r--server/lib/job-queue/handlers/utils/activitypub-http-utils.ts8
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts2
-rw-r--r--server/lib/job-queue/handlers/video-import.ts2
-rw-r--r--server/lib/job-queue/handlers/video-redundancy.ts20
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts17
-rw-r--r--server/lib/job-queue/handlers/video-views.ts4
-rw-r--r--server/lib/job-queue/job-queue.ts49
-rw-r--r--server/lib/moderation.ts22
-rw-r--r--server/lib/notifier.ts47
-rw-r--r--server/lib/plugins/plugin-index.ts4
-rw-r--r--server/lib/plugins/plugin-manager.ts44
-rw-r--r--server/lib/redis.ts41
-rw-r--r--server/lib/redundancy.ts8
-rw-r--r--server/lib/schedulers/auto-follow-index-instances.ts3
-rw-r--r--server/lib/schedulers/plugins-check-scheduler.ts2
-rw-r--r--server/lib/schedulers/remove-old-views-scheduler.ts2
-rw-r--r--server/lib/schedulers/update-videos-scheduler.ts1
-rw-r--r--server/lib/schedulers/videos-redundancy-scheduler.ts63
-rw-r--r--server/lib/thumbnail.ts18
-rw-r--r--server/lib/user.ts16
-rw-r--r--server/lib/video-blacklist.ts20
-rw-r--r--server/lib/video-channel.ts3
-rw-r--r--server/lib/video-comment.ts6
-rw-r--r--server/middlewares/activitypub.ts14
-rw-r--r--server/middlewares/csp.ts30
-rw-r--r--server/middlewares/dnt.ts3
-rw-r--r--server/middlewares/oauth.ts1
-rw-r--r--server/middlewares/sort.ts25
-rw-r--r--server/middlewares/validators/avatar.ts4
-rw-r--r--server/middlewares/validators/config.ts2
-rw-r--r--server/middlewares/validators/feeds.ts8
-rw-r--r--server/middlewares/validators/redundancy.ts74
-rw-r--r--server/middlewares/validators/sort.ts3
-rw-r--r--server/middlewares/validators/users.ts7
-rw-r--r--server/middlewares/validators/videos/video-captions.ts10
-rw-r--r--server/middlewares/validators/videos/video-comments.ts2
-rw-r--r--server/middlewares/validators/videos/video-imports.ts9
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts9
-rw-r--r--server/middlewares/validators/videos/video-rates.ts6
-rw-r--r--server/middlewares/validators/videos/videos.ts28
-rw-r--r--server/middlewares/validators/webfinger.ts5
-rw-r--r--server/models/account/account-blocklist.ts2
-rw-r--r--server/models/account/account-video-rate.ts12
-rw-r--r--server/models/account/account.ts8
-rw-r--r--server/models/account/user-notification.ts4
-rw-r--r--server/models/account/user.ts86
-rw-r--r--server/models/activitypub/actor-follow.ts53
-rw-r--r--server/models/activitypub/actor.ts45
-rw-r--r--server/models/oauth/oauth-token.ts4
-rw-r--r--server/models/redundancy/video-redundancy.ts218
-rw-r--r--server/models/server/plugin.ts8
-rw-r--r--server/models/server/server-blocklist.ts2
-rw-r--r--server/models/utils.ts4
-rw-r--r--server/models/video/thumbnail.ts15
-rw-r--r--server/models/video/video-abuse.ts6
-rw-r--r--server/models/video/video-caption.ts27
-rw-r--r--server/models/video/video-channel.ts33
-rw-r--r--server/models/video/video-comment.ts20
-rw-r--r--server/models/video/video-format-utils.ts34
-rw-r--r--server/models/video/video-playlist-element.ts8
-rw-r--r--server/models/video/video-playlist.ts36
-rw-r--r--server/models/video/video.ts318
-rw-r--r--server/tests/api/activitypub/client.ts4
-rw-r--r--server/tests/api/activitypub/fetch.ts4
-rw-r--r--server/tests/api/activitypub/helpers.ts2
-rw-r--r--server/tests/api/activitypub/refresher.ts70
-rw-r--r--server/tests/api/activitypub/security.ts13
-rw-r--r--server/tests/api/check-params/accounts.ts2
-rw-r--r--server/tests/api/check-params/blocklist.ts2
-rw-r--r--server/tests/api/check-params/config.ts2
-rw-r--r--server/tests/api/check-params/contact-form.ts18
-rw-r--r--server/tests/api/check-params/debug.ts7
-rw-r--r--server/tests/api/check-params/follows.ts2
-rw-r--r--server/tests/api/check-params/jobs.ts8
-rw-r--r--server/tests/api/check-params/logs.ts8
-rw-r--r--server/tests/api/check-params/plugins.ts2
-rw-r--r--server/tests/api/check-params/redundancy.ts145
-rw-r--r--server/tests/api/check-params/search.ts2
-rw-r--r--server/tests/api/check-params/services.ts2
-rw-r--r--server/tests/api/check-params/user-notifications.ts2
-rw-r--r--server/tests/api/check-params/user-subscriptions.ts2
-rw-r--r--server/tests/api/check-params/users.ts13
-rw-r--r--server/tests/api/check-params/video-abuses.ts4
-rw-r--r--server/tests/api/check-params/video-blacklist.ts15
-rw-r--r--server/tests/api/check-params/video-captions.ts4
-rw-r--r--server/tests/api/check-params/video-channels.ts12
-rw-r--r--server/tests/api/check-params/video-comments.ts2
-rw-r--r--server/tests/api/check-params/video-imports.ts17
-rw-r--r--server/tests/api/check-params/video-playlists.ts7
-rw-r--r--server/tests/api/check-params/videos-filter.ts6
-rw-r--r--server/tests/api/check-params/videos-history.ts9
-rw-r--r--server/tests/api/check-params/videos.ts38
-rw-r--r--server/tests/api/notifications/user-notifications.ts88
-rw-r--r--server/tests/api/redundancy/index.ts1
-rw-r--r--server/tests/api/redundancy/manage-redundancy.ts373
-rw-r--r--server/tests/api/redundancy/redundancy.ts182
-rw-r--r--server/tests/api/search/search-activitypub-video-channels.ts44
-rw-r--r--server/tests/api/search/search-activitypub-videos.ts8
-rw-r--r--server/tests/api/search/search-videos.ts14
-rw-r--r--server/tests/api/server/auto-follows.ts19
-rw-r--r--server/tests/api/server/config.ts11
-rw-r--r--server/tests/api/server/contact-form.ts12
-rw-r--r--server/tests/api/server/email.ts2
-rw-r--r--server/tests/api/server/follow-constraints.ts8
-rw-r--r--server/tests/api/server/follows-moderation.ts18
-rw-r--r--server/tests/api/server/follows.ts88
-rw-r--r--server/tests/api/server/handle-down.ts36
-rw-r--r--server/tests/api/server/jobs.ts16
-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.ts25
-rw-r--r--server/tests/api/server/reverse-proxy.ts2
-rw-r--r--server/tests/api/server/stats.ts7
-rw-r--r--server/tests/api/server/tracker.ts2
-rw-r--r--server/tests/api/users/blocklist.ts259
-rw-r--r--server/tests/api/users/user-subscriptions.ts15
-rw-r--r--server/tests/api/users/users-multiple-servers.ts11
-rw-r--r--server/tests/api/users/users-verification.ts2
-rw-r--r--server/tests/api/users/users.ts55
-rw-r--r--server/tests/api/videos/audio-only.ts27
-rw-r--r--server/tests/api/videos/multiple-servers.ts32
-rw-r--r--server/tests/api/videos/services.ts10
-rw-r--r--server/tests/api/videos/single-server.ts2
-rw-r--r--server/tests/api/videos/video-abuse.ts18
-rw-r--r--server/tests/api/videos/video-blacklist.ts86
-rw-r--r--server/tests/api/videos/video-captions.ts9
-rw-r--r--server/tests/api/videos/video-change-ownership.ts18
-rw-r--r--server/tests/api/videos/video-channels.ts35
-rw-r--r--server/tests/api/videos/video-comments.ts6
-rw-r--r--server/tests/api/videos/video-description.ts7
-rw-r--r--server/tests/api/videos/video-hls.ts29
-rw-r--r--server/tests/api/videos/video-imports.ts11
-rw-r--r--server/tests/api/videos/video-nsfw.ts34
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts53
-rw-r--r--server/tests/api/videos/video-playlists.ts392
-rw-r--r--server/tests/api/videos/video-privacy.ts11
-rw-r--r--server/tests/api/videos/video-schedule-update.ts3
-rw-r--r--server/tests/api/videos/video-transcoder.ts84
-rw-r--r--server/tests/api/videos/videos-filter.ts12
-rw-r--r--server/tests/api/videos/videos-history.ts2
-rw-r--r--server/tests/api/videos/videos-overview.ts2
-rw-r--r--server/tests/api/videos/videos-views-cleaner.ts16
-rw-r--r--server/tests/cli/create-import-video-file-job.ts6
-rw-r--r--server/tests/cli/create-transcoding-job.ts15
-rw-r--r--server/tests/cli/optimize-old-videos.ts4
-rw-r--r--server/tests/cli/peertube.ts84
-rw-r--r--server/tests/cli/plugins.ts2
-rw-r--r--server/tests/cli/prune-storage.ts19
-rw-r--r--server/tests/cli/update-host.ts2
-rw-r--r--server/tests/client.ts2
-rw-r--r--server/tests/feeds/feeds.ts58
-rw-r--r--server/tests/helpers/comment-model.ts4
-rw-r--r--server/tests/helpers/core-utils.ts2
-rw-r--r--server/tests/helpers/request.ts2
-rw-r--r--server/tests/misc-endpoints.ts2
-rw-r--r--server/tests/plugins/action-hooks.ts17
-rw-r--r--server/tests/plugins/filter-hooks.ts41
-rw-r--r--server/tests/plugins/translations.ts35
-rw-r--r--server/tests/plugins/video-constants.ts58
-rw-r--r--server/tests/real-world/populate-database.ts122
-rw-r--r--server/tests/real-world/real-world.ts375
-rw-r--r--server/tools/cli.ts62
-rw-r--r--server/tools/package.json5
-rw-r--r--server/tools/peertube-auth.ts15
-rw-r--r--server/tools/peertube-import-videos.ts87
-rw-r--r--server/tools/peertube-plugins.ts34
-rw-r--r--server/tools/peertube-redundancy.ts197
-rw-r--r--server/tools/peertube-repl.ts41
-rw-r--r--server/tools/peertube-upload.ts21
-rw-r--r--server/tools/peertube-watch.ts10
-rw-r--r--server/tools/peertube.ts24
-rw-r--r--server/tools/yarn.lock23
-rw-r--r--server/typings/express.ts3
-rw-r--r--server/typings/models/account/account-blocklist.ts6
-rw-r--r--server/typings/models/account/account.ts42
-rw-r--r--server/typings/models/account/actor-follow.ts24
-rw-r--r--server/typings/models/account/actor.ts54
-rw-r--r--server/typings/models/account/avatar.ts3
-rw-r--r--server/typings/models/oauth/oauth-token.ts3
-rw-r--r--server/typings/models/server/plugin.ts3
-rw-r--r--server/typings/models/server/server-blocklist.ts6
-rw-r--r--server/typings/models/server/server.ts6
-rw-r--r--server/typings/models/user/user-notification.ts42
-rw-r--r--server/typings/models/user/user.ts33
-rw-r--r--server/typings/models/video/schedule-video-update.ts3
-rw-r--r--server/typings/models/video/video-abuse.ts9
-rw-r--r--server/typings/models/video/video-blacklist.ts9
-rw-r--r--server/typings/models/video/video-caption.ts7
-rw-r--r--server/typings/models/video/video-change-ownership.ts6
-rw-r--r--server/typings/models/video/video-channels.ts57
-rw-r--r--server/typings/models/video/video-comment.ts27
-rw-r--r--server/typings/models/video/video-file.ts21
-rw-r--r--server/typings/models/video/video-import.ts12
-rw-r--r--server/typings/models/video/video-playlist-element.ts12
-rw-r--r--server/typings/models/video/video-playlist.ts36
-rw-r--r--server/typings/models/video/video-rate.ts9
-rw-r--r--server/typings/models/video/video-redundancy.ts15
-rw-r--r--server/typings/models/video/video-share.ts6
-rw-r--r--server/typings/models/video/video-streaming-playlist.ts22
-rw-r--r--server/typings/models/video/video.ts93
-rw-r--r--server/typings/utils.ts2
-rw-r--r--shared/core-utils/miscs/miscs.ts2
-rw-r--r--shared/extra-utils/instances-index/mock-instances-index.ts2
-rw-r--r--shared/extra-utils/miscs/miscs.ts33
-rw-r--r--shared/extra-utils/miscs/sql.ts13
-rw-r--r--shared/extra-utils/requests/requests.ts58
-rw-r--r--shared/extra-utils/search/videos.ts2
-rw-r--r--shared/extra-utils/server/clients.ts4
-rw-r--r--shared/extra-utils/server/contact-form.ts10
-rw-r--r--shared/extra-utils/server/follows.ts26
-rw-r--r--shared/extra-utils/server/jobs.ts22
-rw-r--r--shared/extra-utils/server/plugins.ts72
-rw-r--r--shared/extra-utils/server/redundancy.ts70
-rw-r--r--shared/extra-utils/server/servers.ts27
-rw-r--r--shared/extra-utils/users/accounts.ts2
-rw-r--r--shared/extra-utils/users/blocklist.ts2
-rw-r--r--shared/extra-utils/users/login.ts2
-rw-r--r--shared/extra-utils/users/user-notifications.ts33
-rw-r--r--shared/extra-utils/users/users.ts40
-rw-r--r--shared/extra-utils/videos/video-blacklist.ts28
-rw-r--r--shared/extra-utils/videos/video-captions.ts6
-rw-r--r--shared/extra-utils/videos/video-channels.ts20
-rw-r--r--shared/extra-utils/videos/video-comments.ts2
-rw-r--r--shared/extra-utils/videos/video-imports.ts2
-rw-r--r--shared/extra-utils/videos/video-playlists.ts50
-rw-r--r--shared/extra-utils/videos/video-streaming-playlists.ts2
-rw-r--r--shared/extra-utils/videos/videos.ts31
-rw-r--r--shared/models/activitypub/activity.ts43
-rw-r--r--shared/models/activitypub/activitypub-actor.ts8
-rw-r--r--shared/models/activitypub/activitypub-signature.ts4
-rw-r--r--shared/models/activitypub/objects/cache-file-object.ts2
-rw-r--r--shared/models/activitypub/objects/common-objects.ts33
-rw-r--r--shared/models/activitypub/objects/video-abuse-object.ts2
-rw-r--r--shared/models/activitypub/objects/video-torrent-object.ts8
-rw-r--r--shared/models/activitypub/objects/view-object.ts2
-rw-r--r--shared/models/i18n/i18n.ts2
-rw-r--r--shared/models/nodeinfo/index.d.ts2
-rw-r--r--shared/models/plugins/peertube-plugin-latest-version.model.ts2
-rw-r--r--shared/models/plugins/plugin-package-json.model.ts12
-rw-r--r--shared/models/plugins/server-hook.model.ts2
-rw-r--r--shared/models/redundancy/index.ts4
-rw-r--r--shared/models/redundancy/video-redundancies-filters.model.ts1
-rw-r--r--shared/models/redundancy/video-redundancy.model.ts35
-rw-r--r--shared/models/redundancy/videos-redundancy-strategy.model.ts (renamed from shared/models/redundancy/videos-redundancy.model.ts)3
-rw-r--r--shared/models/server/custom-config.model.ts4
-rw-r--r--shared/models/server/job.model.ts26
-rw-r--r--shared/models/server/server-config.model.ts6
-rw-r--r--shared/models/server/server-stats.model.ts18
-rw-r--r--shared/models/users/user-right.enum.ts4
-rw-r--r--shared/models/users/user-role.ts6
-rw-r--r--shared/models/users/user.model.ts1
-rw-r--r--shared/models/videos/video-transcoding-fps.model.ts8
-rw-r--r--shared/models/videos/video.model.ts2
-rw-r--r--support/doc/api/openapi.yaml6
-rw-r--r--support/doc/production.md2
-rw-r--r--support/doc/tools.md32
-rw-r--r--tslint.json19
-rw-r--r--yarn.lock1622
504 files changed, 8224 insertions, 5531 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 000000000..a86c5bbba
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,88 @@
1{
2 "extends": "standard-with-typescript",
3 "rules": {
4 "eol-last": [
5 "error",
6 "always"
7 ],
8 "indent": "off",
9 "no-lone-blocks": "off",
10 "no-mixed-operators": "off",
11 "max-len": [
12 "error",
13 {
14 "code": 140
15 }
16 ],
17 "array-bracket-spacing": [
18 "error",
19 "always"
20 ],
21 "quote-props": [
22 "error",
23 "consistent-as-needed"
24 ],
25 "padded-blocks": "off",
26 "no-async-promise-executor": "off",
27 "dot-notation": "off",
28 "promise/param-names": "off",
29 "import/first": "off",
30 "operator-linebreak": [
31 "error",
32 "after",
33 {
34 "overrides": {
35 "?": "before",
36 ":": "before"
37 }
38 }
39 ],
40 "@typescript-eslint/indent": [
41 "error",
42 2,
43 {
44 "SwitchCase": 1,
45 "MemberExpression": "off"
46 }
47 ],
48 "@typescript-eslint/consistent-type-assertions": [
49 "error",
50 {
51 "assertionStyle": "as"
52 }
53 ],
54 "@typescript-eslint/array-type": [
55 "error",
56 {
57 "default": "array"
58 }
59 ],
60 "@typescript-eslint/restrict-template-expressions": [
61 "off",
62 {
63 "allowNumber": "true"
64 }
65 ],
66 "@typescript-eslint/quotes": "off",
67 "@typescript-eslint/no-var-requires": "off",
68 "@typescript-eslint/explicit-function-return-type": "off",
69 "@typescript-eslint/promise-function-async": "off",
70 "@typescript-eslint/no-dynamic-delete": "off",
71 "@typescript-eslint/strict-boolean-expressions": "off",
72 "@typescript-eslint/consistent-type-definitions": "off",
73 "@typescript-eslint/no-misused-promises": "off",
74 "@typescript-eslint/no-namespace": "off",
75 "@typescript-eslint/no-extraneous-class": "off",
76 // bugged but useful
77 "@typescript-eslint/restrict-plus-operands": "off"
78 },
79 "ignorePatterns": [
80 "node_modules/"
81 ],
82 "parserOptions": {
83 "project": [
84 "./tsconfig.json",
85 "./server/tools/tsconfig.json"
86 ]
87 }
88}
diff --git a/.gitignore b/.gitignore
index 6ef90385c..07b2fb7ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@ yarn-error.log
12/test5/ 12/test5/
13/test6/ 13/test6/
14/server/tests/fixtures/video_high_bitrate_1080p.mp4 14/server/tests/fixtures/video_high_bitrate_1080p.mp4
15/server/tests/fixtures/video_59fps.mp4
15 16
16# Production 17# Production
17/storage/ 18/storage/
diff --git a/client/package.json b/client/package.json
index cd0a82aa4..66e8990f9 100644
--- a/client/package.json
+++ b/client/package.json
@@ -56,7 +56,6 @@
56 "@ngx-loading-bar/router": "^4.2.0", 56 "@ngx-loading-bar/router": "^4.2.0",
57 "@ngx-meta/core": "^7.0.0", 57 "@ngx-meta/core": "^7.0.0",
58 "@ngx-translate/i18n-polyfill": "^1.0.0", 58 "@ngx-translate/i18n-polyfill": "^1.0.0",
59 "@streamroot/videojs-hlsjs-plugin": "^1.0.10",
60 "@types/core-js": "^2.5.2", 59 "@types/core-js": "^2.5.2",
61 "@types/debug": "^4.1.5", 60 "@types/debug": "^4.1.5",
62 "@types/hls.js": "^0.12.4", 61 "@types/hls.js": "^0.12.4",
@@ -65,11 +64,11 @@
65 "@types/jschannel": "^1.0.0", 64 "@types/jschannel": "^1.0.0",
66 "@types/linkifyjs": "^2.1.2", 65 "@types/linkifyjs": "^2.1.2",
67 "@types/lodash-es": "^4.17.0", 66 "@types/lodash-es": "^4.17.0",
68 "@types/markdown-it": "^0.0.5", 67 "@types/markdown-it": "^0.0.9",
69 "@types/node": "^10.9.2", 68 "@types/node": "^10.9.2",
70 "@types/sanitize-html": "1.18.0", 69 "@types/sanitize-html": "1.18.0",
71 "@types/socket.io-client": "^1.4.32", 70 "@types/socket.io-client": "^1.4.32",
72 "@types/video.js": "^7.2.5", 71 "@types/video.js": "^7.3.3",
73 "@types/webtorrent": "^0.107.0", 72 "@types/webtorrent": "^0.107.0",
74 "angular2-hotkeys": "^2.1.2", 73 "angular2-hotkeys": "^2.1.2",
75 "angularx-qrcode": "1.6.4", 74 "angularx-qrcode": "1.6.4",
@@ -77,6 +76,7 @@
77 "bootstrap": "^4.1.3", 76 "bootstrap": "^4.1.3",
78 "buffer": "^5.1.0", 77 "buffer": "^5.1.0",
79 "cache-chunk-store": "^3.0.0", 78 "cache-chunk-store": "^3.0.0",
79 "chart.js": "^2.9.3",
80 "codelyzer": "^5.0.1", 80 "codelyzer": "^5.0.1",
81 "core-js": "^3.1.4", 81 "core-js": "^3.1.4",
82 "css-loader": "^3.1.0", 82 "css-loader": "^3.1.0",
@@ -132,6 +132,7 @@
132 "videojs-dock": "^2.0.2", 132 "videojs-dock": "^2.0.2",
133 "videojs-hotkeys": "^0.2.21", 133 "videojs-hotkeys": "^0.2.21",
134 "videostream": "~3.2.1", 134 "videostream": "~3.2.1",
135 "vtt.js": "^0.13.0",
135 "webpack-bundle-analyzer": "^3.0.2", 136 "webpack-bundle-analyzer": "^3.0.2",
136 "webpack-cli": "^3.0.8", 137 "webpack-cli": "^3.0.8",
137 "webtorrent": "^0.107.16", 138 "webtorrent": "^0.107.16",
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts
index 87beb13da..c8c156105 100644
--- a/client/src/app/+about/about-instance/about-instance.component.ts
+++ b/client/src/app/+about/about-instance/about-instance.component.ts
@@ -1,12 +1,10 @@
1import { Component, OnInit, ViewChild } from '@angular/core' 1import { Component, OnInit, ViewChild } from '@angular/core'
2import { Notifier, ServerService } from '@app/core' 2import { Notifier, ServerService } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' 3import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
5import { InstanceService } from '@app/shared/instance/instance.service' 4import { InstanceService } from '@app/shared/instance/instance.service'
6import { MarkdownService } from '@app/shared/renderer'
7import { forkJoin } from 'rxjs'
8import { map, switchMap } from 'rxjs/operators'
9import { ServerConfig } from '@shared/models' 5import { ServerConfig } from '@shared/models'
6import { ActivatedRoute } from '@angular/router'
7import { ResolverData } from './about-instance.resolver'
10 8
11@Component({ 9@Component({
12 selector: 'my-about-instance', 10 selector: 'my-about-instance',
@@ -37,11 +35,10 @@ export class AboutInstanceComponent implements OnInit {
37 serverConfig: ServerConfig 35 serverConfig: ServerConfig
38 36
39 constructor ( 37 constructor (
38 private route: ActivatedRoute,
40 private notifier: Notifier, 39 private notifier: Notifier,
41 private serverService: ServerService, 40 private serverService: ServerService,
42 private instanceService: InstanceService, 41 private instanceService: InstanceService
43 private markdownService: MarkdownService,
44 private i18n: I18n
45 ) {} 42 ) {}
46 43
47 get instanceName () { 44 get instanceName () {
@@ -56,35 +53,23 @@ export class AboutInstanceComponent implements OnInit {
56 return this.serverConfig.instance.isNSFW 53 return this.serverConfig.instance.isNSFW
57 } 54 }
58 55
59 ngOnInit () { 56 async ngOnInit () {
60 this.serverConfig = this.serverService.getTmpConfig() 57 this.serverConfig = this.serverService.getTmpConfig()
61 this.serverService.getConfig() 58 this.serverService.getConfig()
62 .subscribe(config => this.serverConfig = config) 59 .subscribe(config => this.serverConfig = config)
63 60
64 this.instanceService.getAbout() 61 const { about, languages, categories }: ResolverData = this.route.snapshot.data.instanceData
65 .pipe( 62
66 switchMap(about => { 63 this.languages = languages
67 return forkJoin([ 64 this.categories = categories
68 this.instanceService.buildTranslatedLanguages(about), 65
69 this.instanceService.buildTranslatedCategories(about) 66 this.shortDescription = about.instance.shortDescription
70 ]).pipe(map(([ languages, categories ]) => ({ about, languages, categories }))) 67
71 }) 68 this.creationReason = about.instance.creationReason
72 ).subscribe( 69 this.maintenanceLifetime = about.instance.maintenanceLifetime
73 async ({ about, languages, categories }) => { 70 this.businessModel = about.instance.businessModel
74 this.languages = languages 71
75 this.categories = categories 72 this.html = await this.instanceService.buildHtml(about)
76
77 this.shortDescription = about.instance.shortDescription
78
79 this.creationReason = about.instance.creationReason
80 this.maintenanceLifetime = about.instance.maintenanceLifetime
81 this.businessModel = about.instance.businessModel
82
83 this.html = await this.instanceService.buildHtml(about)
84 },
85
86 () => this.notifier.error(this.i18n('Cannot get about information from server'))
87 )
88 } 73 }
89 74
90 openContactModal () { 75 openContactModal () {
diff --git a/client/src/app/+about/about-instance/about-instance.resolver.ts b/client/src/app/+about/about-instance/about-instance.resolver.ts
new file mode 100644
index 000000000..94c6abe5a
--- /dev/null
+++ b/client/src/app/+about/about-instance/about-instance.resolver.ts
@@ -0,0 +1,27 @@
1import { Injectable } from '@angular/core'
2import { ActivatedRouteSnapshot, Resolve } from '@angular/router'
3import { map, switchMap } from 'rxjs/operators'
4import { forkJoin } from 'rxjs'
5import { InstanceService } from '@app/shared/instance/instance.service'
6import { About } from '@shared/models/server'
7
8export type ResolverData = { about: About, languages: string[], categories: string[] }
9
10@Injectable()
11export class AboutInstanceResolver implements Resolve<any> {
12 constructor (
13 private instanceService: InstanceService
14 ) {}
15
16 resolve (route: ActivatedRouteSnapshot) {
17 return this.instanceService.getAbout()
18 .pipe(
19 switchMap(about => {
20 return forkJoin([
21 this.instanceService.buildTranslatedLanguages(about),
22 this.instanceService.buildTranslatedCategories(about)
23 ]).pipe(map(([ languages, categories ]) => ({ about, languages, categories })))
24 })
25 )
26 }
27}
diff --git a/client/src/app/+about/about-routing.module.ts b/client/src/app/+about/about-routing.module.ts
index 33e5070cb..91ccb846f 100644
--- a/client/src/app/+about/about-routing.module.ts
+++ b/client/src/app/+about/about-routing.module.ts
@@ -5,6 +5,7 @@ import { AboutComponent } from './about.component'
5import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' 5import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
6import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' 6import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
7import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' 7import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
8import { AboutInstanceResolver } from '@app/+about/about-instance/about-instance.resolver'
8 9
9const aboutRoutes: Routes = [ 10const aboutRoutes: Routes = [
10 { 11 {
@@ -24,6 +25,9 @@ const aboutRoutes: Routes = [
24 meta: { 25 meta: {
25 title: 'About this instance' 26 title: 'About this instance'
26 } 27 }
28 },
29 resolve: {
30 instanceData: AboutInstanceResolver
27 } 31 }
28 }, 32 },
29 { 33 {
diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts
index 14bf76e55..84d697540 100644
--- a/client/src/app/+about/about.module.ts
+++ b/client/src/app/+about/about.module.ts
@@ -7,6 +7,7 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub
7import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' 7import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
8import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component' 8import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
9import { AboutPeertubeContributorsComponent } from '@app/+about/about-peertube/about-peertube-contributors.component' 9import { AboutPeertubeContributorsComponent } from '@app/+about/about-peertube/about-peertube-contributors.component'
10import { AboutInstanceResolver } from '@app/+about/about-instance/about-instance.resolver'
10 11
11@NgModule({ 12@NgModule({
12 imports: [ 13 imports: [
@@ -28,6 +29,7 @@ import { AboutPeertubeContributorsComponent } from '@app/+about/about-peertube/a
28 ], 29 ],
29 30
30 providers: [ 31 providers: [
32 AboutInstanceResolver
31 ] 33 ]
32}) 34})
33export class AboutModule { } 35export class AboutModule { }
diff --git a/client/src/app/+admin/admin.component.html b/client/src/app/+admin/admin.component.html
index 9a3d90c18..0d06aaedc 100644
--- a/client/src/app/+admin/admin.component.html
+++ b/client/src/app/+admin/admin.component.html
@@ -5,7 +5,7 @@
5 </a> 5 </a>
6 6
7 <a i18n *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active" class="title-page"> 7 <a i18n *ngIf="hasServerFollowRight()" routerLink="/admin/follows" routerLinkActive="active" class="title-page">
8 Manage follows 8 Follows & redundancies
9 </a> 9 </a>
10 10
11 <a i18n *ngIf="hasVideoAbusesRight() || hasVideoBlacklistRight()" routerLink="/admin/moderation" routerLinkActive="active" class="title-page"> 11 <a i18n *ngIf="hasVideoAbusesRight() || hasVideoBlacklistRight()" routerLink="/admin/moderation" routerLinkActive="active" class="title-page">
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 9c56b5750..fdbe70314 100644
--- a/client/src/app/+admin/admin.module.ts
+++ b/client/src/app/+admin/admin.module.ts
@@ -5,7 +5,7 @@ import { TableModule } from 'primeng/table'
5import { SharedModule } from '../shared' 5import { SharedModule } from '../shared'
6import { AdminRoutingModule } from './admin-routing.module' 6import { AdminRoutingModule } from './admin-routing.module'
7import { AdminComponent } from './admin.component' 7import { AdminComponent } from './admin.component'
8import { FollowersListComponent, FollowingAddComponent, FollowsComponent } from './follows' 8import { FollowersListComponent, FollowingAddComponent, FollowsComponent, VideoRedundanciesListComponent } from './follows'
9import { FollowingListComponent } from './follows/following-list/following-list.component' 9import { FollowingListComponent } from './follows/following-list/following-list.component'
10import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users' 10import { UserCreateComponent, UserListComponent, UserPasswordComponent, UsersComponent, UserUpdateComponent } from './users'
11import { 11import {
@@ -16,7 +16,6 @@ import {
16} from './moderation' 16} from './moderation'
17import { ModerationComponent } from '@app/+admin/moderation/moderation.component' 17import { ModerationComponent } from '@app/+admin/moderation/moderation.component'
18import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component' 18import { RedundancyCheckboxComponent } from '@app/+admin/follows/shared/redundancy-checkbox.component'
19import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
20import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist' 19import { InstanceAccountBlocklistComponent, InstanceServerBlocklistComponent } from '@app/+admin/moderation/instance-blocklist'
21import { JobsComponent } from '@app/+admin/system/jobs/jobs.component' 20import { JobsComponent } from '@app/+admin/system/jobs/jobs.component'
22import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system' 21import { JobService, LogsComponent, LogsService, SystemComponent } from '@app/+admin/system'
@@ -27,13 +26,18 @@ import { PluginSearchComponent } from '@app/+admin/plugins/plugin-search/plugin-
27import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component' 26import { PluginShowInstalledComponent } from '@app/+admin/plugins/plugin-show-installed/plugin-show-installed.component'
28import { SelectButtonModule } from 'primeng/selectbutton' 27import { SelectButtonModule } from 'primeng/selectbutton'
29import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service' 28import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
29import { VideoRedundancyInformationComponent } from '@app/+admin/follows/video-redundancies-list/video-redundancy-information.component'
30import { ChartModule } from 'primeng/chart'
30 31
31@NgModule({ 32@NgModule({
32 imports: [ 33 imports: [
33 AdminRoutingModule, 34 AdminRoutingModule,
35
36 SharedModule,
37
34 TableModule, 38 TableModule,
35 SelectButtonModule, 39 SelectButtonModule,
36 SharedModule 40 ChartModule
37 ], 41 ],
38 42
39 declarations: [ 43 declarations: [
@@ -44,6 +48,8 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
44 FollowersListComponent, 48 FollowersListComponent,
45 FollowingListComponent, 49 FollowingListComponent,
46 RedundancyCheckboxComponent, 50 RedundancyCheckboxComponent,
51 VideoRedundanciesListComponent,
52 VideoRedundancyInformationComponent,
47 53
48 UsersComponent, 54 UsersComponent,
49 UserCreateComponent, 55 UserCreateComponent,
@@ -78,7 +84,6 @@ import { PluginApiService } from '@app/+admin/plugins/shared/plugin-api.service'
78 ], 84 ],
79 85
80 providers: [ 86 providers: [
81 RedundancyService,
82 JobService, 87 JobService,
83 LogsService, 88 LogsService,
84 DebugService, 89 DebugService,
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
index 915d60090..d806ea355 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
@@ -234,6 +234,9 @@
234 inputName="signupEnabled" formControlName="enabled" 234 inputName="signupEnabled" formControlName="enabled"
235 i18n-labelText labelText="Signup enabled" 235 i18n-labelText labelText="Signup enabled"
236 > 236 >
237 <ng-container ngProjectAs="description">
238 <span i18n>⚠️ This functionality requires a lot of attention and extra moderation.</span>
239 </ng-container>
237 <ng-container ngProjectAs="extra"> 240 <ng-container ngProjectAs="extra">
238 <my-peertube-checkbox [ngClass]="{ 'disabled-checkbox-extra': !isSignupEnabled() }" 241 <my-peertube-checkbox [ngClass]="{ 'disabled-checkbox-extra': !isSignupEnabled() }"
239 inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification" 242 inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification"
@@ -243,10 +246,11 @@
243 <div [ngClass]="{ 'disabled-checkbox-extra': !isSignupEnabled() }" class="mt-3"> 246 <div [ngClass]="{ 'disabled-checkbox-extra': !isSignupEnabled() }" class="mt-3">
244 <label i18n for="signupLimit">Signup limit</label> 247 <label i18n for="signupLimit">Signup limit</label>
245 <input 248 <input
246 type="text" id="signupLimit" 249 type="number" min="-1" id="signupLimit"
247 formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }" 250 formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }"
248 > 251 >
249 <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div> 252 <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div>
253 <small *ngIf="form.value['signup']['limit'] === -1" class="text-muted">Signup won't be limited to a fixed number of users.</small>
250 </div> 254 </div>
251 </ng-container> 255 </ng-container>
252 </my-peertube-checkbox> 256 </my-peertube-checkbox>
@@ -318,7 +322,7 @@
318 i18n-labelText labelText="Blacklist new videos automatically" 322 i18n-labelText labelText="Blacklist new videos automatically"
319 > 323 >
320 <ng-container ngProjectAs="description"> 324 <ng-container ngProjectAs="description">
321 <span i18n>Videos of regular users will stay private until a moderator reviews them. Can be overriden per user.</span> 325 <span i18n>Unless a user is marked as trusted, their videos will stay private until a moderator reviews them.</span>
322 </ng-container> 326 </ng-container>
323 </my-peertube-checkbox> 327 </my-peertube-checkbox>
324 </div> 328 </div>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
index 60d608028..dd70f1c06 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
@@ -10,6 +10,11 @@ input[type=text] {
10 display: block; 10 display: block;
11} 11}
12 12
13input[type=number] {
14 @include peertube-input-text(315px);
15 display: block;
16}
17
13input[type=checkbox] { 18input[type=checkbox] {
14 @include peertube-checkbox(1px); 19 @include peertube-checkbox(1px);
15} 20}
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.scss b/client/src/app/+admin/follows/following-add/following-add.component.scss
index 1baddc95f..df104c14e 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.scss
+++ b/client/src/app/+admin/follows/following-add/following-add.component.scss
@@ -7,7 +7,7 @@ textarea {
7 7
8.form-control { 8.form-control {
9 &, &:focus { 9 &, &:focus {
10 background-color: var(--inputColor); 10 background-color: var(--inputBackgroundColor);
11 color: var(--mainForegroundColor); 11 color: var(--mainForegroundColor);
12 } 12 }
13} 13}
diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html
index 21d477132..46581daf9 100644
--- a/client/src/app/+admin/follows/follows.component.html
+++ b/client/src/app/+admin/follows/follows.component.html
@@ -1,5 +1,5 @@
1<div class="admin-sub-header"> 1<div class="admin-sub-header">
2 <div i18n class="form-sub-title">Manage follows</div> 2 <div i18n class="form-sub-title">Follows & redundancies</div>
3 3
4 <div class="admin-sub-nav"> 4 <div class="admin-sub-nav">
5 <a i18n routerLink="following-list" routerLinkActive="active">Following</a> 5 <a i18n routerLink="following-list" routerLinkActive="active">Following</a>
@@ -7,7 +7,9 @@
7 <a i18n routerLink="following-add" routerLinkActive="active">Follow</a> 7 <a i18n routerLink="following-add" routerLinkActive="active">Follow</a>
8 8
9 <a i18n routerLink="followers-list" routerLinkActive="active">Followers</a> 9 <a i18n routerLink="followers-list" routerLinkActive="active">Followers</a>
10
11 <a i18n routerLink="video-redundancies-list" routerLinkActive="active">Video redundancies</a>
10 </div> 12 </div>
11</div> 13</div>
12 14
13<router-outlet></router-outlet> \ No newline at end of file 15<router-outlet></router-outlet>
diff --git a/client/src/app/+admin/follows/follows.routes.ts b/client/src/app/+admin/follows/follows.routes.ts
index e84c79e82..298733eb0 100644
--- a/client/src/app/+admin/follows/follows.routes.ts
+++ b/client/src/app/+admin/follows/follows.routes.ts
@@ -6,6 +6,7 @@ import { FollowingAddComponent } from './following-add'
6import { FollowersListComponent } from './followers-list' 6import { FollowersListComponent } from './followers-list'
7import { UserRight } from '../../../../../shared' 7import { UserRight } from '../../../../../shared'
8import { FollowingListComponent } from './following-list/following-list.component' 8import { FollowingListComponent } from './following-list/following-list.component'
9import { VideoRedundanciesListComponent } from '@app/+admin/follows/video-redundancies-list'
9 10
10export const FollowsRoutes: Routes = [ 11export const FollowsRoutes: Routes = [
11 { 12 {
@@ -47,6 +48,10 @@ export const FollowsRoutes: Routes = [
47 title: 'Add follow' 48 title: 'Add follow'
48 } 49 }
49 } 50 }
51 },
52 {
53 path: 'video-redundancies-list',
54 component: VideoRedundanciesListComponent
50 } 55 }
51 ] 56 ]
52 } 57 }
diff --git a/client/src/app/+admin/follows/index.ts b/client/src/app/+admin/follows/index.ts
index e94f33710..4fcb35cb1 100644
--- a/client/src/app/+admin/follows/index.ts
+++ b/client/src/app/+admin/follows/index.ts
@@ -1,5 +1,6 @@
1export * from './following-add' 1export * from './following-add'
2export * from './followers-list' 2export * from './followers-list'
3export * from './following-list' 3export * from './following-list'
4export * from './video-redundancies-list'
4export * from './follows.component' 5export * from './follows.component'
5export * from './follows.routes' 6export * from './follows.routes'
diff --git a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
index fa1da26bf..9d7883d97 100644
--- a/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
+++ b/client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
@@ -1,7 +1,7 @@
1import { Component, Input } from '@angular/core' 1import { Component, Input } from '@angular/core'
2import { Notifier } from '@app/core' 2import { Notifier } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service' 4import { RedundancyService } from '@app/shared/video/redundancy.service'
5 5
6@Component({ 6@Component({
7 selector: 'my-redundancy-checkbox', 7 selector: 'my-redundancy-checkbox',
diff --git a/client/src/app/+admin/follows/shared/redundancy.service.ts b/client/src/app/+admin/follows/shared/redundancy.service.ts
deleted file mode 100644
index 87ae01c04..000000000
--- a/client/src/app/+admin/follows/shared/redundancy.service.ts
+++ /dev/null
@@ -1,28 +0,0 @@
1import { catchError, map } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { RestExtractor } from '@app/shared'
5import { environment } from '../../../../environments/environment'
6
7@Injectable()
8export class RedundancyService {
9 static BASE_USER_SUBSCRIPTIONS_URL = environment.apiUrl + '/api/v1/server/redundancy'
10
11 constructor (
12 private authHttp: HttpClient,
13 private restExtractor: RestExtractor
14 ) { }
15
16 updateRedundancy (host: string, redundancyAllowed: boolean) {
17 const url = RedundancyService.BASE_USER_SUBSCRIPTIONS_URL + '/' + host
18
19 const body = { redundancyAllowed }
20
21 return this.authHttp.put(url, body)
22 .pipe(
23 map(this.restExtractor.extractDataBool),
24 catchError(err => this.restExtractor.handleError(err))
25 )
26 }
27
28}
diff --git a/client/src/app/+admin/follows/video-redundancies-list/index.ts b/client/src/app/+admin/follows/video-redundancies-list/index.ts
new file mode 100644
index 000000000..6a7c7f483
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/index.ts
@@ -0,0 +1 @@
export * from './video-redundancies-list.component'
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
new file mode 100644
index 000000000..80c66ec60
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.html
@@ -0,0 +1,82 @@
1<div class="admin-sub-header">
2 <div i18n class="form-sub-title">Video redundancies list</div>
3
4 <div class="select-filter-block">
5 <label for="displayType" i18n>Display</label>
6
7 <div class="peertube-select-container">
8 <select id="displayType" name="displayType" [(ngModel)]="displayType" (ngModelChange)="onDisplayTypeChanged()">
9 <option value="my-videos">My videos duplicated by remote instances</option>
10 <option value="remote-videos">Remote videos duplicated by my instance</option>
11 </select>
12 </div>
13 </div>
14</div>
15
16<p-table
17 [value]="videoRedundancies" [lazy]="true" [paginator]="true" [totalRecords]="totalRecords" [rows]="rowsPerPage"
18 [sortField]="sort.field" [sortOrder]="sort.order" (onLazyLoad)="loadLazy($event)" dataKey="id"
19>
20 <ng-template pTemplate="header">
21 <tr>
22 <th i18n *ngIf="isDisplayingRemoteVideos()">Strategy</th>
23 <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
24 <th i18n>Video URL</th>
25 <th i18n *ngIf="isDisplayingRemoteVideos()">Total size</th>
26 <th></th>
27 </tr>
28 </ng-template>
29
30 <ng-template pTemplate="body" let-redundancy>
31 <tr class="expander" [pRowToggler]="redundancy">
32 <td *ngIf="isDisplayingRemoteVideos()">{{ getRedundancyStrategy(redundancy) }}</td>
33
34 <td>{{ redundancy.name }}</td>
35
36 <td>
37 <a target="_blank" rel="noopener noreferrer" [href]="redundancy.url">{{ redundancy.url }}</a>
38 </td>
39
40 <td *ngIf="isDisplayingRemoteVideos()">{{ getTotalSize(redundancy) | bytes: 1 }}</td>
41
42 <td class="action-cell">
43 <my-delete-button (click)="removeRedundancy(redundancy)"></my-delete-button>
44 </td>
45 </tr>
46 </ng-template>
47
48 <ng-template pTemplate="rowexpansion" let-redundancy>
49 <tr>
50 <td colspan="2">
51 <div *ngFor="let file of redundancy.redundancies.files" class="expansion-block">
52 <my-video-redundancy-information [redundancyElement]="file"></my-video-redundancy-information>
53 </div>
54 </td>
55 </tr>
56
57 <tr>
58 <td colspan="2">
59 <div *ngFor="let playlist of redundancy.redundancies.streamingPlaylists">
60 <my-video-redundancy-information [redundancyElement]="playlist"></my-video-redundancy-information>
61 </div>
62 </td>
63 </tr>
64 </ng-template>
65</p-table>
66
67
68<div class="redundancies-charts" *ngIf="isDisplayingRemoteVideos()">
69 <div class="form-sub-title" i18n>Enabled strategies stats</div>
70
71 <div class="chart-blocks">
72
73 <div *ngIf="noRedundancies" i18n class="no-results">
74 No redundancy strategy is enabled on your instance.
75 </div>
76
77 <div class="chart-block" *ngFor="let r of redundanciesGraphsData">
78 <p-chart type="pie" [data]="r.graphData" [options]="r.options" width="300px" height="300px"></p-chart>
79 </div>
80
81 </div>
82</div>
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss
new file mode 100644
index 000000000..05018c281
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.scss
@@ -0,0 +1,37 @@
1@import '_variables';
2@import '_mixins';
3
4.expansion-block {
5 margin-bottom: 20px;
6}
7
8.admin-sub-header {
9 align-items: flex-end;
10
11 .select-filter-block {
12 &:not(:last-child) {
13 margin-right: 10px;
14 }
15
16 label {
17 margin-bottom: 2px;
18 }
19
20 .peertube-select-container {
21 @include peertube-select-container(auto);
22 }
23 }
24}
25
26.redundancies-charts {
27 margin-top: 50px;
28
29 .chart-blocks {
30 display: flex;
31 justify-content: center;
32
33 .chart-block {
34 margin: 0 20px;
35 }
36 }
37}
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
new file mode 100644
index 000000000..4b41d1d86
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancies-list.component.ts
@@ -0,0 +1,178 @@
1import { Component, OnInit } from '@angular/core'
2import { Notifier, ServerService } from '@app/core'
3import { SortMeta } from 'primeng/api'
4import { ConfirmService } from '../../../core/confirm/confirm.service'
5import { RestPagination, RestTable } from '../../../shared'
6import { I18n } from '@ngx-translate/i18n-polyfill'
7import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
8import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
9import { VideosRedundancyStats } from '@shared/models/server'
10import { BytesPipe } from 'ngx-pipes'
11import { RedundancyService } from '@app/shared/video/redundancy.service'
12
13@Component({
14 selector: 'my-video-redundancies-list',
15 templateUrl: './video-redundancies-list.component.html',
16 styleUrls: [ './video-redundancies-list.component.scss' ]
17})
18export class VideoRedundanciesListComponent extends RestTable implements OnInit {
19 private static LOCAL_STORAGE_DISPLAY_TYPE = 'video-redundancies-list-display-type'
20
21 videoRedundancies: VideoRedundancy[] = []
22 totalRecords = 0
23 rowsPerPage = 10
24
25 sort: SortMeta = { field: 'name', order: 1 }
26 pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
27 displayType: VideoRedundanciesTarget = 'my-videos'
28
29 redundanciesGraphsData: { stats: VideosRedundancyStats, graphData: object, options: object }[] = []
30
31 noRedundancies = false
32
33 private bytesPipe: BytesPipe
34
35 constructor (
36 private notifier: Notifier,
37 private confirmService: ConfirmService,
38 private redundancyService: RedundancyService,
39 private serverService: ServerService,
40 private i18n: I18n
41 ) {
42 super()
43
44 this.bytesPipe = new BytesPipe()
45 }
46
47 ngOnInit () {
48 this.loadSelectLocalStorage()
49
50 this.initialize()
51
52 this.serverService.getServerStats()
53 .subscribe(res => {
54 const redundancies = res.videosRedundancy
55
56 if (redundancies.length === 0) this.noRedundancies = true
57
58 for (const r of redundancies) {
59 this.buildPieData(r)
60 }
61 })
62 }
63
64 isDisplayingRemoteVideos () {
65 return this.displayType === 'remote-videos'
66 }
67
68 getTotalSize (redundancy: VideoRedundancy) {
69 return redundancy.redundancies.files.reduce((a, b) => a + b.size, 0) +
70 redundancy.redundancies.streamingPlaylists.reduce((a, b) => a + b.size, 0)
71 }
72
73 onDisplayTypeChanged () {
74 this.pagination.start = 0
75 this.saveSelectLocalStorage()
76
77 this.loadData()
78 }
79
80 getRedundancyStrategy (redundancy: VideoRedundancy) {
81 if (redundancy.redundancies.files.length !== 0) return redundancy.redundancies.files[0].strategy
82 if (redundancy.redundancies.streamingPlaylists.length !== 0) return redundancy.redundancies.streamingPlaylists[0].strategy
83
84 return ''
85 }
86
87 buildPieData (stats: VideosRedundancyStats) {
88 const totalSize = stats.totalSize
89 ? stats.totalSize - stats.totalUsed
90 : stats.totalUsed
91
92 if (totalSize === 0) return
93
94 this.redundanciesGraphsData.push({
95 stats,
96 graphData: {
97 labels: [ this.i18n('Used'), this.i18n('Available') ],
98 datasets: [
99 {
100 data: [ stats.totalUsed, totalSize ],
101 backgroundColor: [
102 '#FF6384',
103 '#36A2EB'
104 ],
105 hoverBackgroundColor: [
106 '#FF6384',
107 '#36A2EB'
108 ]
109 }
110 ]
111 },
112 options: {
113 title: {
114 display: true,
115 text: stats.strategy
116 },
117
118 tooltips: {
119 callbacks: {
120 label: (tooltipItem: any, data: any) => {
121 const dataset = data.datasets[tooltipItem.datasetIndex]
122 let label = data.labels[tooltipItem.index]
123 if (label) label += ': '
124 else label = ''
125
126 label += this.bytesPipe.transform(dataset.data[tooltipItem.index], 1)
127 return label
128 }
129 }
130 }
131 }
132 })
133 }
134
135 async removeRedundancy (redundancy: VideoRedundancy) {
136 const message = this.i18n('Do you really want to remove this video redundancy?')
137 const res = await this.confirmService.confirm(message, this.i18n('Remove redundancy'))
138 if (res === false) return
139
140 this.redundancyService.removeVideoRedundancies(redundancy)
141 .subscribe(
142 () => {
143 this.notifier.success(this.i18n('Video redundancies removed!'))
144 this.loadData()
145 },
146
147 err => this.notifier.error(err.message)
148 )
149
150 }
151
152 protected loadData () {
153 const options = {
154 pagination: this.pagination,
155 sort: this.sort,
156 target: this.displayType
157 }
158
159 this.redundancyService.listVideoRedundancies(options)
160 .subscribe(
161 resultList => {
162 this.videoRedundancies = resultList.data
163 this.totalRecords = resultList.total
164 },
165
166 err => this.notifier.error(err.message)
167 )
168 }
169
170 private loadSelectLocalStorage () {
171 const displayType = peertubeLocalStorage.getItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE)
172 if (displayType) this.displayType = displayType as VideoRedundanciesTarget
173 }
174
175 private saveSelectLocalStorage () {
176 peertubeLocalStorage.setItem(VideoRedundanciesListComponent.LOCAL_STORAGE_DISPLAY_TYPE, this.displayType)
177 }
178}
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html
new file mode 100644
index 000000000..a379520e3
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.html
@@ -0,0 +1,24 @@
1<div>
2 <span class="label">Url</span>
3 <a target="_blank" rel="noopener noreferrer" [href]="redundancyElement.fileUrl">{{ redundancyElement.fileUrl }}</a>
4</div>
5
6<div>
7 <span class="label">Created on</span>
8 <span>{{ redundancyElement.createdAt | date: 'medium' }}</span>
9</div>
10
11<div>
12 <span class="label">Expires on</span>
13 <span>{{ redundancyElement.expiresOn | date: 'medium' }}</span>
14</div>
15
16<div>
17 <span class="label">Size</span>
18 <span>{{ redundancyElement.size | bytes: 1 }}</span>
19</div>
20
21<div *ngIf="redundancyElement.strategy">
22 <span class="label">Strategy</span>
23 <span>{{ redundancyElement.strategy }}</span>
24</div>
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss
new file mode 100644
index 000000000..6b09fbb01
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.scss
@@ -0,0 +1,8 @@
1@import '_variables';
2@import '_mixins';
3
4.label {
5 display: inline-block;
6 min-width: 100px;
7 font-weight: $font-semibold;
8}
diff --git a/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts
new file mode 100644
index 000000000..6f3090c08
--- /dev/null
+++ b/client/src/app/+admin/follows/video-redundancies-list/video-redundancy-information.component.ts
@@ -0,0 +1,11 @@
1import { Component, Input } from '@angular/core'
2import { FileRedundancyInformation, StreamingPlaylistRedundancyInformation } from '@shared/models'
3
4@Component({
5 selector: 'my-video-redundancy-information',
6 templateUrl: './video-redundancy-information.component.html',
7 styleUrls: [ './video-redundancy-information.component.scss' ]
8})
9export class VideoRedundancyInformationComponent {
10 @Input() redundancyElement: FileRedundancyInformation | StreamingPlaylistRedundancyInformation
11}
diff --git a/client/src/app/+admin/system/jobs/jobs.component.ts b/client/src/app/+admin/system/jobs/jobs.component.ts
index 20c8ea71a..bc40452cf 100644
--- a/client/src/app/+admin/system/jobs/jobs.component.ts
+++ b/client/src/app/+admin/system/jobs/jobs.component.ts
@@ -16,8 +16,8 @@ import { JobTypeClient } from '../../../../types/job-type-client.type'
16 styleUrls: [ './jobs.component.scss' ] 16 styleUrls: [ './jobs.component.scss' ]
17}) 17})
18export class JobsComponent extends RestTable implements OnInit { 18export class JobsComponent extends RestTable implements OnInit {
19 private static JOB_STATE_LOCAL_STORAGE_STATE = 'jobs-list-state' 19 private static LOCAL_STORAGE_STATE = 'jobs-list-state'
20 private static JOB_STATE_LOCAL_STORAGE_TYPE = 'jobs-list-type' 20 private static LOCAL_STORAGE_TYPE = 'jobs-list-type'
21 21
22 jobState: JobStateClient = 'waiting' 22 jobState: JobStateClient = 'waiting'
23 jobStates: JobStateClient[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ] 23 jobStates: JobStateClient[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ]
@@ -34,7 +34,8 @@ export class JobsComponent extends RestTable implements OnInit {
34 'video-file-import', 34 'video-file-import',
35 'video-import', 35 'video-import',
36 'videos-views', 36 'videos-views',
37 'activitypub-refresher' 37 'activitypub-refresher',
38 'video-redundancy'
38 ] 39 ]
39 40
40 jobs: Job[] = [] 41 jobs: Job[] = []
@@ -77,15 +78,15 @@ export class JobsComponent extends RestTable implements OnInit {
77 } 78 }
78 79
79 private loadJobStateAndType () { 80 private loadJobStateAndType () {
80 const state = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE) 81 const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE)
81 if (state) this.jobState = state as JobState 82 if (state) this.jobState = state as JobState
82 83
83 const type = peertubeLocalStorage.getItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE) 84 const type = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE)
84 if (type) this.jobType = type as JobType 85 if (type) this.jobType = type as JobType
85 } 86 }
86 87
87 private saveJobStateAndType () { 88 private saveJobStateAndType () {
88 peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_STATE, this.jobState) 89 peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_STATE, this.jobState)
89 peertubeLocalStorage.setItem(JobsComponent.JOB_STATE_LOCAL_STORAGE_TYPE, this.jobType) 90 peertubeLocalStorage.setItem(JobsComponent.LOCAL_STORAGE_TYPE, this.jobType)
90 } 91 }
91} 92}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts
index 7479442d1..355cb4f55 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts
@@ -9,7 +9,7 @@ export abstract class MyAccountVideoChannelEdit extends FormReactive {
9 abstract isCreation (): boolean 9 abstract isCreation (): boolean
10 abstract getFormButtonTitle (): string 10 abstract getFormButtonTitle (): string
11 11
12 // FIXME: We need this method so angular does not complain in the child template 12 // We need this method so angular does not complain in child template that doesn't need this
13 onAvatarChange (formData: FormData) { /* empty */ } 13 onAvatarChange (formData: FormData) { /* empty */ }
14 14
15 // Should be implemented by the child 15 // Should be implemented by the child
diff --git a/client/src/app/+signup/+register/register-step-channel.component.html b/client/src/app/+signup/+register/register-step-channel.component.html
index 88ff6e3ff..170c2964e 100644
--- a/client/src/app/+signup/+register/register-step-channel.component.html
+++ b/client/src/app/+signup/+register/register-step-channel.component.html
@@ -40,7 +40,7 @@
40 </div> 40 </div>
41 41
42 <div class="name-information" i18n> 42 <div class="name-information" i18n>
43 The channel name is a unique identifier of your channel on this instance. It's like an address mail, so other people can find your channel. 43 The channel name is a unique identifier of your channel on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it.
44 </div> 44 </div>
45 45
46 <div *ngIf="formErrors.name" class="form-error"> 46 <div *ngIf="formErrors.name" class="form-error">
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html
index a2a657660..6bac4e4a4 100644
--- a/client/src/app/+signup/+register/register-step-user.component.html
+++ b/client/src/app/+signup/+register/register-step-user.component.html
@@ -29,7 +29,7 @@
29 </div> 29 </div>
30 30
31 <div class="name-information" i18n> 31 <div class="name-information" i18n>
32 The username is a unique identifier of your account on this instance. It's like an address mail, so other people can find you. 32 The username is a unique identifier of your account on this and all the other instances. It's as unique as an email address, which makes it easy for other people to interact with it.
33 </div> 33 </div>
34 34
35 <div *ngIf="formErrors.username" class="form-error"> 35 <div *ngIf="formErrors.username" class="form-error">
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index dda705811..62915ec54 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -48,8 +48,6 @@ export function metaFactory (serverService: ServerService): MetaLoader {
48 ], 48 ],
49 imports: [ 49 imports: [
50 BrowserModule, 50 BrowserModule,
51 // FIXME: https://github.com/maxisam/ngx-clipboard/issues/133
52 ClipboardModule,
53 51
54 CoreModule, 52 CoreModule,
55 SharedModule, 53 SharedModule,
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index cdcbcb528..1f6cfb596 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -9,6 +9,7 @@ import { VideoConstant } from '../../../../../shared/models/videos'
9import { isDefaultLocale, peertubeTranslate } from '../../../../../shared/models/i18n' 9import { isDefaultLocale, peertubeTranslate } from '../../../../../shared/models/i18n'
10import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' 10import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
11import { sortBy } from '@app/shared/misc/utils' 11import { sortBy } from '@app/shared/misc/utils'
12import { ServerStats } from '@shared/models/server'
12 13
13@Injectable() 14@Injectable()
14export class ServerService { 15export class ServerService {
@@ -16,6 +17,8 @@ export class ServerService {
16 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 17 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
17 private static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' 18 private static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/'
18 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' 19 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
20 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats'
21
19 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config' 22 private static CONFIG_LOCAL_STORAGE_KEY = 'server-config'
20 23
21 configReloaded = new Subject<void>() 24 configReloaded = new Subject<void>()
@@ -235,6 +238,10 @@ export class ServerService {
235 return this.localeObservable.pipe(first()) 238 return this.localeObservable.pipe(first())
236 } 239 }
237 240
241 getServerStats () {
242 return this.http.get<ServerStats>(ServerService.BASE_STATS_URL)
243 }
244
238 private loadAttributeEnum <T extends string | number> ( 245 private loadAttributeEnum <T extends string | number> (
239 baseUrl: string, 246 baseUrl: string,
240 attributeName: 'categories' | 'licences' | 'languages' | 'privacies', 247 attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
diff --git a/client/src/app/search/search-filters.component.html b/client/src/app/search/search-filters.component.html
index 07fb2c048..c275285d5 100644
--- a/client/src/app/search/search-filters.component.html
+++ b/client/src/app/search/search-filters.component.html
@@ -103,7 +103,7 @@
103 </button> 103 </button>
104 <div class="peertube-select-container"> 104 <div class="peertube-select-container">
105 <select id="category" name="category" [(ngModel)]="advancedSearch.categoryOneOf"> 105 <select id="category" name="category" [(ngModel)]="advancedSearch.categoryOneOf">
106 <option [value]="undefined" i18n>Any or no category set</option> 106 <option [value]="undefined" i18n>Display all categories</option>
107 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> 107 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
108 </select> 108 </select>
109 </div> 109 </div>
@@ -116,7 +116,7 @@
116 </button> 116 </button>
117 <div class="peertube-select-container"> 117 <div class="peertube-select-container">
118 <select id="licence" name="licence" [(ngModel)]="advancedSearch.licenceOneOf"> 118 <select id="licence" name="licence" [(ngModel)]="advancedSearch.licenceOneOf">
119 <option [value]="undefined" i18n>Any or no license set</option> 119 <option [value]="undefined" i18n>Display all licenses</option>
120 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> 120 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
121 </select> 121 </select>
122 </div> 122 </div>
@@ -129,7 +129,7 @@
129 </button> 129 </button>
130 <div class="peertube-select-container"> 130 <div class="peertube-select-container">
131 <select id="language" name="language" [(ngModel)]="advancedSearch.languageOneOf"> 131 <select id="language" name="language" [(ngModel)]="advancedSearch.languageOneOf">
132 <option [value]="undefined" i18n>Any or no language set</option> 132 <option [value]="undefined" i18n>Display all languages</option>
133 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> 133 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
134 </select> 134 </select>
135 </div> 135 </div>
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/buttons/button.component.scss
index 2a8cfc748..3ccfefd7e 100644
--- a/client/src/app/shared/buttons/button.component.scss
+++ b/client/src/app/shared/buttons/button.component.scss
@@ -10,11 +10,26 @@ my-small-loader ::ng-deep .root {
10.action-button { 10.action-button {
11 @include peertube-button-link; 11 @include peertube-button-link;
12 @include button-with-icon(21px, 0, -2px); 12 @include button-with-icon(21px, 0, -2px);
13}
13 14
14 // FIXME: Firefox does not apply global .orange-button icon color 15.orange-button {
15 &.orange-button { 16 @include peertube-button;
16 @include apply-svg-color(#fff) 17 @include orange-button;
17 } 18}
19
20.orange-button-link {
21 @include peertube-button-link;
22 @include orange-button;
23}
24
25.grey-button {
26 @include peertube-button;
27 @include grey-button;
28}
29
30.grey-button-link {
31 @include peertube-button-link;
32 @include grey-button;
18} 33}
19 34
20// In a table, try to minimize the space taken by this button 35// In a table, try to minimize the space taken by this button
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
index 767e3f026..d20754d11 100644
--- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts
@@ -56,7 +56,7 @@ export class CustomConfigValidatorsService {
56 } 56 }
57 57
58 this.SIGNUP_LIMIT = { 58 this.SIGNUP_LIMIT = {
59 VALIDATORS: [ Validators.required, Validators.min(1), Validators.pattern('[0-9]+') ], 59 VALIDATORS: [ Validators.required, Validators.min(-1), Validators.pattern('-?[0-9]+') ],
60 MESSAGES: { 60 MESSAGES: {
61 'required': this.i18n('Signup limit is required.'), 61 'required': this.i18n('Signup limit is required.'),
62 'min': this.i18n('Signup limit must be greater than 1.'), 62 'min': this.i18n('Signup limit must be greater than 1.'),
diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts
index 806aca347..b6e641228 100644
--- a/client/src/app/shared/images/global-icon.component.ts
+++ b/client/src/app/shared/images/global-icon.component.ts
@@ -1,6 +1,5 @@
1import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit } from '@angular/core' 1import { ChangeDetectionStrategy, Component, ElementRef, Input, OnInit } from '@angular/core'
2import { HooksService } from '@app/core/plugins/hooks.service' 2import { HooksService } from '@app/core/plugins/hooks.service'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4 3
5const icons = { 4const icons = {
6 'add': require('!!raw-loader?!../../../assets/images/global/add.svg'), 5 'add': require('!!raw-loader?!../../../assets/images/global/add.svg'),
diff --git a/client/src/app/shared/instance/instance-statistics.component.ts b/client/src/app/shared/instance/instance-statistics.component.ts
index 8ec728f05..40aa8a4c0 100644
--- a/client/src/app/shared/instance/instance-statistics.component.ts
+++ b/client/src/app/shared/instance/instance-statistics.component.ts
@@ -1,9 +1,6 @@
1import { map } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { ServerStats } from '@shared/models/server' 2import { ServerStats } from '@shared/models/server'
6import { environment } from '../../../environments/environment' 3import { ServerService } from '@app/core'
7 4
8@Component({ 5@Component({
9 selector: 'my-instance-statistics', 6 selector: 'my-instance-statistics',
@@ -11,27 +8,15 @@ import { environment } from '../../../environments/environment'
11 styleUrls: [ './instance-statistics.component.scss' ] 8 styleUrls: [ './instance-statistics.component.scss' ]
12}) 9})
13export class InstanceStatisticsComponent implements OnInit { 10export class InstanceStatisticsComponent implements OnInit {
14 private static BASE_STATS_URL = environment.apiUrl + '/api/v1/server/stats'
15
16 serverStats: ServerStats = null 11 serverStats: ServerStats = null
17 12
18 constructor ( 13 constructor (
19 private http: HttpClient, 14 private serverService: ServerService
20 private i18n: I18n
21 ) { 15 ) {
22 } 16 }
23 17
24 ngOnInit () { 18 ngOnInit () {
25 this.getStats() 19 this.serverService.getServerStats()
26 .subscribe( 20 .subscribe(res => this.serverStats = res)
27 res => {
28 this.serverStats = res
29 }
30 )
31 }
32
33 getStats () {
34 return this.http
35 .get<ServerStats>(InstanceStatisticsComponent.BASE_STATS_URL)
36 } 21 }
37} 22}
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts
index 5ccdafb54..24a083654 100644
--- a/client/src/app/shared/menu/top-menu-dropdown.component.ts
+++ b/client/src/app/shared/menu/top-menu-dropdown.component.ts
@@ -49,8 +49,7 @@ export class TopMenuDropdownComponent implements OnInit, OnDestroy {
49 e => e.children && e.children.some(c => !!c.iconName) 49 e => e.children && e.children.some(c => !!c.iconName)
50 ) 50 )
51 51
52 // FIXME: We have to set body for the container to avoid because of scroll overflow on mobile view 52 // We have to set body for the container to avoid scroll overflow on mobile view
53 // But this break our hovering system
54 if (this.screen.isInMobileView()) { 53 if (this.screen.isInMobileView()) {
55 this.container = 'body' 54 this.container = 'body'
56 } 55 }
diff --git a/client/src/app/shared/renderer/markdown.service.ts b/client/src/app/shared/renderer/markdown.service.ts
index 0d3fde537..f0c87326f 100644
--- a/client/src/app/shared/renderer/markdown.service.ts
+++ b/client/src/app/shared/renderer/markdown.service.ts
@@ -1,7 +1,7 @@
1import { Injectable } from '@angular/core' 1import { Injectable } from '@angular/core'
2import { MarkdownIt } from 'markdown-it'
3import { buildVideoLink } from '../../../assets/player/utils' 2import { buildVideoLink } from '../../../assets/player/utils'
4import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service' 3import { HtmlRendererService } from '@app/shared/renderer/html-renderer.service'
4import * as MarkdownIt from 'markdown-it'
5 5
6type MarkdownParsers = { 6type MarkdownParsers = {
7 textMarkdownIt: MarkdownIt 7 textMarkdownIt: MarkdownIt
@@ -100,7 +100,7 @@ export class MarkdownService {
100 } 100 }
101 101
102 private async createMarkdownIt (config: MarkdownConfig) { 102 private async createMarkdownIt (config: MarkdownConfig) {
103 // FIXME: import('...') returns a struct module, containing a "default" field corresponding to our sanitizeHtml function 103 // FIXME: import('...') returns a struct module, containing a "default" field
104 const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default 104 const MarkdownItClass: typeof import ('markdown-it') = (await import('markdown-it') as any).default
105 105
106 const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html: config.html }) 106 const markdownIt = new MarkdownItClass('zero', { linkify: true, breaks: true, html: config.html })
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index b2eb13f73..d06d37d8c 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -98,6 +98,7 @@ import { FollowService } from '@app/shared/instance/follow.service'
98import { MultiSelectModule } from 'primeng/multiselect' 98import { MultiSelectModule } from 'primeng/multiselect'
99import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' 99import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component'
100import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component' 100import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-copy.component'
101import { RedundancyService } from '@app/shared/video/redundancy.service'
101 102
102@NgModule({ 103@NgModule({
103 imports: [ 104 imports: [
@@ -300,6 +301,7 @@ import { InputReadonlyCopyComponent } from '@app/shared/forms/input-readonly-cop
300 UserNotificationService, 301 UserNotificationService,
301 302
302 FollowService, 303 FollowService,
304 RedundancyService,
303 305
304 I18n 306 I18n
305 ] 307 ]
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts
index 9f613c5fa..f09c3d1fc 100644
--- a/client/src/app/shared/video/infinite-scroller.directive.ts
+++ b/client/src/app/shared/video/infinite-scroller.directive.ts
@@ -1,4 +1,4 @@
1import { distinctUntilChanged, filter, map, share, startWith, tap, throttleTime } from 'rxjs/operators' 1import { distinctUntilChanged, filter, map, share, startWith, throttleTime } from 'rxjs/operators'
2import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' 2import { AfterContentChecked, Directive, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'
3import { fromEvent, Observable, Subscription } from 'rxjs' 3import { fromEvent, Observable, Subscription } from 'rxjs'
4 4
@@ -53,7 +53,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy, AfterConten
53 const scrollableElement = this.onItself ? this.container : window 53 const scrollableElement = this.onItself ? this.container : window
54 const scrollObservable = fromEvent(scrollableElement, 'scroll') 54 const scrollObservable = fromEvent(scrollableElement, 'scroll')
55 .pipe( 55 .pipe(
56 startWith(null as string), // FIXME: typings 56 startWith(true),
57 throttleTime(200, undefined, throttleOptions), 57 throttleTime(200, undefined, throttleOptions),
58 map(() => this.getScrollInfo()), 58 map(() => this.getScrollInfo()),
59 distinctUntilChanged((o1, o2) => o1.current === o2.current), 59 distinctUntilChanged((o1, o2) => o1.current === o2.current),
diff --git a/client/src/app/shared/video/redundancy.service.ts b/client/src/app/shared/video/redundancy.service.ts
new file mode 100644
index 000000000..fb918d73b
--- /dev/null
+++ b/client/src/app/shared/video/redundancy.service.ts
@@ -0,0 +1,73 @@
1import { catchError, map, toArray } from 'rxjs/operators'
2import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { RestExtractor, RestPagination, RestService } from '@app/shared/rest'
5import { SortMeta } from 'primeng/api'
6import { ResultList, Video, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
7import { concat, Observable } from 'rxjs'
8import { environment } from '../../../environments/environment'
9
10@Injectable()
11export class RedundancyService {
12 static BASE_REDUNDANCY_URL = environment.apiUrl + '/api/v1/server/redundancy'
13
14 constructor (
15 private authHttp: HttpClient,
16 private restService: RestService,
17 private restExtractor: RestExtractor
18 ) { }
19
20 updateRedundancy (host: string, redundancyAllowed: boolean) {
21 const url = RedundancyService.BASE_REDUNDANCY_URL + '/' + host
22
23 const body = { redundancyAllowed }
24
25 return this.authHttp.put(url, body)
26 .pipe(
27 map(this.restExtractor.extractDataBool),
28 catchError(err => this.restExtractor.handleError(err))
29 )
30 }
31
32 listVideoRedundancies (options: {
33 pagination: RestPagination,
34 sort: SortMeta,
35 target?: VideoRedundanciesTarget
36 }): Observable<ResultList<VideoRedundancy>> {
37 const { pagination, sort, target } = options
38
39 let params = new HttpParams()
40 params = this.restService.addRestGetParams(params, pagination, sort)
41
42 if (target) params = params.append('target', target)
43
44 return this.authHttp.get<ResultList<VideoRedundancy>>(RedundancyService.BASE_REDUNDANCY_URL + '/videos', { params })
45 .pipe(
46 catchError(res => this.restExtractor.handleError(res))
47 )
48 }
49
50 addVideoRedundancy (video: Video) {
51 return this.authHttp.post(RedundancyService.BASE_REDUNDANCY_URL + '/videos', { videoId: video.id })
52 .pipe(
53 catchError(res => this.restExtractor.handleError(res))
54 )
55 }
56
57 removeVideoRedundancies (redundancy: VideoRedundancy) {
58 const observables = redundancy.redundancies.streamingPlaylists.map(r => r.id)
59 .concat(redundancy.redundancies.files.map(r => r.id))
60 .map(id => this.removeRedundancy(id))
61
62 return concat(...observables)
63 .pipe(toArray())
64 }
65
66 private removeRedundancy (redundancyId: number) {
67 return this.authHttp.delete(RedundancyService.BASE_REDUNDANCY_URL + '/videos/' + redundancyId)
68 .pipe(
69 map(this.restExtractor.extractDataBool),
70 catchError(res => this.restExtractor.handleError(res))
71 )
72 }
73}
diff --git a/client/src/app/shared/video/video-actions-dropdown.component.ts b/client/src/app/shared/video/video-actions-dropdown.component.ts
index afdeab18d..390d74c52 100644
--- a/client/src/app/shared/video/video-actions-dropdown.component.ts
+++ b/client/src/app/shared/video/video-actions-dropdown.component.ts
@@ -14,6 +14,7 @@ import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklis
14import { VideoBlacklistService } from '@app/shared/video-blacklist' 14import { VideoBlacklistService } from '@app/shared/video-blacklist'
15import { ScreenService } from '@app/shared/misc/screen.service' 15import { ScreenService } from '@app/shared/misc/screen.service'
16import { VideoCaption } from '@shared/models' 16import { VideoCaption } from '@shared/models'
17import { RedundancyService } from '@app/shared/video/redundancy.service'
17 18
18export type VideoActionsDisplayType = { 19export type VideoActionsDisplayType = {
19 playlist?: boolean 20 playlist?: boolean
@@ -22,6 +23,7 @@ export type VideoActionsDisplayType = {
22 blacklist?: boolean 23 blacklist?: boolean
23 delete?: boolean 24 delete?: boolean
24 report?: boolean 25 report?: boolean
26 duplicate?: boolean
25} 27}
26 28
27@Component({ 29@Component({
@@ -46,7 +48,8 @@ export class VideoActionsDropdownComponent implements OnChanges {
46 update: true, 48 update: true,
47 blacklist: true, 49 blacklist: true,
48 delete: true, 50 delete: true,
49 report: true 51 report: true,
52 duplicate: true
50 } 53 }
51 @Input() placement = 'left' 54 @Input() placement = 'left'
52 55
@@ -74,6 +77,7 @@ export class VideoActionsDropdownComponent implements OnChanges {
74 private screenService: ScreenService, 77 private screenService: ScreenService,
75 private videoService: VideoService, 78 private videoService: VideoService,
76 private blocklistService: BlocklistService, 79 private blocklistService: BlocklistService,
80 private redundancyService: RedundancyService,
77 private i18n: I18n 81 private i18n: I18n
78 ) { } 82 ) { }
79 83
@@ -144,6 +148,10 @@ export class VideoActionsDropdownComponent implements OnChanges {
144 return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled 148 return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled
145 } 149 }
146 150
151 canVideoBeDuplicated () {
152 return this.video.canBeDuplicatedBy(this.user)
153 }
154
147 /* Action handlers */ 155 /* Action handlers */
148 156
149 async unblacklistVideo () { 157 async unblacklistVideo () {
@@ -186,6 +194,18 @@ export class VideoActionsDropdownComponent implements OnChanges {
186 ) 194 )
187 } 195 }
188 196
197 duplicateVideo () {
198 this.redundancyService.addVideoRedundancy(this.video)
199 .subscribe(
200 () => {
201 const message = this.i18n('This video will be duplicated by your instance.')
202 this.notifier.success(message)
203 },
204
205 err => this.notifier.error(err.message)
206 )
207 }
208
189 onVideoBlacklisted () { 209 onVideoBlacklisted () {
190 this.videoBlacklisted.emit() 210 this.videoBlacklisted.emit()
191 } 211 }
@@ -234,6 +254,12 @@ export class VideoActionsDropdownComponent implements OnChanges {
234 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoUnblacklistable() 254 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.blacklist && this.isVideoUnblacklistable()
235 }, 255 },
236 { 256 {
257 label: this.i18n('Duplicate (redundancy)'),
258 handler: () => this.duplicateVideo(),
259 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.duplicate && this.canVideoBeDuplicated(),
260 iconName: 'cloud-download'
261 },
262 {
237 label: this.i18n('Delete'), 263 label: this.i18n('Delete'),
238 handler: () => this.removeVideo(), 264 handler: () => this.removeVideo(),
239 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.delete && this.isVideoRemovable(), 265 isDisplayed: () => this.authService.isLoggedIn() && this.displayOptions.delete && this.isVideoRemovable(),
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html
index 46c49c15b..819be6d48 100644
--- a/client/src/app/shared/video/video-miniature.component.html
+++ b/client/src/app/shared/video/video-miniature.component.html
@@ -50,7 +50,7 @@
50 </div> 50 </div>
51 51
52 <div class="video-actions"> 52 <div class="video-actions">
53 <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown --> 53 <!-- FIXME: remove bottom placement when overflow is fixed in bootstrap dropdown: https://github.com/ng-bootstrap/ng-bootstrap/issues/3495 -->
54 <my-video-actions-dropdown 54 <my-video-actions-dropdown
55 *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left" 55 *ngIf="showActions" [video]="video" [displayOptions]="videoActionsDisplayOptions" placement="bottom-left bottom-right left"
56 (videoRemoved)="onVideoRemoved()" (videoBlacklisted)="onVideoBlacklisted()" (videoUnblacklisted)="onVideoUnblacklisted()" 56 (videoRemoved)="onVideoRemoved()" (videoBlacklisted)="onVideoBlacklisted()" (videoUnblacklisted)="onVideoUnblacklisted()"
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts
index 598a7a983..1dfb3eec7 100644
--- a/client/src/app/shared/video/video-miniature.component.ts
+++ b/client/src/app/shared/video/video-miniature.component.ts
@@ -64,7 +64,8 @@ export class VideoMiniatureComponent implements OnInit {
64 update: true, 64 update: true,
65 blacklist: true, 65 blacklist: true,
66 delete: true, 66 delete: true,
67 report: true 67 report: true,
68 duplicate: false
68 } 69 }
69 showActions = false 70 showActions = false
70 serverConfig: ServerConfig 71 serverConfig: ServerConfig
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index fb98d5382..546518cca 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -42,6 +42,9 @@ export class Video implements VideoServerModel {
42 dislikes: number 42 dislikes: number
43 nsfw: boolean 43 nsfw: boolean
44 44
45 originInstanceUrl: string
46 originInstanceHost: string
47
45 waitTranscoding?: boolean 48 waitTranscoding?: boolean
46 state?: VideoConstant<VideoState> 49 state?: VideoConstant<VideoState>
47 scheduledUpdate?: VideoScheduleUpdate 50 scheduledUpdate?: VideoScheduleUpdate
@@ -86,22 +89,31 @@ export class Video implements VideoServerModel {
86 this.waitTranscoding = hash.waitTranscoding 89 this.waitTranscoding = hash.waitTranscoding
87 this.state = hash.state 90 this.state = hash.state
88 this.description = hash.description 91 this.description = hash.description
92
89 this.duration = hash.duration 93 this.duration = hash.duration
90 this.durationLabel = durationToString(hash.duration) 94 this.durationLabel = durationToString(hash.duration)
95
91 this.id = hash.id 96 this.id = hash.id
92 this.uuid = hash.uuid 97 this.uuid = hash.uuid
98
93 this.isLocal = hash.isLocal 99 this.isLocal = hash.isLocal
94 this.name = hash.name 100 this.name = hash.name
101
95 this.thumbnailPath = hash.thumbnailPath 102 this.thumbnailPath = hash.thumbnailPath
96 this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath 103 this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
104
97 this.previewPath = hash.previewPath 105 this.previewPath = hash.previewPath
98 this.previewUrl = absoluteAPIUrl + hash.previewPath 106 this.previewUrl = absoluteAPIUrl + hash.previewPath
107
99 this.embedPath = hash.embedPath 108 this.embedPath = hash.embedPath
100 this.embedUrl = absoluteAPIUrl + hash.embedPath 109 this.embedUrl = absoluteAPIUrl + hash.embedPath
110
101 this.views = hash.views 111 this.views = hash.views
102 this.likes = hash.likes 112 this.likes = hash.likes
103 this.dislikes = hash.dislikes 113 this.dislikes = hash.dislikes
114
104 this.nsfw = hash.nsfw 115 this.nsfw = hash.nsfw
116
105 this.account = hash.account 117 this.account = hash.account
106 this.channel = hash.channel 118 this.channel = hash.channel
107 119
@@ -124,6 +136,9 @@ export class Video implements VideoServerModel {
124 this.blacklistedReason = hash.blacklistedReason 136 this.blacklistedReason = hash.blacklistedReason
125 137
126 this.userHistory = hash.userHistory 138 this.userHistory = hash.userHistory
139
140 this.originInstanceHost = this.account.host
141 this.originInstanceUrl = 'https://' + this.originInstanceHost
127 } 142 }
128 143
129 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) { 144 isVideoNSFWForUser (user: User, serverConfig: ServerConfig) {
@@ -152,4 +167,8 @@ export class Video implements VideoServerModel {
152 isUpdatableBy (user: AuthUser) { 167 isUpdatableBy (user: AuthUser) {
153 return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO)) 168 return user && this.isLocal === true && (this.account.name === user.username || user.hasRight(UserRight.UPDATE_ANY_VIDEO))
154 } 169 }
170
171 canBeDuplicatedBy (user: AuthUser) {
172 return user && this.isLocal === false && user.hasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES)
173 }
155} 174}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index bc3a3ffdd..a382777f5 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -188,6 +188,11 @@
188 <span class="video-attribute-value">{{ video.privacy.label }}</span> 188 <span class="video-attribute-value">{{ video.privacy.label }}</span>
189 </div> 189 </div>
190 190
191 <div *ngIf="video.isLocal === false" class="video-attribute">
192 <span i18n class="video-attribute-label">Origin instance</span>
193 <a class="video-attribute-value" target="_blank" rel="noopener noreferrer" [href]="video.originInstanceUrl">{{ video.originInstanceHost }}</a>
194 </div>
195
191 <div *ngIf="!!video.originallyPublishedAt" class="video-attribute"> 196 <div *ngIf="!!video.originallyPublishedAt" class="video-attribute">
192 <span i18n class="video-attribute-label">Originally published</span> 197 <span i18n class="video-attribute-label">Originally published</span>
193 <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span> 198 <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span>
diff --git a/client/src/assets/player/bezels/bezels-plugin.ts b/client/src/assets/player/bezels/bezels-plugin.ts
index c2c251961..499177526 100644
--- a/client/src/assets/player/bezels/bezels-plugin.ts
+++ b/client/src/assets/player/bezels/bezels-plugin.ts
@@ -1,85 +1,12 @@
1// @ts-ignore 1import videojs, { VideoJsPlayer } from 'video.js'
2import * as videojs from 'video.js' 2import './pause-bezel'
3import { VideoJSComponentInterface } from '../peertube-videojs-typings'
4 3
5function getPauseBezel () { 4const Plugin = videojs.getPlugin('plugin')
6 return `
7 <div class="vjs-bezels-pause">
8 <div class="vjs-bezel" role="status" aria-label="Pause">
9 <div class="vjs-bezel-icon">
10 <svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
11 <use class="vjs-svg-shadow" xlink:href="#vjs-id-1"></use>
12 <path class="vjs-svg-fill" d="M 12,26 16,26 16,10 12,10 z M 21,26 25,26 25,10 21,10 z" id="vjs-id-1"></path>
13 </svg>
14 </div>
15 </div>
16 </div>
17 `
18}
19
20function getPlayBezel () {
21 return `
22 <div class="vjs-bezels-play">
23 <div class="vjs-bezel" role="status" aria-label="Play">
24 <div class="vjs-bezel-icon">
25 <svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
26 <use class="vjs-svg-shadow" xlink:href="#vjs-id-2"></use>
27 <path class="vjs-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-2"></path>
28 </svg>
29 </div>
30 </div>
31 </div>
32 `
33}
34
35// @ts-ignore-start
36const Component = videojs.getComponent('Component')
37class PauseBezel extends Component {
38 options_: any
39 container: HTMLBodyElement
40
41 constructor (player: videojs.Player, options: any) {
42 super(player, options)
43 this.options_ = options
44
45 player.on('pause', (_: any) => {
46 if (player.seeking() || player.ended()) return
47 this.container.innerHTML = getPauseBezel()
48 this.showBezel()
49 })
50
51 player.on('play', (_: any) => {
52 if (player.seeking()) return
53 this.container.innerHTML = getPlayBezel()
54 this.showBezel()
55 })
56 }
57 5
58 createEl () {
59 const container = super.createEl('div', {
60 className: 'vjs-bezels-content'
61 })
62 this.container = container
63 container.style.display = 'none'
64
65 return container
66 }
67
68 showBezel () {
69 this.container.style.display = 'inherit'
70 setTimeout(() => {
71 this.container.style.display = 'none'
72 }, 500) // matching the animation duration
73 }
74}
75// @ts-ignore-end
76
77videojs.registerComponent('PauseBezel', PauseBezel)
78
79const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
80class BezelsPlugin extends Plugin { 6class BezelsPlugin extends Plugin {
81 constructor (player: videojs.Player, options: any = {}) { 7
82 super(player, options) 8 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
9 super(player)
83 10
84 this.player.ready(() => { 11 this.player.ready(() => {
85 player.addClass('vjs-bezels') 12 player.addClass('vjs-bezels')
@@ -90,4 +17,5 @@ class BezelsPlugin extends Plugin {
90} 17}
91 18
92videojs.registerPlugin('bezels', BezelsPlugin) 19videojs.registerPlugin('bezels', BezelsPlugin)
20
93export { BezelsPlugin } 21export { BezelsPlugin }
diff --git a/client/src/assets/player/bezels/pause-bezel.ts b/client/src/assets/player/bezels/pause-bezel.ts
new file mode 100644
index 000000000..98eb12099
--- /dev/null
+++ b/client/src/assets/player/bezels/pause-bezel.ts
@@ -0,0 +1,72 @@
1import videojs, { VideoJsPlayer } from 'video.js'
2
3function getPauseBezel () {
4 return `
5 <div class="vjs-bezels-pause">
6 <div class="vjs-bezel" role="status" aria-label="Pause">
7 <div class="vjs-bezel-icon">
8 <svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
9 <use class="vjs-svg-shadow" xlink:href="#vjs-id-1"></use>
10 <path class="vjs-svg-fill" d="M 12,26 16,26 16,10 12,10 z M 21,26 25,26 25,10 21,10 z" id="vjs-id-1"></path>
11 </svg>
12 </div>
13 </div>
14 </div>
15 `
16}
17
18function getPlayBezel () {
19 return `
20 <div class="vjs-bezels-play">
21 <div class="vjs-bezel" role="status" aria-label="Play">
22 <div class="vjs-bezel-icon">
23 <svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%">
24 <use class="vjs-svg-shadow" xlink:href="#vjs-id-2"></use>
25 <path class="vjs-svg-fill" d="M 12,26 18.5,22 18.5,14 12,10 z M 18.5,22 25,18 25,18 18.5,14 z" id="ytp-id-2"></path>
26 </svg>
27 </div>
28 </div>
29 </div>
30 `
31}
32
33const Component = videojs.getComponent('Component')
34class PauseBezel extends Component {
35 container: HTMLDivElement
36
37 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
38 super(player, options)
39
40 player.on('pause', (_: any) => {
41 if (player.seeking() || player.ended()) return
42 this.container.innerHTML = getPauseBezel()
43 this.showBezel()
44 })
45
46 player.on('play', (_: any) => {
47 if (player.seeking()) return
48 this.container.innerHTML = getPlayBezel()
49 this.showBezel()
50 })
51 }
52
53 createEl () {
54 this.container = super.createEl('div', {
55 className: 'vjs-bezels-content'
56 }) as HTMLDivElement
57
58 this.container.style.display = 'none'
59
60 return this.container
61 }
62
63 showBezel () {
64 this.container.style.display = 'inherit'
65
66 setTimeout(() => {
67 this.container.style.display = 'none'
68 }, 500) // matching the animation duration
69 }
70}
71
72videojs.registerComponent('PauseBezel', PauseBezel)
diff --git a/client/src/assets/player/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
new file mode 100644
index 000000000..d78e1ab90
--- /dev/null
+++ b/client/src/assets/player/p2p-media-loader/hls-plugin.ts
@@ -0,0 +1,626 @@
1// Thanks https://github.com/streamroot/videojs-hlsjs-plugin
2// We duplicated this plugin to choose the hls.js version we want, because streamroot only provide a bundled file
3
4import * as Hlsjs from 'hls.js'
5import videojs, { VideoJsPlayer } from 'video.js'
6import { HlsjsConfigHandlerOptions, QualityLevelRepresentation, QualityLevels, VideoJSTechHLS } from '../peertube-videojs-typings'
7
8type ErrorCounts = {
9 [ type: string ]: number
10}
11
12type Metadata = {
13 levels: Hlsjs.Level[]
14}
15
16const registerSourceHandler = function (vjs: typeof videojs) {
17 if (!Hlsjs.isSupported()) {
18 console.warn('Hls.js is not supported in this browser!')
19 return
20 }
21
22 const html5 = vjs.getTech('Html5')
23
24 if (!html5) {
25 console.error('Not supported version if video.js')
26 return
27 }
28
29 // FIXME: typings
30 (html5 as any).registerSourceHandler({
31 canHandleSource: function (source: videojs.Tech.SourceObject) {
32 const hlsTypeRE = /^application\/x-mpegURL|application\/vnd\.apple\.mpegurl$/i
33 const hlsExtRE = /\.m3u8/i
34
35 if (hlsTypeRE.test(source.type)) return 'probably'
36 if (hlsExtRE.test(source.src)) return 'maybe'
37
38 return ''
39 },
40
41 handleSource: function (source: videojs.Tech.SourceObject, tech: VideoJSTechHLS) {
42 if (tech.hlsProvider) {
43 tech.hlsProvider.dispose()
44 }
45
46 tech.hlsProvider = new Html5Hlsjs(vjs, source, tech)
47
48 return tech.hlsProvider
49 }
50 }, 0);
51
52 // FIXME: typings
53 (vjs as any).Html5Hlsjs = Html5Hlsjs
54}
55
56function hlsjsConfigHandler (this: VideoJsPlayer, options: HlsjsConfigHandlerOptions) {
57 const player = this
58
59 if (!options) return
60
61 if (!player.srOptions_) {
62 player.srOptions_ = {}
63 }
64
65 if (!player.srOptions_.hlsjsConfig) {
66 player.srOptions_.hlsjsConfig = options.hlsjsConfig
67 }
68
69 if (!player.srOptions_.captionConfig) {
70 player.srOptions_.captionConfig = options.captionConfig
71 }
72
73 if (options.levelLabelHandler && !player.srOptions_.levelLabelHandler) {
74 player.srOptions_.levelLabelHandler = options.levelLabelHandler
75 }
76}
77
78const registerConfigPlugin = function (vjs: typeof videojs) {
79 // Used in Brightcove since we don't pass options directly there
80 const registerVjsPlugin = vjs.registerPlugin || vjs.plugin
81 registerVjsPlugin('hlsjs', hlsjsConfigHandler)
82}
83
84class Html5Hlsjs {
85 private static readonly hooks: { [id: string]: Function[] } = {}
86
87 private readonly videoElement: HTMLVideoElement
88 private readonly errorCounts: ErrorCounts = {}
89 private readonly player: VideoJsPlayer
90 private readonly tech: videojs.Tech
91 private readonly source: videojs.Tech.SourceObject
92 private readonly vjs: typeof videojs
93
94 private hls: Hlsjs & { manualLevel?: number } // FIXME: typings
95 private hlsjsConfig: Partial<Hlsjs.Config & { cueHandler: any }> = null
96
97 private _duration: number = null
98 private metadata: Metadata = null
99 private isLive: boolean = null
100 private dvrDuration: number = null
101 private edgeMargin: number = null
102
103 private handlers: { [ id in 'play' | 'addtrack' | 'playing' | 'textTracksChange' | 'audioTracksChange' ]: EventListener } = {
104 play: null,
105 addtrack: null,
106 playing: null,
107 textTracksChange: null,
108 audioTracksChange: null
109 }
110
111 private uiTextTrackHandled = false
112
113 constructor (vjs: typeof videojs, source: videojs.Tech.SourceObject, tech: videojs.Tech) {
114 this.vjs = vjs
115 this.source = source
116
117 this.tech = tech;
118 (this.tech as any).name_ = 'Hlsjs'
119
120 this.videoElement = tech.el() as HTMLVideoElement
121 this.player = vjs((tech.options_ as any).playerId)
122
123 this.videoElement.addEventListener('error', event => {
124 let errorTxt: string
125 const mediaError = (event.currentTarget as HTMLVideoElement).error
126
127 switch (mediaError.code) {
128 case mediaError.MEDIA_ERR_ABORTED:
129 errorTxt = 'You aborted the video playback'
130 break
131 case mediaError.MEDIA_ERR_DECODE:
132 errorTxt = 'The video playback was aborted due to a corruption problem or because the video used features your browser did not support'
133 this._handleMediaError(mediaError)
134 break
135 case mediaError.MEDIA_ERR_NETWORK:
136 errorTxt = 'A network error caused the video download to fail part-way'
137 break
138 case mediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
139 errorTxt = 'The video could not be loaded, either because the server or network failed or because the format is not supported'
140 break
141
142 default:
143 errorTxt = mediaError.message
144 }
145
146 console.error('MEDIA_ERROR: ', errorTxt)
147 })
148
149 this.initialize()
150 }
151
152 duration () {
153 return this._duration || this.videoElement.duration || 0
154 }
155
156 seekable () {
157 if (this.hls.media) {
158 if (!this.isLive) {
159 return this.vjs.createTimeRanges(0, this.hls.media.duration)
160 }
161
162 // Video.js doesn't seem to like floating point timeranges
163 const startTime = Math.round(this.hls.media.duration - this.dvrDuration)
164 const endTime = Math.round(this.hls.media.duration - this.edgeMargin)
165
166 return this.vjs.createTimeRanges(startTime, endTime)
167 }
168
169 return this.vjs.createTimeRanges()
170 }
171
172 // See comment for `initialize` method.
173 dispose () {
174 this.videoElement.removeEventListener('play', this.handlers.play)
175 this.videoElement.textTracks.removeEventListener('addtrack', this.handlers.addtrack)
176 this.videoElement.removeEventListener('playing', this.handlers.playing)
177
178 this.player.textTracks().removeEventListener('change', this.handlers.textTracksChange)
179 this.uiTextTrackHandled = false
180
181 this.player.audioTracks().removeEventListener('change', this.handlers.audioTracksChange)
182
183 this.hls.destroy()
184 }
185
186 static addHook (type: string, callback: Function) {
187 Html5Hlsjs.hooks[ type ] = this.hooks[ type ] || []
188 Html5Hlsjs.hooks[ type ].push(callback)
189 }
190
191 static removeHook (type: string, callback: Function) {
192 if (Html5Hlsjs.hooks[ type ] === undefined) return false
193
194 const index = Html5Hlsjs.hooks[ type ].indexOf(callback)
195 if (index === -1) return false
196
197 Html5Hlsjs.hooks[ type ].splice(index, 1)
198
199 return true
200 }
201
202 private _executeHooksFor (type: string) {
203 if (Html5Hlsjs.hooks[ type ] === undefined) {
204 return
205 }
206
207 // ES3 and IE < 9
208 for (let i = 0; i < Html5Hlsjs.hooks[ type ].length; i++) {
209 Html5Hlsjs.hooks[ type ][ i ](this.player, this.hls)
210 }
211 }
212
213 private _handleMediaError (error: any) {
214 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 1) {
215 console.info('trying to recover media error')
216 this.hls.recoverMediaError()
217 return
218 }
219
220 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] === 2) {
221 console.info('2nd try to recover media error (by swapping audio codec')
222 this.hls.swapAudioCodec()
223 this.hls.recoverMediaError()
224 return
225 }
226
227 if (this.errorCounts[ Hlsjs.ErrorTypes.MEDIA_ERROR ] > 2) {
228 console.info('bubbling media error up to VIDEOJS')
229 this.tech.error = () => error
230 this.tech.trigger('error')
231 return
232 }
233 }
234
235 private _onError (_event: any, data: Hlsjs.errorData) {
236 const error: { message: string, code?: number } = {
237 message: `HLS.js error: ${data.type} - fatal: ${data.fatal} - ${data.details}`
238 }
239 console.error(error.message)
240
241 // increment/set error count
242 if (this.errorCounts[ data.type ]) this.errorCounts[ data.type ] += 1
243 else this.errorCounts[ data.type ] = 1
244
245 // Implement simple error handling based on hls.js documentation
246 // https://github.com/dailymotion/hls.js/blob/master/API.md#fifth-step-error-handling
247 if (data.fatal) {
248 switch (data.type) {
249 case Hlsjs.ErrorTypes.NETWORK_ERROR:
250 console.info('bubbling network error up to VIDEOJS')
251 error.code = 2
252 this.tech.error = () => error as any
253 this.tech.trigger('error')
254 break
255
256 case Hlsjs.ErrorTypes.MEDIA_ERROR:
257 error.code = 3
258 this._handleMediaError(error)
259 break
260
261 default:
262 // cannot recover
263 this.hls.destroy()
264 console.info('bubbling error up to VIDEOJS')
265 this.tech.error = () => error as any
266 this.tech.trigger('error')
267 break
268 }
269 }
270 }
271
272 private switchQuality (qualityId: number) {
273 this.hls.nextLevel = qualityId
274 }
275
276 private _levelLabel (level: Hlsjs.Level) {
277 if (this.player.srOptions_.levelLabelHandler) {
278 return this.player.srOptions_.levelLabelHandler(level)
279 }
280
281 if (level.height) return level.height + 'p'
282 if (level.width) return Math.round(level.width * 9 / 16) + 'p'
283 if (level.bitrate) return (level.bitrate / 1000) + 'kbps'
284
285 return 0
286 }
287
288 private _relayQualityChange (qualityLevels: QualityLevels) {
289 // Determine if it is "Auto" (all tracks enabled)
290 let isAuto = true
291
292 for (let i = 0; i < qualityLevels.length; i++) {
293 if (!qualityLevels[ i ]._enabled) {
294 isAuto = false
295 break
296 }
297 }
298
299 // Interact with ME
300 if (isAuto) {
301 this.hls.currentLevel = -1
302 return
303 }
304
305 // Find ID of highest enabled track
306 let selectedTrack: number
307
308 for (selectedTrack = qualityLevels.length - 1; selectedTrack >= 0; selectedTrack--) {
309 if (qualityLevels[ selectedTrack ]._enabled) {
310 break
311 }
312 }
313
314 this.hls.currentLevel = selectedTrack
315 }
316
317 private _handleQualityLevels () {
318 if (!this.metadata) return
319
320 const qualityLevels = this.player.qualityLevels && this.player.qualityLevels()
321 if (!qualityLevels) return
322
323 for (let i = 0; i < this.metadata.levels.length; i++) {
324 const details = this.metadata.levels[ i ]
325 const representation: QualityLevelRepresentation = {
326 id: i,
327 width: details.width,
328 height: details.height,
329 bandwidth: details.bitrate,
330 bitrate: details.bitrate,
331 _enabled: true
332 }
333
334 const self = this
335 representation.enabled = function (this: QualityLevels, level: number, toggle?: boolean) {
336 // Brightcove switcher works TextTracks-style (enable tracks that it wants to ABR on)
337 if (typeof toggle === 'boolean') {
338 this[ level ]._enabled = toggle
339 self._relayQualityChange(this)
340 }
341
342 return this[ level ]._enabled
343 }
344
345 qualityLevels.addQualityLevel(representation)
346 }
347 }
348
349 private _notifyVideoQualities () {
350 if (!this.metadata) return
351 const cleanTracklist = []
352
353 if (this.metadata.levels.length > 1) {
354 const autoLevel = {
355 id: -1,
356 label: 'auto',
357 selected: this.hls.manualLevel === -1
358 }
359 cleanTracklist.push(autoLevel)
360 }
361
362 this.metadata.levels.forEach((level, index) => {
363 // Don't write in level (shared reference with Hls.js)
364 const quality = {
365 id: index,
366 selected: index === this.hls.manualLevel,
367 label: this._levelLabel(level)
368 }
369
370 cleanTracklist.push(quality)
371 })
372
373 const payload = {
374 qualityData: { video: cleanTracklist },
375 qualitySwitchCallback: this.switchQuality.bind(this)
376 }
377
378 this.tech.trigger('loadedqualitydata', payload)
379
380 // Self-de-register so we don't raise the payload multiple times
381 this.videoElement.removeEventListener('playing', this.handlers.playing)
382 }
383
384 private _updateSelectedAudioTrack () {
385 const playerAudioTracks = this.tech.audioTracks()
386 for (let j = 0; j < playerAudioTracks.length; j++) {
387 // FIXME: typings
388 if ((playerAudioTracks[ j ] as any).enabled) {
389 this.hls.audioTrack = j
390 break
391 }
392 }
393 }
394
395 private _onAudioTracks () {
396 const hlsAudioTracks = this.hls.audioTracks as (AudioTrack & { name?: string, lang?: string })[] // FIXME typings
397 const playerAudioTracks = this.tech.audioTracks()
398
399 if (hlsAudioTracks.length > 1 && playerAudioTracks.length === 0) {
400 // Add Hls.js audio tracks if not added yet
401 for (let i = 0; i < hlsAudioTracks.length; i++) {
402 playerAudioTracks.addTrack(new this.vjs.AudioTrack({
403 id: i.toString(),
404 kind: 'alternative',
405 label: hlsAudioTracks[ i ].name || hlsAudioTracks[ i ].lang,
406 language: hlsAudioTracks[ i ].lang,
407 enabled: i === this.hls.audioTrack
408 }))
409 }
410
411 // Handle audio track change event
412 this.handlers.audioTracksChange = this._updateSelectedAudioTrack.bind(this)
413 playerAudioTracks.addEventListener('change', this.handlers.audioTracksChange)
414 }
415 }
416
417 private _getTextTrackLabel (textTrack: TextTrack) {
418 // Label here is readable label and is optional (used in the UI so if it is there it should be different)
419 return textTrack.label ? textTrack.label : textTrack.language
420 }
421
422 private _isSameTextTrack (track1: TextTrack, track2: TextTrack) {
423 return this._getTextTrackLabel(track1) === this._getTextTrackLabel(track2)
424 && track1.kind === track2.kind
425 }
426
427 private _updateSelectedTextTrack () {
428 const playerTextTracks = this.player.textTracks()
429 let activeTrack: TextTrack = null
430
431 for (let j = 0; j < playerTextTracks.length; j++) {
432 if (playerTextTracks[ j ].mode === 'showing') {
433 activeTrack = playerTextTracks[ j ]
434 break
435 }
436 }
437
438 const hlsjsTracks = this.videoElement.textTracks
439 for (let k = 0; k < hlsjsTracks.length; k++) {
440 if (hlsjsTracks[ k ].kind === 'subtitles' || hlsjsTracks[ k ].kind === 'captions') {
441 hlsjsTracks[ k ].mode = activeTrack && this._isSameTextTrack(hlsjsTracks[ k ], activeTrack)
442 ? 'showing'
443 : 'disabled'
444 }
445 }
446 }
447
448 private _startLoad () {
449 this.hls.startLoad(-1)
450 this.videoElement.removeEventListener('play', this.handlers.play)
451 }
452
453 private _oneLevelObjClone (obj: object) {
454 const result = {}
455 const objKeys = Object.keys(obj)
456 for (let i = 0; i < objKeys.length; i++) {
457 result[ objKeys[ i ] ] = obj[ objKeys[ i ] ]
458 }
459
460 return result
461 }
462
463 private _filterDisplayableTextTracks (textTracks: TextTrackList) {
464 const displayableTracks = []
465
466 // Filter out tracks that is displayable (captions or subtitles)
467 for (let idx = 0; idx < textTracks.length; idx++) {
468 if (textTracks[ idx ].kind === 'subtitles' || textTracks[ idx ].kind === 'captions') {
469 displayableTracks.push(textTracks[ idx ])
470 }
471 }
472
473 return displayableTracks
474 }
475
476 private _updateTextTrackList () {
477 const displayableTracks = this._filterDisplayableTextTracks(this.videoElement.textTracks)
478 const playerTextTracks = this.player.textTracks()
479
480 // Add stubs to make the caption switcher shows up
481 // Adding the Hls.js text track in will make us have double captions
482 for (let idx = 0; idx < displayableTracks.length; idx++) {
483 let isAdded = false
484
485 for (let jdx = 0; jdx < playerTextTracks.length; jdx++) {
486 if (this._isSameTextTrack(displayableTracks[ idx ], playerTextTracks[ jdx ])) {
487 isAdded = true
488 break
489 }
490 }
491
492 if (!isAdded) {
493 const hlsjsTextTrack = displayableTracks[ idx ]
494 this.player.addRemoteTextTrack({
495 kind: hlsjsTextTrack.kind as videojs.TextTrack.Kind,
496 label: this._getTextTrackLabel(hlsjsTextTrack),
497 language: hlsjsTextTrack.language,
498 srclang: hlsjsTextTrack.language
499 }, false)
500 }
501 }
502
503 // Handle UI switching
504 this._updateSelectedTextTrack()
505
506 if (!this.uiTextTrackHandled) {
507 this.handlers.textTracksChange = this._updateSelectedTextTrack.bind(this)
508 playerTextTracks.addEventListener('change', this.handlers.textTracksChange)
509
510 this.uiTextTrackHandled = true
511 }
512 }
513
514 private _onMetaData (_event: any, data: Hlsjs.manifestLoadedData) {
515 // This could arrive before 'loadedqualitydata' handlers is registered, remember it so we can raise it later
516 this.metadata = data as any
517 this._handleQualityLevels()
518 }
519
520 private _createCueHandler (captionConfig: any) {
521 return {
522 newCue: (track: any, startTime: number, endTime: number, captionScreen: { rows: any[] }) => {
523 let row: any
524 let cue: VTTCue
525 let text: string
526 const VTTCue = (window as any).VTTCue || (window as any).TextTrackCue
527
528 for (let r = 0; r < captionScreen.rows.length; r++) {
529 row = captionScreen.rows[ r ]
530 text = ''
531
532 if (!row.isEmpty()) {
533 for (let c = 0; c < row.chars.length; c++) {
534 text += row.chars[ c ].ucharj
535 }
536
537 cue = new VTTCue(startTime, endTime, text.trim())
538
539 // typeof null === 'object'
540 if (captionConfig != null && typeof captionConfig === 'object') {
541 // Copy client overridden property into the cue object
542 const configKeys = Object.keys(captionConfig)
543
544 for (let k = 0; k < configKeys.length; k++) {
545 cue[ configKeys[ k ] ] = captionConfig[ configKeys[ k ] ]
546 }
547 }
548 track.addCue(cue)
549 if (endTime === startTime) track.addCue(new VTTCue(endTime + 5, ''))
550 }
551 }
552 }
553 }
554 }
555
556 private _initHlsjs () {
557 const techOptions = this.tech.options_ as HlsjsConfigHandlerOptions
558 const srOptions_ = this.player.srOptions_
559
560 const hlsjsConfigRef = srOptions_ && srOptions_.hlsjsConfig || techOptions.hlsjsConfig
561 // Hls.js will write to the reference thus change the object for later streams
562 this.hlsjsConfig = hlsjsConfigRef ? this._oneLevelObjClone(hlsjsConfigRef) : {}
563
564 if ([ '', 'auto' ].includes(this.videoElement.preload) && !this.videoElement.autoplay && this.hlsjsConfig.autoStartLoad === undefined) {
565 this.hlsjsConfig.autoStartLoad = false
566 }
567
568 const captionConfig = srOptions_ && srOptions_.captionConfig || techOptions.captionConfig
569 if (captionConfig) {
570 this.hlsjsConfig.cueHandler = this._createCueHandler(captionConfig)
571 }
572
573 // If the user explicitly sets autoStartLoad to false, we're not going to enter the if block above
574 // That's why we have a separate if block here to set the 'play' listener
575 if (this.hlsjsConfig.autoStartLoad === false) {
576 this.handlers.play = this._startLoad.bind(this)
577 this.videoElement.addEventListener('play', this.handlers.play)
578 }
579
580 // _notifyVideoQualities sometimes runs before the quality picker event handler is registered -> no video switcher
581 this.handlers.playing = this._notifyVideoQualities.bind(this)
582 this.videoElement.addEventListener('playing', this.handlers.playing)
583
584 this.hls = new Hlsjs(this.hlsjsConfig)
585
586 this._executeHooksFor('beforeinitialize')
587
588 this.hls.on(Hlsjs.Events.ERROR, (event, data) => this._onError(event, data))
589 this.hls.on(Hlsjs.Events.AUDIO_TRACKS_UPDATED, () => this._onAudioTracks())
590 this.hls.on(Hlsjs.Events.MANIFEST_PARSED, (event, data) => this._onMetaData(event, data as any)) // FIXME: typings
591 this.hls.on(Hlsjs.Events.LEVEL_LOADED, (event, data) => {
592 // The DVR plugin will auto seek to "live edge" on start up
593 if (this.hlsjsConfig.liveSyncDuration) {
594 this.edgeMargin = this.hlsjsConfig.liveSyncDuration
595 } else if (this.hlsjsConfig.liveSyncDurationCount) {
596 this.edgeMargin = this.hlsjsConfig.liveSyncDurationCount * data.details.targetduration
597 }
598
599 this.isLive = data.details.live
600 this.dvrDuration = data.details.totalduration
601 this._duration = this.isLive ? Infinity : data.details.totalduration
602 })
603 this.hls.once(Hlsjs.Events.FRAG_LOADED, () => {
604 // Emit custom 'loadedmetadata' event for parity with `videojs-contrib-hls`
605 // Ref: https://github.com/videojs/videojs-contrib-hls#loadedmetadata
606 this.tech.trigger('loadedmetadata')
607 })
608
609 this.hls.attachMedia(this.videoElement)
610
611 this.handlers.addtrack = this._updateTextTrackList.bind(this)
612 this.videoElement.textTracks.addEventListener('addtrack', this.handlers.addtrack)
613
614 this.hls.loadSource(this.source.src)
615 }
616
617 private initialize () {
618 this._initHlsjs()
619 }
620}
621
622export {
623 Html5Hlsjs,
624 registerSourceHandler,
625 registerConfigPlugin
626}
diff --git a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
index c3f863f72..e86900faa 100644
--- a/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
+++ b/client/src/assets/player/p2p-media-loader/p2p-media-loader-plugin.ts
@@ -1,16 +1,15 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore 2import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo } from '../peertube-videojs-typings'
3import * as videojs from 'video.js'
4import { P2PMediaLoaderPluginOptions, PlayerNetworkInfo, VideoJSComponentInterface } from '../peertube-videojs-typings'
5import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs' 3import { Engine, initHlsJsPlayer, initVideoJsContribHlsJsPlayer } from 'p2p-media-loader-hlsjs'
6import { Events, Segment } from 'p2p-media-loader-core' 4import { Events, Segment } from 'p2p-media-loader-core'
7import { timeToInt } from '../utils' 5import { timeToInt } from '../utils'
6import { registerConfigPlugin, registerSourceHandler } from './hls-plugin'
7import * as Hlsjs from 'hls.js'
8 8
9// videojs-hlsjs-plugin needs videojs in window 9registerConfigPlugin(videojs)
10window['videojs'] = videojs 10registerSourceHandler(videojs)
11require('@streamroot/videojs-hlsjs-plugin')
12 11
13const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') 12const Plugin = videojs.getPlugin('plugin')
14class P2pMediaLoaderPlugin extends Plugin { 13class P2pMediaLoaderPlugin extends Plugin {
15 14
16 private readonly CONSTANTS = { 15 private readonly CONSTANTS = {
@@ -18,7 +17,7 @@ class P2pMediaLoaderPlugin extends Plugin {
18 } 17 }
19 private readonly options: P2PMediaLoaderPluginOptions 18 private readonly options: P2PMediaLoaderPluginOptions
20 19
21 private hlsjs: any // Don't type hlsjs to not bundle the module 20 private hlsjs: Hlsjs
22 private p2pEngine: Engine 21 private p2pEngine: Engine
23 private statsP2PBytes = { 22 private statsP2PBytes = {
24 pendingDownload: [] as number[], 23 pendingDownload: [] as number[],
@@ -37,12 +36,13 @@ class P2pMediaLoaderPlugin extends Plugin {
37 36
38 private networkInfoInterval: any 37 private networkInfoInterval: any
39 38
40 constructor (player: videojs.Player, options: P2PMediaLoaderPluginOptions) { 39 constructor (player: VideoJsPlayer, options?: P2PMediaLoaderPluginOptions) {
41 super(player, options) 40 super(player)
42 41
43 this.options = options 42 this.options = options
44 43
45 if (!videojs.Html5Hlsjs) { 44 // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
45 if (!(videojs as any).Html5Hlsjs) {
46 const message = 'HLS.js does not seem to be supported.' 46 const message = 'HLS.js does not seem to be supported.'
47 console.warn(message) 47 console.warn(message)
48 48
@@ -50,7 +50,8 @@ class P2pMediaLoaderPlugin extends Plugin {
50 return 50 return
51 } 51 }
52 52
53 videojs.Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => { 53 // FIXME: typings https://github.com/Microsoft/TypeScript/issues/14080
54 (videojs as any).Html5Hlsjs.addHook('beforeinitialize', (videojsPlayer: any, hlsjs: any) => {
54 this.hlsjs = hlsjs 55 this.hlsjs = hlsjs
55 }) 56 })
56 57
@@ -84,12 +85,11 @@ class P2pMediaLoaderPlugin extends Plugin {
84 private initialize () { 85 private initialize () {
85 initHlsJsPlayer(this.hlsjs) 86 initHlsJsPlayer(this.hlsjs)
86 87
87 const tech = this.player.tech_ 88 // FIXME: typings
88 this.p2pEngine = tech.options_.hlsjsConfig.loader.getEngine() 89 const options = this.player.tech(true).options_ as any
90 this.p2pEngine = options.hlsjsConfig.loader.getEngine()
89 91
90 // Avoid using constants to not import hls.hs 92 this.hlsjs.on(Hlsjs.Events.LEVEL_SWITCHING, (_: any, data: any) => {
91 // https://github.com/video-dev/hls.js/blob/master/src/events.js#L37
92 this.hlsjs.on('hlsLevelSwitching', (_: any, data: any) => {
93 this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height }) 93 this.trigger('resolutionChange', { auto: this.hlsjs.autoLevelEnabled, resolutionId: data.height })
94 }) 94 })
95 95
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index d9e02cd7d..dbf631a5e 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -1,21 +1,26 @@
1import { VideoFile } from '../../../../shared/models/videos' 1import { VideoFile } from '../../../../shared/models/videos'
2// @ts-ignore 2import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from 'video.js'
3import * as videojs from 'video.js'
4import 'videojs-hotkeys' 3import 'videojs-hotkeys'
5import 'videojs-dock' 4import 'videojs-dock'
6import 'videojs-contextmenu-ui' 5import 'videojs-contextmenu-ui'
7import 'videojs-contrib-quality-levels' 6import 'videojs-contrib-quality-levels'
7import './upnext/end-card'
8import './upnext/upnext-plugin' 8import './upnext/upnext-plugin'
9import './bezels/bezels-plugin' 9import './bezels/bezels-plugin'
10import './peertube-plugin' 10import './peertube-plugin'
11import './videojs-components/next-video-button' 11import './videojs-components/next-video-button'
12import './videojs-components/p2p-info-button'
12import './videojs-components/peertube-link-button' 13import './videojs-components/peertube-link-button'
14import './videojs-components/peertube-load-progress-bar'
13import './videojs-components/resolution-menu-button' 15import './videojs-components/resolution-menu-button'
16import './videojs-components/resolution-menu-item'
17import './videojs-components/settings-dialog'
14import './videojs-components/settings-menu-button' 18import './videojs-components/settings-menu-button'
15import './videojs-components/p2p-info-button' 19import './videojs-components/settings-menu-item'
16import './videojs-components/peertube-load-progress-bar' 20import './videojs-components/settings-panel'
21import './videojs-components/settings-panel-child'
17import './videojs-components/theater-button' 22import './videojs-components/theater-button'
18import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions, videojsUntyped } from './peertube-videojs-typings' 23import { P2PMediaLoaderPluginOptions, UserWatching, VideoJSCaption, VideoJSPluginOptions } from './peertube-videojs-typings'
19import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils' 24import { buildVideoEmbed, buildVideoLink, copyToClipboard, getRtcConfig } from './utils'
20import { isDefaultLocale } from '../../../../shared/models/i18n/i18n' 25import { isDefaultLocale } from '../../../../shared/models/i18n/i18n'
21import { segmentValidatorFactory } from './p2p-media-loader/segment-validator' 26import { segmentValidatorFactory } from './p2p-media-loader/segment-validator'
@@ -24,12 +29,17 @@ import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
24import { getStoredP2PEnabled } from './peertube-player-local-storage' 29import { getStoredP2PEnabled } from './peertube-player-local-storage'
25import { TranslationsManager } from './translations-manager' 30import { TranslationsManager } from './translations-manager'
26 31
32// For VideoJS
33(window as any).WebVTT = require('vtt.js/lib/vtt.js').WebVTT;
34
27// Change 'Playback Rate' to 'Speed' (smaller for our settings menu) 35// Change 'Playback Rate' to 'Speed' (smaller for our settings menu)
28videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' 36(videojs.getComponent('PlaybackRateMenuButton') as any).prototype.controlText_ = 'Speed'
37
38const CaptionsButton = videojs.getComponent('CaptionsButton') as any
29// Change Captions to Subtitles/CC 39// Change Captions to Subtitles/CC
30videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitles/CC' 40CaptionsButton.prototype.controlText_ = 'Subtitles/CC'
31// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know) 41// We just want to display 'Off' instead of 'captions off', keep a space so the variable == true (hacky I know)
32videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' ' 42CaptionsButton.prototype.label_ = ' '
33 43
34export type PlayerMode = 'webtorrent' | 'p2p-media-loader' 44export type PlayerMode = 'webtorrent' | 'p2p-media-loader'
35 45
@@ -92,9 +102,9 @@ export type PeertubePlayerManagerOptions = {
92 102
93export class PeertubePlayerManager { 103export class PeertubePlayerManager {
94 private static playerElementClassName: string 104 private static playerElementClassName: string
95 private static onPlayerChange: (player: any) => void 105 private static onPlayerChange: (player: VideoJsPlayer) => void
96 106
97 static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: any) => void) { 107 static async initialize (mode: PlayerMode, options: PeertubePlayerManagerOptions, onPlayerChange: (player: VideoJsPlayer) => void) {
98 let p2pMediaLoader: any 108 let p2pMediaLoader: any
99 109
100 this.onPlayerChange = onPlayerChange 110 this.onPlayerChange = onPlayerChange
@@ -114,12 +124,12 @@ export class PeertubePlayerManager {
114 124
115 const self = this 125 const self = this
116 return new Promise(res => { 126 return new Promise(res => {
117 videojs(options.common.playerElement, videojsOptions, function (this: any) { 127 videojs(options.common.playerElement, videojsOptions, function (this: VideoJsPlayer) {
118 const player = this 128 const player = this
119 129
120 let alreadyFallback = false 130 let alreadyFallback = false
121 131
122 player.tech_.one('error', () => { 132 player.tech(true).one('error', () => {
123 if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options) 133 if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
124 alreadyFallback = true 134 alreadyFallback = true
125 }) 135 })
@@ -164,7 +174,7 @@ export class PeertubePlayerManager {
164 const videojsOptions = this.getVideojsOptions(mode, options) 174 const videojsOptions = this.getVideojsOptions(mode, options)
165 175
166 const self = this 176 const self = this
167 videojs(newVideoElement, videojsOptions, function (this: any) { 177 videojs(newVideoElement, videojsOptions, function (this: VideoJsPlayer) {
168 const player = this 178 const player = this
169 179
170 self.addContextMenu(mode, player, options.common.embedUrl) 180 self.addContextMenu(mode, player, options.common.embedUrl)
@@ -173,7 +183,11 @@ export class PeertubePlayerManager {
173 }) 183 })
174 } 184 }
175 185
176 private static getVideojsOptions (mode: PlayerMode, options: PeertubePlayerManagerOptions, p2pMediaLoaderModule?: any) { 186 private static getVideojsOptions (
187 mode: PlayerMode,
188 options: PeertubePlayerManagerOptions,
189 p2pMediaLoaderModule?: any
190 ): VideoJsPlayerOptions {
177 const commonOptions = options.common 191 const commonOptions = options.common
178 192
179 let autoplay = commonOptions.autoplay 193 let autoplay = commonOptions.autoplay
@@ -197,9 +211,9 @@ export class PeertubePlayerManager {
197 } 211 }
198 212
199 if (mode === 'p2p-media-loader') { 213 if (mode === 'p2p-media-loader') {
200 const { streamrootHls } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule) 214 const { hlsjs } = PeertubePlayerManager.addP2PMediaLoaderOptions(plugins, options, p2pMediaLoaderModule)
201 215
202 html5 = streamrootHls.html5 216 html5 = hlsjs.html5
203 } 217 }
204 218
205 if (mode === 'webtorrent') { 219 if (mode === 'webtorrent') {
@@ -213,7 +227,7 @@ export class PeertubePlayerManager {
213 html5, 227 html5,
214 228
215 // We don't use text track settings for now 229 // We don't use text track settings for now
216 textTrackSettings: false, 230 textTrackSettings: false as any, // FIXME: typings
217 controls: commonOptions.controls !== undefined ? commonOptions.controls : true, 231 controls: commonOptions.controls !== undefined ? commonOptions.controls : true,
218 loop: commonOptions.loop !== undefined ? commonOptions.loop : false, 232 loop: commonOptions.loop !== undefined ? commonOptions.loop : false,
219 233
@@ -237,7 +251,7 @@ export class PeertubePlayerManager {
237 peertubeLink: commonOptions.peertubeLink, 251 peertubeLink: commonOptions.peertubeLink,
238 theaterButton: commonOptions.theaterButton, 252 theaterButton: commonOptions.theaterButton,
239 nextVideo: commonOptions.nextVideo 253 nextVideo: commonOptions.nextVideo
240 }) 254 }) as any // FIXME: typings
241 } 255 }
242 } 256 }
243 257
@@ -289,7 +303,7 @@ export class PeertubePlayerManager {
289 swarmId: p2pMediaLoaderOptions.playlistUrl 303 swarmId: p2pMediaLoaderOptions.playlistUrl
290 } 304 }
291 } 305 }
292 const streamrootHls = { 306 const hlsjs = {
293 levelLabelHandler: (level: { height: number, width: number }) => { 307 levelLabelHandler: (level: { height: number, width: number }) => {
294 const file = p2pMediaLoaderOptions.videoFiles.find(f => f.resolution.id === level.height) 308 const file = p2pMediaLoaderOptions.videoFiles.find(f => f.resolution.id === level.height)
295 309
@@ -308,7 +322,7 @@ export class PeertubePlayerManager {
308 } 322 }
309 } 323 }
310 324
311 const toAssign = { p2pMediaLoader, streamrootHls } 325 const toAssign = { p2pMediaLoader, hlsjs }
312 Object.assign(plugins, toAssign) 326 Object.assign(plugins, toAssign)
313 327
314 return toAssign 328 return toAssign
@@ -406,7 +420,7 @@ export class PeertubePlayerManager {
406 return children 420 return children
407 } 421 }
408 422
409 private static addContextMenu (mode: PlayerMode, player: any, videoEmbedUrl: string) { 423 private static addContextMenu (mode: PlayerMode, player: VideoJsPlayer, videoEmbedUrl: string) {
410 const content = [ 424 const content = [
411 { 425 {
412 label: player.localize('Copy the video URL'), 426 label: player.localize('Copy the video URL'),
@@ -416,9 +430,8 @@ export class PeertubePlayerManager {
416 }, 430 },
417 { 431 {
418 label: player.localize('Copy the video URL at the current time'), 432 label: player.localize('Copy the video URL at the current time'),
419 listener: function () { 433 listener: function (this: VideoJsPlayer) {
420 const player = this as videojs.Player 434 copyToClipboard(buildVideoLink({ startTime: this.currentTime() }))
421 copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
422 } 435 }
423 }, 436 },
424 { 437 {
@@ -432,9 +445,8 @@ export class PeertubePlayerManager {
432 if (mode === 'webtorrent') { 445 if (mode === 'webtorrent') {
433 content.push({ 446 content.push({
434 label: player.localize('Copy magnet URI'), 447 label: player.localize('Copy magnet URI'),
435 listener: function () { 448 listener: function (this: VideoJsPlayer) {
436 const player = this as videojs.Player 449 copyToClipboard(this.webtorrent().getCurrentVideoFile().magnetUri)
437 copyToClipboard(player.webtorrent().getCurrentVideoFile().magnetUri)
438 } 450 }
439 }) 451 })
440 } 452 }
@@ -472,7 +484,8 @@ export class PeertubePlayerManager {
472 return event.key === '>' 484 return event.key === '>'
473 }, 485 },
474 handler: function (player: videojs.Player) { 486 handler: function (player: videojs.Player) {
475 player.playbackRate((player.playbackRate() + 0.1).toFixed(2)) 487 const newValue = Math.min(player.playbackRate() + 0.1, 5)
488 player.playbackRate(parseFloat(newValue.toFixed(2)))
476 } 489 }
477 }, 490 },
478 decreasePlaybackRateKey: { 491 decreasePlaybackRateKey: {
@@ -480,7 +493,8 @@ export class PeertubePlayerManager {
480 return event.key === '<' 493 return event.key === '<'
481 }, 494 },
482 handler: function (player: videojs.Player) { 495 handler: function (player: videojs.Player) {
483 player.playbackRate((player.playbackRate() - 0.1).toFixed(2)) 496 const newValue = Math.max(player.playbackRate() - 0.1, 0.10)
497 player.playbackRate(parseFloat(newValue.toFixed(2)))
484 } 498 }
485 }, 499 },
486 frameByFrame: { 500 frameByFrame: {
diff --git a/client/src/assets/player/peertube-plugin.ts b/client/src/assets/player/peertube-plugin.ts
index 9824c43b5..19d104676 100644
--- a/client/src/assets/player/peertube-plugin.ts
+++ b/client/src/assets/player/peertube-plugin.ts
@@ -1,14 +1,10 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore
3import * as videojs from 'video.js'
4import './videojs-components/settings-menu-button' 2import './videojs-components/settings-menu-button'
5import { 3import {
6 PeerTubePluginOptions, 4 PeerTubePluginOptions,
7 ResolutionUpdateData, 5 ResolutionUpdateData,
8 UserWatching, 6 UserWatching,
9 VideoJSCaption, 7 VideoJSCaption
10 VideoJSComponentInterface,
11 videojsUntyped
12} from './peertube-videojs-typings' 8} from './peertube-videojs-typings'
13import { isMobile, timeToInt } from './utils' 9import { isMobile, timeToInt } from './utils'
14import { 10import {
@@ -20,7 +16,8 @@ import {
20 saveVolumeInStore 16 saveVolumeInStore
21} from './peertube-player-local-storage' 17} from './peertube-player-local-storage'
22 18
23const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') 19const Plugin = videojs.getPlugin('plugin')
20
24class PeerTubePlugin extends Plugin { 21class PeerTubePlugin extends Plugin {
25 private readonly videoViewUrl: string 22 private readonly videoViewUrl: string
26 private readonly videoDuration: number 23 private readonly videoDuration: number
@@ -28,7 +25,6 @@ class PeerTubePlugin extends Plugin {
28 USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video 25 USER_WATCHING_VIDEO_INTERVAL: 5000 // Every 5 seconds, notify the user is watching the video
29 } 26 }
30 27
31 private player: any
32 private videoCaptions: VideoJSCaption[] 28 private videoCaptions: VideoJSCaption[]
33 private defaultSubtitle: string 29 private defaultSubtitle: string
34 30
@@ -40,8 +36,8 @@ class PeerTubePlugin extends Plugin {
40 private mouseInControlBar = false 36 private mouseInControlBar = false
41 private readonly savedInactivityTimeout: number 37 private readonly savedInactivityTimeout: number
42 38
43 constructor (player: videojs.Player, options: PeerTubePluginOptions) { 39 constructor (player: VideoJsPlayer, options?: PeerTubePluginOptions) {
44 super(player, options) 40 super(player)
45 41
46 this.videoViewUrl = options.videoViewUrl 42 this.videoViewUrl = options.videoViewUrl
47 this.videoDuration = options.videoDuration 43 this.videoDuration = options.videoDuration
@@ -67,7 +63,7 @@ class PeerTubePlugin extends Plugin {
67 this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d)) 63 this.player.p2pMediaLoader().on('resolutionChange', (_: any, d: any) => this.handleResolutionChange(d))
68 } 64 }
69 65
70 this.player.tech_.on('loadedqualitydata', () => { 66 this.player.tech(true).on('loadedqualitydata', () => {
71 setTimeout(() => { 67 setTimeout(() => {
72 // Replay a resolution change, now we loaded all quality data 68 // Replay a resolution change, now we loaded all quality data
73 if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange) 69 if (this.lastResolutionChange) this.handleResolutionChange(this.lastResolutionChange)
@@ -102,7 +98,7 @@ class PeerTubePlugin extends Plugin {
102 } 98 }
103 99
104 this.player.textTracks().on('change', () => { 100 this.player.textTracks().on('change', () => {
105 const showing = this.player.textTracks().tracks_.find((t: { kind: string, mode: string }) => { 101 const showing = this.player.textTracks().tracks_.find(t => {
106 return t.kind === 'captions' && t.mode === 'showing' 102 return t.kind === 'captions' && t.mode === 'showing'
107 }) 103 })
108 104
@@ -262,7 +258,7 @@ class PeerTubePlugin extends Plugin {
262 258
263 // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657 259 // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657
264 private initSmoothProgressBar () { 260 private initSmoothProgressBar () {
265 const SeekBar = videojsUntyped.getComponent('SeekBar') 261 const SeekBar = videojs.getComponent('SeekBar') as any
266 SeekBar.prototype.getPercent = function getPercent () { 262 SeekBar.prototype.getPercent = function getPercent () {
267 // Allows for smooth scrubbing, when player can't keep up. 263 // Allows for smooth scrubbing, when player can't keep up.
268 // const time = (this.player_.scrubbing()) ? 264 // const time = (this.player_.scrubbing()) ?
diff --git a/client/src/assets/player/peertube-videojs-typings.ts b/client/src/assets/player/peertube-videojs-typings.ts
index aad4dbb4f..a4e4c580c 100644
--- a/client/src/assets/player/peertube-videojs-typings.ts
+++ b/client/src/assets/player/peertube-videojs-typings.ts
@@ -1,28 +1,81 @@
1// FIXME: something weird with our path definition in tsconfig and typings
2// @ts-ignore
3import * as videojs from 'video.js'
4
5import { PeerTubePlugin } from './peertube-plugin' 1import { PeerTubePlugin } from './peertube-plugin'
6import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin' 2import { WebTorrentPlugin } from './webtorrent/webtorrent-plugin'
7import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin' 3import { P2pMediaLoaderPlugin } from './p2p-media-loader/p2p-media-loader-plugin'
8import { PlayerMode } from './peertube-player-manager' 4import { PlayerMode } from './peertube-player-manager'
9import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager' 5import { RedundancyUrlManager } from './p2p-media-loader/redundancy-url-manager'
10import { VideoFile } from '@shared/models' 6import { VideoFile } from '@shared/models'
7import videojs from 'video.js'
8import { Config, Level } from 'hls.js'
9
10declare module 'video.js' {
11
12 export interface VideoJsPlayer {
13 srOptions_: HlsjsConfigHandlerOptions
14
15 theaterEnabled: boolean
16
17 // FIXME: add it to upstream typings
18 posterImage: {
19 show (): void
20 hide (): void
21 }
22
23 handleTechSeeked_ (): void
24
25 // Plugins
11 26
12declare namespace videojs {
13 interface Player {
14 peertube (): PeerTubePlugin 27 peertube (): PeerTubePlugin
28
15 webtorrent (): WebTorrentPlugin 29 webtorrent (): WebTorrentPlugin
30
16 p2pMediaLoader (): P2pMediaLoaderPlugin 31 p2pMediaLoader (): P2pMediaLoaderPlugin
32
33 contextmenuUI (options: any): any
34
35 bezels (): void
36
37 qualityLevels (): QualityLevels
38
39 textTracks (): TextTrackList & {
40 on: Function
41 tracks_: { kind: string, mode: string, language: string }[]
42 }
43
44 audioTracks (): AudioTrackList
45
46 dock (options: { title: string, description: string }): void
17 } 47 }
18} 48}
19 49
20interface VideoJSComponentInterface { 50export interface VideoJSTechHLS extends videojs.Tech {
21 _player: videojs.Player 51 hlsProvider: any // FIXME: typings
52}
53
54export interface HlsjsConfigHandlerOptions {
55 hlsjsConfig?: Config & { cueHandler: any }// FIXME: typings
56 captionConfig?: any // FIXME: typings
57
58 levelLabelHandler?: (level: Level) => string
59}
60
61type QualityLevelRepresentation = {
62 id: number
63 height: number
64
65 label?: string
66 width?: number
67 bandwidth?: number
68 bitrate?: number
22 69
23 new (player: videojs.Player, options?: any): any 70 enabled?: Function
71 _enabled: boolean
72}
73
74type QualityLevels = QualityLevelRepresentation[] & {
75 selectedIndex: number
76 selectedIndex_: number
24 77
25 registerComponent (name: string, obj: any): any 78 addQualityLevel (representation: QualityLevelRepresentation): void
26} 79}
27 80
28type VideoJSCaption = { 81type VideoJSCaption = {
@@ -78,9 +131,6 @@ type VideoJSPluginOptions = {
78 p2pMediaLoader?: P2PMediaLoaderPluginOptions 131 p2pMediaLoader?: P2PMediaLoaderPluginOptions
79} 132}
80 133
81// videojs typings don't have some method we need
82const videojsUntyped = videojs as any
83
84type LoadedQualityData = { 134type LoadedQualityData = {
85 qualitySwitchCallback: Function, 135 qualitySwitchCallback: Function,
86 qualityData: { 136 qualityData: {
@@ -123,13 +173,13 @@ export {
123 PlayerNetworkInfo, 173 PlayerNetworkInfo,
124 ResolutionUpdateData, 174 ResolutionUpdateData,
125 AutoResolutionUpdateData, 175 AutoResolutionUpdateData,
126 VideoJSComponentInterface,
127 videojsUntyped,
128 VideoJSCaption, 176 VideoJSCaption,
129 UserWatching, 177 UserWatching,
130 PeerTubePluginOptions, 178 PeerTubePluginOptions,
131 WebtorrentPluginOptions, 179 WebtorrentPluginOptions,
132 P2PMediaLoaderPluginOptions, 180 P2PMediaLoaderPluginOptions,
133 VideoJSPluginOptions, 181 VideoJSPluginOptions,
134 LoadedQualityData 182 LoadedQualityData,
183 QualityLevelRepresentation,
184 QualityLevels
135} 185}
diff --git a/client/src/assets/player/upnext/end-card.ts b/client/src/assets/player/upnext/end-card.ts
new file mode 100644
index 000000000..d121a83a9
--- /dev/null
+++ b/client/src/assets/player/upnext/end-card.ts
@@ -0,0 +1,155 @@
1import videojs, { VideoJsPlayer } from 'video.js'
2
3function getMainTemplate (options: any) {
4 return `
5 <div class="vjs-upnext-top">
6 <span class="vjs-upnext-headtext">${options.headText}</span>
7 <div class="vjs-upnext-title"></div>
8 </div>
9 <div class="vjs-upnext-autoplay-icon">
10 <svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
11 <circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
12 <circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle>
13 <polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
14 </div>
15 <span class="vjs-upnext-bottom">
16 <span class="vjs-upnext-cancel">
17 <button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
18 </span>
19 <span class="vjs-upnext-suspended">${options.suspendedText}</span>
20 </span>
21 `
22}
23
24export interface EndCardOptions extends videojs.ComponentOptions {
25 next: Function,
26 getTitle: () => string
27 timeout: number
28 cancelText: string
29 headText: string
30 suspendedText: string
31 condition: () => boolean
32 suspended: () => boolean
33}
34
35const Component = videojs.getComponent('Component')
36class EndCard extends Component {
37 options_: EndCardOptions
38
39 dashOffsetTotal = 586
40 dashOffsetStart = 293
41 interval = 50
42 upNextEvents = new videojs.EventTarget()
43 ticks = 0
44 totalTicks: number
45
46 container: HTMLDivElement
47 title: HTMLElement
48 autoplayRing: HTMLElement
49 cancelButton: HTMLElement
50 suspendedMessage: HTMLElement
51 nextButton: HTMLElement
52
53 constructor (player: VideoJsPlayer, options: EndCardOptions) {
54 super(player, options)
55
56 this.totalTicks = this.options_.timeout / this.interval
57
58 player.on('ended', (_: any) => {
59 if (!this.options_.condition()) return
60
61 player.addClass('vjs-upnext--showing')
62 this.showCard((canceled: boolean) => {
63 player.removeClass('vjs-upnext--showing')
64 this.container.style.display = 'none'
65 if (!canceled) {
66 this.options_.next()
67 }
68 })
69 })
70
71 player.on('playing', () => {
72 this.upNextEvents.trigger('playing')
73 })
74 }
75
76 createEl () {
77 const container = super.createEl('div', {
78 className: 'vjs-upnext-content',
79 innerHTML: getMainTemplate(this.options_)
80 }) as HTMLDivElement
81
82 this.container = container
83 container.style.display = 'none'
84
85 this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0] as HTMLElement
86 this.title = container.getElementsByClassName('vjs-upnext-title')[0] as HTMLElement
87 this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0] as HTMLElement
88 this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0] as HTMLElement
89 this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0] as HTMLElement
90
91 this.cancelButton.onclick = () => {
92 this.upNextEvents.trigger('cancel')
93 }
94
95 this.nextButton.onclick = () => {
96 this.upNextEvents.trigger('next')
97 }
98
99 return container
100 }
101
102 showCard (cb: Function) {
103 let timeout: any
104
105 this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
106 this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
107
108 this.title.innerHTML = this.options_.getTitle()
109
110 this.upNextEvents.one('cancel', () => {
111 clearTimeout(timeout)
112 cb(true)
113 })
114
115 this.upNextEvents.one('playing', () => {
116 clearTimeout(timeout)
117 cb(true)
118 })
119
120 this.upNextEvents.one('next', () => {
121 clearTimeout(timeout)
122 cb(false)
123 })
124
125 const goToPercent = (percent: number) => {
126 const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
127 this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
128 }
129
130 const tick = () => {
131 goToPercent((this.ticks++) * 100 / this.totalTicks)
132 }
133
134 const update = () => {
135 if (this.options_.suspended()) {
136 this.suspendedMessage.innerText = this.options_.suspendedText
137 goToPercent(0)
138 this.ticks = 0
139 timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
140 } else if (this.ticks >= this.totalTicks) {
141 clearTimeout(timeout)
142 cb(false)
143 } else {
144 this.suspendedMessage.innerText = ''
145 tick()
146 timeout = setTimeout(update.bind(this), this.interval)
147 }
148 }
149
150 this.container.style.display = 'block'
151 timeout = setTimeout(update.bind(this), this.interval)
152 }
153}
154
155videojs.registerComponent('EndCard', EndCard)
diff --git a/client/src/assets/player/upnext/upnext-plugin.ts b/client/src/assets/player/upnext/upnext-plugin.ts
index a3747b25f..6512fec2c 100644
--- a/client/src/assets/player/upnext/upnext-plugin.ts
+++ b/client/src/assets/player/upnext/upnext-plugin.ts
@@ -1,154 +1,11 @@
1// @ts-ignore 1import videojs, { VideoJsPlayer } from 'video.js'
2import * as videojs from 'video.js' 2import { EndCardOptions } from './end-card'
3import { VideoJSComponentInterface } from '../peertube-videojs-typings'
4 3
5function getMainTemplate (options: any) { 4const Plugin = videojs.getPlugin('plugin')
6 return `
7 <div class="vjs-upnext-top">
8 <span class="vjs-upnext-headtext">${options.headText}</span>
9 <div class="vjs-upnext-title"></div>
10 </div>
11 <div class="vjs-upnext-autoplay-icon">
12 <svg height="100%" version="1.1" viewbox="0 0 98 98" width="100%">
13 <circle class="vjs-upnext-svg-autoplay-circle" cx="49" cy="49" fill="#000" fill-opacity="0.8" r="48"></circle>
14 <circle class="vjs-upnext-svg-autoplay-ring" cx="-49" cy="49" fill-opacity="0" r="46.5" stroke="#FFFFFF" stroke-width="4" transform="rotate(-90)"></circle>
15 <polygon class="vjs-upnext-svg-autoplay-triangle" fill="#fff" points="32,27 72,49 32,71"></polygon></svg>
16 </div>
17 <span class="vjs-upnext-bottom">
18 <span class="vjs-upnext-cancel">
19 <button class="vjs-upnext-cancel-button" tabindex="0" aria-label="Cancel autoplay">${options.cancelText}</button>
20 </span>
21 <span class="vjs-upnext-suspended">${options.suspendedText}</span>
22 </span>
23 `
24}
25
26// @ts-ignore-start
27const Component = videojs.getComponent('Component')
28class EndCard extends Component {
29 options_: any
30 dashOffsetTotal = 586
31 dashOffsetStart = 293
32 interval = 50
33 upNextEvents = new videojs.EventTarget()
34 ticks = 0
35 totalTicks: number
36
37 container: HTMLElement
38 title: HTMLElement
39 autoplayRing: HTMLElement
40 cancelButton: HTMLElement
41 suspendedMessage: HTMLElement
42 nextButton: HTMLElement
43
44 constructor (player: videojs.Player, options: any) {
45 super(player, options)
46
47 this.totalTicks = this.options_.timeout / this.interval
48
49 player.on('ended', (_: any) => {
50 if (!this.options_.condition()) return
51
52 player.addClass('vjs-upnext--showing')
53 this.showCard((canceled: boolean) => {
54 player.removeClass('vjs-upnext--showing')
55 this.container.style.display = 'none'
56 if (!canceled) {
57 this.options_.next()
58 }
59 })
60 })
61
62 player.on('playing', () => {
63 this.upNextEvents.trigger('playing')
64 })
65 }
66
67 createEl () {
68 const container = super.createEl('div', {
69 className: 'vjs-upnext-content',
70 innerHTML: getMainTemplate(this.options_)
71 })
72
73 this.container = container
74 container.style.display = 'none'
75
76 this.autoplayRing = container.getElementsByClassName('vjs-upnext-svg-autoplay-ring')[0]
77 this.title = container.getElementsByClassName('vjs-upnext-title')[0]
78 this.cancelButton = container.getElementsByClassName('vjs-upnext-cancel-button')[0]
79 this.suspendedMessage = container.getElementsByClassName('vjs-upnext-suspended')[0]
80 this.nextButton = container.getElementsByClassName('vjs-upnext-autoplay-icon')[0]
81
82 this.cancelButton.onclick = () => {
83 this.upNextEvents.trigger('cancel')
84 }
85
86 this.nextButton.onclick = () => {
87 this.upNextEvents.trigger('next')
88 }
89
90 return container
91 }
92 5
93 showCard (cb: Function) {
94 let timeout: any
95
96 this.autoplayRing.setAttribute('stroke-dasharray', '' + this.dashOffsetStart)
97 this.autoplayRing.setAttribute('stroke-dashoffset', '' + -this.dashOffsetStart)
98
99 this.title.innerHTML = this.options_.getTitle()
100
101 this.upNextEvents.one('cancel', () => {
102 clearTimeout(timeout)
103 cb(true)
104 })
105
106 this.upNextEvents.one('playing', () => {
107 clearTimeout(timeout)
108 cb(true)
109 })
110
111 this.upNextEvents.one('next', () => {
112 clearTimeout(timeout)
113 cb(false)
114 })
115
116 const goToPercent = (percent: number) => {
117 const newOffset = Math.max(-this.dashOffsetTotal, - this.dashOffsetStart - percent * this.dashOffsetTotal / 2 / 100)
118 this.autoplayRing.setAttribute('stroke-dashoffset', '' + newOffset)
119 }
120
121 const tick = () => {
122 goToPercent((this.ticks++) * 100 / this.totalTicks)
123 }
124
125 const update = () => {
126 if (this.options_.suspended()) {
127 this.suspendedMessage.innerText = this.options_.suspendedText
128 goToPercent(0)
129 this.ticks = 0
130 timeout = setTimeout(update.bind(this), 300) // checks once supsended can be a bit longer
131 } else if (this.ticks >= this.totalTicks) {
132 clearTimeout(timeout)
133 cb(false)
134 } else {
135 this.suspendedMessage.innerText = ''
136 tick()
137 timeout = setTimeout(update.bind(this), this.interval)
138 }
139 }
140
141 this.container.style.display = 'block'
142 timeout = setTimeout(update.bind(this), this.interval)
143 }
144}
145// @ts-ignore-end
146
147videojs.registerComponent('EndCard', EndCard)
148
149const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin')
150class UpNextPlugin extends Plugin { 6class UpNextPlugin extends Plugin {
151 constructor (player: videojs.Player, options: any = {}) { 7
8 constructor (player: VideoJsPlayer, options: Partial<EndCardOptions> = {}) {
152 const settings = { 9 const settings = {
153 next: options.next, 10 next: options.next,
154 getTitle: options.getTitle, 11 getTitle: options.getTitle,
@@ -160,7 +17,7 @@ class UpNextPlugin extends Plugin {
160 suspended: options.suspended 17 suspended: options.suspended
161 } 18 }
162 19
163 super(player, settings) 20 super(player)
164 21
165 this.player.ready(() => { 22 this.player.ready(() => {
166 player.addClass('vjs-upnext') 23 player.addClass('vjs-upnext')
diff --git a/client/src/assets/player/videojs-components/next-video-button.ts b/client/src/assets/player/videojs-components/next-video-button.ts
index bf5c1aba4..bdb245dcc 100644
--- a/client/src/assets/player/videojs-components/next-video-button.ts
+++ b/client/src/assets/player/videojs-components/next-video-button.ts
@@ -1,21 +1,25 @@
1import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 1import videojs, { VideoJsPlayer } from 'video.js'
2// FIXME: something weird with our path definition in tsconfig and typings
3// @ts-ignore
4import { Player } from 'video.js'
5 2
6const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 3const Button = videojs.getComponent('Button')
4
5export interface NextVideoButtonOptions extends videojs.ComponentOptions {
6 handler: Function
7}
7 8
8class NextVideoButton extends Button { 9class NextVideoButton extends Button {
10 private readonly nextVideoButtonOptions: NextVideoButtonOptions
9 11
10 constructor (player: Player, options: any) { 12 constructor (player: VideoJsPlayer, options?: NextVideoButtonOptions) {
11 super(player, options) 13 super(player, options)
14
15 this.nextVideoButtonOptions = options
12 } 16 }
13 17
14 createEl () { 18 createEl () {
15 const button = videojsUntyped.dom.createEl('button', { 19 const button = videojs.dom.createEl('button', {
16 className: 'vjs-next-video' 20 className: 'vjs-next-video'
17 }) 21 }) as HTMLButtonElement
18 const nextIcon = videojsUntyped.dom.createEl('span', { 22 const nextIcon = videojs.dom.createEl('span', {
19 className: 'icon icon-next' 23 className: 'icon icon-next'
20 }) 24 })
21 button.appendChild(nextIcon) 25 button.appendChild(nextIcon)
@@ -26,11 +30,8 @@ class NextVideoButton extends Button {
26 } 30 }
27 31
28 handleClick () { 32 handleClick () {
29 this.options_.handler() 33 this.nextVideoButtonOptions.handler()
30 } 34 }
31
32} 35}
33 36
34NextVideoButton.prototype.controlText_ = 'Next video' 37videojs.registerComponent('NextVideoButton', NextVideoButton)
35
36NextVideoButton.registerComponent('NextVideoButton', NextVideoButton)
diff --git a/client/src/assets/player/videojs-components/p2p-info-button.ts b/client/src/assets/player/videojs-components/p2p-info-button.ts
index 6424787b2..db6806fed 100644
--- a/client/src/assets/player/videojs-components/p2p-info-button.ts
+++ b/client/src/assets/player/videojs-components/p2p-info-button.ts
@@ -1,63 +1,64 @@
1import { PlayerNetworkInfo, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 1import { PlayerNetworkInfo } from '../peertube-videojs-typings'
2import videojs from 'video.js'
2import { bytes } from '../utils' 3import { bytes } from '../utils'
3 4
4const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 5const Button = videojs.getComponent('Button')
5class P2pInfoButton extends Button { 6class P2pInfoButton extends Button {
6 7
7 createEl () { 8 createEl () {
8 const div = videojsUntyped.dom.createEl('div', { 9 const div = videojs.dom.createEl('div', {
9 className: 'vjs-peertube' 10 className: 'vjs-peertube'
10 }) 11 })
11 const subDivWebtorrent = videojsUntyped.dom.createEl('div', { 12 const subDivWebtorrent = videojs.dom.createEl('div', {
12 className: 'vjs-peertube-hidden' // Hide the stats before we get the info 13 className: 'vjs-peertube-hidden' // Hide the stats before we get the info
13 }) 14 }) as HTMLDivElement
14 div.appendChild(subDivWebtorrent) 15 div.appendChild(subDivWebtorrent)
15 16
16 const downloadIcon = videojsUntyped.dom.createEl('span', { 17 const downloadIcon = videojs.dom.createEl('span', {
17 className: 'icon icon-download' 18 className: 'icon icon-download'
18 }) 19 })
19 subDivWebtorrent.appendChild(downloadIcon) 20 subDivWebtorrent.appendChild(downloadIcon)
20 21
21 const downloadSpeedText = videojsUntyped.dom.createEl('span', { 22 const downloadSpeedText = videojs.dom.createEl('span', {
22 className: 'download-speed-text' 23 className: 'download-speed-text'
23 }) 24 })
24 const downloadSpeedNumber = videojsUntyped.dom.createEl('span', { 25 const downloadSpeedNumber = videojs.dom.createEl('span', {
25 className: 'download-speed-number' 26 className: 'download-speed-number'
26 }) 27 })
27 const downloadSpeedUnit = videojsUntyped.dom.createEl('span') 28 const downloadSpeedUnit = videojs.dom.createEl('span')
28 downloadSpeedText.appendChild(downloadSpeedNumber) 29 downloadSpeedText.appendChild(downloadSpeedNumber)
29 downloadSpeedText.appendChild(downloadSpeedUnit) 30 downloadSpeedText.appendChild(downloadSpeedUnit)
30 subDivWebtorrent.appendChild(downloadSpeedText) 31 subDivWebtorrent.appendChild(downloadSpeedText)
31 32
32 const uploadIcon = videojsUntyped.dom.createEl('span', { 33 const uploadIcon = videojs.dom.createEl('span', {
33 className: 'icon icon-upload' 34 className: 'icon icon-upload'
34 }) 35 })
35 subDivWebtorrent.appendChild(uploadIcon) 36 subDivWebtorrent.appendChild(uploadIcon)
36 37
37 const uploadSpeedText = videojsUntyped.dom.createEl('span', { 38 const uploadSpeedText = videojs.dom.createEl('span', {
38 className: 'upload-speed-text' 39 className: 'upload-speed-text'
39 }) 40 })
40 const uploadSpeedNumber = videojsUntyped.dom.createEl('span', { 41 const uploadSpeedNumber = videojs.dom.createEl('span', {
41 className: 'upload-speed-number' 42 className: 'upload-speed-number'
42 }) 43 })
43 const uploadSpeedUnit = videojsUntyped.dom.createEl('span') 44 const uploadSpeedUnit = videojs.dom.createEl('span')
44 uploadSpeedText.appendChild(uploadSpeedNumber) 45 uploadSpeedText.appendChild(uploadSpeedNumber)
45 uploadSpeedText.appendChild(uploadSpeedUnit) 46 uploadSpeedText.appendChild(uploadSpeedUnit)
46 subDivWebtorrent.appendChild(uploadSpeedText) 47 subDivWebtorrent.appendChild(uploadSpeedText)
47 48
48 const peersText = videojsUntyped.dom.createEl('span', { 49 const peersText = videojs.dom.createEl('span', {
49 className: 'peers-text' 50 className: 'peers-text'
50 }) 51 })
51 const peersNumber = videojsUntyped.dom.createEl('span', { 52 const peersNumber = videojs.dom.createEl('span', {
52 className: 'peers-number' 53 className: 'peers-number'
53 }) 54 })
54 subDivWebtorrent.appendChild(peersNumber) 55 subDivWebtorrent.appendChild(peersNumber)
55 subDivWebtorrent.appendChild(peersText) 56 subDivWebtorrent.appendChild(peersText)
56 57
57 const subDivHttp = videojsUntyped.dom.createEl('div', { 58 const subDivHttp = videojs.dom.createEl('div', {
58 className: 'vjs-peertube-hidden' 59 className: 'vjs-peertube-hidden'
59 }) 60 })
60 const subDivHttpText = videojsUntyped.dom.createEl('span', { 61 const subDivHttpText = videojs.dom.createEl('span', {
61 className: 'http-fallback', 62 className: 'http-fallback',
62 textContent: 'HTTP' 63 textContent: 'HTTP'
63 }) 64 })
@@ -83,8 +84,8 @@ class P2pInfoButton extends Button {
83 const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded) 84 const totalUploaded = bytes(p2pStats.uploaded + httpStats.uploaded)
84 const numPeers = p2pStats.numPeers 85 const numPeers = p2pStats.numPeers
85 86
86 subDivWebtorrent.title = this.player_.localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' + 87 subDivWebtorrent.title = this.player().localize('Total downloaded: ') + totalDownloaded.join(' ') + '\n' +
87 this.player_.localize('Total uploaded: ' + totalUploaded.join(' ')) 88 this.player().localize('Total uploaded: ' + totalUploaded.join(' '))
88 89
89 downloadSpeedNumber.textContent = downloadSpeed[ 0 ] 90 downloadSpeedNumber.textContent = downloadSpeed[ 0 ]
90 downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ] 91 downloadSpeedUnit.textContent = ' ' + downloadSpeed[ 1 ]
@@ -92,14 +93,15 @@ class P2pInfoButton extends Button {
92 uploadSpeedNumber.textContent = uploadSpeed[ 0 ] 93 uploadSpeedNumber.textContent = uploadSpeed[ 0 ]
93 uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ] 94 uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ]
94 95
95 peersNumber.textContent = numPeers 96 peersNumber.textContent = numPeers.toString()
96 peersText.textContent = ' ' + (numPeers > 1 ? this.player_.localize('peers') : this.player_.localize('peer')) 97 peersText.textContent = ' ' + (numPeers > 1 ? this.player().localize('peers') : this.player_.localize('peer'))
97 98
98 subDivHttp.className = 'vjs-peertube-hidden' 99 subDivHttp.className = 'vjs-peertube-hidden'
99 subDivWebtorrent.className = 'vjs-peertube-displayed' 100 subDivWebtorrent.className = 'vjs-peertube-displayed'
100 }) 101 })
101 102
102 return div 103 return div as HTMLButtonElement
103 } 104 }
104} 105}
105Button.registerComponent('P2PInfoButton', P2pInfoButton) 106
107videojs.registerComponent('P2PInfoButton', P2pInfoButton)
diff --git a/client/src/assets/player/videojs-components/peertube-link-button.ts b/client/src/assets/player/videojs-components/peertube-link-button.ts
index 4d0ea37f5..0db9762a5 100644
--- a/client/src/assets/player/videojs-components/peertube-link-button.ts
+++ b/client/src/assets/player/videojs-components/peertube-link-button.ts
@@ -1,13 +1,10 @@
1import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
2import { buildVideoLink } from '../utils' 1import { buildVideoLink } from '../utils'
3// FIXME: something weird with our path definition in tsconfig and typings 2import videojs, { VideoJsPlayer } from 'video.js'
4// @ts-ignore
5import { Player } from 'video.js'
6 3
7const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 4const Button = videojs.getComponent('Button')
8class PeerTubeLinkButton extends Button { 5class PeerTubeLinkButton extends Button {
9 6
10 constructor (player: Player, options: any) { 7 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
11 super(player, options) 8 super(player, options)
12 } 9 }
13 10
@@ -20,21 +17,22 @@ class PeerTubeLinkButton extends Button {
20 } 17 }
21 18
22 handleClick () { 19 handleClick () {
23 this.player_.pause() 20 this.player().pause()
24 } 21 }
25 22
26 private buildElement () { 23 private buildElement () {
27 const el = videojsUntyped.dom.createEl('a', { 24 const el = videojs.dom.createEl('a', {
28 href: buildVideoLink(), 25 href: buildVideoLink(),
29 innerHTML: 'PeerTube', 26 innerHTML: 'PeerTube',
30 title: this.player_.localize('Go to the video page'), 27 title: this.player().localize('Go to the video page'),
31 className: 'vjs-peertube-link', 28 className: 'vjs-peertube-link',
32 target: '_blank' 29 target: '_blank'
33 }) 30 })
34 31
35 el.addEventListener('mouseenter', () => this.updateHref()) 32 el.addEventListener('mouseenter', () => this.updateHref())
36 33
37 return el 34 return el as HTMLButtonElement
38 } 35 }
39} 36}
40Button.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton) 37
38videojs.registerComponent('PeerTubeLinkButton', PeerTubeLinkButton)
diff --git a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts b/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
index b594fc1c5..8168e8f2d 100644
--- a/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
+++ b/client/src/assets/player/videojs-components/peertube-load-progress-bar.ts
@@ -1,16 +1,12 @@
1import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 1import videojs, { VideoJsPlayer } from 'video.js'
2// FIXME: something weird with our path definition in tsconfig and typings
3// @ts-ignore
4import { Player } from 'video.js'
5 2
6const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') 3const Component = videojs.getComponent('Component')
7 4
8class PeerTubeLoadProgressBar extends Component { 5class PeerTubeLoadProgressBar extends Component {
9 partEls_: any[]
10 6
11 constructor (player: Player, options: any) { 7 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
12 super(player, options) 8 super(player, options)
13 this.partEls_ = [] 9
14 this.on(player, 'progress', this.update) 10 this.on(player, 'progress', this.update)
15 } 11 }
16 12
@@ -22,8 +18,6 @@ class PeerTubeLoadProgressBar extends Component {
22 } 18 }
23 19
24 dispose () { 20 dispose () {
25 this.partEls_ = null
26
27 super.dispose() 21 super.dispose()
28 } 22 }
29 23
@@ -31,7 +25,8 @@ class PeerTubeLoadProgressBar extends Component {
31 const torrent = this.player().webtorrent().getTorrent() 25 const torrent = this.player().webtorrent().getTorrent()
32 if (!torrent) return 26 if (!torrent) return
33 27
34 this.el_.style.width = (torrent.progress * 100) + '%' 28 // FIXME: typings
29 (this.el() as HTMLElement).style.width = (torrent.progress * 100) + '%'
35 } 30 }
36 31
37} 32}
diff --git a/client/src/assets/player/videojs-components/resolution-menu-button.ts b/client/src/assets/player/videojs-components/resolution-menu-button.ts
index 2de3ece19..0fa6272e7 100644
--- a/client/src/assets/player/videojs-components/resolution-menu-button.ts
+++ b/client/src/assets/player/videojs-components/resolution-menu-button.ts
@@ -1,22 +1,19 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore
3import { Player } from 'video.js'
4 2
5import { LoadedQualityData, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 3import { LoadedQualityData } from '../peertube-videojs-typings'
6import { ResolutionMenuItem } from './resolution-menu-item' 4import { ResolutionMenuItem } from './resolution-menu-item'
7 5
8const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu') 6const Menu = videojs.getComponent('Menu')
9const MenuButton: VideoJSComponentInterface = videojsUntyped.getComponent('MenuButton') 7const MenuButton = videojs.getComponent('MenuButton')
10class ResolutionMenuButton extends MenuButton { 8class ResolutionMenuButton extends MenuButton {
11 label: HTMLElement 9 labelEl_: HTMLElement
12 labelEl_: any
13 player: Player
14 10
15 constructor (player: Player, options: any) { 11 constructor (player: VideoJsPlayer, options?: videojs.MenuButtonOptions) {
16 super(player, options) 12 super(player, options)
17 this.player = player
18 13
19 player.tech_.on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data)) 14 this.controlText('Quality')
15
16 player.tech(true).on('loadedqualitydata', (e: any, data: any) => this.buildQualities(data))
20 17
21 player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0)) 18 player.peertube().on('resolutionChange', () => setTimeout(() => this.trigger('updateLabel'), 0))
22 } 19 }
@@ -24,9 +21,9 @@ class ResolutionMenuButton extends MenuButton {
24 createEl () { 21 createEl () {
25 const el = super.createEl() 22 const el = super.createEl()
26 23
27 this.labelEl_ = videojsUntyped.dom.createEl('div', { 24 this.labelEl_ = videojs.dom.createEl('div', {
28 className: 'vjs-resolution-value' 25 className: 'vjs-resolution-value'
29 }) 26 }) as HTMLElement
30 27
31 el.appendChild(this.labelEl_) 28 el.appendChild(this.labelEl_)
32 29
@@ -55,7 +52,7 @@ class ResolutionMenuButton extends MenuButton {
55 52
56 for (const child of children) { 53 for (const child of children) {
57 if (component !== child) { 54 if (component !== child) {
58 child.selected(false) 55 (child as videojs.MenuItem).selected(false)
59 } 56 }
60 } 57 }
61 }) 58 })
@@ -76,7 +73,7 @@ class ResolutionMenuButton extends MenuButton {
76 if (d.id === -1) continue 73 if (d.id === -1) continue
77 74
78 const label = d.label === '0p' 75 const label = d.label === '0p'
79 ? this.player.localize('Audio-only') 76 ? this.player().localize('Audio-only')
80 : d.label 77 : d.label
81 78
82 this.menu.addChild(new ResolutionMenuItem( 79 this.menu.addChild(new ResolutionMenuItem(
@@ -110,6 +107,5 @@ class ResolutionMenuButton extends MenuButton {
110 this.trigger('menuChanged') 107 this.trigger('menuChanged')
111 } 108 }
112} 109}
113ResolutionMenuButton.prototype.controlText_ = 'Quality'
114 110
115MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton) 111videojs.registerComponent('ResolutionMenuButton', ResolutionMenuButton)
diff --git a/client/src/assets/player/videojs-components/resolution-menu-item.ts b/client/src/assets/player/videojs-components/resolution-menu-item.ts
index 6c42fefd2..b039c4572 100644
--- a/client/src/assets/player/videojs-components/resolution-menu-item.ts
+++ b/client/src/assets/player/videojs-components/resolution-menu-item.ts
@@ -1,12 +1,16 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore 2import { AutoResolutionUpdateData, ResolutionUpdateData } from '../peertube-videojs-typings'
3import { Player } from 'video.js'
4 3
5import { AutoResolutionUpdateData, ResolutionUpdateData, VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 4const MenuItem = videojs.getComponent('MenuItem')
5
6export interface ResolutionMenuItemOptions extends videojs.MenuItemOptions {
7 labels?: { [id: number]: string }
8 id: number
9 callback: Function
10}
6 11
7const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem')
8class ResolutionMenuItem extends MenuItem { 12class ResolutionMenuItem extends MenuItem {
9 private readonly id: number 13 private readonly resolutionId: number
10 private readonly label: string 14 private readonly label: string
11 // Only used for the automatic item 15 // Only used for the automatic item
12 private readonly labels: { [id: number]: string } 16 private readonly labels: { [id: number]: string }
@@ -15,7 +19,7 @@ class ResolutionMenuItem extends MenuItem {
15 private autoResolutionPossible: boolean 19 private autoResolutionPossible: boolean
16 private currentResolutionLabel: string 20 private currentResolutionLabel: string
17 21
18 constructor (player: Player, options: any) { 22 constructor (player: VideoJsPlayer, options?: ResolutionMenuItemOptions) {
19 options.selectable = true 23 options.selectable = true
20 24
21 super(player, options) 25 super(player, options)
@@ -23,40 +27,40 @@ class ResolutionMenuItem extends MenuItem {
23 this.autoResolutionPossible = true 27 this.autoResolutionPossible = true
24 this.currentResolutionLabel = '' 28 this.currentResolutionLabel = ''
25 29
30 this.resolutionId = options.id
26 this.label = options.label 31 this.label = options.label
27 this.labels = options.labels 32 this.labels = options.labels
28 this.id = options.id
29 this.callback = options.callback 33 this.callback = options.callback
30 34
31 player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data)) 35 player.peertube().on('resolutionChange', (_: any, data: ResolutionUpdateData) => this.updateSelection(data))
32 36
33 // We only want to disable the "Auto" item 37 // We only want to disable the "Auto" item
34 if (this.id === -1) { 38 if (this.resolutionId === -1) {
35 player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data)) 39 player.peertube().on('autoResolutionChange', (_: any, data: AutoResolutionUpdateData) => this.updateAutoResolution(data))
36 } 40 }
37 } 41 }
38 42
39 handleClick (event: any) { 43 handleClick (event: any) {
40 // Auto button disabled? 44 // Auto button disabled?
41 if (this.autoResolutionPossible === false && this.id === -1) return 45 if (this.autoResolutionPossible === false && this.resolutionId === -1) return
42 46
43 super.handleClick(event) 47 super.handleClick(event)
44 48
45 this.callback(this.id, 'video') 49 this.callback(this.resolutionId, 'video')
46 } 50 }
47 51
48 updateSelection (data: ResolutionUpdateData) { 52 updateSelection (data: ResolutionUpdateData) {
49 if (this.id === -1) { 53 if (this.resolutionId === -1) {
50 this.currentResolutionLabel = this.labels[data.id] 54 this.currentResolutionLabel = this.labels[data.id]
51 } 55 }
52 56
53 // Automatic resolution only 57 // Automatic resolution only
54 if (data.auto === true) { 58 if (data.auto === true) {
55 this.selected(this.id === -1) 59 this.selected(this.resolutionId === -1)
56 return 60 return
57 } 61 }
58 62
59 this.selected(this.id === data.id) 63 this.selected(this.resolutionId === data.id)
60 } 64 }
61 65
62 updateAutoResolution (data: AutoResolutionUpdateData) { 66 updateAutoResolution (data: AutoResolutionUpdateData) {
@@ -71,13 +75,13 @@ class ResolutionMenuItem extends MenuItem {
71 } 75 }
72 76
73 getLabel () { 77 getLabel () {
74 if (this.id === -1) { 78 if (this.resolutionId === -1) {
75 return this.label + ' <small>' + this.currentResolutionLabel + '</small>' 79 return this.label + ' <small>' + this.currentResolutionLabel + '</small>'
76 } 80 }
77 81
78 return this.label 82 return this.label
79 } 83 }
80} 84}
81MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem) 85videojs.registerComponent('ResolutionMenuItem', ResolutionMenuItem)
82 86
83export { ResolutionMenuItem } 87export { ResolutionMenuItem }
diff --git a/client/src/assets/player/videojs-components/settings-dialog.ts b/client/src/assets/player/videojs-components/settings-dialog.ts
new file mode 100644
index 000000000..dd0b1e472
--- /dev/null
+++ b/client/src/assets/player/videojs-components/settings-dialog.ts
@@ -0,0 +1,37 @@
1import videojs, { VideoJsPlayer } from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsDialog extends Component {
6 constructor (player: VideoJsPlayer) {
7 super(player)
8
9 this.hide()
10 }
11
12 /**
13 * Create the component's DOM element
14 *
15 * @return {Element}
16 * @method createEl
17 */
18 createEl () {
19 const uniqueId = this.id()
20 const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
21 const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
22
23 return super.createEl('div', {
24 className: 'vjs-settings-dialog vjs-modal-overlay',
25 innerHTML: '',
26 tabIndex: -1
27 }, {
28 'role': 'dialog',
29 'aria-labelledby': dialogLabelId,
30 'aria-describedby': dialogDescriptionId
31 })
32 }
33}
34
35Component.registerComponent('SettingsDialog', SettingsDialog)
36
37export { SettingsDialog }
diff --git a/client/src/assets/player/videojs-components/settings-menu-button.ts b/client/src/assets/player/videojs-components/settings-menu-button.ts
index b700f4be6..eae628e7d 100644
--- a/client/src/assets/player/videojs-components/settings-menu-button.ts
+++ b/client/src/assets/player/videojs-components/settings-menu-button.ts
@@ -1,43 +1,52 @@
1// Author: Yanko Shterev 1// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
2// Thanks https://github.com/yshterev/videojs-settings-menu
3
4// FIXME: something weird with our path definition in tsconfig and typings
5// @ts-ignore
6import * as videojs from 'video.js'
7
8import { SettingsMenuItem } from './settings-menu-item' 2import { SettingsMenuItem } from './settings-menu-item'
9import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
10import { toTitleCase } from '../utils' 3import { toTitleCase } from '../utils'
4import videojs, { VideoJsPlayer } from 'video.js'
5
6import { SettingsDialog } from './settings-dialog'
7import { SettingsPanel } from './settings-panel'
8import { SettingsPanelChild } from './settings-panel-child'
11 9
12const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 10const Button = videojs.getComponent('Button')
13const Menu: VideoJSComponentInterface = videojsUntyped.getComponent('Menu') 11const Menu = videojs.getComponent('Menu')
14const Component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') 12const Component = videojs.getComponent('Component')
13
14export interface SettingsButtonOptions extends videojs.ComponentOptions {
15 entries: any[]
16 setup?: {
17 maxHeightOffset: number
18 }
19}
15 20
16class SettingsButton extends Button { 21class SettingsButton extends Button {
17 playerComponent = videojs.Player 22 dialog: SettingsDialog
18 dialog: any 23 dialogEl: HTMLElement
19 dialogEl: any 24 menu: videojs.Menu
20 menu: any 25 panel: SettingsPanel
21 panel: any 26 panelChild: SettingsPanelChild
22 panelChild: any 27
23 28 addSettingsItemHandler: typeof SettingsButton.prototype.onAddSettingsItem
24 addSettingsItemHandler: Function 29 disposeSettingsItemHandler: typeof SettingsButton.prototype.onDisposeSettingsItem
25 disposeSettingsItemHandler: Function 30 playerClickHandler: typeof SettingsButton.prototype.onPlayerClick
26 playerClickHandler: Function 31 userInactiveHandler: typeof SettingsButton.prototype.onUserInactive
27 userInactiveHandler: Function 32
28 33 private settingsButtonOptions: SettingsButtonOptions
29 constructor (player: videojs.Player, options: any) { 34
35 constructor (player: VideoJsPlayer, options?: SettingsButtonOptions) {
30 super(player, options) 36 super(player, options)
31 37
32 this.playerComponent = player 38 this.settingsButtonOptions = options
33 this.dialog = this.playerComponent.addChild('settingsDialog') 39
34 this.dialogEl = this.dialog.el_ 40 this.controlText('Settings')
41
42 this.dialog = this.player().addChild('settingsDialog')
43 this.dialogEl = this.dialog.el() as HTMLElement
35 this.menu = null 44 this.menu = null
36 this.panel = this.dialog.addChild('settingsPanel') 45 this.panel = this.dialog.addChild('settingsPanel')
37 this.panelChild = this.panel.addChild('settingsPanelChild') 46 this.panelChild = this.panel.addChild('settingsPanelChild')
38 47
39 this.addClass('vjs-settings') 48 this.addClass('vjs-settings')
40 this.el_.setAttribute('aria-label', 'Settings Button') 49 this.el().setAttribute('aria-label', 'Settings Button')
41 50
42 // Event handlers 51 // Event handlers
43 this.addSettingsItemHandler = this.onAddSettingsItem.bind(this) 52 this.addSettingsItemHandler = this.onAddSettingsItem.bind(this)
@@ -84,7 +93,7 @@ class SettingsButton extends Button {
84 93
85 this.hideDialog() 94 this.hideDialog()
86 95
87 if (this.options_.entries.length === 0) { 96 if (this.settingsButtonOptions.entries.length === 0) {
88 this.addClass('vjs-hidden') 97 this.addClass('vjs-hidden')
89 } 98 }
90 } 99 }
@@ -103,10 +112,10 @@ class SettingsButton extends Button {
103 } 112 }
104 113
105 bindEvents () { 114 bindEvents () {
106 this.playerComponent.on('click', this.playerClickHandler) 115 this.player().on('click', this.playerClickHandler)
107 this.playerComponent.on('addsettingsitem', this.addSettingsItemHandler) 116 this.player().on('addsettingsitem', this.addSettingsItemHandler)
108 this.playerComponent.on('disposesettingsitem', this.disposeSettingsItemHandler) 117 this.player().on('disposesettingsitem', this.disposeSettingsItemHandler)
109 this.playerComponent.on('userinactive', this.userInactiveHandler) 118 this.player().on('userinactive', this.userInactiveHandler)
110 } 119 }
111 120
112 buildCSSClass () { 121 buildCSSClass () {
@@ -122,9 +131,9 @@ class SettingsButton extends Button {
122 } 131 }
123 132
124 showDialog () { 133 showDialog () {
125 this.player_.peertube().onMenuOpen() 134 this.player().peertube().onMenuOpen();
126 135
127 this.menu.el_.style.opacity = '1' 136 (this.menu.el() as HTMLElement).style.opacity = '1'
128 this.dialog.show() 137 this.dialog.show()
129 138
130 this.setDialogSize(this.getComponentSize(this.menu)) 139 this.setDialogSize(this.getComponentSize(this.menu))
@@ -134,23 +143,24 @@ class SettingsButton extends Button {
134 this.player_.peertube().onMenuClosed() 143 this.player_.peertube().onMenuClosed()
135 144
136 this.dialog.hide() 145 this.dialog.hide()
137 this.setDialogSize(this.getComponentSize(this.menu)) 146 this.setDialogSize(this.getComponentSize(this.menu));
138 this.menu.el_.style.opacity = '1' 147 (this.menu.el() as HTMLElement).style.opacity = '1'
139 this.resetChildren() 148 this.resetChildren()
140 } 149 }
141 150
142 getComponentSize (element: any) { 151 getComponentSize (element: videojs.Component | HTMLElement) {
143 let width: number = null 152 let width: number = null
144 let height: number = null 153 let height: number = null
145 154
146 // Could be component or just DOM element 155 // Could be component or just DOM element
147 if (element instanceof Component) { 156 if (element instanceof Component) {
148 width = element.el_.offsetWidth 157 const el = element.el() as HTMLElement
149 height = element.el_.offsetHeight 158
159 width = el.offsetWidth
160 height = el.offsetHeight;
150 161
151 // keep width/height as properties for direct use 162 (element as any).width = width;
152 element.width = width 163 (element as any).height = height
153 element.height = height
154 } else { 164 } else {
155 width = element.offsetWidth 165 width = element.offsetWidth
156 height = element.offsetHeight 166 height = element.offsetHeight
@@ -164,15 +174,17 @@ class SettingsButton extends Button {
164 return 174 return
165 } 175 }
166 176
167 const offset = this.options_.setup.maxHeightOffset 177 const offset = this.settingsButtonOptions.setup.maxHeightOffset
168 const maxHeight = this.playerComponent.el_.offsetHeight - offset 178 const maxHeight = (this.player().el() as HTMLElement).offsetHeight - offset // FIXME: typings
179
180 const panelEl = this.panel.el() as HTMLElement
169 181
170 if (height > maxHeight) { 182 if (height > maxHeight) {
171 height = maxHeight 183 height = maxHeight
172 width += 17 184 width += 17
173 this.panel.el_.style.maxHeight = `${height}px` 185 panelEl.style.maxHeight = `${height}px`
174 } else if (this.panel.el_.style.maxHeight !== '') { 186 } else if (panelEl.style.maxHeight !== '') {
175 this.panel.el_.style.maxHeight = '' 187 panelEl.style.maxHeight = ''
176 } 188 }
177 189
178 this.dialogEl.style.width = `${width}px` 190 this.dialogEl.style.width = `${width}px`
@@ -182,7 +194,7 @@ class SettingsButton extends Button {
182 buildMenu () { 194 buildMenu () {
183 this.menu = new Menu(this.player()) 195 this.menu = new Menu(this.player())
184 this.menu.addClass('vjs-main-menu') 196 this.menu.addClass('vjs-main-menu')
185 const entries = this.options_.entries 197 const entries = this.settingsButtonOptions.entries
186 198
187 if (entries.length === 0) { 199 if (entries.length === 0) {
188 this.addClass('vjs-hidden') 200 this.addClass('vjs-hidden')
@@ -191,7 +203,7 @@ class SettingsButton extends Button {
191 } 203 }
192 204
193 for (const entry of entries) { 205 for (const entry of entries) {
194 this.addMenuItem(entry, this.options_) 206 this.addMenuItem(entry, this.settingsButtonOptions)
195 } 207 }
196 208
197 this.panelChild.addChild(this.menu) 209 this.panelChild.addChild(this.menu)
@@ -199,15 +211,17 @@ class SettingsButton extends Button {
199 211
200 addMenuItem (entry: any, options: any) { 212 addMenuItem (entry: any, options: any) {
201 const openSubMenu = function (this: any) { 213 const openSubMenu = function (this: any) {
202 if (videojsUntyped.dom.hasClass(this.el_, 'open')) { 214 if (videojs.dom.hasClass(this.el_, 'open')) {
203 videojsUntyped.dom.removeClass(this.el_, 'open') 215 videojs.dom.removeClass(this.el_, 'open')
204 } else { 216 } else {
205 videojsUntyped.dom.addClass(this.el_, 'open') 217 videojs.dom.addClass(this.el_, 'open')
206 } 218 }
207 } 219 }
208 220
209 options.name = toTitleCase(entry) 221 options.name = toTitleCase(entry)
210 const settingsMenuItem = new SettingsMenuItem(this.player(), options, entry, this as any) 222
223 const newOptions = Object.assign({}, options, { entry, menuButton: this })
224 const settingsMenuItem = new SettingsMenuItem(this.player(), newOptions)
211 225
212 this.menu.addChild(settingsMenuItem) 226 this.menu.addChild(settingsMenuItem)
213 227
@@ -221,7 +235,7 @@ class SettingsButton extends Button {
221 235
222 resetChildren () { 236 resetChildren () {
223 for (const menuChild of this.menu.children()) { 237 for (const menuChild of this.menu.children()) {
224 menuChild.reset() 238 (menuChild as SettingsMenuItem).reset()
225 } 239 }
226 } 240 }
227 241
@@ -230,75 +244,12 @@ class SettingsButton extends Button {
230 */ 244 */
231 hideChildren () { 245 hideChildren () {
232 for (const menuChild of this.menu.children()) { 246 for (const menuChild of this.menu.children()) {
233 menuChild.hideSubMenu() 247 (menuChild as SettingsMenuItem).hideSubMenu()
234 } 248 }
235 } 249 }
236 250
237} 251}
238 252
239class SettingsPanel extends Component {
240 constructor (player: videojs.Player, options: any) {
241 super(player, options)
242 }
243
244 createEl () {
245 return super.createEl('div', {
246 className: 'vjs-settings-panel',
247 innerHTML: '',
248 tabIndex: -1
249 })
250 }
251}
252
253class SettingsPanelChild extends Component {
254 constructor (player: videojs.Player, options: any) {
255 super(player, options)
256 }
257
258 createEl () {
259 return super.createEl('div', {
260 className: 'vjs-settings-panel-child',
261 innerHTML: '',
262 tabIndex: -1
263 })
264 }
265}
266
267class SettingsDialog extends Component {
268 constructor (player: videojs.Player, options: any) {
269 super(player, options)
270 this.hide()
271 }
272
273 /**
274 * Create the component's DOM element
275 *
276 * @return {Element}
277 * @method createEl
278 */
279 createEl () {
280 const uniqueId = this.id_
281 const dialogLabelId = 'TTsettingsDialogLabel-' + uniqueId
282 const dialogDescriptionId = 'TTsettingsDialogDescription-' + uniqueId
283
284 return super.createEl('div', {
285 className: 'vjs-settings-dialog vjs-modal-overlay',
286 innerHTML: '',
287 tabIndex: -1
288 }, {
289 'role': 'dialog',
290 'aria-labelledby': dialogLabelId,
291 'aria-describedby': dialogDescriptionId
292 })
293 }
294
295}
296
297SettingsButton.prototype.controlText_ = 'Settings'
298
299Component.registerComponent('SettingsButton', SettingsButton) 253Component.registerComponent('SettingsButton', SettingsButton)
300Component.registerComponent('SettingsDialog', SettingsDialog)
301Component.registerComponent('SettingsPanel', SettingsPanel)
302Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
303 254
304export { SettingsButton, SettingsDialog, SettingsPanel, SettingsPanelChild } 255export { SettingsButton }
diff --git a/client/src/assets/player/videojs-components/settings-menu-item.ts b/client/src/assets/player/videojs-components/settings-menu-item.ts
index 84d394c0e..f5671f49d 100644
--- a/client/src/assets/player/videojs-components/settings-menu-item.ts
+++ b/client/src/assets/player/videojs-components/settings-menu-item.ts
@@ -1,57 +1,63 @@
1// Author: Yanko Shterev 1// Thanks to Yanko Shterev: https://github.com/yshterev/videojs-settings-menu
2// Thanks https://github.com/yshterev/videojs-settings-menu
3
4// FIXME: something weird with our path definition in tsconfig and typings
5// @ts-ignore
6import * as videojs from 'video.js'
7
8import { toTitleCase } from '../utils' 2import { toTitleCase } from '../utils'
9import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings' 3import videojs, { VideoJsPlayer } from 'video.js'
10 4import { SettingsButton } from './settings-menu-button'
11const MenuItem: VideoJSComponentInterface = videojsUntyped.getComponent('MenuItem') 5import { SettingsDialog } from './settings-dialog'
12const component: VideoJSComponentInterface = videojsUntyped.getComponent('Component') 6import { SettingsPanel } from './settings-panel'
7import { SettingsPanelChild } from './settings-panel-child'
8
9const MenuItem = videojs.getComponent('MenuItem')
10const component = videojs.getComponent('Component')
11
12export interface SettingsMenuItemOptions extends videojs.MenuItemOptions {
13 entry: string
14 menuButton: SettingsButton
15}
13 16
14class SettingsMenuItem extends MenuItem { 17class SettingsMenuItem extends MenuItem {
15 settingsButton: any 18 settingsButton: SettingsButton
16 dialog: any 19 dialog: SettingsDialog
17 mainMenu: any 20 mainMenu: videojs.Menu
18 panel: any 21 panel: SettingsPanel
19 panelChild: any 22 panelChild: SettingsPanelChild
20 panelChildEl: any 23 panelChildEl: HTMLElement
21 size: any 24 size: number[]
22 menuToLoad: string 25 menuToLoad: string
23 subMenu: any 26 subMenu: SettingsButton
24 27
25 submenuClickHandler: Function 28 submenuClickHandler: typeof SettingsMenuItem.prototype.onSubmenuClick
26 transitionEndHandler: Function 29 transitionEndHandler: typeof SettingsMenuItem.prototype.onTransitionEnd
27 30
28 settingsSubMenuTitleEl_: any 31 settingsSubMenuTitleEl_: HTMLElement
29 settingsSubMenuValueEl_: any 32 settingsSubMenuValueEl_: HTMLElement
30 settingsSubMenuEl_: any 33 settingsSubMenuEl_: HTMLElement
31 34
32 constructor (player: videojs.Player, options: any, entry: string, menuButton: VideoJSComponentInterface) { 35 constructor (player: VideoJsPlayer, options?: SettingsMenuItemOptions) {
33 super(player, options) 36 super(player, options)
34 37
35 this.settingsButton = menuButton 38 this.settingsButton = options.menuButton
36 this.dialog = this.settingsButton.dialog 39 this.dialog = this.settingsButton.dialog
37 this.mainMenu = this.settingsButton.menu 40 this.mainMenu = this.settingsButton.menu
38 this.panel = this.dialog.getChild('settingsPanel') 41 this.panel = this.dialog.getChild('settingsPanel')
39 this.panelChild = this.panel.getChild('settingsPanelChild') 42 this.panelChild = this.panel.getChild('settingsPanelChild')
40 this.panelChildEl = this.panelChild.el_ 43 this.panelChildEl = this.panelChild.el() as HTMLElement
41 44
42 this.size = null 45 this.size = null
43 46
44 // keep state of what menu type is loading next 47 // keep state of what menu type is loading next
45 this.menuToLoad = 'mainmenu' 48 this.menuToLoad = 'mainmenu'
46 49
47 const subMenuName = toTitleCase(entry) 50 const subMenuName = toTitleCase(options.entry)
48 const SubMenuComponent = videojsUntyped.getComponent(subMenuName) 51 const SubMenuComponent = videojs.getComponent(subMenuName)
49 52
50 if (!SubMenuComponent) { 53 if (!SubMenuComponent) {
51 throw new Error(`Component ${subMenuName} does not exist`) 54 throw new Error(`Component ${subMenuName} does not exist`)
52 } 55 }
53 this.subMenu = new SubMenuComponent(this.player(), options, menuButton, this) 56
54 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[0] 57 const newOptions = Object.assign({}, options, { entry: options.menuButton, menuButton: this })
58
59 this.subMenu = new SubMenuComponent(this.player(), newOptions) as any // FIXME: typings
60 const subMenuClass = this.subMenu.buildCSSClass().split(' ')[ 0 ]
55 this.settingsSubMenuEl_.className += ' ' + subMenuClass 61 this.settingsSubMenuEl_.className += ' ' + subMenuClass
56 62
57 this.eventHandlers() 63 this.eventHandlers()
@@ -72,7 +78,7 @@ class SettingsMenuItem extends MenuItem {
72 player.on('captionsChanged', () => { 78 player.on('captionsChanged', () => {
73 setTimeout(() => { 79 setTimeout(() => {
74 this.settingsSubMenuEl_.innerHTML = '' 80 this.settingsSubMenuEl_.innerHTML = ''
75 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_) 81 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
76 this.update() 82 this.update()
77 this.bindClickEvents() 83 this.bindClickEvents()
78 }, 0) 84 }, 0)
@@ -119,27 +125,27 @@ class SettingsMenuItem extends MenuItem {
119 * @method createEl 125 * @method createEl
120 */ 126 */
121 createEl () { 127 createEl () {
122 const el = videojsUntyped.dom.createEl('li', { 128 const el = videojs.dom.createEl('li', {
123 className: 'vjs-menu-item' 129 className: 'vjs-menu-item'
124 }) 130 })
125 131
126 this.settingsSubMenuTitleEl_ = videojsUntyped.dom.createEl('div', { 132 this.settingsSubMenuTitleEl_ = videojs.dom.createEl('div', {
127 className: 'vjs-settings-sub-menu-title' 133 className: 'vjs-settings-sub-menu-title'
128 }) 134 }) as HTMLElement
129 135
130 el.appendChild(this.settingsSubMenuTitleEl_) 136 el.appendChild(this.settingsSubMenuTitleEl_)
131 137
132 this.settingsSubMenuValueEl_ = videojsUntyped.dom.createEl('div', { 138 this.settingsSubMenuValueEl_ = videojs.dom.createEl('div', {
133 className: 'vjs-settings-sub-menu-value' 139 className: 'vjs-settings-sub-menu-value'
134 }) 140 }) as HTMLElement
135 141
136 el.appendChild(this.settingsSubMenuValueEl_) 142 el.appendChild(this.settingsSubMenuValueEl_)
137 143
138 this.settingsSubMenuEl_ = videojsUntyped.dom.createEl('div', { 144 this.settingsSubMenuEl_ = videojs.dom.createEl('div', {
139 className: 'vjs-settings-sub-menu' 145 className: 'vjs-settings-sub-menu'
140 }) 146 }) as HTMLElement
141 147
142 return el 148 return el as HTMLLIElement
143 } 149 }
144 150
145 /** 151 /**
@@ -147,17 +153,17 @@ class SettingsMenuItem extends MenuItem {
147 * 153 *
148 * @method handleClick 154 * @method handleClick
149 */ 155 */
150 handleClick () { 156 handleClick (event: videojs.EventTarget.Event) {
151 this.menuToLoad = 'submenu' 157 this.menuToLoad = 'submenu'
152 // Remove open class to ensure only the open submenu gets this class 158 // Remove open class to ensure only the open submenu gets this class
153 videojsUntyped.dom.removeClass(this.el_, 'open') 159 videojs.dom.removeClass(this.el(), 'open')
154 160
155 super.handleClick() 161 super.handleClick(event);
156 162
157 this.mainMenu.el_.style.opacity = '0' 163 (this.mainMenu.el() as HTMLElement).style.opacity = '0'
158 // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element 164 // Whether to add or remove vjs-hidden class on the settingsSubMenuEl element
159 if (videojsUntyped.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) { 165 if (videojs.dom.hasClass(this.settingsSubMenuEl_, 'vjs-hidden')) {
160 videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') 166 videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
161 167
162 // animation not played without timeout 168 // animation not played without timeout
163 setTimeout(() => { 169 setTimeout(() => {
@@ -167,7 +173,7 @@ class SettingsMenuItem extends MenuItem {
167 173
168 this.settingsButton.setDialogSize(this.size) 174 this.settingsButton.setDialogSize(this.size)
169 } else { 175 } else {
170 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') 176 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
171 } 177 }
172 } 178 }
173 179
@@ -178,9 +184,9 @@ class SettingsMenuItem extends MenuItem {
178 */ 184 */
179 createBackButton () { 185 createBackButton () {
180 const button = this.subMenu.menu.addChild('MenuItem', {}, 0) 186 const button = this.subMenu.menu.addChild('MenuItem', {}, 0)
181 button.name_ = 'BackButton' 187
182 button.addClass('vjs-back-button') 188 button.addClass('vjs-back-button');
183 button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_) 189 (button.el() as HTMLElement).innerHTML = this.player().localize(this.subMenu.controlText())
184 } 190 }
185 191
186 /** 192 /**
@@ -189,17 +195,17 @@ class SettingsMenuItem extends MenuItem {
189 * @method PrefixedEvent 195 * @method PrefixedEvent
190 */ 196 */
191 PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') { 197 PrefixedEvent (element: any, type: any, callback: any, action = 'addEvent') {
192 const prefix = ['webkit', 'moz', 'MS', 'o', ''] 198 const prefix = [ 'webkit', 'moz', 'MS', 'o', '' ]
193 199
194 for (let p = 0; p < prefix.length; p++) { 200 for (let p = 0; p < prefix.length; p++) {
195 if (!prefix[p]) { 201 if (!prefix[ p ]) {
196 type = type.toLowerCase() 202 type = type.toLowerCase()
197 } 203 }
198 204
199 if (action === 'addEvent') { 205 if (action === 'addEvent') {
200 element.addEventListener(prefix[p] + type, callback, false) 206 element.addEventListener(prefix[ p ] + type, callback, false)
201 } else if (action === 'removeEvent') { 207 } else if (action === 'removeEvent') {
202 element.removeEventListener(prefix[p] + type, callback, false) 208 element.removeEventListener(prefix[ p ] + type, callback, false)
203 } 209 }
204 } 210 }
205 } 211 }
@@ -211,7 +217,7 @@ class SettingsMenuItem extends MenuItem {
211 217
212 if (this.menuToLoad === 'mainmenu') { 218 if (this.menuToLoad === 'mainmenu') {
213 // hide submenu 219 // hide submenu
214 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') 220 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
215 221
216 // reset opacity to 0 222 // reset opacity to 0
217 this.settingsSubMenuEl_.style.opacity = '0' 223 this.settingsSubMenuEl_.style.opacity = '0'
@@ -219,25 +225,27 @@ class SettingsMenuItem extends MenuItem {
219 } 225 }
220 226
221 reset () { 227 reset () {
222 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') 228 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
223 this.settingsSubMenuEl_.style.opacity = '0' 229 this.settingsSubMenuEl_.style.opacity = '0'
224 this.setMargin() 230 this.setMargin()
225 } 231 }
226 232
227 loadMainMenu () { 233 loadMainMenu () {
234 const mainMenuEl = this.mainMenu.el() as HTMLElement
228 this.menuToLoad = 'mainmenu' 235 this.menuToLoad = 'mainmenu'
229 this.mainMenu.show() 236 this.mainMenu.show()
230 this.mainMenu.el_.style.opacity = '0' 237 mainMenuEl.style.opacity = '0'
231 238
232 // back button will always take you to main menu, so set dialog sizes 239 // back button will always take you to main menu, so set dialog sizes
233 this.settingsButton.setDialogSize([this.mainMenu.width, this.mainMenu.height]) 240 const mainMenuAny = this.mainMenu as any
241 this.settingsButton.setDialogSize([ mainMenuAny.width, mainMenuAny.height ])
234 242
235 // animation not triggered without timeout (some async stuff ?!?) 243 // animation not triggered without timeout (some async stuff ?!?)
236 setTimeout(() => { 244 setTimeout(() => {
237 // animate margin and opacity before hiding the submenu 245 // animate margin and opacity before hiding the submenu
238 // this triggers CSS Transition event 246 // this triggers CSS Transition event
239 this.setMargin() 247 this.setMargin()
240 this.mainMenu.el_.style.opacity = '1' 248 mainMenuEl.style.opacity = '1'
241 }, 0) 249 }, 0)
242 } 250 }
243 251
@@ -251,8 +259,8 @@ class SettingsMenuItem extends MenuItem {
251 this.update() 259 this.update()
252 }) 260 })
253 261
254 this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_) 262 this.settingsSubMenuTitleEl_.innerHTML = this.player().localize(this.subMenu.controlText())
255 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_) 263 this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el())
256 this.panelChildEl.appendChild(this.settingsSubMenuEl_) 264 this.panelChildEl.appendChild(this.settingsSubMenuEl_)
257 this.update() 265 this.update()
258 266
@@ -283,7 +291,8 @@ class SettingsMenuItem extends MenuItem {
283 // or sets options_['selected'] on the selected playback rate. 291 // or sets options_['selected'] on the selected playback rate.
284 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton 292 // Thus we get the submenu value based on the labelEl of playbackRateMenuButton
285 if (subMenu === 'PlaybackRateMenuButton') { 293 if (subMenu === 'PlaybackRateMenuButton') {
286 setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = this.subMenu.labelEl_.innerHTML, 250) 294 const html = (this.subMenu as any).labelEl_.innerHTML
295 setTimeout(() => this.settingsSubMenuValueEl_.innerHTML = html, 250)
287 } else { 296 } else {
288 // Loop trough the submenu items to find the selected child 297 // Loop trough the submenu items to find the selected child
289 for (const subMenuItem of this.subMenu.menu.children_) { 298 for (const subMenuItem of this.subMenu.menu.children_) {
@@ -292,13 +301,15 @@ class SettingsMenuItem extends MenuItem {
292 } 301 }
293 302
294 if (subMenuItem.hasClass('vjs-selected')) { 303 if (subMenuItem.hasClass('vjs-selected')) {
304 const subMenuItemUntyped = subMenuItem as any
305
295 // Prefer to use the function 306 // Prefer to use the function
296 if (typeof subMenuItem.getLabel === 'function') { 307 if (typeof subMenuItemUntyped.getLabel === 'function') {
297 this.settingsSubMenuValueEl_.innerHTML = subMenuItem.getLabel() 308 this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.getLabel()
298 break 309 break
299 } 310 }
300 311
301 this.settingsSubMenuValueEl_.innerHTML = subMenuItem.options_.label 312 this.settingsSubMenuValueEl_.innerHTML = subMenuItemUntyped.options_.label
302 } 313 }
303 } 314 }
304 } 315 }
@@ -313,7 +324,7 @@ class SettingsMenuItem extends MenuItem {
313 if (!(item instanceof component)) { 324 if (!(item instanceof component)) {
314 continue 325 continue
315 } 326 }
316 item.on(['tap', 'click'], this.submenuClickHandler) 327 item.on([ 'tap', 'click' ], this.submenuClickHandler)
317 } 328 }
318 } 329 }
319 330
@@ -321,11 +332,11 @@ class SettingsMenuItem extends MenuItem {
321 // if number of submenu items change dynamically more logic will be needed 332 // if number of submenu items change dynamically more logic will be needed
322 setSize () { 333 setSize () {
323 this.dialog.removeClass('vjs-hidden') 334 this.dialog.removeClass('vjs-hidden')
324 videojsUntyped.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden') 335 videojs.dom.removeClass(this.settingsSubMenuEl_, 'vjs-hidden')
325 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_) 336 this.size = this.settingsButton.getComponentSize(this.settingsSubMenuEl_)
326 this.setMargin() 337 this.setMargin()
327 this.dialog.addClass('vjs-hidden') 338 this.dialog.addClass('vjs-hidden')
328 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') 339 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
329 } 340 }
330 341
331 setMargin () { 342 setMargin () {
@@ -341,19 +352,19 @@ class SettingsMenuItem extends MenuItem {
341 */ 352 */
342 hideSubMenu () { 353 hideSubMenu () {
343 // after removing settings item this.el_ === null 354 // after removing settings item this.el_ === null
344 if (!this.el_) { 355 if (!this.el()) {
345 return 356 return
346 } 357 }
347 358
348 if (videojsUntyped.dom.hasClass(this.el_, 'open')) { 359 if (videojs.dom.hasClass(this.el(), 'open')) {
349 videojsUntyped.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden') 360 videojs.dom.addClass(this.settingsSubMenuEl_, 'vjs-hidden')
350 videojsUntyped.dom.removeClass(this.el_, 'open') 361 videojs.dom.removeClass(this.el(), 'open')
351 } 362 }
352 } 363 }
353 364
354} 365}
355 366
356SettingsMenuItem.prototype.contentElType = 'button' 367(SettingsMenuItem as any).prototype.contentElType = 'button'
357videojsUntyped.registerComponent('SettingsMenuItem', SettingsMenuItem) 368videojs.registerComponent('SettingsMenuItem', SettingsMenuItem)
358 369
359export { SettingsMenuItem } 370export { SettingsMenuItem }
diff --git a/client/src/assets/player/videojs-components/settings-panel-child.ts b/client/src/assets/player/videojs-components/settings-panel-child.ts
new file mode 100644
index 000000000..d12e8218a
--- /dev/null
+++ b/client/src/assets/player/videojs-components/settings-panel-child.ts
@@ -0,0 +1,22 @@
1import videojs, { VideoJsPlayer } from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsPanelChild extends Component {
6
7 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
8 super(player, options)
9 }
10
11 createEl () {
12 return super.createEl('div', {
13 className: 'vjs-settings-panel-child',
14 innerHTML: '',
15 tabIndex: -1
16 })
17 }
18}
19
20Component.registerComponent('SettingsPanelChild', SettingsPanelChild)
21
22export { SettingsPanelChild }
diff --git a/client/src/assets/player/videojs-components/settings-panel.ts b/client/src/assets/player/videojs-components/settings-panel.ts
new file mode 100644
index 000000000..2090abf45
--- /dev/null
+++ b/client/src/assets/player/videojs-components/settings-panel.ts
@@ -0,0 +1,22 @@
1import videojs, { VideoJsPlayer } from 'video.js'
2
3const Component = videojs.getComponent('Component')
4
5class SettingsPanel extends Component {
6
7 constructor (player: VideoJsPlayer, options?: videojs.ComponentOptions) {
8 super(player, options)
9 }
10
11 createEl () {
12 return super.createEl('div', {
13 className: 'vjs-settings-panel',
14 innerHTML: '',
15 tabIndex: -1
16 })
17 }
18}
19
20Component.registerComponent('SettingsPanel', SettingsPanel)
21
22export { SettingsPanel }
diff --git a/client/src/assets/player/videojs-components/theater-button.ts b/client/src/assets/player/videojs-components/theater-button.ts
index bf383cf34..1c8c9f154 100644
--- a/client/src/assets/player/videojs-components/theater-button.ts
+++ b/client/src/assets/player/videojs-components/theater-button.ts
@@ -1,26 +1,24 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore
3import * as videojs from 'video.js'
4
5import { VideoJSComponentInterface, videojsUntyped } from '../peertube-videojs-typings'
6import { saveTheaterInStore, getStoredTheater } from '../peertube-player-local-storage' 2import { saveTheaterInStore, getStoredTheater } from '../peertube-player-local-storage'
7 3
8const Button: VideoJSComponentInterface = videojsUntyped.getComponent('Button') 4const Button = videojs.getComponent('Button')
9class TheaterButton extends Button { 5class TheaterButton extends Button {
10 6
11 private static readonly THEATER_MODE_CLASS = 'vjs-theater-enabled' 7 private static readonly THEATER_MODE_CLASS = 'vjs-theater-enabled'
12 8
13 constructor (player: videojs.Player, options: any) { 9 constructor (player: VideoJsPlayer, options: videojs.ComponentOptions) {
14 super(player, options) 10 super(player, options)
15 11
16 const enabled = getStoredTheater() 12 const enabled = getStoredTheater()
17 if (enabled === true) { 13 if (enabled === true) {
18 this.player_.addClass(TheaterButton.THEATER_MODE_CLASS) 14 this.player().addClass(TheaterButton.THEATER_MODE_CLASS)
19 15
20 this.handleTheaterChange() 16 this.handleTheaterChange()
21 } 17 }
22 18
23 this.player_.theaterEnabled = enabled 19 this.controlText('Theater mode')
20
21 this.player().theaterEnabled = enabled
24 } 22 }
25 23
26 buildCSSClass () { 24 buildCSSClass () {
@@ -52,6 +50,4 @@ class TheaterButton extends Button {
52 } 50 }
53} 51}
54 52
55TheaterButton.prototype.controlText_ = 'Theater mode' 53videojs.registerComponent('TheaterButton', TheaterButton)
56
57TheaterButton.registerComponent('TheaterButton', TheaterButton)
diff --git a/client/src/assets/player/webtorrent/webtorrent-plugin.ts b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
index 35cf85c99..bf6b0a718 100644
--- a/client/src/assets/player/webtorrent/webtorrent-plugin.ts
+++ b/client/src/assets/player/webtorrent/webtorrent-plugin.ts
@@ -1,17 +1,15 @@
1// FIXME: something weird with our path definition in tsconfig and typings 1import videojs, { VideoJsPlayer } from 'video.js'
2// @ts-ignore
3import * as videojs from 'video.js'
4 2
5import * as WebTorrent from 'webtorrent' 3import * as WebTorrent from 'webtorrent'
6import { renderVideo } from './video-renderer' 4import { renderVideo } from './video-renderer'
7import { LoadedQualityData, PlayerNetworkInfo, VideoJSComponentInterface, WebtorrentPluginOptions } from '../peertube-videojs-typings' 5import { LoadedQualityData, PlayerNetworkInfo, WebtorrentPluginOptions } from '../peertube-videojs-typings'
8import { getRtcConfig, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from '../utils' 6import { getRtcConfig, timeToInt, videoFileMaxByResolution, videoFileMinByResolution } from '../utils'
9import { PeertubeChunkStore } from './peertube-chunk-store' 7import { PeertubeChunkStore } from './peertube-chunk-store'
10import { 8import {
11 getAverageBandwidthInStore, 9 getAverageBandwidthInStore,
12 getStoredMute, 10 getStoredMute,
13 getStoredVolume,
14 getStoredP2PEnabled, 11 getStoredP2PEnabled,
12 getStoredVolume,
15 saveAverageBandwidth 13 saveAverageBandwidth
16} from '../peertube-player-local-storage' 14} from '../peertube-player-local-storage'
17import { VideoFile } from '@shared/models' 15import { VideoFile } from '@shared/models'
@@ -24,14 +22,16 @@ type PlayOptions = {
24 delay?: number 22 delay?: number
25} 23}
26 24
27const Plugin: VideoJSComponentInterface = videojs.getPlugin('plugin') 25const Plugin = videojs.getPlugin('plugin')
26
28class WebTorrentPlugin extends Plugin { 27class WebTorrentPlugin extends Plugin {
28 readonly videoFiles: VideoFile[]
29
29 private readonly playerElement: HTMLVideoElement 30 private readonly playerElement: HTMLVideoElement
30 31
31 private readonly autoplay: boolean = false 32 private readonly autoplay: boolean = false
32 private readonly startTime: number = 0 33 private readonly startTime: number = 0
33 private readonly savePlayerSrcFunction: Function 34 private readonly savePlayerSrcFunction: VideoJsPlayer['src']
34 private readonly videoFiles: VideoFile[]
35 private readonly videoDuration: number 35 private readonly videoDuration: number
36 private readonly CONSTANTS = { 36 private readonly CONSTANTS = {
37 INFO_SCHEDULER: 1000, // Don't change this 37 INFO_SCHEDULER: 1000, // Don't change this
@@ -49,7 +49,6 @@ class WebTorrentPlugin extends Plugin {
49 dht: false 49 dht: false
50 }) 50 })
51 51
52 private player: any
53 private currentVideoFile: VideoFile 52 private currentVideoFile: VideoFile
54 private torrent: WebTorrent.Torrent 53 private torrent: WebTorrent.Torrent
55 54
@@ -70,8 +69,8 @@ class WebTorrentPlugin extends Plugin {
70 69
71 private downloadSpeeds: number[] = [] 70 private downloadSpeeds: number[] = []
72 71
73 constructor (player: videojs.Player, options: WebtorrentPluginOptions) { 72 constructor (player: VideoJsPlayer, options?: WebtorrentPluginOptions) {
74 super(player, options) 73 super(player)
75 74
76 this.startTime = timeToInt(options.startTime) 75 this.startTime = timeToInt(options.startTime)
77 76
@@ -147,12 +146,12 @@ class WebTorrentPlugin extends Plugin {
147 } 146 }
148 147
149 // Do not display error to user because we will have multiple fallback 148 // Do not display error to user because we will have multiple fallback
150 this.disableErrorDisplay() 149 this.disableErrorDisplay();
151 150
152 // Hack to "simulate" src link in video.js >= 6 151 // Hack to "simulate" src link in video.js >= 6
153 // Without this, we can't play the video after pausing it 152 // Without this, we can't play the video after pausing it
154 // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633 153 // https://github.com/videojs/video.js/blob/master/src/js/player.js#L1633
155 this.player.src = () => true 154 (this.player as any).src = () => true
156 const oldPlaybackRate = this.player.playbackRate() 155 const oldPlaybackRate = this.player.playbackRate()
157 156
158 const previousVideoFile = this.currentVideoFile 157 const previousVideoFile = this.currentVideoFile
@@ -333,7 +332,7 @@ class WebTorrentPlugin extends Plugin {
333 332
334 const playPromise = this.player.play() 333 const playPromise = this.player.play()
335 if (playPromise !== undefined) { 334 if (playPromise !== undefined) {
336 return playPromise.then(done) 335 return playPromise.then(() => done())
337 .catch((err: Error) => { 336 .catch((err: Error) => {
338 if (err.message.indexOf('The play() request was interrupted by a call to pause()') !== -1) { 337 if (err.message.indexOf('The play() request was interrupted by a call to pause()') !== -1) {
339 return 338 return
@@ -426,8 +425,8 @@ class WebTorrentPlugin extends Plugin {
426 } 425 }
427 426
428 // Proxy first play 427 // Proxy first play
429 const oldPlay = this.player.play.bind(this.player) 428 const oldPlay = this.player.play.bind(this.player);
430 this.player.play = () => { 429 (this.player as any).play = () => {
431 this.player.addClass('vjs-has-big-play-button-clicked') 430 this.player.addClass('vjs-has-big-play-button-clicked')
432 this.player.play = oldPlay 431 this.player.play = oldPlay
433 432
@@ -619,7 +618,7 @@ class WebTorrentPlugin extends Plugin {
619 video: qualityLevelsPayload 618 video: qualityLevelsPayload
620 } 619 }
621 } 620 }
622 this.player.tech_.trigger('loadedqualitydata', payload) 621 this.player.tech(true).trigger('loadedqualitydata', payload)
623 } 622 }
624 623
625 private buildQualityLabel (file: VideoFile) { 624 private buildQualityLabel (file: VideoFile) {
@@ -651,9 +650,9 @@ class WebTorrentPlugin extends Plugin {
651 return 650 return
652 } 651 }
653 652
654 for (let i = 0; i < qualityLevels; i++) { 653 for (let i = 0; i < qualityLevels.length; i++) {
655 const q = this.player.qualityLevels[i] 654 const q = qualityLevels[i]
656 if (q.height === resolutionId) qualityLevels.selectedIndex = i 655 if (q.height === resolutionId) qualityLevels.selectedIndex_ = i
657 } 656 }
658 } 657 }
659} 658}
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 5dacdd73b..fa2452231 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -32,7 +32,7 @@ body {
32 --menuForegroundColor: #{$menu-color}; 32 --menuForegroundColor: #{$menu-color};
33 --submenuColor: #{$sub-menu-color}; 33 --submenuColor: #{$sub-menu-color};
34 34
35 --inputColor: #{$input-background-color}; 35 --inputBackgroundColor: #{$input-background-color};
36 --inputPlaceholderColor: #{$input-placeholder-color}; 36 --inputPlaceholderColor: #{$input-placeholder-color};
37 37
38 --actionButtonColor: #{$grey-foreground-color}; 38 --actionButtonColor: #{$grey-foreground-color};
@@ -61,7 +61,7 @@ strong {
61 61
62input.readonly { 62input.readonly {
63 /* Force blank on readonly inputs */ 63 /* Force blank on readonly inputs */
64 background-color: var(--inputColor) !important; 64 background-color: var(--inputBackgroundColor) !important;
65} 65}
66 66
67input, textarea { 67input, textarea {
@@ -202,26 +202,6 @@ label {
202 to { transform: scale(1) rotate(360deg);} 202 to { transform: scale(1) rotate(360deg);}
203} 203}
204 204
205.orange-button {
206 @include peertube-button;
207 @include orange-button;
208}
209
210.orange-button-link {
211 @include peertube-button-link;
212 @include orange-button;
213}
214
215.grey-button {
216 @include peertube-button;
217 @include grey-button;
218}
219
220.grey-button-link {
221 @include peertube-button-link;
222 @include grey-button;
223}
224
225// In tables, don't have a hover different background 205// In tables, don't have a hover different background
226table { 206table {
227 .action-button-edit, .action-button-delete { 207 .action-button-edit, .action-button-delete {
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 136eddd3a..317781e0e 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -77,11 +77,17 @@
77 } 77 }
78} 78}
79 79
80@mixin button-focus-visible-shadow($color) {
81 &.focus-visible {
82 box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 4px $color;
83 }
84}
85
80@mixin peertube-input-text($width) { 86@mixin peertube-input-text($width) {
81 display: inline-block; 87 display: inline-block;
82 height: $button-height; 88 height: $button-height;
83 width: $width; 89 width: $width;
84 background: var(--inputColor); 90 background: var(--inputBackgroundColor);
85 border: 1px solid #C6C6C6; 91 border: 1px solid #C6C6C6;
86 border-radius: 3px; 92 border-radius: 3px;
87 padding-left: 15px; 93 padding-left: 15px;
@@ -118,6 +124,8 @@
118} 124}
119 125
120@mixin orange-button { 126@mixin orange-button {
127 @include button-focus-visible-shadow(var(--mainHoverColor));
128
121 &, &:active, &:focus { 129 &, &:active, &:focus {
122 color: #fff; 130 color: #fff;
123 background-color: var(--mainColor); 131 background-color: var(--mainColor);
@@ -169,7 +177,6 @@
169 text-align: center; 177 text-align: center;
170 padding: 0 17px 0 13px; 178 padding: 0 17px 0 13px;
171 cursor: pointer; 179 cursor: pointer;
172 outline: 0;
173} 180}
174 181
175@mixin peertube-button-link { 182@mixin peertube-button-link {
@@ -254,7 +261,7 @@
254 width: $width; 261 width: $width;
255 border-radius: 3px; 262 border-radius: 3px;
256 overflow: hidden; 263 overflow: hidden;
257 background: var(--inputColor); 264 background: var(--inputBackgroundColor);
258 position: relative; 265 position: relative;
259 font-size: 15px; 266 font-size: 15px;
260 267
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index 5b5ac9adc..e087a2548 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -81,7 +81,7 @@ $variables: (
81 --menuForegroundColor: var(--menuForegroundColor), 81 --menuForegroundColor: var(--menuForegroundColor),
82 --submenuColor: var(--submenuColor), 82 --submenuColor: var(--submenuColor),
83 83
84 --inputColor: var(--inputColor), 84 --inputBackgroundColor: var(--inputBackgroundColor),
85 --inputPlaceholderColor: var(--inputPlaceholderColor), 85 --inputPlaceholderColor: var(--inputPlaceholderColor),
86 86
87 --actionButtonColor: var(--actionButtonColor), 87 --actionButtonColor: var(--actionButtonColor),
diff --git a/client/src/sass/player/_player-variables.scss b/client/src/sass/player/_player-variables.scss
index 0c2359ac7..935a60b49 100644
--- a/client/src/sass/player/_player-variables.scss
+++ b/client/src/sass/player/_player-variables.scss
@@ -1,7 +1,7 @@
1$primary-foreground-color: #fff; 1$primary-foreground-color: #fff;
2$primary-foreground-opacity: 0.9; 2$primary-foreground-opacity: 0.9;
3$primary-foreground-opacity-hover: 1; 3$primary-foreground-opacity-hover: 1;
4$primary-background-color: #000; 4$primary-background-color: rgba(0, 0, 0, 0.8);
5 5
6$font-size: 13px; 6$font-size: 13px;
7$control-bar-height: 34px; 7$control-bar-height: 34px;
diff --git a/client/src/sass/player/bezels.scss b/client/src/sass/player/bezels.scss
index ff3448511..853a030a3 100644
--- a/client/src/sass/player/bezels.scss
+++ b/client/src/sass/player/bezels.scss
@@ -32,11 +32,3 @@
32 fill: #fff; 32 fill: #fff;
33 } 33 }
34} 34}
35
36.video-js {
37
38 .vjs-bezel-content {
39
40 }
41
42}
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index 41e2a535c..f80106428 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -21,6 +21,12 @@ body {
21 21
22 .vjs-dock-text { 22 .vjs-dock-text {
23 padding-right: 10px; 23 padding-right: 10px;
24 background: linear-gradient(to bottom, rgba(0, 0, 0, .6) 0, rgba(0, 0, 0, 0.2) 70%, rgba(0, 0, 0, 0) 100%);
25 }
26
27 .vjs-dock-title,
28 .vjs-dock-description {
29 text-shadow: 0 0 2px rgba(0, 0, 0, .5);
24 } 30 }
25 31
26 .vjs-dock-description { 32 .vjs-dock-description {
@@ -55,7 +61,7 @@ body {
55 $big-play-width: 1.2em; 61 $big-play-width: 1.2em;
56 $big-play-height: 1.2em; 62 $big-play-height: 1.2em;
57 63
58 border: 4px solid #fff; 64 border: 2px solid #fff;
59 border-radius: 100%; 65 border-radius: 100%;
60 66
61 left: 50%; 67 left: 50%;
@@ -185,7 +191,6 @@ body {
185 191
186 .vjs-play-progress { 192 .vjs-play-progress {
187 background: var(--embedForegroundColor); 193 background: var(--embedForegroundColor);
188 transition: all .2s ease 0s;
189 194
190 // Not display the circle if the progress is not hovered 195 // Not display the circle if the progress is not hovered
191 &::before { 196 &::before {
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index c91ae08b9..d5b42a025 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,15 +1,11 @@
1import './embed.scss' 1import './embed.scss'
2 2
3import { 3import {
4 getCompleteLocale,
5 is18nLocale,
6 isDefaultLocale,
7 peertubeTranslate, 4 peertubeTranslate,
8 ResultList, 5 ResultList,
9 ServerConfig, 6 ServerConfig,
10 VideoDetails 7 VideoDetails
11} from '../../../../shared' 8} from '../../../../shared'
12import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
13import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 9import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
14import { 10import {
15 P2PMediaLoaderOptions, 11 P2PMediaLoaderOptions,
@@ -19,10 +15,14 @@ import {
19import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 15import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
20import { PeerTubeEmbedApi } from './embed-api' 16import { PeerTubeEmbedApi } from './embed-api'
21import { TranslationsManager } from '../../assets/player/translations-manager' 17import { TranslationsManager } from '../../assets/player/translations-manager'
18import { VideoJsPlayer } from 'video.js'
19import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
20
21type Translations = { [ id: string ]: string }
22 22
23export class PeerTubeEmbed { 23export class PeerTubeEmbed {
24 videoElement: HTMLVideoElement 24 videoElement: HTMLVideoElement
25 player: any 25 player: VideoJsPlayer
26 api: PeerTubeEmbedApi = null 26 api: PeerTubeEmbedApi = null
27 autoplay: boolean 27 autoplay: boolean
28 controls: boolean 28 controls: boolean
@@ -71,7 +71,7 @@ export class PeerTubeEmbed {
71 element.parentElement.removeChild(element) 71 element.parentElement.removeChild(element)
72 } 72 }
73 73
74 displayError (text: string, translations?: { [ id: string ]: string }) { 74 displayError (text: string, translations?: Translations) {
75 // Remove video element 75 // Remove video element
76 if (this.videoElement) this.removeElement(this.videoElement) 76 if (this.videoElement) this.removeElement(this.videoElement)
77 77
@@ -90,12 +90,12 @@ export class PeerTubeEmbed {
90 errorText.innerHTML = translatedText 90 errorText.innerHTML = translatedText
91 } 91 }
92 92
93 videoNotFound (translations?: { [ id: string ]: string }) { 93 videoNotFound (translations?: Translations) {
94 const text = 'This video does not exist.' 94 const text = 'This video does not exist.'
95 this.displayError(text, translations) 95 this.displayError(text, translations)
96 } 96 }
97 97
98 videoFetchError (translations?: { [ id: string ]: string }) { 98 videoFetchError (translations?: Translations) {
99 const text = 'We cannot fetch the video. Please try again later.' 99 const text = 'We cannot fetch the video. Please try again later.'
100 this.displayError(text, translations) 100 this.displayError(text, translations)
101 } 101 }
@@ -237,7 +237,7 @@ export class PeerTubeEmbed {
237 }) 237 })
238 } 238 }
239 239
240 this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: any) => this.player = player) 240 this.player = await PeertubePlayerManager.initialize(this.mode, options, (player: VideoJsPlayer) => this.player = player)
241 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) 241 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
242 242
243 window[ 'videojsPlayer' ] = this.player 243 window[ 'videojsPlayer' ] = this.player
@@ -261,19 +261,19 @@ export class PeerTubeEmbed {
261 } 261 }
262 262
263 private async buildDock (videoInfo: VideoDetails, configResponse: Response) { 263 private async buildDock (videoInfo: VideoDetails, configResponse: Response) {
264 if (this.controls) { 264 if (!this.controls) return
265 const title = this.title ? videoInfo.name : undefined
266 265
267 const config: ServerConfig = await configResponse.json() 266 const title = this.title ? videoInfo.name : undefined
268 const description = config.tracker.enabled && this.warningTitle
269 ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>'
270 : undefined
271 267
272 this.player.dock({ 268 const config: ServerConfig = await configResponse.json()
273 title, 269 const description = config.tracker.enabled && this.warningTitle
274 description 270 ? '<span class="text">' + peertubeTranslate('Watching this video may reveal your IP address to others.') + '</span>'
275 }) 271 : undefined
276 } 272
273 this.player.dock({
274 title,
275 description
276 })
277 } 277 }
278 278
279 private buildCSS () { 279 private buildCSS () {
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 8824c4f7c..c4f2d6a6a 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -26,7 +26,7 @@
26 "paths": { 26 "paths": {
27 "@app/*": [ "src/app/*" ], 27 "@app/*": [ "src/app/*" ],
28 "@shared/*": [ "../shared/*" ], 28 "@shared/*": [ "../shared/*" ],
29 "video.js": [ "node_modules/video.js/dist/alt/video.core.js" ], 29 "video.js": [ "node_modules/video.js/dist/alt/video.core.novtt" ],
30 "fs": [ "src/shims/noop" ], 30 "fs": [ "src/shims/noop" ],
31 "http": [ "src/shims/http" ], 31 "http": [ "src/shims/http" ],
32 "https": [ "src/shims/https" ], 32 "https": [ "src/shims/https" ],
diff --git a/client/webpack/webpack.video-embed.js b/client/webpack/webpack.video-embed.js
index 909048cca..f6d532556 100644
--- a/client/webpack/webpack.video-embed.js
+++ b/client/webpack/webpack.video-embed.js
@@ -27,7 +27,7 @@ module.exports = function () {
27 modules: [ helpers.root('src'), helpers.root('node_modules') ], 27 modules: [ helpers.root('src'), helpers.root('node_modules') ],
28 28
29 alias: { 29 alias: {
30 'video.js$': path.resolve('node_modules/video.js/dist/alt/video.core.js') 30 'video.js$': path.resolve('node_modules/video.js/dist/alt/video.core.novtt.js')
31 } 31 }
32 }, 32 },
33 33
diff --git a/client/yarn.lock b/client/yarn.lock
index 0855a2570..1b9c2cef1 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -987,11 +987,6 @@
987 semver "6.3.0" 987 semver "6.3.0"
988 semver-intersect "1.4.0" 988 semver-intersect "1.4.0"
989 989
990"@streamroot/videojs-hlsjs-plugin@^1.0.10":
991 version "1.0.13"
992 resolved "https://registry.yarnpkg.com/@streamroot/videojs-hlsjs-plugin/-/videojs-hlsjs-plugin-1.0.13.tgz#ae3afb3a5a3cd90e7b424b6b4cb14de1cde40836"
993 integrity sha512-A55213sFj8nuoj23YiR0r73cRV4dlnSwXGwT1Qiu+oqhsauhqN+lHSRHFztMIU4EMf2Cafvv5P4R+A2c/Uj6nw==
994
995"@types/bittorrent-protocol@*": 990"@types/bittorrent-protocol@*":
996 version "2.2.4" 991 version "2.2.4"
997 resolved "https://registry.yarnpkg.com/@types/bittorrent-protocol/-/bittorrent-protocol-2.2.4.tgz#7dc0716924bc6a904753d39846ad235c7dab4641" 992 resolved "https://registry.yarnpkg.com/@types/bittorrent-protocol/-/bittorrent-protocol-2.2.4.tgz#7dc0716924bc6a904753d39846ad235c7dab4641"
@@ -1045,6 +1040,11 @@
1045 resolved "https://registry.yarnpkg.com/@types/jschannel/-/jschannel-1.0.1.tgz#79d582ccf42554c8457230526a3054d018d559f0" 1040 resolved "https://registry.yarnpkg.com/@types/jschannel/-/jschannel-1.0.1.tgz#79d582ccf42554c8457230526a3054d018d559f0"
1046 integrity sha512-S34NuOoOOKXbft3f9GDeLKp777ABCGArZaqUWOuu1Xn+1S75Osmk8kCeqmw5x2TuASyjE082DwDAuoaXNIRCTw== 1041 integrity sha512-S34NuOoOOKXbft3f9GDeLKp777ABCGArZaqUWOuu1Xn+1S75Osmk8kCeqmw5x2TuASyjE082DwDAuoaXNIRCTw==
1047 1042
1043"@types/linkify-it@*":
1044 version "2.1.0"
1045 resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806"
1046 integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw==
1047
1048"@types/linkifyjs@^2.1.2": 1048"@types/linkifyjs@^2.1.2":
1049 version "2.1.2" 1049 version "2.1.2"
1050 resolved "https://registry.yarnpkg.com/@types/linkifyjs/-/linkifyjs-2.1.2.tgz#8244f4e6d7be65359cc25a34da8977fce87a7b2e" 1050 resolved "https://registry.yarnpkg.com/@types/linkifyjs/-/linkifyjs-2.1.2.tgz#8244f4e6d7be65359cc25a34da8977fce87a7b2e"
@@ -1071,10 +1071,12 @@
1071 dependencies: 1071 dependencies:
1072 "@types/node" "*" 1072 "@types/node" "*"
1073 1073
1074"@types/markdown-it@^0.0.5": 1074"@types/markdown-it@^0.0.9":
1075 version "0.0.5" 1075 version "0.0.9"
1076 resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.5.tgz#5cdcbe08e81075d5dbf15466b311359b02a30c2b" 1076 resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
1077 integrity sha512-Bhc4jTJ3g+WU+dBvyhwwssHifjqapauyjV+0cTWVWRjwDAaK9PebZBFpLJmoOCp47qlkDeeT1Y9sV9LyyaG02w== 1077 integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
1078 dependencies:
1079 "@types/linkify-it" "*"
1078 1080
1079"@types/minimatch@*": 1081"@types/minimatch@*":
1080 version "3.0.3" 1082 version "3.0.3"
@@ -1157,10 +1159,10 @@
1157 resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" 1159 resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
1158 integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== 1160 integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
1159 1161
1160"@types/video.js@^7.2.5": 1162"@types/video.js@^7.3.3":
1161 version "7.2.15" 1163 version "7.3.3"
1162 resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.2.15.tgz#03d950f01c985a5082ead4d1b73064455a1c8c6f" 1164 resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.3.3.tgz#b6870d954473dfd694e10b55a90c0f3be8522da3"
1163 integrity sha512-NsojVfvTwdVqDe0+vJaoHOO2iuLm0sp/u8jEsZeLGsM3gNfg5WIOFd6NC0cQR9JHUuDPPSPF70jxdklGWm5jhQ== 1165 integrity sha512-yAb46+4A0dKFxOQRVLoLyfC/S/BmHLE10MxPXt/t88+7R4GWLHosHelVtYpKBRykjptdkqfQXNRXoQzDeKm6MA==
1164 1166
1165"@types/webpack-sources@^0.1.5": 1167"@types/webpack-sources@^0.1.5":
1166 version "0.1.5" 1168 version "0.1.5"
@@ -2586,6 +2588,29 @@ chardet@^0.7.0:
2586 resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" 2588 resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
2587 integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== 2589 integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
2588 2590
2591chart.js@^2.9.3:
2592 version "2.9.3"
2593 resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.3.tgz#ae3884114dafd381bc600f5b35a189138aac1ef7"
2594 integrity sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==
2595 dependencies:
2596 chartjs-color "^2.1.0"
2597 moment "^2.10.2"
2598
2599chartjs-color-string@^0.6.0:
2600 version "0.6.0"
2601 resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
2602 integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
2603 dependencies:
2604 color-name "^1.0.0"
2605
2606chartjs-color@^2.1.0:
2607 version "2.4.1"
2608 resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.4.1.tgz#6118bba202fe1ea79dd7f7c0f9da93467296c3b0"
2609 integrity sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==
2610 dependencies:
2611 chartjs-color-string "^0.6.0"
2612 color-convert "^1.9.3"
2613
2589check-types@^8.0.3: 2614check-types@^8.0.3:
2590 version "8.0.3" 2615 version "8.0.3"
2591 resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552" 2616 resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
@@ -2800,7 +2825,7 @@ collection-visit@^1.0.0:
2800 map-visit "^1.0.0" 2825 map-visit "^1.0.0"
2801 object-visit "^1.0.0" 2826 object-visit "^1.0.0"
2802 2827
2803color-convert@^1.9.0: 2828color-convert@^1.9.0, color-convert@^1.9.3:
2804 version "1.9.3" 2829 version "1.9.3"
2805 resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 2830 resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
2806 integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 2831 integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@@ -2812,6 +2837,11 @@ color-name@1.1.3:
2812 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 2837 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
2813 integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 2838 integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
2814 2839
2840color-name@^1.0.0:
2841 version "1.1.4"
2842 resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
2843 integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
2844
2815colors@1.1.2: 2845colors@1.1.2:
2816 version "1.1.2" 2846 version "1.1.2"
2817 resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" 2847 resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -6941,6 +6971,11 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
6941 dependencies: 6971 dependencies:
6942 minimist "0.0.8" 6972 minimist "0.0.8"
6943 6973
6974moment@^2.10.2:
6975 version "2.24.0"
6976 resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
6977 integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
6978
6944mousetrap@^1.6.0: 6979mousetrap@^1.6.0:
6945 version "1.6.3" 6980 version "1.6.3"
6946 resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.3.tgz#80fee49665fd478bccf072c9d46bdf1bfed3558a" 6981 resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.3.tgz#80fee49665fd478bccf072c9d46bdf1bfed3558a"
@@ -10809,6 +10844,11 @@ void-elements@^2.0.0:
10809 resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" 10844 resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
10810 integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= 10845 integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
10811 10846
10847vtt.js@^0.13.0:
10848 version "0.13.0"
10849 resolved "https://registry.yarnpkg.com/vtt.js/-/vtt.js-0.13.0.tgz#955c667b34d5325b2012cb9e8ba9bad6e0b11ff8"
10850 integrity sha1-lVxmezTVMlsgEsuei6m61uCxH/g=
10851
10812watchpack@^1.6.0: 10852watchpack@^1.6.0:
10813 version "1.6.0" 10853 version "1.6.0"
10814 resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" 10854 resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"
diff --git a/config/test.yaml b/config/test.yaml
index 5758c1887..74979f3a7 100644
--- a/config/test.yaml
+++ b/config/test.yaml
@@ -40,18 +40,18 @@ contact_form:
40 40
41redundancy: 41redundancy:
42 videos: 42 videos:
43 check_interval: '10 minutes' 43 check_interval: '1 minute'
44 strategies: 44 strategies:
45 - 45 -
46 size: '10MB' 46 size: '1000MB'
47 min_lifetime: '10 minutes' 47 min_lifetime: '10 minutes'
48 strategy: 'most-views' 48 strategy: 'most-views'
49 - 49 -
50 size: '10MB' 50 size: '1000MB'
51 min_lifetime: '10 minutes' 51 min_lifetime: '10 minutes'
52 strategy: 'trending' 52 strategy: 'trending'
53 - 53 -
54 size: '10MB' 54 size: '1000MB'
55 min_lifetime: '10 minutes' 55 min_lifetime: '10 minutes'
56 strategy: 'recently-added' 56 strategy: 'recently-added'
57 min_views: 1 57 min_views: 1
diff --git a/package.json b/package.json
index d4858725a..0a5484d2a 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
5 "private": true, 5 "private": true,
6 "licence": "AGPL-3.0", 6 "licence": "AGPL-3.0",
7 "engines": { 7 "engines": {
8 "node": ">=8.x" 8 "node": ">=10.x"
9 }, 9 },
10 "bin": { 10 "bin": {
11 "peertube": "dist/server/tools/peertube.js" 11 "peertube": "dist/server/tools/peertube.js"
@@ -37,11 +37,10 @@
37 "i18n:generate": "scripty", 37 "i18n:generate": "scripty",
38 "plugin:install": "node ./dist/scripts/plugin/install.js", 38 "plugin:install": "node ./dist/scripts/plugin/install.js",
39 "plugin:uninstall": "node ./dist/scripts/plugin/uninstall.js", 39 "plugin:uninstall": "node ./dist/scripts/plugin/uninstall.js",
40 "i18n:xliff2json": "node ./dist/scripts/i18n/xliff2json.js",
41 "i18n:create-custom-files": "node ./dist/scripts/i18n/create-custom-files.js", 40 "i18n:create-custom-files": "node ./dist/scripts/i18n/create-custom-files.js",
42 "reset-password": "node ./dist/scripts/reset-password.js", 41 "reset-password": "node ./dist/scripts/reset-password.js",
43 "play": "scripty", 42 "play": "scripty",
44 "dev": "scripty", 43 "dev": "sh ./scripts/dev/index.sh",
45 "dev:server": "sh ./scripts/dev/server.sh", 44 "dev:server": "sh ./scripts/dev/server.sh",
46 "dev:embed": "scripty", 45 "dev:embed": "scripty",
47 "dev:client": "sh ./scripts/dev/client.sh", 46 "dev:client": "sh ./scripts/dev/client.sh",
@@ -64,7 +63,7 @@
64 "ng": "ng", 63 "ng": "ng",
65 "nodemon": "nodemon", 64 "nodemon": "nodemon",
66 "ts-node": "ts-node", 65 "ts-node": "ts-node",
67 "tslint": "tslint", 66 "eslint": "eslint",
68 "concurrently": "concurrently", 67 "concurrently": "concurrently",
69 "mocha-parallel-tests": "mocha-parallel-tests", 68 "mocha-parallel-tests": "mocha-parallel-tests",
70 "sasslint": "sass-lint --verbose --no-exit", 69 "sasslint": "sass-lint --verbose --no-exit",
@@ -78,9 +77,6 @@
78 "swagger-cli": "swagger-cli", 77 "swagger-cli": "swagger-cli",
79 "sass-lint": "sass-lint" 78 "sass-lint": "sass-lint"
80 }, 79 },
81 "resolutions": {
82 "@types/bluebird": "3.5.27"
83 },
84 "dependencies": { 80 "dependencies": {
85 "apicache": "^1.4.0", 81 "apicache": "^1.4.0",
86 "async": "^3.0.1", 82 "async": "^3.0.1",
@@ -100,7 +96,7 @@
100 "express": "^4.12.4", 96 "express": "^4.12.4",
101 "express-oauth-server": "^2.0.0", 97 "express-oauth-server": "^2.0.0",
102 "express-rate-limit": "^4.0.4", 98 "express-rate-limit": "^4.0.4",
103 "express-validator": "^6.1.1", 99 "express-validator": "^6.4.0",
104 "flat": "^5.0.0", 100 "flat": "^5.0.0",
105 "fluent-ffmpeg": "^2.1.0", 101 "fluent-ffmpeg": "^2.1.0",
106 "fs-extra": "^8.0.1", 102 "fs-extra": "^8.0.1",
@@ -109,7 +105,7 @@
109 "ip-anonymize": "^0.1.0", 105 "ip-anonymize": "^0.1.0",
110 "ipaddr.js": "1.9.1", 106 "ipaddr.js": "1.9.1",
111 "is-cidr": "^3.0.0", 107 "is-cidr": "^3.0.0",
112 "iso-639-3": "^1.0.1", 108 "iso-639-3": "^2.0.0",
113 "js-yaml": "^3.5.4", 109 "js-yaml": "^3.5.4",
114 "jsonld": "~2.0.1", 110 "jsonld": "~2.0.1",
115 "lodash": "^4.17.10", 111 "lodash": "^4.17.10",
@@ -131,7 +127,7 @@
131 "scripty": "^1.5.0", 127 "scripty": "^1.5.0",
132 "sequelize": "5.21.3", 128 "sequelize": "5.21.3",
133 "sequelize-typescript": "^1.0.0-beta.4", 129 "sequelize-typescript": "^1.0.0-beta.4",
134 "sharp": "^0.23.3", 130 "sharp": "^0.24.0",
135 "sitemap": "^5.0.0", 131 "sitemap": "^5.0.0",
136 "socket.io": "^2.2.0", 132 "socket.io": "^2.2.0",
137 "srt-to-vtt": "^1.1.2", 133 "srt-to-vtt": "^1.1.2",
@@ -143,16 +139,16 @@
143 "webtorrent": "^0.107.16", 139 "webtorrent": "^0.107.16",
144 "winston": "3.2.1", 140 "winston": "3.2.1",
145 "ws": "^7.0.0", 141 "ws": "^7.0.0",
146 "youtube-dl": "^2.0.0" 142 "youtube-dl": "^3.0.2"
147 }, 143 },
148 "devDependencies": { 144 "devDependencies": {
149 "@types/apicache": "^1.2.0", 145 "@types/apicache": "^1.2.0",
150 "@types/async": "^3.0.0", 146 "@types/async": "^3.0.0",
151 "@types/async-lock": "^1.1.0", 147 "@types/async-lock": "^1.1.0",
152 "@types/bcrypt": "^3.0.0", 148 "@types/bcrypt": "^3.0.0",
153 "@types/bluebird": "3.5.21", 149 "@types/bluebird": "3.5.29",
154 "@types/body-parser": "^1.16.3", 150 "@types/body-parser": "^1.16.3",
155 "@types/bull": "3.4.0", 151 "@types/bull": "3.12.0",
156 "@types/bytes": "^3.0.0", 152 "@types/bytes": "^3.0.0",
157 "@types/chai": "^4.0.4", 153 "@types/chai": "^4.0.4",
158 "@types/chai-json-schema": "^1.4.3", 154 "@types/chai-json-schema": "^1.4.3",
@@ -169,7 +165,7 @@
169 "@types/maildev": "^0.0.1", 165 "@types/maildev": "^0.0.1",
170 "@types/memoizee": "^0.4.2", 166 "@types/memoizee": "^0.4.2",
171 "@types/mkdirp": "^0.5.1", 167 "@types/mkdirp": "^0.5.1",
172 "@types/mocha": "^5.0.0", 168 "@types/mocha": "^7.0.1",
173 "@types/morgan": "^1.7.32", 169 "@types/morgan": "^1.7.32",
174 "@types/multer": "^1.3.3", 170 "@types/multer": "^1.3.3",
175 "@types/node": "^10.0.8", 171 "@types/node": "^10.0.8",
@@ -178,18 +174,24 @@
178 "@types/pem": "^1.9.3", 174 "@types/pem": "^1.9.3",
179 "@types/redis": "^2.8.5", 175 "@types/redis": "^2.8.5",
180 "@types/request": "^2.0.3", 176 "@types/request": "^2.0.3",
181 "@types/sharp": "^0.23.0", 177 "@types/sharp": "^0.24.0",
182 "@types/socket.io": "^2.1.2", 178 "@types/socket.io": "^2.1.2",
183 "@types/supertest": "^2.0.3", 179 "@types/supertest": "^2.0.3",
184 "@types/validator": "^12.0.1", 180 "@types/validator": "^12.0.1",
185 "@types/webtorrent": "^0.107.0", 181 "@types/webtorrent": "^0.107.0",
186 "@types/ws": "^6.0.0", 182 "@types/ws": "^7.2.1",
183 "@typescript-eslint/eslint-plugin": "^2.18.0",
187 "chai": "^4.1.1", 184 "chai": "^4.1.1",
188 "chai-json-schema": "^1.5.0", 185 "chai-json-schema": "^1.5.0",
189 "chai-xml": "^0.3.2", 186 "chai-xml": "^0.3.2",
190 "concurrently": "^5.0.0", 187 "concurrently": "^5.0.0",
188 "eslint": "^6.8.0",
189 "eslint-config-standard-with-typescript": "^12.0.1",
190 "eslint-plugin-import": "^2.20.1",
191 "eslint-plugin-node": "^11.0.0",
192 "eslint-plugin-promise": "^4.2.1",
193 "eslint-plugin-standard": "^4.0.1",
191 "libxmljs": "0.19.7", 194 "libxmljs": "0.19.7",
192 "lint-staged": "^9.2.0",
193 "maildev": "^1.0.0-rc3", 195 "maildev": "^1.0.0-rc3",
194 "marked": "^0.8.0", 196 "marked": "^0.8.0",
195 "marked-man": "^0.7.0", 197 "marked-man": "^0.7.0",
@@ -198,12 +200,9 @@
198 "nodemon": "^2.0.1", 200 "nodemon": "^2.0.1",
199 "source-map-support": "^0.5.0", 201 "source-map-support": "^0.5.0",
200 "supertest": "^4.0.2", 202 "supertest": "^4.0.2",
201 "swagger-cli": "^2.2.0", 203 "swagger-cli": "^3.0.1",
202 "ts-node": "8.5.4", 204 "ts-node": "8.6.2",
203 "tslint": "^5.7.0", 205 "typescript": "^3.7.2"
204 "tslint-config-standard": "^9.0.0",
205 "typescript": "^3.7.2",
206 "xliff": "^4.0.0"
207 }, 206 },
208 "scripty": { 207 "scripty": {
209 "silent": true 208 "silent": true
diff --git a/scripts/ci.sh b/scripts/ci.sh
index d111b7447..aea009d9f 100755
--- a/scripts/ci.sh
+++ b/scripts/ci.sh
@@ -12,7 +12,7 @@ killall -q peertube || true
12perl -0777 -i -pe 's#proxy:(\n\s+)enabled: false\n\s+url: ""#proxy:$1enabled: true$1url: "http://188.165.225.149:7899"#' config/test.yaml 12perl -0777 -i -pe 's#proxy:(\n\s+)enabled: false\n\s+url: ""#proxy:$1enabled: true$1url: "http://188.165.225.149:7899"#' config/test.yaml
13 13
14if [ "$1" = "misc" ]; then 14if [ "$1" = "misc" ]; then
15 npm run build -- --light-fr 15 npm run build -- --light
16 mocha --timeout 5000 --exit --require ts-node/register --require tsconfig-paths/register --bail server/tests/client.ts \ 16 mocha --timeout 5000 --exit --require ts-node/register --require tsconfig-paths/register --bail server/tests/client.ts \
17 server/tests/feeds/index.ts \ 17 server/tests/feeds/index.ts \
18 server/tests/misc-endpoints.ts \ 18 server/tests/misc-endpoints.ts \
@@ -35,7 +35,7 @@ elif [ "$1" = "api-4" ]; then
35 npm run build:server 35 npm run build:server
36 sh ./server/tests/api/ci-4.sh 2 36 sh ./server/tests/api/ci-4.sh 2
37elif [ "$1" = "lint" ]; then 37elif [ "$1" = "lint" ]; then
38 npm run tslint -- --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" "shared/**/*.ts" 38 npm run eslint -- --ext .ts "server/**/*.ts" "shared/**/*.ts"
39 npm run swagger-cli -- validate support/doc/api/openapi.yaml 39 npm run swagger-cli -- validate support/doc/api/openapi.yaml
40 40
41 ( cd client 41 ( cd client
diff --git a/scripts/create-import-video-file-job.ts b/scripts/create-import-video-file-job.ts
index 204337d55..37738ca40 100644
--- a/scripts/create-import-video-file-job.ts
+++ b/scripts/create-import-video-file-job.ts
@@ -38,6 +38,6 @@ async function run () {
38 } 38 }
39 39
40 await JobQueue.Instance.init() 40 await JobQueue.Instance.init()
41 await JobQueue.Instance.createJob({ type: 'video-file-import', payload: dataInput }) 41 await JobQueue.Instance.createJobWithPromise({ type: 'video-file-import', payload: dataInput })
42 console.log('Import job for video %s created.', video.uuid) 42 console.log('Import job for video %s created.', video.uuid)
43} 43}
diff --git a/scripts/create-transcoding-job.ts b/scripts/create-transcoding-job.ts
index 27170299d..fec58da2e 100755
--- a/scripts/create-transcoding-job.ts
+++ b/scripts/create-transcoding-job.ts
@@ -72,7 +72,7 @@ async function run () {
72 await JobQueue.Instance.init() 72 await JobQueue.Instance.init()
73 73
74 for (const d of dataInput) { 74 for (const d of dataInput) {
75 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: d }) 75 await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: d })
76 console.log('Transcoding job for video %s created.', video.uuid) 76 console.log('Transcoding job for video %s created.', video.uuid)
77 } 77 }
78} 78}
diff --git a/scripts/dev/index.sh b/scripts/dev/index.sh
index d221d2fc8..9e89516b8 100755
--- a/scripts/dev/index.sh
+++ b/scripts/dev/index.sh
@@ -3,5 +3,5 @@
3set -eu 3set -eu
4 4
5NODE_ENV=test npm run concurrently -- -k \ 5NODE_ENV=test npm run concurrently -- -k \
6 "npm run dev:client -- --skip-server" \ 6 "sh scripts/dev/client.sh --skip-server" \
7 "npm run dev:server" 7 "sh scripts/dev/server.sh"
diff --git a/server.ts b/server.ts
index 8df3add2c..b14ebf623 100644
--- a/server.ts
+++ b/server.ts
@@ -1,10 +1,6 @@
1import { registerTSPaths } from './server/helpers/register-ts-paths' 1import { registerTSPaths } from './server/helpers/register-ts-paths'
2
3registerTSPaths() 2registerTSPaths()
4 3
5// FIXME: https://github.com/nodejs/node/pull/16853
6require('tls').DEFAULT_ECDH_CURVE = 'auto'
7
8import { isTestInstance } from './server/helpers/core-utils' 4import { isTestInstance } from './server/helpers/core-utils'
9if (isTestInstance()) { 5if (isTestInstance()) {
10 require('source-map-support').install() 6 require('source-map-support').install()
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 62412cd62..2812bfe1e 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -14,7 +14,7 @@ import {
14 videosCustomGetValidator, 14 videosCustomGetValidator,
15 videosShareValidator 15 videosShareValidator
16} from '../../middlewares' 16} from '../../middlewares'
17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' 17import { getAccountVideoRateValidatorFactory, videoCommentGetValidator } from '../../middlewares/validators'
18import { AccountModel } from '../../models/account/account' 18import { AccountModel } from '../../models/account/account'
19import { ActorFollowModel } from '../../models/activitypub/actor-follow' 19import { ActorFollowModel } from '../../models/activitypub/actor-follow'
20import { VideoModel } from '../../models/video/video' 20import { VideoModel } from '../../models/video/video'
@@ -63,13 +63,13 @@ activityPubClientRouter.get('/accounts?/:name/playlists',
63) 63)
64activityPubClientRouter.get('/accounts?/:name/likes/:videoId', 64activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
65 executeIfActivityPub, 65 executeIfActivityPub,
66 asyncMiddleware(getAccountVideoRateValidator('like')), 66 asyncMiddleware(getAccountVideoRateValidatorFactory('like')),
67 getAccountVideoRate('like') 67 getAccountVideoRateFactory('like')
68) 68)
69activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', 69activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
70 executeIfActivityPub, 70 executeIfActivityPub,
71 asyncMiddleware(getAccountVideoRateValidator('dislike')), 71 asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')),
72 getAccountVideoRate('dislike') 72 getAccountVideoRateFactory('dislike')
73) 73)
74 74
75activityPubClientRouter.get('/videos/watch/:id', 75activityPubClientRouter.get('/videos/watch/:id',
@@ -122,7 +122,7 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
122activityPubClientRouter.get('/video-channels/:name', 122activityPubClientRouter.get('/video-channels/:name',
123 executeIfActivityPub, 123 executeIfActivityPub,
124 asyncMiddleware(localVideoChannelValidator), 124 asyncMiddleware(localVideoChannelValidator),
125 asyncMiddleware(videoChannelController) 125 videoChannelController
126) 126)
127activityPubClientRouter.get('/video-channels/:name/followers', 127activityPubClientRouter.get('/video-channels/:name/followers',
128 executeIfActivityPub, 128 executeIfActivityPub,
@@ -154,7 +154,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId',
154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', 154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
155 executeIfActivityPub, 155 executeIfActivityPub,
156 asyncMiddleware(videoPlaylistElementAPGetValidator), 156 asyncMiddleware(videoPlaylistElementAPGetValidator),
157 asyncMiddleware(videoPlaylistElementController) 157 videoPlaylistElementController
158) 158)
159 159
160// --------------------------------------------------------------------------- 160// ---------------------------------------------------------------------------
@@ -192,7 +192,7 @@ async function accountPlaylistsController (req: express.Request, res: express.Re
192 return activityPubResponse(activityPubContextify(activityPubResult), res) 192 return activityPubResponse(activityPubContextify(activityPubResult), res)
193} 193}
194 194
195function getAccountVideoRate (rateType: VideoRateType) { 195function getAccountVideoRateFactory (rateType: VideoRateType) {
196 return (req: express.Request, res: express.Response) => { 196 return (req: express.Request, res: express.Response) => {
197 const accountVideoRate = res.locals.accountVideoRate 197 const accountVideoRate = res.locals.accountVideoRate
198 198
@@ -234,7 +234,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
234 234
235 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) 235 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined)
236 236
237 return activityPubResponse(activityPubContextify(activity), res) 237 return activityPubResponse(activityPubContextify(activity, 'Announce'), res)
238} 238}
239 239
240async function videoAnnouncesController (req: express.Request, res: express.Response) { 240async function videoAnnouncesController (req: express.Request, res: express.Response) {
@@ -281,7 +281,7 @@ async function videoCommentsController (req: express.Request, res: express.Respo
281 return activityPubResponse(activityPubContextify(json), res) 281 return activityPubResponse(activityPubContextify(json), res)
282} 282}
283 283
284async function videoChannelController (req: express.Request, res: express.Response) { 284function videoChannelController (req: express.Request, res: express.Response) {
285 const videoChannel = res.locals.videoChannel 285 const videoChannel = res.locals.videoChannel
286 286
287 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) 287 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res)
@@ -353,7 +353,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo
353 return activityPubResponse(activityPubContextify(object), res) 353 return activityPubResponse(activityPubContextify(object), res)
354} 354}
355 355
356async function videoPlaylistElementController (req: express.Request, res: express.Response) { 356function videoPlaylistElementController (req: express.Request, res: express.Response) {
357 const videoPlaylistElement = res.locals.videoPlaylistElementAP 357 const videoPlaylistElement = res.locals.videoPlaylistElementAP
358 358
359 const json = videoPlaylistElement.toActivityPubObject() 359 const json = videoPlaylistElement.toActivityPubObject()
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index ca42106b8..3b8fb34a8 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -46,11 +46,15 @@ const inboxQueue = queue<QueueParam, Error>((task, cb) => {
46 46
47 processActivities(task.activities, options) 47 processActivities(task.activities, options)
48 .then(() => cb()) 48 .then(() => cb())
49 .catch(err => {
50 logger.error('Error in process activities.', { err })
51 cb()
52 })
49}) 53})
50 54
51function inboxController (req: express.Request, res: express.Response) { 55function inboxController (req: express.Request, res: express.Response) {
52 const rootActivity: RootActivity = req.body 56 const rootActivity: RootActivity = req.body
53 let activities: Activity[] = [] 57 let activities: Activity[]
54 58
55 if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) { 59 if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) {
56 activities = (rootActivity as ActivityPubCollection).items 60 activities = (rootActivity as ActivityPubCollection).items
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 05740318e..f354ccf24 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -16,21 +16,17 @@ import {
16 accountNameWithHostGetValidator, 16 accountNameWithHostGetValidator,
17 accountsSortValidator, 17 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator, 18 ensureAuthUserOwnsAccountValidator,
19 videosSortValidator, 19 videoChannelsSortValidator,
20 videoChannelsSortValidator 20 videosSortValidator
21} from '../../middlewares/validators' 21} from '../../middlewares/validators'
22import { AccountModel } from '../../models/account/account' 22import { AccountModel } from '../../models/account/account'
23import { AccountVideoRateModel } from '../../models/account/account-video-rate' 23import { AccountVideoRateModel } from '../../models/account/account-video-rate'
24import { VideoModel } from '../../models/video/video' 24import { VideoModel } from '../../models/video/video'
25import { buildNSFWFilter, isUserAbleToSearchRemoteURI, getCountVideos } from '../../helpers/express-utils' 25import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
26import { VideoChannelModel } from '../../models/video/video-channel' 26import { VideoChannelModel } from '../../models/video/video-channel'
27import { JobQueue } from '../../lib/job-queue' 27import { JobQueue } from '../../lib/job-queue'
28import { logger } from '../../helpers/logger'
29import { VideoPlaylistModel } from '../../models/video/video-playlist' 28import { VideoPlaylistModel } from '../../models/video/video-playlist'
30import { 29import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
31 commonVideoPlaylistFiltersValidator,
32 videoPlaylistsSearchValidator
33} from '../../middlewares/validators/videos/video-playlists'
34 30
35const accountsRouter = express.Router() 31const accountsRouter = express.Router()
36 32
@@ -104,7 +100,6 @@ function getAccount (req: express.Request, res: express.Response) {
104 100
105 if (account.isOutdated()) { 101 if (account.isOutdated()) {
106 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) 102 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
107 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err }))
108 } 103 }
109 104
110 return res.json(account.toFormattedJSON()) 105 return res.json(account.toFormattedJSON())
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index ae4e00248..69940f395 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -31,12 +31,12 @@ configRouter.get('/',
31configRouter.get('/custom', 31configRouter.get('/custom',
32 authenticate, 32 authenticate,
33 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 33 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
34 asyncMiddleware(getCustomConfig) 34 getCustomConfig
35) 35)
36configRouter.put('/custom', 36configRouter.put('/custom',
37 authenticate, 37 authenticate,
38 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 38 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
39 asyncMiddleware(customConfigUpdateValidator), 39 customConfigUpdateValidator,
40 asyncMiddleware(updateCustomConfig) 40 asyncMiddleware(updateCustomConfig)
41) 41)
42configRouter.delete('/custom', 42configRouter.delete('/custom',
@@ -196,7 +196,7 @@ function getAbout (req: express.Request, res: express.Response) {
196 return res.json(about).end() 196 return res.json(about).end()
197} 197}
198 198
199async function getCustomConfig (req: express.Request, res: express.Response) { 199function getCustomConfig (req: express.Request, res: express.Response) {
200 const data = customConfig() 200 const data = customConfig()
201 201
202 return res.json(data).end() 202 return res.json(data).end()
@@ -250,7 +250,7 @@ function getRegisteredThemes () {
250 250
251function getEnabledResolutions () { 251function getEnabledResolutions () {
252 return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) 252 return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
253 .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[ key ] === true) 253 .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
254 .map(r => parseInt(r, 10)) 254 .map(r => parseInt(r, 10))
255} 255}
256 256
@@ -340,13 +340,13 @@ function customConfig (): CustomConfig {
340 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES, 340 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
341 threads: CONFIG.TRANSCODING.THREADS, 341 threads: CONFIG.TRANSCODING.THREADS,
342 resolutions: { 342 resolutions: {
343 '0p': CONFIG.TRANSCODING.RESOLUTIONS[ '0p' ], 343 '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'],
344 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], 344 '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'],
345 '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ], 345 '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'],
346 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 346 '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'],
347 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 347 '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'],
348 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ], 348 '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'],
349 '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ] 349 '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p']
350 }, 350 },
351 webtorrent: { 351 webtorrent: {
352 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED 352 enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts
index 05320311e..13fc04d18 100644
--- a/server/controllers/api/jobs.ts
+++ b/server/controllers/api/jobs.ts
@@ -50,7 +50,7 @@ async function listJobs (req: express.Request, res: express.Response) {
50 }) 50 })
51 const total = await JobQueue.Instance.count(state) 51 const total = await JobQueue.Instance.count(state)
52 52
53 const result: ResultList<any> = { 53 const result: ResultList<Job> = {
54 total, 54 total,
55 data: jobs.map(j => formatJob(j, state)) 55 data: jobs.map(j => formatJob(j, state))
56 } 56 }
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts
index 46e76ac6b..75f3baedb 100644
--- a/server/controllers/api/overviews.ts
+++ b/server/controllers/api/overviews.ts
@@ -24,7 +24,7 @@ export { overviewsRouter }
24const buildSamples = memoizee(async function () { 24const buildSamples = memoizee(async function () {
25 const [ categories, channels, tags ] = await Promise.all([ 25 const [ categories, channels, tags ] = await Promise.all([
26 VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), 26 VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
27 VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT), 27 VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
28 TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT) 28 TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT)
29 ]) 29 ])
30 30
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts
index 4450038f6..e12fc1dd4 100644
--- a/server/controllers/api/server/debug.ts
+++ b/server/controllers/api/server/debug.ts
@@ -1,13 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { authenticate, ensureUserHasRight } from '../../../middlewares'
4 4
5const debugRouter = express.Router() 5const debugRouter = express.Router()
6 6
7debugRouter.get('/debug', 7debugRouter.get('/debug',
8 authenticate, 8 authenticate,
9 ensureUserHasRight(UserRight.MANAGE_DEBUG), 9 ensureUserHasRight(UserRight.MANAGE_DEBUG),
10 asyncMiddleware(getDebug) 10 getDebug
11) 11)
12 12
13// --------------------------------------------------------------------------- 13// ---------------------------------------------------------------------------
@@ -18,7 +18,7 @@ export {
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
21async function getDebug (req: express.Request, res: express.Response) { 21function getDebug (req: express.Request, res: express.Response) {
22 return res.json({ 22 return res.json({
23 ip: req.ip 23 ip: req.ip
24 }).end() 24 }).end()
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 29a403a43..0bc20bd60 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -24,7 +24,7 @@ import {
24} from '../../../middlewares/validators' 24} from '../../../middlewares/validators'
25import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 25import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
26import { JobQueue } from '../../../lib/job-queue' 26import { JobQueue } from '../../../lib/job-queue'
27import { removeRedundancyOf } from '../../../lib/redundancy' 27import { removeRedundanciesOfServer } from '../../../lib/redundancy'
28import { sequelizeTypescript } from '../../../initializers/database' 28import { sequelizeTypescript } from '../../../initializers/database'
29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' 29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
30 30
@@ -135,7 +135,6 @@ async function followInstance (req: express.Request, res: express.Response) {
135 } 135 }
136 136
137 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 137 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
138 .catch(err => logger.error('Cannot create follow job for %s.', host, err))
139 } 138 }
140 139
141 return res.status(204).end() 140 return res.status(204).end()
@@ -153,7 +152,7 @@ async function removeFollowing (req: express.Request, res: express.Response) {
153 await server.save({ transaction: t }) 152 await server.save({ transaction: t })
154 153
155 // Async, could be long 154 // Async, could be long
156 removeRedundancyOf(server.id) 155 removeRedundanciesOfServer(server.id)
157 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err)) 156 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err))
158 157
159 await follow.destroy({ transaction: t }) 158 await follow.destroy({ transaction: t })
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts
index cd1e0f5bf..4b543d686 100644
--- a/server/controllers/api/server/logs.ts
+++ b/server/controllers/api/server/logs.ts
@@ -59,9 +59,9 @@ async function getLogs (req: express.Request, res: express.Response) {
59} 59}
60 60
61async function generateOutput (options: { 61async function generateOutput (options: {
62 startDateQuery: string, 62 startDateQuery: string
63 endDateQuery?: string, 63 endDateQuery?: string
64 level: LogLevel, 64 level: LogLevel
65 nameFilter: RegExp 65 nameFilter: RegExp
66}) { 66}) {
67 const { startDateQuery, level, nameFilter } = options 67 const { startDateQuery, level, nameFilter } = options
@@ -111,7 +111,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
111 const output: any[] = [] 111 const output: any[] = []
112 112
113 for (let i = lines.length - 1; i >= 0; i--) { 113 for (let i = lines.length - 1; i >= 0; i--) {
114 const line = lines[ i ] 114 const line = lines[i]
115 let log: any 115 let log: any
116 116
117 try { 117 try {
@@ -122,7 +122,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
122 } 122 }
123 123
124 logTime = new Date(log.timestamp).getTime() 124 logTime = new Date(log.timestamp).getTime()
125 if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) { 125 if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) {
126 output.push(log) 126 output.push(log)
127 127
128 currentSize += line.length 128 currentSize += line.length
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts
index 4ea6164a3..1ced0759e 100644
--- a/server/controllers/api/server/redundancy.ts
+++ b/server/controllers/api/server/redundancy.ts
@@ -1,9 +1,24 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import {
4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' 4 asyncMiddleware,
5import { removeRedundancyOf } from '../../../lib/redundancy' 5 authenticate,
6 ensureUserHasRight,
7 paginationValidator,
8 setDefaultPagination,
9 setDefaultVideoRedundanciesSort,
10 videoRedundanciesSortValidator
11} from '../../../middlewares'
12import {
13 listVideoRedundanciesValidator,
14 updateServerRedundancyValidator,
15 addVideoRedundancyValidator,
16 removeVideoRedundancyValidator
17} from '../../../middlewares/validators/redundancy'
18import { removeRedundanciesOfServer, removeVideoRedundancy } from '../../../lib/redundancy'
6import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
20import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
21import { JobQueue } from '@server/lib/job-queue'
7 22
8const serverRedundancyRouter = express.Router() 23const serverRedundancyRouter = express.Router()
9 24
@@ -14,6 +29,31 @@ serverRedundancyRouter.put('/redundancy/:host',
14 asyncMiddleware(updateRedundancy) 29 asyncMiddleware(updateRedundancy)
15) 30)
16 31
32serverRedundancyRouter.get('/redundancy/videos',
33 authenticate,
34 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
35 listVideoRedundanciesValidator,
36 paginationValidator,
37 videoRedundanciesSortValidator,
38 setDefaultVideoRedundanciesSort,
39 setDefaultPagination,
40 asyncMiddleware(listVideoRedundancies)
41)
42
43serverRedundancyRouter.post('/redundancy/videos',
44 authenticate,
45 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
46 addVideoRedundancyValidator,
47 asyncMiddleware(addVideoRedundancy)
48)
49
50serverRedundancyRouter.delete('/redundancy/videos/:redundancyId',
51 authenticate,
52 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
53 removeVideoRedundancyValidator,
54 asyncMiddleware(removeVideoRedundancyController)
55)
56
17// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
18 58
19export { 59export {
@@ -22,6 +62,42 @@ export {
22 62
23// --------------------------------------------------------------------------- 63// ---------------------------------------------------------------------------
24 64
65async function listVideoRedundancies (req: express.Request, res: express.Response) {
66 const resultList = await VideoRedundancyModel.listForApi({
67 start: req.query.start,
68 count: req.query.count,
69 sort: req.query.sort,
70 target: req.query.target,
71 strategy: req.query.strategy
72 })
73
74 const result = {
75 total: resultList.total,
76 data: resultList.data.map(r => VideoRedundancyModel.toFormattedJSONStatic(r))
77 }
78
79 return res.json(result)
80}
81
82async function addVideoRedundancy (req: express.Request, res: express.Response) {
83 const payload = {
84 videoId: res.locals.onlyVideo.id
85 }
86
87 await JobQueue.Instance.createJobWithPromise({
88 type: 'video-redundancy',
89 payload
90 })
91
92 return res.sendStatus(204)
93}
94
95async function removeVideoRedundancyController (req: express.Request, res: express.Response) {
96 await removeVideoRedundancy(res.locals.videoRedundancy)
97
98 return res.sendStatus(204)
99}
100
25async function updateRedundancy (req: express.Request, res: express.Response) { 101async function updateRedundancy (req: express.Request, res: express.Response) {
26 const server = res.locals.server 102 const server = res.locals.server
27 103
@@ -30,7 +106,7 @@ async function updateRedundancy (req: express.Request, res: express.Response) {
30 await server.save() 106 await server.save()
31 107
32 // Async, could be long 108 // Async, could be long
33 removeRedundancyOf(server.id) 109 removeRedundanciesOfServer(server.id)
34 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err })) 110 .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err }))
35 111
36 return res.sendStatus(204) 112 return res.sendStatus(204)
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index 3616c074d..6d508a481 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -10,6 +10,7 @@ import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
10import { cacheRoute } from '../../../middlewares/cache' 10import { cacheRoute } from '../../../middlewares/cache'
11import { VideoFileModel } from '../../../models/video/video-file' 11import { VideoFileModel } from '../../../models/video/video-file'
12import { CONFIG } from '../../../initializers/config' 12import { CONFIG } from '../../../initializers/config'
13import { VideoRedundancyStrategyWithManual } from '@shared/models'
13 14
14const statsRouter = express.Router() 15const statsRouter = express.Router()
15 16
@@ -25,8 +26,15 @@ async function getStats (req: express.Request, res: express.Response) {
25 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() 26 const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
26 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() 27 const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
27 28
29 const strategies: { strategy: VideoRedundancyStrategyWithManual, size: number }[] = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES
30 .map(r => ({
31 strategy: r.strategy,
32 size: r.size
33 }))
34 strategies.push({ strategy: 'manual', size: null })
35
28 const videosRedundancyStats = await Promise.all( 36 const videosRedundancyStats = await Promise.all(
29 CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => { 37 strategies.map(r => {
30 return VideoRedundancyModel.getStats(r.strategy) 38 return VideoRedundancyModel.getStats(r.strategy)
31 .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size })) 39 .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size }))
32 }) 40 })
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index ac7c62aab..23890e20c 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -39,7 +39,7 @@ meRouter.get('/me',
39) 39)
40meRouter.delete('/me', 40meRouter.delete('/me',
41 authenticate, 41 authenticate,
42 asyncMiddleware(deleteMeValidator), 42 deleteMeValidator,
43 asyncMiddleware(deleteMe) 43 asyncMiddleware(deleteMe)
44) 44)
45 45
@@ -214,7 +214,7 @@ async function updateMe (req: express.Request, res: express.Response) {
214} 214}
215 215
216async function updateMyAvatar (req: express.Request, res: express.Response) { 216async function updateMyAvatar (req: express.Request, res: express.Response) {
217 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 217 const avatarPhysicalFile = req.files['avatarfile'][0]
218 const user = res.locals.oauth.token.user 218 const user = res.locals.oauth.token.user
219 219
220 const userAccount = await AccountModel.load(user.Account.id) 220 const userAccount = await AccountModel.load(user.Account.id)
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index 43c4c37d8..888392b8b 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -19,7 +19,6 @@ import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
19import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 19import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
20import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
21import { JobQueue } from '../../../lib/job-queue' 21import { JobQueue } from '../../../lib/job-queue'
22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database' 22import { sequelizeTypescript } from '../../../initializers/database'
24 23
25const mySubscriptionsRouter = express.Router() 24const mySubscriptionsRouter = express.Router()
@@ -52,7 +51,7 @@ mySubscriptionsRouter.get('/me/subscriptions',
52mySubscriptionsRouter.post('/me/subscriptions', 51mySubscriptionsRouter.post('/me/subscriptions',
53 authenticate, 52 authenticate,
54 userSubscriptionAddValidator, 53 userSubscriptionAddValidator,
55 asyncMiddleware(addUserSubscription) 54 addUserSubscription
56) 55)
57 56
58mySubscriptionsRouter.get('/me/subscriptions/:uri', 57mySubscriptionsRouter.get('/me/subscriptions/:uri',
@@ -106,7 +105,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
106 return res.json(existObject) 105 return res.json(existObject)
107} 106}
108 107
109async function addUserSubscription (req: express.Request, res: express.Response) { 108function addUserSubscription (req: express.Request, res: express.Response) {
110 const user = res.locals.oauth.token.User 109 const user = res.locals.oauth.token.User
111 const [ name, host ] = req.body.uri.split('@') 110 const [ name, host ] = req.body.uri.split('@')
112 111
@@ -117,7 +116,6 @@ async function addUserSubscription (req: express.Request, res: express.Response)
117 } 116 }
118 117
119 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 118 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
120 .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
121 119
122 return res.status(204).end() 120 return res.status(204).end()
123} 121}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index e1f37a8fb..a808896ff 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -119,7 +119,7 @@ async function listVideoChannels (req: express.Request, res: express.Response) {
119} 119}
120 120
121async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { 121async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
122 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 122 const avatarPhysicalFile = req.files['avatarfile'][0]
123 const videoChannel = res.locals.videoChannel 123 const videoChannel = res.locals.videoChannel
124 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) 124 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
125 125
@@ -232,7 +232,6 @@ async function getVideoChannel (req: express.Request, res: express.Response) {
232 232
233 if (videoChannelWithVideos.isOutdated()) { 233 if (videoChannelWithVideos.isOutdated()) {
234 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } }) 234 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
235 .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
236 } 235 }
237 236
238 return res.json(videoChannelWithVideos.toFormattedJSON()) 237 return res.json(videoChannelWithVideos.toFormattedJSON())
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index d9f0ff925..b51490bf9 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -144,7 +144,6 @@ function getVideoPlaylist (req: express.Request, res: express.Response) {
144 144
145 if (videoPlaylist.isOutdated()) { 145 if (videoPlaylist.isOutdated()) {
146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) 146 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
147 .catch(err => logger.error('Cannot create AP refresher job for playlist %s.', videoPlaylist.url, { err }))
148 } 147 }
149 148
150 return res.json(videoPlaylist.toFormattedJSON()) 149 return res.json(videoPlaylist.toFormattedJSON())
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 37481d12f..fd7b165fb 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -66,7 +66,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
66 await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) 66 await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption)
67 67
68 await sequelizeTypescript.transaction(async t => { 68 await sequelizeTypescript.transaction(async t => {
69 await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) 69 await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t)
70 70
71 // Update video update 71 // Update video update
72 await federateVideoIfNeeded(video, false, t) 72 await federateVideoIfNeeded(video, false, t)
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 28ced5836..ed223cbc9 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -88,12 +88,12 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
88 const buf = await readFile(torrentfile.path) 88 const buf = await readFile(torrentfile.path)
89 const parsedTorrent = parseTorrent(buf) 89 const parsedTorrent = parseTorrent(buf)
90 90
91 videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[ 0 ] : parsedTorrent.name as string 91 videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[0] : parsedTorrent.name as string
92 } else { 92 } else {
93 magnetUri = body.magnetUri 93 magnetUri = body.magnetUri
94 94
95 const parsed = magnetUtil.decode(magnetUri) 95 const parsed = magnetUtil.decode(magnetUri)
96 videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string 96 videoName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string
97 } 97 }
98 98
99 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 99 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
@@ -124,7 +124,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
124 videoImportId: videoImport.id, 124 videoImportId: videoImport.id,
125 magnetUri 125 magnetUri
126 } 126 }
127 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 127 await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload })
128 128
129 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) 129 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
130 130
@@ -176,7 +176,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
176 downloadThumbnail: !thumbnailModel, 176 downloadThumbnail: !thumbnailModel,
177 downloadPreview: !previewModel 177 downloadPreview: !previewModel
178 } 178 }
179 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 179 await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload })
180 180
181 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) 181 auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON()))
182 182
@@ -211,7 +211,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
211async function processThumbnail (req: express.Request, video: VideoModel) { 211async function processThumbnail (req: express.Request, video: VideoModel) {
212 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined 212 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
213 if (thumbnailField) { 213 if (thumbnailField) {
214 const thumbnailPhysicalFile = thumbnailField[ 0 ] 214 const thumbnailPhysicalFile = thumbnailField[0]
215 215
216 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false) 216 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false)
217 } 217 }
@@ -231,12 +231,12 @@ async function processPreview (req: express.Request, video: VideoModel) {
231} 231}
232 232
233function insertIntoDB (parameters: { 233function insertIntoDB (parameters: {
234 video: MVideoThumbnailAccountDefault, 234 video: MVideoThumbnailAccountDefault
235 thumbnailModel: MThumbnail, 235 thumbnailModel: MThumbnail
236 previewModel: MThumbnail, 236 previewModel: MThumbnail
237 videoChannel: MChannelAccountDefault, 237 videoChannel: MChannelAccountDefault
238 tags: string[], 238 tags: string[]
239 videoImportAttributes: Partial<MVideoImport>, 239 videoImportAttributes: Partial<MVideoImport>
240 user: MUser 240 user: MUser
241}): Bluebird<MVideoImportFormattable> { 241}): Bluebird<MVideoImportFormattable> {
242 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters 242 const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 8d4ff07eb..1d61f8427 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -307,7 +307,7 @@ async function addVideo (req: express.Request, res: express.Response) {
307 } 307 }
308 } 308 }
309 309
310 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 310 await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput })
311 } 311 }
312 312
313 Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) 313 Hooks.runAction('action:api.video.uploaded', { video: videoCreated })
@@ -452,7 +452,6 @@ async function getVideo (req: express.Request, res: express.Response) {
452 452
453 if (video.isOutdated()) { 453 if (video.isOutdated()) {
454 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) 454 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } })
455 .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err }))
456 } 455 }
457 456
458 return res.json(video.toFormattedDetailsJSON()) 457 return res.json(video.toFormattedDetailsJSON())
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index dc3ff18fc..e4643e171 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -66,7 +66,7 @@ export {
66 66
67// --------------------------------------------------------------------------- 67// ---------------------------------------------------------------------------
68 68
69async function serveServerTranslations (req: express.Request, res: express.Response) { 69function serveServerTranslations (req: express.Request, res: express.Response) {
70 const locale = req.params.locale 70 const locale = req.params.locale
71 const file = req.params.file 71 const file = req.params.file
72 72
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index a4bb3a4d9..4c6cf9597 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -45,12 +45,12 @@ staticRouter.use(
45staticRouter.use( 45staticRouter.use(
46 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', 46 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent',
47 asyncMiddleware(videosDownloadValidator), 47 asyncMiddleware(videosDownloadValidator),
48 asyncMiddleware(downloadTorrent) 48 downloadTorrent
49) 49)
50staticRouter.use( 50staticRouter.use(
51 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent', 51 STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent',
52 asyncMiddleware(videosDownloadValidator), 52 asyncMiddleware(videosDownloadValidator),
53 asyncMiddleware(downloadHLSVideoFileTorrent) 53 downloadHLSVideoFileTorrent
54) 54)
55 55
56// Videos path for webseeding 56// Videos path for webseeding
@@ -68,13 +68,13 @@ staticRouter.use(
68staticRouter.use( 68staticRouter.use(
69 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', 69 STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
70 asyncMiddleware(videosDownloadValidator), 70 asyncMiddleware(videosDownloadValidator),
71 asyncMiddleware(downloadVideoFile) 71 downloadVideoFile
72) 72)
73 73
74staticRouter.use( 74staticRouter.use(
75 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', 75 STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension',
76 asyncMiddleware(videosDownloadValidator), 76 asyncMiddleware(videosDownloadValidator),
77 asyncMiddleware(downloadHLSVideoFile) 77 downloadHLSVideoFile
78) 78)
79 79
80// HLS 80// HLS
@@ -325,7 +325,7 @@ async function generateNodeinfo (req: express.Request, res: express.Response) {
325 return res.send(json).end() 325 return res.send(json).end()
326} 326}
327 327
328async function downloadTorrent (req: express.Request, res: express.Response) { 328function downloadTorrent (req: express.Request, res: express.Response) {
329 const video = res.locals.videoAll 329 const video = res.locals.videoAll
330 330
331 const videoFile = getVideoFile(req, video.VideoFiles) 331 const videoFile = getVideoFile(req, video.VideoFiles)
@@ -334,7 +334,7 @@ async function downloadTorrent (req: express.Request, res: express.Response) {
334 return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`) 334 return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`)
335} 335}
336 336
337async function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { 337function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) {
338 const video = res.locals.videoAll 338 const video = res.locals.videoAll
339 339
340 const playlist = getHLSPlaylist(video) 340 const playlist = getHLSPlaylist(video)
@@ -346,7 +346,7 @@ async function downloadHLSVideoFileTorrent (req: express.Request, res: express.R
346 return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`) 346 return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`)
347} 347}
348 348
349async function downloadVideoFile (req: express.Request, res: express.Response) { 349function downloadVideoFile (req: express.Request, res: express.Response) {
350 const video = res.locals.videoAll 350 const video = res.locals.videoAll
351 351
352 const videoFile = getVideoFile(req, video.VideoFiles) 352 const videoFile = getVideoFile(req, video.VideoFiles)
@@ -355,7 +355,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response) {
355 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) 355 return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`)
356} 356}
357 357
358async function downloadHLSVideoFile (req: express.Request, res: express.Response) { 358function downloadHLSVideoFile (req: express.Request, res: express.Response) {
359 const video = res.locals.videoAll 359 const video = res.locals.videoAll
360 const playlist = getHLSPlaylist(video) 360 const playlist = getHLSPlaylist(video)
361 if (!playlist) return res.status(404).end 361 if (!playlist) return res.status(404).end
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts
index 2ae1cf86c..e9c8a13da 100644
--- a/server/controllers/tracker.ts
+++ b/server/controllers/tracker.ts
@@ -6,7 +6,6 @@ import * as proxyAddr from 'proxy-addr'
6import { Server as WebSocketServer } from 'ws' 6import { Server as WebSocketServer } from 'ws'
7import { TRACKER_RATE_LIMITS } from '../initializers/constants' 7import { TRACKER_RATE_LIMITS } from '../initializers/constants'
8import { VideoFileModel } from '../models/video/video-file' 8import { VideoFileModel } from '../models/video/video-file'
9import { parse } from 'url'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { CONFIG } from '../initializers/config' 10import { CONFIG } from '../initializers/config'
12 11
@@ -38,11 +37,11 @@ const trackerServer = new TrackerServer({
38 37
39 const key = ip + '-' + infoHash 38 const key = ip + '-' + infoHash
40 39
41 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1 40 peersIps[ip] = peersIps[ip] ? peersIps[ip] + 1 : 1
42 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1 41 peersIpInfoHash[key] = peersIpInfoHash[key] ? peersIpInfoHash[key] + 1 : 1
43 42
44 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { 43 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
45 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) 44 return cb(new Error(`Too many requests (${peersIpInfoHash[key]} of ip ${ip} for torrent ${infoHash}`))
46 } 45 }
47 46
48 try { 47 try {
@@ -87,10 +86,8 @@ function createWebsocketTrackerServer (app: express.Application) {
87 trackerServer.onWebSocketConnection(ws) 86 trackerServer.onWebSocketConnection(ws)
88 }) 87 })
89 88
90 server.on('upgrade', (request, socket, head) => { 89 server.on('upgrade', (request: express.Request, socket, head) => {
91 const pathname = parse(request.url).pathname 90 if (request.path === '/tracker/socket') {
92
93 if (pathname === '/tracker/socket') {
94 wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request)) 91 wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
95 } 92 }
96 93
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index fc9575160..77c851880 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -18,7 +18,7 @@ export {
18// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
19 19
20function webfingerController (req: express.Request, res: express.Response) { 20function webfingerController (req: express.Request, res: express.Response) {
21 const actor = res.locals.actorFull 21 const actor = res.locals.actorUrl
22 22
23 const json = { 23 const json = {
24 subject: req.query.resource, 24 subject: req.query.resource,
@@ -32,5 +32,5 @@ function webfingerController (req: express.Request, res: express.Response) {
32 ] 32 ]
33 } 33 }
34 34
35 return res.json(json).end() 35 return res.json(json)
36} 36}
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
index 239d8291d..326785b68 100644
--- a/server/helpers/activitypub.ts
+++ b/server/helpers/activitypub.ts
@@ -2,99 +2,106 @@ import * as Bluebird from 'bluebird'
2import validator from 'validator' 2import validator from 'validator'
3import { ResultList } from '../../shared/models' 3import { ResultList } from '../../shared/models'
4import { Activity } from '../../shared/models/activitypub' 4import { Activity } from '../../shared/models/activitypub'
5import { ACTIVITY_PUB } from '../initializers/constants' 5import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants'
6import { signJsonLDObject } from './peertube-crypto' 6import { signJsonLDObject } from './peertube-crypto'
7import { pageToStartAndCount } from './core-utils' 7import { pageToStartAndCount } from './core-utils'
8import { parse } from 'url' 8import { URL } from 'url'
9import { MActor } from '../typings/models' 9import { MActor, MVideoAccountLight } from '../typings/models'
10 10
11function activityPubContextify <T> (data: T) { 11export type ContextType = 'All' | 'View' | 'Announce'
12 return Object.assign(data, { 12
13function activityPubContextify <T> (data: T, type: ContextType = 'All') {
14 const base = {
15 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017'
16 }
17
18 if (type === 'All') {
19 Object.assign(base, {
20 pt: 'https://joinpeertube.org/ns#',
21 sc: 'http://schema.org#',
22 Hashtag: 'as:Hashtag',
23 uuid: 'sc:identifier',
24 category: 'sc:category',
25 licence: 'sc:license',
26 subtitleLanguage: 'sc:subtitleLanguage',
27 sensitive: 'as:sensitive',
28 language: 'sc:inLanguage',
29 expires: 'sc:expires',
30 CacheFile: 'pt:CacheFile',
31 Infohash: 'pt:Infohash',
32 originallyPublishedAt: 'sc:datePublished',
33 views: {
34 '@type': 'sc:Number',
35 '@id': 'pt:views'
36 },
37 state: {
38 '@type': 'sc:Number',
39 '@id': 'pt:state'
40 },
41 size: {
42 '@type': 'sc:Number',
43 '@id': 'pt:size'
44 },
45 fps: {
46 '@type': 'sc:Number',
47 '@id': 'pt:fps'
48 },
49 startTimestamp: {
50 '@type': 'sc:Number',
51 '@id': 'pt:startTimestamp'
52 },
53 stopTimestamp: {
54 '@type': 'sc:Number',
55 '@id': 'pt:stopTimestamp'
56 },
57 position: {
58 '@type': 'sc:Number',
59 '@id': 'pt:position'
60 },
61 commentsEnabled: {
62 '@type': 'sc:Boolean',
63 '@id': 'pt:commentsEnabled'
64 },
65 downloadEnabled: {
66 '@type': 'sc:Boolean',
67 '@id': 'pt:downloadEnabled'
68 },
69 waitTranscoding: {
70 '@type': 'sc:Boolean',
71 '@id': 'pt:waitTranscoding'
72 },
73 support: {
74 '@type': 'sc:Text',
75 '@id': 'pt:support'
76 },
77 likes: {
78 '@id': 'as:likes',
79 '@type': '@id'
80 },
81 dislikes: {
82 '@id': 'as:dislikes',
83 '@type': '@id'
84 },
85 playlists: {
86 '@id': 'pt:playlists',
87 '@type': '@id'
88 },
89 shares: {
90 '@id': 'as:shares',
91 '@type': '@id'
92 },
93 comments: {
94 '@id': 'as:comments',
95 '@type': '@id'
96 }
97 })
98 }
99
100 return Object.assign({}, data, {
13 '@context': [ 101 '@context': [
14 'https://www.w3.org/ns/activitystreams', 102 'https://www.w3.org/ns/activitystreams',
15 'https://w3id.org/security/v1', 103 'https://w3id.org/security/v1',
16 { 104 base
17 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017',
18 pt: 'https://joinpeertube.org/ns#',
19 sc: 'http://schema.org#',
20 Hashtag: 'as:Hashtag',
21 uuid: 'sc:identifier',
22 category: 'sc:category',
23 licence: 'sc:license',
24 subtitleLanguage: 'sc:subtitleLanguage',
25 sensitive: 'as:sensitive',
26 language: 'sc:inLanguage',
27 expires: 'sc:expires',
28 CacheFile: 'pt:CacheFile',
29 Infohash: 'pt:Infohash',
30 originallyPublishedAt: 'sc:datePublished',
31 views: {
32 '@type': 'sc:Number',
33 '@id': 'pt:views'
34 },
35 state: {
36 '@type': 'sc:Number',
37 '@id': 'pt:state'
38 },
39 size: {
40 '@type': 'sc:Number',
41 '@id': 'pt:size'
42 },
43 fps: {
44 '@type': 'sc:Number',
45 '@id': 'pt:fps'
46 },
47 startTimestamp: {
48 '@type': 'sc:Number',
49 '@id': 'pt:startTimestamp'
50 },
51 stopTimestamp: {
52 '@type': 'sc:Number',
53 '@id': 'pt:stopTimestamp'
54 },
55 position: {
56 '@type': 'sc:Number',
57 '@id': 'pt:position'
58 },
59 commentsEnabled: {
60 '@type': 'sc:Boolean',
61 '@id': 'pt:commentsEnabled'
62 },
63 downloadEnabled: {
64 '@type': 'sc:Boolean',
65 '@id': 'pt:downloadEnabled'
66 },
67 waitTranscoding: {
68 '@type': 'sc:Boolean',
69 '@id': 'pt:waitTranscoding'
70 },
71 support: {
72 '@type': 'sc:Text',
73 '@id': 'pt:support'
74 }
75 },
76 {
77 likes: {
78 '@id': 'as:likes',
79 '@type': '@id'
80 },
81 dislikes: {
82 '@id': 'as:dislikes',
83 '@type': '@id'
84 },
85 playlists: {
86 '@id': 'pt:playlists',
87 '@type': '@id'
88 },
89 shares: {
90 '@id': 'as:shares',
91 '@type': '@id'
92 },
93 comments: {
94 '@id': 'as:comments',
95 '@type': '@id'
96 }
97 }
98 ] 105 ]
99 }) 106 })
100} 107}
@@ -148,8 +155,8 @@ async function activityPubCollectionPagination (
148 155
149} 156}
150 157
151function buildSignedActivity (byActor: MActor, data: Object) { 158function buildSignedActivity (byActor: MActor, data: Object, contextType?: ContextType) {
152 const activity = activityPubContextify(data) 159 const activity = activityPubContextify(data, contextType)
153 160
154 return signJsonLDObject(byActor, activity) as Promise<Activity> 161 return signJsonLDObject(byActor, activity) as Promise<Activity>
155} 162}
@@ -161,12 +168,18 @@ function getAPId (activity: string | { id: string }) {
161} 168}
162 169
163function checkUrlsSameHost (url1: string, url2: string) { 170function checkUrlsSameHost (url1: string, url2: string) {
164 const idHost = parse(url1).host 171 const idHost = new URL(url1).host
165 const actorHost = parse(url2).host 172 const actorHost = new URL(url2).host
166 173
167 return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() 174 return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase()
168} 175}
169 176
177function buildRemoteVideoBaseUrl (video: MVideoAccountLight, path: string) {
178 const host = video.VideoChannel.Account.Actor.Server.host
179
180 return REMOTE_SCHEME.HTTP + '://' + host + path
181}
182
170// --------------------------------------------------------------------------- 183// ---------------------------------------------------------------------------
171 184
172export { 185export {
@@ -174,5 +187,6 @@ export {
174 getAPId, 187 getAPId,
175 activityPubContextify, 188 activityPubContextify,
176 activityPubCollectionPagination, 189 activityPubCollectionPagination,
177 buildSignedActivity 190 buildSignedActivity,
191 buildRemoteVideoBaseUrl
178} 192}
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts
index 9b258dc3a..a4cfeef76 100644
--- a/server/helpers/audit-logger.ts
+++ b/server/helpers/audit-logger.ts
@@ -81,7 +81,8 @@ function auditLoggerFactory (domain: string) {
81} 81}
82 82
83abstract class EntityAuditView { 83abstract class EntityAuditView {
84 constructor (private keysToKeep: Array<string>, private prefix: string, private entityInfos: object) { } 84 constructor (private readonly keysToKeep: string[], private readonly prefix: string, private readonly entityInfos: object) { }
85
85 toLogKeys (): object { 86 toLogKeys (): object {
86 return chain(flatten(this.entityInfos, { delimiter: '-', safe: true })) 87 return chain(flatten(this.entityInfos, { delimiter: '-', safe: true }))
87 .pick(this.keysToKeep) 88 .pick(this.keysToKeep)
@@ -121,7 +122,7 @@ const videoKeysToKeep = [
121 'downloadEnabled' 122 'downloadEnabled'
122] 123]
123class VideoAuditView extends EntityAuditView { 124class VideoAuditView extends EntityAuditView {
124 constructor (private video: VideoDetails) { 125 constructor (private readonly video: VideoDetails) {
125 super(videoKeysToKeep, 'video', video) 126 super(videoKeysToKeep, 'video', video)
126 } 127 }
127} 128}
@@ -132,7 +133,7 @@ const videoImportKeysToKeep = [
132 'video-name' 133 'video-name'
133] 134]
134class VideoImportAuditView extends EntityAuditView { 135class VideoImportAuditView extends EntityAuditView {
135 constructor (private videoImport: VideoImport) { 136 constructor (private readonly videoImport: VideoImport) {
136 super(videoImportKeysToKeep, 'video-import', videoImport) 137 super(videoImportKeysToKeep, 'video-import', videoImport)
137 } 138 }
138} 139}
@@ -151,7 +152,7 @@ const commentKeysToKeep = [
151 'account-name' 152 'account-name'
152] 153]
153class CommentAuditView extends EntityAuditView { 154class CommentAuditView extends EntityAuditView {
154 constructor (private comment: VideoComment) { 155 constructor (private readonly comment: VideoComment) {
155 super(commentKeysToKeep, 'comment', comment) 156 super(commentKeysToKeep, 'comment', comment)
156 } 157 }
157} 158}
@@ -180,7 +181,7 @@ const userKeysToKeep = [
180 'videoChannels' 181 'videoChannels'
181] 182]
182class UserAuditView extends EntityAuditView { 183class UserAuditView extends EntityAuditView {
183 constructor (private user: User) { 184 constructor (private readonly user: User) {
184 super(userKeysToKeep, 'user', user) 185 super(userKeysToKeep, 'user', user)
185 } 186 }
186} 187}
@@ -206,7 +207,7 @@ const channelKeysToKeep = [
206 'ownerAccount-displayedName' 207 'ownerAccount-displayedName'
207] 208]
208class VideoChannelAuditView extends EntityAuditView { 209class VideoChannelAuditView extends EntityAuditView {
209 constructor (private channel: VideoChannel) { 210 constructor (private readonly channel: VideoChannel) {
210 super(channelKeysToKeep, 'channel', channel) 211 super(channelKeysToKeep, 'channel', channel)
211 } 212 }
212} 213}
@@ -221,7 +222,7 @@ const videoAbuseKeysToKeep = [
221 'createdAt' 222 'createdAt'
222] 223]
223class VideoAbuseAuditView extends EntityAuditView { 224class VideoAbuseAuditView extends EntityAuditView {
224 constructor (private videoAbuse: VideoAbuse) { 225 constructor (private readonly videoAbuse: VideoAbuse) {
225 super(videoAbuseKeysToKeep, 'abuse', videoAbuse) 226 super(videoAbuseKeysToKeep, 'abuse', videoAbuse)
226 } 227 }
227} 228}
@@ -253,9 +254,12 @@ class CustomConfigAuditView extends EntityAuditView {
253 const infos: any = customConfig 254 const infos: any = customConfig
254 const resolutionsDict = infos.transcoding.resolutions 255 const resolutionsDict = infos.transcoding.resolutions
255 const resolutionsArray = [] 256 const resolutionsArray = []
256 Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => { 257
257 if (isEnabled) resolutionsArray.push(resolution) 258 Object.entries(resolutionsDict)
258 }) 259 .forEach(([ resolution, isEnabled ]) => {
260 if (isEnabled) resolutionsArray.push(resolution)
261 })
262
259 Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } }) 263 Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } })
260 super(customConfigKeysToKeep, 'config', infos) 264 super(customConfigKeysToKeep, 'config', infos)
261 } 265 }
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 7e8252aa4..2cecea450 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -1,9 +1,11 @@
1/* eslint-disable no-useless-call */
2
1/* 3/*
2 Different from 'utils' because we don't not import other PeerTube modules. 4 Different from 'utils' because we don't not import other PeerTube modules.
3 Useful to avoid circular dependencies. 5 Useful to avoid circular dependencies.
4*/ 6*/
5 7
6import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' 8import { createHash, HexBase64Latin1Encoding, randomBytes } from 'crypto'
7import { basename, isAbsolute, join, resolve } from 'path' 9import { basename, isAbsolute, join, resolve } from 'path'
8import * as pem from 'pem' 10import * as pem from 'pem'
9import { URL } from 'url' 11import { URL } from 'url'
@@ -22,31 +24,31 @@ const objectConverter = (oldObject: any, keyConverter: (e: string) => string, va
22 const newObject = {} 24 const newObject = {}
23 Object.keys(oldObject).forEach(oldKey => { 25 Object.keys(oldObject).forEach(oldKey => {
24 const newKey = keyConverter(oldKey) 26 const newKey = keyConverter(oldKey)
25 newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter) 27 newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter)
26 }) 28 })
27 29
28 return newObject 30 return newObject
29} 31}
30 32
31const timeTable = { 33const timeTable = {
32 ms: 1, 34 ms: 1,
33 second: 1000, 35 second: 1000,
34 minute: 60000, 36 minute: 60000,
35 hour: 3600000, 37 hour: 3600000,
36 day: 3600000 * 24, 38 day: 3600000 * 24,
37 week: 3600000 * 24 * 7, 39 week: 3600000 * 24 * 7,
38 month: 3600000 * 24 * 30 40 month: 3600000 * 24 * 30
39} 41}
40 42
41export function parseDurationToMs (duration: number | string): number { 43export function parseDurationToMs (duration: number | string): number {
42 if (typeof duration === 'number') return duration 44 if (typeof duration === 'number') return duration
43 45
44 if (typeof duration === 'string') { 46 if (typeof duration === 'string') {
45 const split = duration.match(/^([\d\.,]+)\s?(\w+)$/) 47 const split = duration.match(/^([\d.,]+)\s?(\w+)$/)
46 48
47 if (split.length === 3) { 49 if (split.length === 3) {
48 const len = parseFloat(split[1]) 50 const len = parseFloat(split[1])
49 let unit = split[2].replace(/s$/i,'').toLowerCase() 51 let unit = split[2].replace(/s$/i, '').toLowerCase()
50 if (unit === 'm') { 52 if (unit === 'm') {
51 unit = 'ms' 53 unit = 'ms'
52 } 54 }
@@ -73,21 +75,21 @@ export function parseBytes (value: string | number): number {
73 75
74 if (value.match(tgm)) { 76 if (value.match(tgm)) {
75 match = value.match(tgm) 77 match = value.match(tgm)
76 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 78 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
77 + parseInt(match[2], 10) * 1024 * 1024 * 1024 79 parseInt(match[2], 10) * 1024 * 1024 * 1024 +
78 + parseInt(match[3], 10) * 1024 * 1024 80 parseInt(match[3], 10) * 1024 * 1024
79 } else if (value.match(tg)) { 81 } else if (value.match(tg)) {
80 match = value.match(tg) 82 match = value.match(tg)
81 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 83 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
82 + parseInt(match[2], 10) * 1024 * 1024 * 1024 84 parseInt(match[2], 10) * 1024 * 1024 * 1024
83 } else if (value.match(tm)) { 85 } else if (value.match(tm)) {
84 match = value.match(tm) 86 match = value.match(tm)
85 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 87 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 +
86 + parseInt(match[2], 10) * 1024 * 1024 88 parseInt(match[2], 10) * 1024 * 1024
87 } else if (value.match(gm)) { 89 } else if (value.match(gm)) {
88 match = value.match(gm) 90 match = value.match(gm)
89 return parseInt(match[1], 10) * 1024 * 1024 * 1024 91 return parseInt(match[1], 10) * 1024 * 1024 * 1024 +
90 + parseInt(match[2], 10) * 1024 * 1024 92 parseInt(match[2], 10) * 1024 * 1024
91 } else if (value.match(t)) { 93 } else if (value.match(t)) {
92 match = value.match(t) 94 match = value.match(t)
93 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 95 return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024
@@ -137,6 +139,7 @@ function getAppNumber () {
137} 139}
138 140
139let rootPath: string 141let rootPath: string
142
140function root () { 143function root () {
141 if (rootPath) return rootPath 144 if (rootPath) return rootPath
142 145
@@ -163,7 +166,7 @@ function escapeHTML (stringParam) {
163 '=': '&#x3D;' 166 '=': '&#x3D;'
164 } 167 }
165 168
166 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s]) 169 return String(stringParam).replace(/[&<>"'`=/]/g, s => entityMap[s])
167} 170}
168 171
169function pageToStartAndCount (page: number, itemsPerPage: number) { 172function pageToStartAndCount (page: number, itemsPerPage: number) {
@@ -202,6 +205,7 @@ function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex')
202function execShell (command: string, options?: ExecOptions) { 205function execShell (command: string, options?: ExecOptions) {
203 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { 206 return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => {
204 exec(command, options, (err, stdout, stderr) => { 207 exec(command, options, (err, stdout, stderr) => {
208 // eslint-disable-next-line prefer-promise-reject-errors
205 if (err) return rej({ err, stdout, stderr }) 209 if (err) return rej({ err, stdout, stderr })
206 210
207 return res({ stdout, stderr }) 211 return res({ stdout, stderr })
@@ -226,14 +230,6 @@ function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) =>
226 } 230 }
227} 231}
228 232
229function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> {
230 return function promisified (arg: T): Promise<void> {
231 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
232 func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ])
233 })
234 }
235}
236
237function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> { 233function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
238 return function promisified (arg1: T, arg2: U): Promise<A> { 234 return function promisified (arg1: T, arg2: U): Promise<A> {
239 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { 235 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
@@ -242,15 +238,7 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A)
242 } 238 }
243} 239}
244 240
245function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> { 241const randomBytesPromise = promisify1<number, Buffer>(randomBytes)
246 return function promisified (arg1: T, arg2: U): Promise<void> {
247 return new Promise<void>((resolve: () => void, reject: (err: any) => void) => {
248 func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ])
249 })
250 }
251}
252
253const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
254const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) 242const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
255const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) 243const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
256const execPromise2 = promisify2<string, any, string>(exec) 244const execPromise2 = promisify2<string, any, string>(exec)
@@ -280,7 +268,7 @@ export {
280 promisify1, 268 promisify1,
281 promisify2, 269 promisify2,
282 270
283 pseudoRandomBytesPromise, 271 randomBytesPromise,
284 createPrivateKey, 272 createPrivateKey,
285 getPublicKey, 273 getPublicKey,
286 execPromise2, 274 execPromise2,
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts
index a407a9fec..749c50cb3 100644
--- a/server/helpers/custom-jsonld-signature.ts
+++ b/server/helpers/custom-jsonld-signature.ts
@@ -5,52 +5,52 @@ import { logger } from './logger'
5const CACHE = { 5const CACHE = {
6 'https://w3id.org/security/v1': { 6 'https://w3id.org/security/v1': {
7 '@context': { 7 '@context': {
8 'id': '@id', 8 id: '@id',
9 'type': '@type', 9 type: '@type',
10 10
11 'dc': 'http://purl.org/dc/terms/', 11 dc: 'http://purl.org/dc/terms/',
12 'sec': 'https://w3id.org/security#', 12 sec: 'https://w3id.org/security#',
13 'xsd': 'http://www.w3.org/2001/XMLSchema#', 13 xsd: 'http://www.w3.org/2001/XMLSchema#',
14 14
15 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', 15 EcdsaKoblitzSignature2016: 'sec:EcdsaKoblitzSignature2016',
16 'Ed25519Signature2018': 'sec:Ed25519Signature2018', 16 Ed25519Signature2018: 'sec:Ed25519Signature2018',
17 'EncryptedMessage': 'sec:EncryptedMessage', 17 EncryptedMessage: 'sec:EncryptedMessage',
18 'GraphSignature2012': 'sec:GraphSignature2012', 18 GraphSignature2012: 'sec:GraphSignature2012',
19 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', 19 LinkedDataSignature2015: 'sec:LinkedDataSignature2015',
20 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', 20 LinkedDataSignature2016: 'sec:LinkedDataSignature2016',
21 'CryptographicKey': 'sec:Key', 21 CryptographicKey: 'sec:Key',
22 22
23 'authenticationTag': 'sec:authenticationTag', 23 authenticationTag: 'sec:authenticationTag',
24 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', 24 canonicalizationAlgorithm: 'sec:canonicalizationAlgorithm',
25 'cipherAlgorithm': 'sec:cipherAlgorithm', 25 cipherAlgorithm: 'sec:cipherAlgorithm',
26 'cipherData': 'sec:cipherData', 26 cipherData: 'sec:cipherData',
27 'cipherKey': 'sec:cipherKey', 27 cipherKey: 'sec:cipherKey',
28 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, 28 created: { '@id': 'dc:created', '@type': 'xsd:dateTime' },
29 'creator': { '@id': 'dc:creator', '@type': '@id' }, 29 creator: { '@id': 'dc:creator', '@type': '@id' },
30 'digestAlgorithm': 'sec:digestAlgorithm', 30 digestAlgorithm: 'sec:digestAlgorithm',
31 'digestValue': 'sec:digestValue', 31 digestValue: 'sec:digestValue',
32 'domain': 'sec:domain', 32 domain: 'sec:domain',
33 'encryptionKey': 'sec:encryptionKey', 33 encryptionKey: 'sec:encryptionKey',
34 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, 34 expiration: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
35 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, 35 expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' },
36 'initializationVector': 'sec:initializationVector', 36 initializationVector: 'sec:initializationVector',
37 'iterationCount': 'sec:iterationCount', 37 iterationCount: 'sec:iterationCount',
38 'nonce': 'sec:nonce', 38 nonce: 'sec:nonce',
39 'normalizationAlgorithm': 'sec:normalizationAlgorithm', 39 normalizationAlgorithm: 'sec:normalizationAlgorithm',
40 'owner': { '@id': 'sec:owner', '@type': '@id' }, 40 owner: { '@id': 'sec:owner', '@type': '@id' },
41 'password': 'sec:password', 41 password: 'sec:password',
42 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, 42 privateKey: { '@id': 'sec:privateKey', '@type': '@id' },
43 'privateKeyPem': 'sec:privateKeyPem', 43 privateKeyPem: 'sec:privateKeyPem',
44 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, 44 publicKey: { '@id': 'sec:publicKey', '@type': '@id' },
45 'publicKeyBase58': 'sec:publicKeyBase58', 45 publicKeyBase58: 'sec:publicKeyBase58',
46 'publicKeyPem': 'sec:publicKeyPem', 46 publicKeyPem: 'sec:publicKeyPem',
47 'publicKeyWif': 'sec:publicKeyWif', 47 publicKeyWif: 'sec:publicKeyWif',
48 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, 48 publicKeyService: { '@id': 'sec:publicKeyService', '@type': '@id' },
49 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, 49 revoked: { '@id': 'sec:revoked', '@type': 'xsd:dateTime' },
50 'salt': 'sec:salt', 50 salt: 'sec:salt',
51 'signature': 'sec:signature', 51 signature: 'sec:signature',
52 'signatureAlgorithm': 'sec:signingAlgorithm', 52 signatureAlgorithm: 'sec:signingAlgorithm',
53 'signatureValue': 'sec:signatureValue' 53 signatureValue: 'sec:signatureValue'
54 } 54 }
55 } 55 }
56} 56}
@@ -60,12 +60,12 @@ const nodeDocumentLoader = jsonld.documentLoaders.node()
60const lru = new AsyncLRU({ 60const lru = new AsyncLRU({
61 max: 10, 61 max: 10,
62 load: (url, cb) => { 62 load: (url, cb) => {
63 if (CACHE[ url ] !== undefined) { 63 if (CACHE[url] !== undefined) {
64 logger.debug('Using cache for JSON-LD %s.', url) 64 logger.debug('Using cache for JSON-LD %s.', url)
65 65
66 return cb(null, { 66 return cb(null, {
67 contextUrl: null, 67 contextUrl: null,
68 document: CACHE[ url ], 68 document: CACHE[url],
69 documentUrl: url 69 documentUrl: url
70 }) 70 })
71 } 71 }
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts
index fa58e163f..fec67823d 100644
--- a/server/helpers/custom-validators/activitypub/actor.ts
+++ b/server/helpers/custom-validators/activitypub/actor.ts
@@ -6,7 +6,7 @@ import { isHostValid } from '../servers'
6import { peertubeTruncate } from '@server/helpers/core-utils' 6import { peertubeTruncate } from '@server/helpers/core-utils'
7 7
8function isActorEndpointsObjectValid (endpointObject: any) { 8function isActorEndpointsObjectValid (endpointObject: any) {
9 if (endpointObject && endpointObject.sharedInbox) { 9 if (endpointObject?.sharedInbox) {
10 return isActivityPubUrlValid(endpointObject.sharedInbox) 10 return isActivityPubUrlValid(endpointObject.sharedInbox)
11 } 11 }
12 12
@@ -101,8 +101,6 @@ function normalizeActor (actor: any) {
101 actor.summary = null 101 actor.summary = null
102 } 102 }
103 } 103 }
104
105 return
106} 104}
107 105
108function isValidActorHandle (handle: string) { 106function isValidActorHandle (handle: string) {
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts
index 21d5c53ca..c5b3b4d9f 100644
--- a/server/helpers/custom-validators/activitypub/cache-file.ts
+++ b/server/helpers/custom-validators/activitypub/cache-file.ts
@@ -6,7 +6,7 @@ import { CacheFileObject } from '../../../../shared/models/activitypub/objects'
6function isCacheFileObjectValid (object: CacheFileObject) { 6function isCacheFileObjectValid (object: CacheFileObject) {
7 return exists(object) && 7 return exists(object) &&
8 object.type === 'CacheFile' && 8 object.type === 'CacheFile' &&
9 isDateValid(object.expires) && 9 (object.expires === null || isDateValid(object.expires)) &&
10 isActivityPubUrlValid(object.object) && 10 isActivityPubUrlValid(object.object) &&
11 (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) 11 (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
12} 12}
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts
index aa3c246b5..ea852c491 100644
--- a/server/helpers/custom-validators/activitypub/video-comments.ts
+++ b/server/helpers/custom-validators/activitypub/video-comments.ts
@@ -48,8 +48,6 @@ function normalizeComment (comment: any) {
48 if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url 48 if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url
49 else comment.url = comment.id 49 else comment.url = comment.id
50 } 50 }
51
52 return
53} 51}
54 52
55function isCommentTypeValid (comment: any): boolean { 53function isCommentTypeValid (comment: any): boolean {
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index fe94bd58a..22b5e14a2 100644
--- a/server/helpers/custom-validators/activitypub/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
@@ -51,11 +51,16 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
51 logger.debug('Video has invalid captions', { video }) 51 logger.debug('Video has invalid captions', { video })
52 return false 52 return false
53 } 53 }
54 if (!setValidRemoteIcon(video)) {
55 logger.debug('Video has invalid icons', { video })
56 return false
57 }
54 58
55 // Default attributes 59 // Default attributes
56 if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED 60 if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
57 if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false 61 if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
58 if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true 62 if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true
63 if (!isBooleanValid(video.commentsEnabled)) video.commentsEnabled = false
59 64
60 return isActivityPubUrlValid(video.id) && 65 return isActivityPubUrlValid(video.id) &&
61 isVideoNameValid(video.name) && 66 isVideoNameValid(video.name) &&
@@ -72,7 +77,6 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
72 isDateValid(video.updated) && 77 isDateValid(video.updated) &&
73 (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && 78 (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) &&
74 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && 79 (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) &&
75 isRemoteVideoIconValid(video.icon) &&
76 video.url.length !== 0 && 80 video.url.length !== 0 &&
77 video.attributedTo.length !== 0 81 video.attributedTo.length !== 0
78} 82}
@@ -131,6 +135,8 @@ function setValidRemoteCaptions (video: any) {
131 if (Array.isArray(video.subtitleLanguage) === false) return false 135 if (Array.isArray(video.subtitleLanguage) === false) return false
132 136
133 video.subtitleLanguage = video.subtitleLanguage.filter(caption => { 137 video.subtitleLanguage = video.subtitleLanguage.filter(caption => {
138 if (!isActivityPubUrlValid(caption.url)) caption.url = null
139
134 return isRemoteStringIdentifierValid(caption) 140 return isRemoteStringIdentifierValid(caption)
135 }) 141 })
136 142
@@ -149,12 +155,19 @@ function isRemoteVideoContentValid (mediaType: string, content: string) {
149 return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) 155 return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content)
150} 156}
151 157
152function isRemoteVideoIconValid (icon: any) { 158function setValidRemoteIcon (video: any) {
153 return icon.type === 'Image' && 159 if (video.icon && !isArray(video.icon)) video.icon = [ video.icon ]
154 isActivityPubUrlValid(icon.url) && 160 if (!video.icon) video.icon = []
155 icon.mediaType === 'image/jpeg' && 161
156 validator.isInt(icon.width + '', { min: 0 }) && 162 video.icon = video.icon.filter(icon => {
157 validator.isInt(icon.height + '', { min: 0 }) 163 return icon.type === 'Image' &&
164 isActivityPubUrlValid(icon.url) &&
165 icon.mediaType === 'image/jpeg' &&
166 validator.isInt(icon.width + '', { min: 0 }) &&
167 validator.isInt(icon.height + '', { min: 0 })
168 })
169
170 return video.icon.length !== 0
158} 171}
159 172
160function setValidRemoteVideoUrls (video: any) { 173function setValidRemoteVideoUrls (video: any) {
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts
index 89149b3e0..cf32201c4 100644
--- a/server/helpers/custom-validators/misc.ts
+++ b/server/helpers/custom-validators/misc.ts
@@ -94,13 +94,13 @@ function isFileValid (
94 if (isArray(files)) return optional 94 if (isArray(files)) return optional
95 95
96 // Should have a file 96 // Should have a file
97 const fileArray = files[ field ] 97 const fileArray = files[field]
98 if (!fileArray || fileArray.length === 0) { 98 if (!fileArray || fileArray.length === 0) {
99 return optional 99 return optional
100 } 100 }
101 101
102 // The file should exist 102 // The file should exist
103 const file = fileArray[ 0 ] 103 const file = fileArray[0]
104 if (!file || !file.originalname) return false 104 if (!file || !file.originalname) return false
105 105
106 // Check size 106 // Check size
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts
index 3af72547b..5a4531f72 100644
--- a/server/helpers/custom-validators/plugins.ts
+++ b/server/helpers/custom-validators/plugins.ts
@@ -14,7 +14,7 @@ function isPluginTypeValid (value: any) {
14function isPluginNameValid (value: string) { 14function isPluginNameValid (value: string) {
15 return exists(value) && 15 return exists(value) &&
16 validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && 16 validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) &&
17 validator.matches(value, /^[a-z\-]+$/) 17 validator.matches(value, /^[a-z-]+$/)
18} 18}
19 19
20function isNpmPluginNameValid (value: string) { 20function isNpmPluginNameValid (value: string) {
@@ -146,8 +146,8 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT
146} 146}
147 147
148function isLibraryCodeValid (library: any) { 148function isLibraryCodeValid (library: any) {
149 return typeof library.register === 'function' 149 return typeof library.register === 'function' &&
150 && typeof library.unregister === 'function' 150 typeof library.unregister === 'function'
151} 151}
152 152
153export { 153export {
diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts
index 5a4d10504..8a33b895b 100644
--- a/server/helpers/custom-validators/user-notifications.ts
+++ b/server/helpers/custom-validators/user-notifications.ts
@@ -9,7 +9,8 @@ function isUserNotificationTypeValid (value: any) {
9 9
10function isUserNotificationSettingValid (value: any) { 10function isUserNotificationSettingValid (value: any) {
11 return exists(value) && 11 return exists(value) &&
12 validator.isInt('' + value) && ( 12 validator.isInt('' + value) &&
13 (
13 value === UserNotificationSettingValue.NONE || 14 value === UserNotificationSettingValue.NONE ||
14 value === UserNotificationSettingValue.WEB || 15 value === UserNotificationSettingValue.WEB ||
15 value === UserNotificationSettingValue.EMAIL || 16 value === UserNotificationSettingValue.EMAIL ||
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts
index a9478c76a..5c7bc6fd9 100644
--- a/server/helpers/custom-validators/video-abuses.ts
+++ b/server/helpers/custom-validators/video-abuses.ts
@@ -1,8 +1,6 @@
1import { Response } from 'express'
2import validator from 'validator' 1import validator from 'validator'
3import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' 2import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants'
4import { exists } from './misc' 3import { exists } from './misc'
5import { VideoAbuseModel } from '../../models/video/video-abuse'
6 4
7const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES 5const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES
8 6
@@ -15,7 +13,7 @@ function isVideoAbuseModerationCommentValid (value: string) {
15} 13}
16 14
17function isVideoAbuseStateValid (value: string) { 15function isVideoAbuseStateValid (value: string) {
18 return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined 16 return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined
19} 17}
20 18
21// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts
index d06eb3695..9abbce04a 100644
--- a/server/helpers/custom-validators/video-captions.ts
+++ b/server/helpers/custom-validators/video-captions.ts
@@ -2,7 +2,7 @@ import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initialize
2import { exists, isFileValid } from './misc' 2import { exists, isFileValid } from './misc'
3 3
4function isVideoCaptionLanguageValid (value: any) { 4function isVideoCaptionLanguageValid (value: any) {
5 return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined 5 return exists(value) && VIDEO_LANGUAGES[value] !== undefined
6} 6}
7 7
8const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) 8const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT)
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts
index ffad482b4..c571f5ddd 100644
--- a/server/helpers/custom-validators/video-imports.ts
+++ b/server/helpers/custom-validators/video-imports.ts
@@ -20,7 +20,7 @@ function isVideoImportTargetUrlValid (url: string) {
20} 20}
21 21
22function isVideoImportStateValid (value: any) { 22function isVideoImportStateValid (value: any) {
23 return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined 23 return exists(value) && VIDEO_IMPORT_STATES[value] !== undefined
24} 24}
25 25
26const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`) 26const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`)
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts
index 4bb8384ab..180018fc5 100644
--- a/server/helpers/custom-validators/video-playlists.ts
+++ b/server/helpers/custom-validators/video-playlists.ts
@@ -1,8 +1,6 @@
1import { exists } from './misc' 1import { exists } from './misc'
2import validator from 'validator' 2import validator from 'validator'
3import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' 3import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants'
4import * as express from 'express'
5import { VideoPlaylistModel } from '../../models/video/video-playlist'
6 4
7const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS 5const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS
8 6
@@ -15,7 +13,7 @@ function isVideoPlaylistDescriptionValid (value: any) {
15} 13}
16 14
17function isVideoPlaylistPrivacyValid (value: number) { 15function isVideoPlaylistPrivacyValid (value: number) {
18 return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined 16 return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[value] !== undefined
19} 17}
20 18
21function isVideoPlaylistTimestampValid (value: any) { 19function isVideoPlaylistTimestampValid (value: any) {
@@ -23,7 +21,7 @@ function isVideoPlaylistTimestampValid (value: any) {
23} 21}
24 22
25function isVideoPlaylistTypeValid (value: any) { 23function isVideoPlaylistTypeValid (value: any) {
26 return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined 24 return exists(value) && VIDEO_PLAYLIST_TYPES[value] !== undefined
27} 25}
28 26
29// --------------------------------------------------------------------------- 27// ---------------------------------------------------------------------------
diff --git a/server/helpers/custom-validators/video-redundancies.ts b/server/helpers/custom-validators/video-redundancies.ts
new file mode 100644
index 000000000..50a559c4f
--- /dev/null
+++ b/server/helpers/custom-validators/video-redundancies.ts
@@ -0,0 +1,12 @@
1import { exists } from './misc'
2
3function isVideoRedundancyTarget (value: any) {
4 return exists(value) &&
5 (value === 'my-videos' || value === 'remote-videos')
6}
7
8// ---------------------------------------------------------------------------
9
10export {
11 isVideoRedundancyTarget
12}
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts
index a9e859e54..cfb430c63 100644
--- a/server/helpers/custom-validators/videos.ts
+++ b/server/helpers/custom-validators/videos.ts
@@ -20,15 +20,15 @@ function isVideoFilterValid (filter: VideoFilter) {
20} 20}
21 21
22function isVideoCategoryValid (value: any) { 22function isVideoCategoryValid (value: any) {
23 return value === null || VIDEO_CATEGORIES[ value ] !== undefined 23 return value === null || VIDEO_CATEGORIES[value] !== undefined
24} 24}
25 25
26function isVideoStateValid (value: any) { 26function isVideoStateValid (value: any) {
27 return exists(value) && VIDEO_STATES[ value ] !== undefined 27 return exists(value) && VIDEO_STATES[value] !== undefined
28} 28}
29 29
30function isVideoLicenceValid (value: any) { 30function isVideoLicenceValid (value: any) {
31 return value === null || VIDEO_LICENCES[ value ] !== undefined 31 return value === null || VIDEO_LICENCES[value] !== undefined
32} 32}
33 33
34function isVideoLanguageValid (value: any) { 34function isVideoLanguageValid (value: any) {
@@ -98,7 +98,7 @@ function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } |
98} 98}
99 99
100function isVideoPrivacyValid (value: number) { 100function isVideoPrivacyValid (value: number) {
101 return VIDEO_PRIVACIES[ value ] !== undefined 101 return VIDEO_PRIVACIES[value] !== undefined
102} 102}
103 103
104function isScheduleVideoUpdatePrivacyValid (value: number) { 104function isScheduleVideoUpdatePrivacyValid (value: number) {
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index 9bf6d85a8..f46812977 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -12,7 +12,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
12 if (paramNSFW === 'false') return false 12 if (paramNSFW === 'false') return false
13 if (paramNSFW === 'both') return undefined 13 if (paramNSFW === 'both') return undefined
14 14
15 if (res && res.locals.oauth) { 15 if (res?.locals.oauth) {
16 const user = res.locals.oauth.token.User 16 const user = res.locals.oauth.token.User
17 17
18 // User does not want NSFW videos 18 // User does not want NSFW videos
@@ -28,7 +28,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
28 return null 28 return null
29} 29}
30 30
31function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[] }) { 31function cleanUpReqFiles (req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }) {
32 const files = req.files 32 const files = req.files
33 33
34 if (!files) return 34 if (!files) return
@@ -39,7 +39,7 @@ function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.
39 } 39 }
40 40
41 for (const key of Object.keys(files)) { 41 for (const key of Object.keys(files)) {
42 const file = files[ key ] 42 const file = files[key]
43 43
44 if (isArray(file)) file.forEach(f => deleteFileAsync(f.path)) 44 if (isArray(file)) file.forEach(f => deleteFileAsync(f.path))
45 else deleteFileAsync(file.path) 45 else deleteFileAsync(file.path)
@@ -65,18 +65,18 @@ function badRequest (req: express.Request, res: express.Response) {
65 65
66function createReqFiles ( 66function createReqFiles (
67 fieldNames: string[], 67 fieldNames: string[],
68 mimeTypes: { [ id: string ]: string }, 68 mimeTypes: { [id: string]: string },
69 destinations: { [ fieldName: string ]: string } 69 destinations: { [fieldName: string]: string }
70) { 70) {
71 const storage = multer.diskStorage({ 71 const storage = multer.diskStorage({
72 destination: (req, file, cb) => { 72 destination: (req, file, cb) => {
73 cb(null, destinations[ file.fieldname ]) 73 cb(null, destinations[file.fieldname])
74 }, 74 },
75 75
76 filename: async (req, file, cb) => { 76 filename: async (req, file, cb) => {
77 let extension: string 77 let extension: string
78 const fileExtension = extname(file.originalname) 78 const fileExtension = extname(file.originalname)
79 const extensionFromMimetype = mimeTypes[ file.mimetype ] 79 const extensionFromMimetype = mimeTypes[file.mimetype]
80 80
81 // Take the file extension if we don't understand the mime type 81 // Take the file extension if we don't understand the mime type
82 // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file 82 // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file
@@ -99,7 +99,7 @@ function createReqFiles (
99 } 99 }
100 }) 100 })
101 101
102 let fields: { name: string, maxCount: number }[] = [] 102 const fields: { name: string, maxCount: number }[] = []
103 for (const fieldName of fieldNames) { 103 for (const fieldName of fieldNames) {
104 fields.push({ 104 fields.push({
105 name: fieldName, 105 name: fieldName,
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 00c32e99a..084516e55 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,6 +1,6 @@
1import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
2import { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos' 3import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos'
4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' 4import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
5import { processImage } from './image-utils' 5import { processImage } from './image-utils'
6import { logger } from './logger' 6import { logger } from './logger'
@@ -8,6 +8,71 @@ import { checkFFmpegEncoders } from '../initializers/checker-before-init'
8import { readFile, remove, writeFile } from 'fs-extra' 8import { readFile, remove, writeFile } from 'fs-extra'
9import { CONFIG } from '../initializers/config' 9import { CONFIG } from '../initializers/config'
10 10
11/**
12 * A toolbox to play with audio
13 */
14namespace audio {
15 export const get = (videoPath: string) => {
16 // without position, ffprobe considers the last input only
17 // we make it consider the first input only
18 // if you pass a file path to pos, then ffprobe acts on that file directly
19 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
20
21 function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
22 if (err) return rej(err)
23
24 if ('streams' in data) {
25 const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
26 if (audioStream) {
27 return res({
28 absolutePath: data.format.filename,
29 audioStream
30 })
31 }
32 }
33
34 return res({ absolutePath: data.format.filename })
35 }
36
37 return ffmpeg.ffprobe(videoPath, parseFfprobe)
38 })
39 }
40
41 export namespace bitrate {
42 const baseKbitrate = 384
43
44 const toBits = (kbits: number) => kbits * 8000
45
46 export const aac = (bitrate: number): number => {
47 switch (true) {
48 case bitrate > toBits(baseKbitrate):
49 return baseKbitrate
50
51 default:
52 return -1 // we interpret it as a signal to copy the audio stream as is
53 }
54 }
55
56 export const mp3 = (bitrate: number): number => {
57 /*
58 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
59 That's why, when using aac, we can go to lower kbit/sec. The equivalences
60 made here are not made to be accurate, especially with good mp3 encoders.
61 */
62 switch (true) {
63 case bitrate <= toBits(192):
64 return 128
65
66 case bitrate <= toBits(384):
67 return 256
68
69 default:
70 return baseKbitrate
71 }
72 }
73 }
74}
75
11function computeResolutionsToTranscode (videoFileHeight: number) { 76function computeResolutionsToTranscode (videoFileHeight: number) {
12 const resolutionsEnabled: number[] = [] 77 const resolutionsEnabled: number[] = []
13 const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS 78 const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
@@ -24,7 +89,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
24 ] 89 ]
25 90
26 for (const resolution of resolutions) { 91 for (const resolution of resolutions) {
27 if (configResolutions[ resolution + 'p' ] === true && videoFileHeight > resolution) { 92 if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) {
28 resolutionsEnabled.push(resolution) 93 resolutionsEnabled.push(resolution)
29 } 94 }
30 } 95 }
@@ -48,9 +113,9 @@ async function getVideoStreamCodec (path: string) {
48 const videoCodec = videoStream.codec_tag_string 113 const videoCodec = videoStream.codec_tag_string
49 114
50 const baseProfileMatrix = { 115 const baseProfileMatrix = {
51 'High': '6400', 116 High: '6400',
52 'Main': '4D40', 117 Main: '4D40',
53 'Baseline': '42E0' 118 Baseline: '42E0'
54 } 119 }
55 120
56 let baseProfile = baseProfileMatrix[videoStream.profile] 121 let baseProfile = baseProfileMatrix[videoStream.profile]
@@ -91,7 +156,7 @@ async function getVideoFileFPS (path: string) {
91 if (videoStream === null) return 0 156 if (videoStream === null) return 0
92 157
93 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { 158 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
94 const valuesText: string = videoStream[ key ] 159 const valuesText: string = videoStream[key]
95 if (!valuesText) continue 160 if (!valuesText) continue
96 161
97 const [ frames, seconds ] = valuesText.split('/') 162 const [ frames, seconds ] = valuesText.split('/')
@@ -191,7 +256,8 @@ interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions {
191 type: 'only-audio' 256 type: 'only-audio'
192} 257}
193 258
194type TranscodeOptions = HLSTranscodeOptions 259type TranscodeOptions =
260 HLSTranscodeOptions
195 | VideoTranscodeOptions 261 | VideoTranscodeOptions
196 | MergeAudioTranscodeOptions 262 | MergeAudioTranscodeOptions
197 | OnlyAudioTranscodeOptions 263 | OnlyAudioTranscodeOptions
@@ -204,13 +270,13 @@ function transcode (options: TranscodeOptions) {
204 .output(options.outputPath) 270 .output(options.outputPath)
205 271
206 if (options.type === 'quick-transcode') { 272 if (options.type === 'quick-transcode') {
207 command = await buildQuickTranscodeCommand(command) 273 command = buildQuickTranscodeCommand(command)
208 } else if (options.type === 'hls') { 274 } else if (options.type === 'hls') {
209 command = await buildHLSCommand(command, options) 275 command = await buildHLSCommand(command, options)
210 } else if (options.type === 'merge-audio') { 276 } else if (options.type === 'merge-audio') {
211 command = await buildAudioMergeCommand(command, options) 277 command = await buildAudioMergeCommand(command, options)
212 } else if (options.type === 'only-audio') { 278 } else if (options.type === 'only-audio') {
213 command = await buildOnlyAudioCommand(command, options) 279 command = buildOnlyAudioCommand(command, options)
214 } else { 280 } else {
215 command = await buildx264Command(command, options) 281 command = await buildx264Command(command, options)
216 } 282 }
@@ -247,22 +313,27 @@ async function canDoQuickTranscode (path: string): Promise<boolean> {
247 313
248 // check video params 314 // check video params
249 if (videoStream == null) return false 315 if (videoStream == null) return false
250 if (videoStream[ 'codec_name' ] !== 'h264') return false 316 if (videoStream['codec_name'] !== 'h264') return false
251 if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false 317 if (videoStream['pix_fmt'] !== 'yuv420p') return false
252 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false 318 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
253 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false 319 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
254 320
255 // check audio params (if audio stream exists) 321 // check audio params (if audio stream exists)
256 if (parsedAudio.audioStream) { 322 if (parsedAudio.audioStream) {
257 if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false 323 if (parsedAudio.audioStream['codec_name'] !== 'aac') return false
258 324
259 const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ]) 325 const maxAudioBitrate = audio.bitrate['aac'](parsedAudio.audioStream['bit_rate'])
260 if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false 326 if (maxAudioBitrate !== -1 && parsedAudio.audioStream['bit_rate'] > maxAudioBitrate) return false
261 } 327 }
262 328
263 return true 329 return true
264} 330}
265 331
332function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number {
333 return VIDEO_TRANSCODING_FPS[type].slice(0)
334 .sort((a, b) => fps % a - fps % b)[0]
335}
336
266// --------------------------------------------------------------------------- 337// ---------------------------------------------------------------------------
267 338
268export { 339export {
@@ -286,13 +357,14 @@ export {
286 357
287async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { 358async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
288 let fps = await getVideoFileFPS(options.inputPath) 359 let fps = await getVideoFileFPS(options.inputPath)
289 // On small/medium resolutions, limit FPS
290 if ( 360 if (
361 // On small/medium resolutions, limit FPS
291 options.resolution !== undefined && 362 options.resolution !== undefined &&
292 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && 363 options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
293 fps > VIDEO_TRANSCODING_FPS.AVERAGE 364 fps > VIDEO_TRANSCODING_FPS.AVERAGE
294 ) { 365 ) {
295 fps = VIDEO_TRANSCODING_FPS.AVERAGE 366 // Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
367 fps = getClosestFramerateStandard(fps, 'STANDARD')
296 } 368 }
297 369
298 command = await presetH264(command, options.inputPath, options.resolution, fps) 370 command = await presetH264(command, options.inputPath, options.resolution, fps)
@@ -305,7 +377,7 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
305 377
306 if (fps) { 378 if (fps) {
307 // Hard FPS limits 379 // Hard FPS limits
308 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX 380 if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
309 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN 381 else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN
310 382
311 command = command.withFPS(fps) 383 command = command.withFPS(fps)
@@ -327,14 +399,14 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M
327 return command 399 return command
328} 400}
329 401
330async function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) { 402function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) {
331 command = await presetOnlyAudio(command) 403 command = presetOnlyAudio(command)
332 404
333 return command 405 return command
334} 406}
335 407
336async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { 408function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
337 command = await presetCopy(command) 409 command = presetCopy(command)
338 410
339 command = command.outputOption('-map_metadata -1') // strip all metadata 411 command = command.outputOption('-map_metadata -1') // strip all metadata
340 .outputOption('-movflags faststart') 412 .outputOption('-movflags faststart')
@@ -345,7 +417,7 @@ async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
345async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { 417async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
346 const videoPath = getHLSVideoPath(options) 418 const videoPath = getHLSVideoPath(options)
347 419
348 if (options.copyCodecs) command = await presetCopy(command) 420 if (options.copyCodecs) command = presetCopy(command)
349 else command = await buildx264Command(command, options) 421 else command = await buildx264Command(command, options)
350 422
351 command = command.outputOption('-hls_time 4') 423 command = command.outputOption('-hls_time 4')
@@ -413,71 +485,6 @@ async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string,
413} 485}
414 486
415/** 487/**
416 * A toolbox to play with audio
417 */
418namespace audio {
419 export const get = (videoPath: string) => {
420 // without position, ffprobe considers the last input only
421 // we make it consider the first input only
422 // if you pass a file path to pos, then ffprobe acts on that file directly
423 return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
424
425 function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
426 if (err) return rej(err)
427
428 if ('streams' in data) {
429 const audioStream = data.streams.find(stream => stream[ 'codec_type' ] === 'audio')
430 if (audioStream) {
431 return res({
432 absolutePath: data.format.filename,
433 audioStream
434 })
435 }
436 }
437
438 return res({ absolutePath: data.format.filename })
439 }
440
441 return ffmpeg.ffprobe(videoPath, parseFfprobe)
442 })
443 }
444
445 export namespace bitrate {
446 const baseKbitrate = 384
447
448 const toBits = (kbits: number) => kbits * 8000
449
450 export const aac = (bitrate: number): number => {
451 switch (true) {
452 case bitrate > toBits(baseKbitrate):
453 return baseKbitrate
454
455 default:
456 return -1 // we interpret it as a signal to copy the audio stream as is
457 }
458 }
459
460 export const mp3 = (bitrate: number): number => {
461 /*
462 a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
463 That's why, when using aac, we can go to lower kbit/sec. The equivalences
464 made here are not made to be accurate, especially with good mp3 encoders.
465 */
466 switch (true) {
467 case bitrate <= toBits(192):
468 return 128
469
470 case bitrate <= toBits(384):
471 return 256
472
473 default:
474 return baseKbitrate
475 }
476 }
477 }
478}
479
480/**
481 * Standard profile, with variable bitrate audio and faststart. 488 * Standard profile, with variable bitrate audio and faststart.
482 * 489 *
483 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel 490 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel
@@ -507,10 +514,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut
507 // of course this is far from perfect, but it might save some space in the end 514 // of course this is far from perfect, but it might save some space in the end
508 localCommand = localCommand.audioCodec('aac') 515 localCommand = localCommand.audioCodec('aac')
509 516
510 const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] 517 const audioCodecName = parsedAudio.audioStream['codec_name']
511 518
512 if (audio.bitrate[ audioCodecName ]) { 519 if (audio.bitrate[audioCodecName]) {
513 const bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) 520 const bitrate = audio.bitrate[audioCodecName](parsedAudio.audioStream['bit_rate'])
514 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) 521 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate)
515 } 522 }
516 } 523 }
@@ -531,14 +538,14 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut
531 return localCommand 538 return localCommand
532} 539}
533 540
534async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { 541function presetCopy (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
535 return command 542 return command
536 .format('mp4') 543 .format('mp4')
537 .videoCodec('copy') 544 .videoCodec('copy')
538 .audioCodec('copy') 545 .audioCodec('copy')
539} 546}
540 547
541async function presetOnlyAudio (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { 548function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
542 return command 549 return command
543 .format('mp4') 550 .format('mp4')
544 .audioCodec('copy') 551 .audioCodec('copy')
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 395417612..b8ae28b3f 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -5,7 +5,7 @@ import * as winston from 'winston'
5import { FileTransportOptions } from 'winston/lib/winston/transports' 5import { FileTransportOptions } from 'winston/lib/winston/transports'
6import { CONFIG } from '../initializers/config' 6import { CONFIG } from '../initializers/config'
7import { omit } from 'lodash' 7import { omit } from 'lodash'
8import { LOG_FILENAME } from '@server/initializers/constants' 8import { LOG_FILENAME } from '../initializers/constants'
9 9
10const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT 10const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT
11 11
@@ -27,7 +27,7 @@ function getLoggerReplacer () {
27 if (value instanceof Error) { 27 if (value instanceof Error) {
28 const error = {} 28 const error = {}
29 29
30 Object.getOwnPropertyNames(value).forEach(key => error[ key ] = value[ key ]) 30 Object.getOwnPropertyNames(value).forEach(key => { error[key] = value[key] })
31 31
32 return error 32 return error
33 } 33 }
@@ -98,19 +98,20 @@ function bunyanLogFactory (level: string) {
98 let args: any[] = [] 98 let args: any[] = []
99 args.concat(arguments) 99 args.concat(arguments)
100 100
101 if (arguments[ 0 ] instanceof Error) { 101 if (arguments[0] instanceof Error) {
102 meta = arguments[ 0 ].toString() 102 meta = arguments[0].toString()
103 args = Array.prototype.slice.call(arguments, 1) 103 args = Array.prototype.slice.call(arguments, 1)
104 args.push(meta) 104 args.push(meta)
105 } else if (typeof (args[ 0 ]) !== 'string') { 105 } else if (typeof (args[0]) !== 'string') {
106 meta = arguments[ 0 ] 106 meta = arguments[0]
107 args = Array.prototype.slice.call(arguments, 1) 107 args = Array.prototype.slice.call(arguments, 1)
108 args.push(meta) 108 args.push(meta)
109 } 109 }
110 110
111 logger[ level ].apply(logger, args) 111 logger[level].apply(logger, args)
112 } 112 }
113} 113}
114
114const bunyanLogger = { 115const bunyanLogger = {
115 trace: bunyanLogFactory('debug'), 116 trace: bunyanLogFactory('debug'),
116 debug: bunyanLogFactory('debug'), 117 debug: bunyanLogFactory('debug'),
diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts
index 2336654b0..cfc2be488 100644
--- a/server/helpers/regexp.ts
+++ b/server/helpers/regexp.ts
@@ -1,8 +1,8 @@
1// Thanks to https://regex101.com 1// Thanks to https://regex101.com
2function regexpCapture (str: string, regex: RegExp, maxIterations = 100) { 2function regexpCapture (str: string, regex: RegExp, maxIterations = 100) {
3 const result: RegExpExecArray[] = []
3 let m: RegExpExecArray 4 let m: RegExpExecArray
4 let i = 0 5 let i = 0
5 let result: RegExpExecArray[] = []
6 6
7 // tslint:disable:no-conditional-assignment 7 // tslint:disable:no-conditional-assignment
8 while ((m = regex.exec(str)) !== null && i < maxIterations) { 8 while ((m = regex.exec(str)) !== null && i < maxIterations) {
diff --git a/server/helpers/register-ts-paths.ts b/server/helpers/register-ts-paths.ts
index e8db369e3..eec7fed3e 100644
--- a/server/helpers/register-ts-paths.ts
+++ b/server/helpers/register-ts-paths.ts
@@ -1,5 +1,5 @@
1import { resolve } from 'path' 1import { resolve } from 'path'
2const tsConfigPaths = require('tsconfig-paths') 2import tsConfigPaths = require('tsconfig-paths')
3 3
4const tsConfig = require('../../tsconfig.json') 4const tsConfig = require('../../tsconfig.json')
5 5
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts
index 7c73f7c5c..d34ff2db5 100644
--- a/server/helpers/signup.ts
+++ b/server/helpers/signup.ts
@@ -21,7 +21,7 @@ async function isSignupAllowed (): Promise<{ allowed: boolean, errorMessage?: st
21 21
22function isSignupAllowedForCurrentIP (ip: string) { 22function isSignupAllowedForCurrentIP (ip: string) {
23 const addr = ipaddr.parse(ip) 23 const addr = ipaddr.parse(ip)
24 let excludeList = [ 'blacklist' ] 24 const excludeList = [ 'blacklist' ]
25 let matched = '' 25 let matched = ''
26 26
27 // if there is a valid, non-empty whitelist, we exclude all unknown adresses too 27 // if there is a valid, non-empty whitelist, we exclude all unknown adresses too
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts
index 4c6f200f8..7a4c781cc 100644
--- a/server/helpers/utils.ts
+++ b/server/helpers/utils.ts
@@ -1,6 +1,6 @@
1import { ResultList } from '../../shared' 1import { ResultList } from '../../shared'
2import { ApplicationModel } from '../models/application/application' 2import { ApplicationModel } from '../models/application/application'
3import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' 3import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils'
4import { logger } from './logger' 4import { logger } from './logger'
5import { join } from 'path' 5import { join } from 'path'
6import { Instance as ParseTorrent } from 'parse-torrent' 6import { Instance as ParseTorrent } from 'parse-torrent'
@@ -14,7 +14,7 @@ function deleteFileAsync (path: string) {
14} 14}
15 15
16async function generateRandomString (size: number) { 16async function generateRandomString (size: number) {
17 const raw = await pseudoRandomBytesPromise(size) 17 const raw = await randomBytesPromise(size)
18 18
19 return raw.toString('hex') 19 return raw.toString('hex')
20} 20}
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts
index 3a99518c6..b25e44fcd 100644
--- a/server/helpers/webtorrent.ts
+++ b/server/helpers/webtorrent.ts
@@ -9,12 +9,12 @@ import { promisify2 } from './core-utils'
9import { MVideo } from '@server/typings/models/video/video' 9import { MVideo } from '@server/typings/models/video/video'
10import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file' 10import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file'
11import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist' 11import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist'
12import { STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' 12import { WEBSERVER } from '@server/initializers/constants'
13import * as parseTorrent from 'parse-torrent' 13import * as parseTorrent from 'parse-torrent'
14import * as magnetUtil from 'magnet-uri' 14import * as magnetUtil from 'magnet-uri'
15import { isArray } from '@server/helpers/custom-validators/misc' 15import { isArray } from '@server/helpers/custom-validators/misc'
16import { extractVideo } from '@server/lib/videos' 16import { extractVideo } from '@server/lib/videos'
17import { getTorrentFileName, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 17import { getTorrentFileName, getVideoFilePath } from '@server/lib/video-paths'
18 18
19const createTorrentPromise = promisify2<string, any, any>(createTorrent) 19const createTorrentPromise = promisify2<string, any, any>(createTorrent)
20 20
@@ -39,7 +39,7 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
39 if (torrent.files.length !== 1) { 39 if (torrent.files.length !== 1) {
40 if (timer) clearTimeout(timer) 40 if (timer) clearTimeout(timer)
41 41
42 for (let file of torrent.files) { 42 for (const file of torrent.files) {
43 deleteDownloadedFile({ directoryPath, filepath: file.path }) 43 deleteDownloadedFile({ directoryPath, filepath: file.path })
44 } 44 }
45 45
@@ -47,15 +47,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
47 .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it'))) 47 .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it')))
48 } 48 }
49 49
50 file = torrent.files[ 0 ] 50 file = torrent.files[0]
51 51
52 // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed 52 // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed
53 const writeStream = createWriteStream(path) 53 const writeStream = createWriteStream(path)
54 writeStream.on('finish', () => { 54 writeStream.on('finish', () => {
55 if (timer) clearTimeout(timer) 55 if (timer) clearTimeout(timer)
56 56
57 return safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName) 57 safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName)
58 .then(() => res(path)) 58 .then(() => res(path))
59 .catch(err => logger.error('Cannot destroy webtorrent.', { err }))
59 }) 60 })
60 61
61 file.createReadStream().pipe(writeStream) 62 file.createReadStream().pipe(writeStream)
@@ -63,9 +64,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
63 64
64 torrent.on('error', err => rej(err)) 65 torrent.on('error', err => rej(err))
65 66
66 timer = setTimeout(async () => { 67 timer = setTimeout(() => {
67 return safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName) 68 const err = new Error('Webtorrent download timeout.')
68 .then(() => rej(new Error('Webtorrent download timeout.'))) 69
70 safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName)
71 .then(() => rej(err))
72 .catch(destroyErr => {
73 logger.error('Cannot destroy webtorrent.', { err: destroyErr })
74 rej(err)
75 })
76
69 }, timeout) 77 }, timeout)
70 }) 78 })
71} 79}
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts
index 577a59dbf..fc9d416a1 100644
--- a/server/helpers/youtube-dl.ts
+++ b/server/helpers/youtube-dl.ts
@@ -24,20 +24,23 @@ const processOptions = {
24} 24}
25 25
26function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { 26function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> {
27 return new Promise<YoutubeDLInfo>(async (res, rej) => { 27 return new Promise<YoutubeDLInfo>((res, rej) => {
28 let args = opts || [ '-j', '--flat-playlist' ] 28 let args = opts || [ '-j', '--flat-playlist' ]
29 args = wrapWithProxyOptions(args) 29 args = wrapWithProxyOptions(args)
30 30
31 const youtubeDL = await safeGetYoutubeDL() 31 safeGetYoutubeDL()
32 youtubeDL.getInfo(url, args, processOptions, (err, info) => { 32 .then(youtubeDL => {
33 if (err) return rej(err) 33 youtubeDL.getInfo(url, args, processOptions, (err, info) => {
34 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) 34 if (err) return rej(err)
35 if (info.is_live === true) return rej(new Error('Cannot download a live streaming.'))
35 36
36 const obj = buildVideoInfo(normalizeObject(info)) 37 const obj = buildVideoInfo(normalizeObject(info))
37 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' 38 if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video'
38 39
39 return res(obj) 40 return res(obj)
40 }) 41 })
42 })
43 .catch(err => rej(err))
41 }) 44 })
42} 45}
43 46
@@ -54,26 +57,34 @@ function downloadYoutubeDLVideo (url: string, timeout: number) {
54 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) 57 options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ])
55 } 58 }
56 59
57 return new Promise<string>(async (res, rej) => { 60 return new Promise<string>((res, rej) => {
58 const youtubeDL = await safeGetYoutubeDL() 61 safeGetYoutubeDL()
59 youtubeDL.exec(url, options, processOptions, err => { 62 .then(youtubeDL => {
60 clearTimeout(timer) 63 youtubeDL.exec(url, options, processOptions, err => {
64 clearTimeout(timer)
61 65
62 if (err) { 66 if (err) {
63 remove(path) 67 remove(path)
64 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) 68 .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err }))
65 69
66 return rej(err) 70 return rej(err)
67 } 71 }
68 72
69 return res(path) 73 return res(path)
70 }) 74 })
71 75
72 timer = setTimeout(async () => { 76 timer = setTimeout(() => {
73 await remove(path) 77 const err = new Error('YoutubeDL download timeout.')
74 78
75 return rej(new Error('YoutubeDL download timeout.')) 79 remove(path)
76 }, timeout) 80 .finally(() => rej(err))
81 .catch(err => {
82 logger.error('Cannot remove %s in youtubeDL timeout.', path, { err })
83 return rej(err)
84 })
85 }, timeout)
86 })
87 .catch(err => rej(err))
77 }) 88 })
78} 89}
79 90
@@ -103,7 +114,7 @@ async function updateYoutubeDLBinary () {
103 114
104 const url = result.headers.location 115 const url = result.headers.location
105 const downloadFile = request.get(url) 116 const downloadFile = request.get(url)
106 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[ 1 ] 117 const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[1]
107 118
108 downloadFile.on('response', result => { 119 downloadFile.on('response', result => {
109 if (result.statusCode !== 200) { 120 if (result.statusCode !== 200) {
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 44efd346c..978023129 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -3,7 +3,7 @@ import { isProdInstance, isTestInstance } from '../helpers/core-utils'
3import { UserModel } from '../models/account/user' 3import { UserModel } from '../models/account/user'
4import { ApplicationModel } from '../models/application/application' 4import { ApplicationModel } from '../models/application/application'
5import { OAuthClientModel } from '../models/oauth/oauth-client' 5import { OAuthClientModel } from '../models/oauth/oauth-client'
6import { parse } from 'url' 6import { URL } from 'url'
7import { CONFIG } from './config' 7import { CONFIG } from './config'
8import { logger } from '../helpers/logger' 8import { logger } from '../helpers/logger'
9import { getServerActor } from '../helpers/utils' 9import { getServerActor } from '../helpers/utils'
@@ -16,7 +16,7 @@ import { WEBSERVER } from './constants'
16async function checkActivityPubUrls () { 16async function checkActivityPubUrls () {
17 const actor = await getServerActor() 17 const actor = await getServerActor()
18 18
19 const parsed = parse(actor.url) 19 const parsed = new URL(actor.url)
20 if (WEBSERVER.HOST !== parsed.host) { 20 if (WEBSERVER.HOST !== parsed.host) {
21 const NODE_ENV = config.util.getEnv('NODE_ENV') 21 const NODE_ENV = config.util.getEnv('NODE_ENV')
22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') 22 const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR')
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 9731a0af9..a75f2cec2 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -35,8 +35,8 @@ function checkMissedConfig () {
35 ] 35 ]
36 const requiredAlternatives = [ 36 const requiredAlternatives = [
37 [ // set 37 [ // set
38 ['redis.hostname', 'redis.port'], // alternative 38 [ 'redis.hostname', 'redis.port' ], // alternative
39 ['redis.socket'] 39 [ 'redis.socket' ]
40 ] 40 ]
41 ] 41 ]
42 const miss: string[] = [] 42 const miss: string[] = []
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 7fd77f3e8..75372fa4e 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -1,6 +1,6 @@
1import { IConfig } from 'config' 1import { IConfig } from 'config'
2import { dirname, join } from 'path' 2import { dirname, join } from 'path'
3import { VideosRedundancy } from '../../shared/models' 3import { VideosRedundancyStrategy } from '../../shared/models'
4// Do not use barrels, remain constants as independent as possible 4// Do not use barrels, remain constants as independent as possible
5import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' 5import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils'
6import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 6import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
@@ -301,10 +301,10 @@ function getLocalConfigFilePath () {
301 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` 301 if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}`
302 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` 302 if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}`
303 303
304 return join(dirname(configSources[ 0 ].name), filename + '.json') 304 return join(dirname(configSources[0].name), filename + '.json')
305} 305}
306 306
307function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { 307function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] {
308 if (!objs) return [] 308 if (!objs) return []
309 309
310 if (!Array.isArray(objs)) return objs 310 if (!Array.isArray(objs)) return objs
@@ -330,7 +330,7 @@ export function reloadConfig () {
330 330
331 function purge () { 331 function purge () {
332 for (const fileName in require.cache) { 332 for (const fileName in require.cache) {
333 if (-1 === fileName.indexOf(directory())) { 333 if (fileName.indexOf(directory()) === -1) {
334 continue 334 continue
335 } 335 }
336 336
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index 032f63c8f..fb8ae7cd6 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -4,7 +4,7 @@ import { ActivityPubActorType } from '../../shared/models/activitypub'
4import { FollowState } from '../../shared/models/actors' 4import { FollowState } from '../../shared/models/actors'
5import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' 5import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos'
6// Do not use barrels, remain constants as independent as possible 6// Do not use barrels, remain constants as independent as possible
7import { isTestInstance, sanitizeHost, sanitizeUrl, root, parseDurationToMs } from '../helpers/core-utils' 7import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils'
8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' 8import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type'
9import { invert } from 'lodash' 9import { invert } from 'lodash'
10import { CronRepeatOptions, EveryRepeatOptions } from 'bull' 10import { CronRepeatOptions, EveryRepeatOptions } from 'bull'
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
14 14
15// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
16 16
17const LAST_MIGRATION_VERSION = 470 17const LAST_MIGRATION_VERSION = 480
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
@@ -73,7 +73,9 @@ const SORTABLE_COLUMNS = {
73 73
74 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ], 74 PLUGINS: [ 'name', 'createdAt', 'updatedAt' ],
75 75
76 AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ] 76 AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ],
77
78 VIDEO_REDUNDANCIES: [ 'name' ]
77} 79}
78 80
79const OAUTH_LIFETIME = { 81const OAUTH_LIFETIME = {
@@ -117,45 +119,44 @@ const REMOTE_SCHEME = {
117 WS: 'wss' 119 WS: 'wss'
118} 120}
119 121
120// TODO: remove 'video-file' 122const JOB_ATTEMPTS: { [id in JobType]: number } = {
121const JOB_ATTEMPTS: { [id in (JobType | 'video-file')]: number } = {
122 'activitypub-http-broadcast': 5, 123 'activitypub-http-broadcast': 5,
123 'activitypub-http-unicast': 5, 124 'activitypub-http-unicast': 5,
124 'activitypub-http-fetcher': 5, 125 'activitypub-http-fetcher': 5,
125 'activitypub-follow': 5, 126 'activitypub-follow': 5,
126 'video-file-import': 1, 127 'video-file-import': 1,
127 'video-transcoding': 1, 128 'video-transcoding': 1,
128 'video-file': 1,
129 'video-import': 1, 129 'video-import': 1,
130 'email': 5, 130 'email': 5,
131 'videos-views': 1, 131 'videos-views': 1,
132 'activitypub-refresher': 1 132 'activitypub-refresher': 1,
133 'video-redundancy': 1
133} 134}
134const JOB_CONCURRENCY: { [id in (JobType | 'video-file')]: number } = { 135const JOB_CONCURRENCY: { [id in JobType]: number } = {
135 'activitypub-http-broadcast': 1, 136 'activitypub-http-broadcast': 1,
136 'activitypub-http-unicast': 5, 137 'activitypub-http-unicast': 5,
137 'activitypub-http-fetcher': 1, 138 'activitypub-http-fetcher': 1,
138 'activitypub-follow': 1, 139 'activitypub-follow': 1,
139 'video-file-import': 1, 140 'video-file-import': 1,
140 'video-transcoding': 1, 141 'video-transcoding': 1,
141 'video-file': 1,
142 'video-import': 1, 142 'video-import': 1,
143 'email': 5, 143 'email': 5,
144 'videos-views': 1, 144 'videos-views': 1,
145 'activitypub-refresher': 1 145 'activitypub-refresher': 1,
146 'video-redundancy': 1
146} 147}
147const JOB_TTL: { [id in (JobType | 'video-file')]: number } = { 148const JOB_TTL: { [id in JobType]: number } = {
148 'activitypub-http-broadcast': 60000 * 10, // 10 minutes 149 'activitypub-http-broadcast': 60000 * 10, // 10 minutes
149 'activitypub-http-unicast': 60000 * 10, // 10 minutes 150 'activitypub-http-unicast': 60000 * 10, // 10 minutes
150 'activitypub-http-fetcher': 60000 * 10, // 10 minutes 151 'activitypub-http-fetcher': 60000 * 10, // 10 minutes
151 'activitypub-follow': 60000 * 10, // 10 minutes 152 'activitypub-follow': 60000 * 10, // 10 minutes
152 'video-file-import': 1000 * 3600, // 1 hour 153 'video-file-import': 1000 * 3600, // 1 hour
153 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long 154 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long
154 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long
155 'video-import': 1000 * 3600 * 2, // hours 155 'video-import': 1000 * 3600 * 2, // hours
156 'email': 60000 * 10, // 10 minutes 156 'email': 60000 * 10, // 10 minutes
157 'videos-views': undefined, // Unlimited 157 'videos-views': undefined, // Unlimited
158 'activitypub-refresher': 60000 * 10 // 10 minutes 158 'activitypub-refresher': 60000 * 10, // 10 minutes
159 'video-redundancy': 1000 * 3600 * 3 // 3 hours
159} 160}
160const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = { 161const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = {
161 'videos-views': { 162 'videos-views': {
@@ -309,6 +310,8 @@ let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour
309 310
310const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { 311const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
311 MIN: 10, 312 MIN: 10,
313 STANDARD: [ 24, 25, 30 ],
314 HD_STANDARD: [ 50, 60 ],
312 AVERAGE: 30, 315 AVERAGE: 30,
313 MAX: 60, 316 MAX: 60,
314 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) 317 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
@@ -358,42 +361,42 @@ const VIDEO_LICENCES = {
358 7: 'Public Domain Dedication' 361 7: 'Public Domain Dedication'
359} 362}
360 363
361let VIDEO_LANGUAGES: { [id: string]: string } = {} 364const VIDEO_LANGUAGES: { [id: string]: string } = {}
362 365
363const VIDEO_PRIVACIES = { 366const VIDEO_PRIVACIES = {
364 [ VideoPrivacy.PUBLIC ]: 'Public', 367 [VideoPrivacy.PUBLIC]: 'Public',
365 [ VideoPrivacy.UNLISTED ]: 'Unlisted', 368 [VideoPrivacy.UNLISTED]: 'Unlisted',
366 [ VideoPrivacy.PRIVATE ]: 'Private', 369 [VideoPrivacy.PRIVATE]: 'Private',
367 [ VideoPrivacy.INTERNAL ]: 'Internal' 370 [VideoPrivacy.INTERNAL]: 'Internal'
368} 371}
369 372
370const VIDEO_STATES = { 373const VIDEO_STATES = {
371 [ VideoState.PUBLISHED ]: 'Published', 374 [VideoState.PUBLISHED]: 'Published',
372 [ VideoState.TO_TRANSCODE ]: 'To transcode', 375 [VideoState.TO_TRANSCODE]: 'To transcode',
373 [ VideoState.TO_IMPORT ]: 'To import' 376 [VideoState.TO_IMPORT]: 'To import'
374} 377}
375 378
376const VIDEO_IMPORT_STATES = { 379const VIDEO_IMPORT_STATES = {
377 [ VideoImportState.FAILED ]: 'Failed', 380 [VideoImportState.FAILED]: 'Failed',
378 [ VideoImportState.PENDING ]: 'Pending', 381 [VideoImportState.PENDING]: 'Pending',
379 [ VideoImportState.SUCCESS ]: 'Success' 382 [VideoImportState.SUCCESS]: 'Success'
380} 383}
381 384
382const VIDEO_ABUSE_STATES = { 385const VIDEO_ABUSE_STATES = {
383 [ VideoAbuseState.PENDING ]: 'Pending', 386 [VideoAbuseState.PENDING]: 'Pending',
384 [ VideoAbuseState.REJECTED ]: 'Rejected', 387 [VideoAbuseState.REJECTED]: 'Rejected',
385 [ VideoAbuseState.ACCEPTED ]: 'Accepted' 388 [VideoAbuseState.ACCEPTED]: 'Accepted'
386} 389}
387 390
388const VIDEO_PLAYLIST_PRIVACIES = { 391const VIDEO_PLAYLIST_PRIVACIES = {
389 [ VideoPlaylistPrivacy.PUBLIC ]: 'Public', 392 [VideoPlaylistPrivacy.PUBLIC]: 'Public',
390 [ VideoPlaylistPrivacy.UNLISTED ]: 'Unlisted', 393 [VideoPlaylistPrivacy.UNLISTED]: 'Unlisted',
391 [ VideoPlaylistPrivacy.PRIVATE ]: 'Private' 394 [VideoPlaylistPrivacy.PRIVATE]: 'Private'
392} 395}
393 396
394const VIDEO_PLAYLIST_TYPES = { 397const VIDEO_PLAYLIST_TYPES = {
395 [ VideoPlaylistType.REGULAR ]: 'Regular', 398 [VideoPlaylistType.REGULAR]: 'Regular',
396 [ VideoPlaylistType.WATCH_LATER ]: 'Watch later' 399 [VideoPlaylistType.WATCH_LATER]: 'Watch later'
397} 400}
398 401
399const MIMETYPES = { 402const MIMETYPES = {
@@ -530,7 +533,7 @@ const LAZY_STATIC_PATHS = {
530} 533}
531 534
532// Cache control 535// Cache control
533let STATIC_MAX_AGE = { 536const STATIC_MAX_AGE = {
534 SERVER: '2h', 537 SERVER: '2h',
535 CLIENT: '30d' 538 CLIENT: '30d'
536} 539}
@@ -538,11 +541,13 @@ let STATIC_MAX_AGE = {
538// Videos thumbnail size 541// Videos thumbnail size
539const THUMBNAILS_SIZE = { 542const THUMBNAILS_SIZE = {
540 width: 223, 543 width: 223,
541 height: 122 544 height: 122,
545 minWidth: 150
542} 546}
543const PREVIEWS_SIZE = { 547const PREVIEWS_SIZE = {
544 width: 850, 548 width: 850,
545 height: 480 549 height: 480,
550 minWidth: 400
546} 551}
547const AVATARS_SIZE = { 552const AVATARS_SIZE = {
548 width: 120, 553 width: 120,
@@ -666,14 +671,14 @@ if (isTestInstance() === true) {
666 SCHEDULER_INTERVALS_MS.removeOldViews = 5000 671 SCHEDULER_INTERVALS_MS.removeOldViews = 5000
667 SCHEDULER_INTERVALS_MS.updateVideos = 5000 672 SCHEDULER_INTERVALS_MS.updateVideos = 5000
668 SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 673 SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000
669 REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } 674 REPEAT_JOBS['videos-views'] = { every: 5000 }
670 675
671 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 676 REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
672 677
673 VIDEO_VIEW_LIFETIME = 1000 // 1 second 678 VIDEO_VIEW_LIFETIME = 1000 // 1 second
674 CONTACT_FORM_LIFETIME = 1000 // 1 second 679 CONTACT_FORM_LIFETIME = 1000 // 1 second
675 680
676 JOB_ATTEMPTS[ 'email' ] = 1 681 JOB_ATTEMPTS['email'] = 1
677 682
678 FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 683 FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000
679 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 684 MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1
@@ -833,42 +838,42 @@ function loadLanguages () {
833function buildLanguages () { 838function buildLanguages () {
834 const iso639 = require('iso-639-3') 839 const iso639 = require('iso-639-3')
835 840
836 const languages: { [ id: string ]: string } = {} 841 const languages: { [id: string]: string } = {}
837 842
838 const additionalLanguages = { 843 const additionalLanguages = {
839 'sgn': true, // Sign languages (macro language) 844 sgn: true, // Sign languages (macro language)
840 'ase': true, // American sign language 845 ase: true, // American sign language
841 'sdl': true, // Arabian sign language 846 sdl: true, // Arabian sign language
842 'bfi': true, // British sign language 847 bfi: true, // British sign language
843 'bzs': true, // Brazilian sign language 848 bzs: true, // Brazilian sign language
844 'csl': true, // Chinese sign language 849 csl: true, // Chinese sign language
845 'cse': true, // Czech sign language 850 cse: true, // Czech sign language
846 'dsl': true, // Danish sign language 851 dsl: true, // Danish sign language
847 'fsl': true, // French sign language 852 fsl: true, // French sign language
848 'gsg': true, // German sign language 853 gsg: true, // German sign language
849 'pks': true, // Pakistan sign language 854 pks: true, // Pakistan sign language
850 'jsl': true, // Japanese sign language 855 jsl: true, // Japanese sign language
851 'sfs': true, // South African sign language 856 sfs: true, // South African sign language
852 'swl': true, // Swedish sign language 857 swl: true, // Swedish sign language
853 'rsl': true, // Russian sign language: true 858 rsl: true, // Russian sign language: true
854 859
855 'epo': true, // Esperanto 860 epo: true, // Esperanto
856 'tlh': true, // Klingon 861 tlh: true, // Klingon
857 'jbo': true, // Lojban 862 jbo: true, // Lojban
858 'avk': true // Kotava 863 avk: true // Kotava
859 } 864 }
860 865
861 // Only add ISO639-1 languages and some sign languages (ISO639-3) 866 // Only add ISO639-1 languages and some sign languages (ISO639-3)
862 iso639 867 iso639
863 .filter(l => { 868 .filter(l => {
864 return (l.iso6391 !== null && l.type === 'living') || 869 return (l.iso6391 !== null && l.type === 'living') ||
865 additionalLanguages[ l.iso6393 ] === true 870 additionalLanguages[l.iso6393] === true
866 }) 871 })
867 .forEach(l => languages[ l.iso6391 || l.iso6393 ] = l.name) 872 .forEach(l => { languages[l.iso6391 || l.iso6393] = l.name })
868 873
869 // Override Occitan label 874 // Override Occitan label
870 languages[ 'oc' ] = 'Occitan' 875 languages['oc'] = 'Occitan'
871 languages[ 'el' ] = 'Greek' 876 languages['el'] = 'Greek'
872 877
873 return languages 878 return languages
874} 879}
diff --git a/server/initializers/database.ts b/server/initializers/database.ts
index 9ec146ab1..eedaf3c4e 100644
--- a/server/initializers/database.ts
+++ b/server/initializers/database.ts
@@ -119,8 +119,6 @@ async function initDatabaseModels (silent: boolean) {
119 await createFunctions() 119 await createFunctions()
120 120
121 if (!silent) logger.info('Database %s is ready.', dbname) 121 if (!silent) logger.info('Database %s is ready.', dbname)
122
123 return
124} 122}
125 123
126// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
diff --git a/server/initializers/migrations/0005-email-pod.ts b/server/initializers/migrations/0005-email-pod.ts
index c34a12255..417c33b1f 100644
--- a/server/initializers/migrations/0005-email-pod.ts
+++ b/server/initializers/migrations/0005-email-pod.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0010-email-user.ts b/server/initializers/migrations/0010-email-user.ts
index 37a7b0bb3..f7d01f6d6 100644
--- a/server/initializers/migrations/0010-email-user.ts
+++ b/server/initializers/migrations/0010-email-user.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0015-video-views.ts b/server/initializers/migrations/0015-video-views.ts
index 25164ff4d..47dd4069b 100644
--- a/server/initializers/migrations/0015-video-views.ts
+++ b/server/initializers/migrations/0015-video-views.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0020-video-likes.ts b/server/initializers/migrations/0020-video-likes.ts
index 945be5a98..44333f3b0 100644
--- a/server/initializers/migrations/0020-video-likes.ts
+++ b/server/initializers/migrations/0020-video-likes.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0025-video-dislikes.ts b/server/initializers/migrations/0025-video-dislikes.ts
index 27144c437..2aa22e2d7 100644
--- a/server/initializers/migrations/0025-video-dislikes.ts
+++ b/server/initializers/migrations/0025-video-dislikes.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0030-video-category.ts b/server/initializers/migrations/0030-video-category.ts
index f784f820d..00cd2d8cf 100644
--- a/server/initializers/migrations/0030-video-category.ts
+++ b/server/initializers/migrations/0030-video-category.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0035-video-licence.ts b/server/initializers/migrations/0035-video-licence.ts
index 3d0b0bac9..61d666c5e 100644
--- a/server/initializers/migrations/0035-video-licence.ts
+++ b/server/initializers/migrations/0035-video-licence.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0040-video-nsfw.ts b/server/initializers/migrations/0040-video-nsfw.ts
index f7f70d3c4..44aec8a6c 100644
--- a/server/initializers/migrations/0040-video-nsfw.ts
+++ b/server/initializers/migrations/0040-video-nsfw.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0045-user-display-nsfw.ts b/server/initializers/migrations/0045-user-display-nsfw.ts
index aef420f0e..07795bd75 100644
--- a/server/initializers/migrations/0045-user-display-nsfw.ts
+++ b/server/initializers/migrations/0045-user-display-nsfw.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0050-video-language.ts b/server/initializers/migrations/0050-video-language.ts
index 796fa5f95..6f90abb44 100644
--- a/server/initializers/migrations/0050-video-language.ts
+++ b/server/initializers/migrations/0050-video-language.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0055-video-uuid.ts b/server/initializers/migrations/0055-video-uuid.ts
index e0f904080..8a58aebb8 100644
--- a/server/initializers/migrations/0055-video-uuid.ts
+++ b/server/initializers/migrations/0055-video-uuid.ts
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0060-video-file.ts b/server/initializers/migrations/0060-video-file.ts
index c362cf71a..00647e60e 100644
--- a/server/initializers/migrations/0060-video-file.ts
+++ b/server/initializers/migrations/0060-video-file.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0065-video-file-size.ts b/server/initializers/migrations/0065-video-file-size.ts
index e9ce77e50..0bdc675c2 100644
--- a/server/initializers/migrations/0065-video-file-size.ts
+++ b/server/initializers/migrations/0065-video-file-size.ts
@@ -5,9 +5,9 @@ import { VideoModel } from '../../models/video/video'
5import { getVideoFilePath } from '@server/lib/video-paths' 5import { getVideoFilePath } from '@server/lib/video-paths'
6 6
7function up (utils: { 7function up (utils: {
8 transaction: Sequelize.Transaction, 8 transaction: Sequelize.Transaction
9 queryInterface: Sequelize.QueryInterface, 9 queryInterface: Sequelize.QueryInterface
10 sequelize: Sequelize.Sequelize, 10 sequelize: Sequelize.Sequelize
11 db: any 11 db: any
12}): Promise<void> { 12}): Promise<void> {
13 return utils.db.Video.listOwnedAndPopulateAuthorAndTags() 13 return utils.db.Video.listOwnedAndPopulateAuthorAndTags()
diff --git a/server/initializers/migrations/0070-user-video-quota.ts b/server/initializers/migrations/0070-user-video-quota.ts
index 37683432f..1d073f244 100644
--- a/server/initializers/migrations/0070-user-video-quota.ts
+++ b/server/initializers/migrations/0070-user-video-quota.ts
@@ -3,9 +3,9 @@ import * as Promise from 'bluebird'
3import { Migration } from '../../models/migrations' 3import { Migration } from '../../models/migrations'
4 4
5function up (utils: { 5function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize, 8 sequelize: Sequelize.Sequelize
9 db: any 9 db: any
10}): Promise<void> { 10}): Promise<void> {
11 const q = utils.queryInterface 11 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts
index e4f26cb77..f56c1b2c3 100644
--- a/server/initializers/migrations/0075-video-resolutions.ts
+++ b/server/initializers/migrations/0075-video-resolutions.ts
@@ -5,9 +5,9 @@ import { getVideoFileResolution } from '../../helpers/ffmpeg-utils'
5import { readdir, rename } from 'fs-extra' 5import { readdir, rename } from 'fs-extra'
6 6
7function up (utils: { 7function up (utils: {
8 transaction: Sequelize.Transaction, 8 transaction: Sequelize.Transaction
9 queryInterface: Sequelize.QueryInterface, 9 queryInterface: Sequelize.QueryInterface
10 sequelize: Sequelize.Sequelize, 10 sequelize: Sequelize.Sequelize
11 db: any 11 db: any
12}): Promise<void> { 12}): Promise<void> {
13 const torrentDir = CONFIG.STORAGE.TORRENTS_DIR 13 const torrentDir = CONFIG.STORAGE.TORRENTS_DIR
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts
index 5512bdcf1..b8e9bd6d0 100644
--- a/server/initializers/migrations/0080-video-channels.ts
+++ b/server/initializers/migrations/0080-video-channels.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4' 2import * as uuidv4 from 'uuid/v4'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 const q = utils.queryInterface 10 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0085-user-role.ts b/server/initializers/migrations/0085-user-role.ts
index de75faec2..ec7428fd5 100644
--- a/server/initializers/migrations/0085-user-role.ts
+++ b/server/initializers/migrations/0085-user-role.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0090-videos-description.ts b/server/initializers/migrations/0090-videos-description.ts
index 6f98dcade..32e518d75 100644
--- a/server/initializers/migrations/0090-videos-description.ts
+++ b/server/initializers/migrations/0090-videos-description.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0095-videos-privacy.ts b/server/initializers/migrations/0095-videos-privacy.ts
index 4c2bf91d0..c732d6f6a 100644
--- a/server/initializers/migrations/0095-videos-privacy.ts
+++ b/server/initializers/migrations/0095-videos-privacy.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index 96d44a7ce..05fd37406 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -7,9 +7,9 @@ import { ApplicationModel } from '../../models/application/application'
7import { SERVER_ACTOR_NAME } from '../constants' 7import { SERVER_ACTOR_NAME } from '../constants'
8 8
9async function up (utils: { 9async function up (utils: {
10 transaction: Sequelize.Transaction, 10 transaction: Sequelize.Transaction
11 queryInterface: Sequelize.QueryInterface, 11 queryInterface: Sequelize.QueryInterface
12 sequelize: Sequelize.Sequelize, 12 sequelize: Sequelize.Sequelize
13 db: any 13 db: any
14}): Promise<void> { 14}): Promise<void> {
15 const q = utils.queryInterface 15 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0105-server-mail.ts b/server/initializers/migrations/0105-server-mail.ts
index 4b9600e91..5ee37c418 100644
--- a/server/initializers/migrations/0105-server-mail.ts
+++ b/server/initializers/migrations/0105-server-mail.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('Servers', 'email') 9 await utils.queryInterface.removeColumn('Servers', 'email')
diff --git a/server/initializers/migrations/0110-server-key.ts b/server/initializers/migrations/0110-server-key.ts
index 5ff6daf69..354cd7e76 100644
--- a/server/initializers/migrations/0110-server-key.ts
+++ b/server/initializers/migrations/0110-server-key.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('Servers', 'publicKey') 9 await utils.queryInterface.removeColumn('Servers', 'publicKey')
diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts
index b318e8163..604b6394a 100644
--- a/server/initializers/migrations/0115-account-avatar.ts
+++ b/server/initializers/migrations/0115-account-avatar.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.db.Avatar.sync() 9 await utils.db.Avatar.sync()
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts
index 6d253f04f..1b407b270 100644
--- a/server/initializers/migrations/0120-video-null.ts
+++ b/server/initializers/migrations/0120-video-null.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0125-table-lowercase.ts b/server/initializers/migrations/0125-table-lowercase.ts
index 78041ccb0..f75a56791 100644
--- a/server/initializers/migrations/0125-table-lowercase.ts
+++ b/server/initializers/migrations/0125-table-lowercase.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.renameTable('Applications', 'application') 8 await utils.queryInterface.renameTable('Applications', 'application')
diff --git a/server/initializers/migrations/0130-user-autoplay-video.ts b/server/initializers/migrations/0130-user-autoplay-video.ts
index 9f6878e39..d57934588 100644
--- a/server/initializers/migrations/0130-user-autoplay-video.ts
+++ b/server/initializers/migrations/0130-user-autoplay-video.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import * as Promise from 'bluebird' 2import * as Promise from 'bluebird'
3 3
4function up (utils: { 4function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const q = utils.queryInterface 9 const q = utils.queryInterface
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts
index 5ace0f4d2..c0c343384 100644
--- a/server/initializers/migrations/0135-video-channel-actor.ts
+++ b/server/initializers/migrations/0135-video-channel-actor.ts
@@ -3,8 +3,8 @@ import { DataType } from 'sequelize-typescript'
3import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' 3import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
4 4
5async function up (utils: { 5async function up (utils: {
6 transaction: Sequelize.Transaction, 6 transaction: Sequelize.Transaction
7 queryInterface: Sequelize.QueryInterface, 7 queryInterface: Sequelize.QueryInterface
8 sequelize: Sequelize.Sequelize 8 sequelize: Sequelize.Sequelize
9}): Promise<void> { 9}): Promise<void> {
10 // Create actor table 10 // Create actor table
@@ -64,10 +64,10 @@ async function up (utils: {
64 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 64 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
65 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 65 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
66 ) 66 )
67 SELECT 67 SELECT
68 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 68 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
69 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 69 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
70 FROM account 70 FROM account
71 WHERE "applicationId" IS NOT NULL 71 WHERE "applicationId" IS NOT NULL
72 ` 72 `
73 await utils.sequelize.query(query1) 73 await utils.sequelize.query(query1)
@@ -79,10 +79,10 @@ async function up (utils: {
79 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 79 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
80 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 80 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
81 ) 81 )
82 SELECT 82 SELECT
83 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 83 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
84 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 84 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
85 FROM account 85 FROM account
86 WHERE "applicationId" IS NULL 86 WHERE "applicationId" IS NULL
87 ` 87 `
88 await utils.sequelize.query(query2) 88 await utils.sequelize.query(query2)
@@ -108,17 +108,17 @@ async function up (utils: {
108 } 108 }
109 109
110 { 110 {
111 const query = ` 111 const query = `
112 INSERT INTO actor 112 INSERT INTO actor
113 ( 113 (
114 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", 114 type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl",
115 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" 115 "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt"
116 ) 116 )
117 SELECT 117 SELECT
118 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox', 118 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox',
119 "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following', 119 "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following',
120 null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt" 120 null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt"
121 FROM "videoChannel" 121 FROM "videoChannel"
122 INNER JOIN "account" on "videoChannel"."accountId" = "account".id 122 INNER JOIN "account" on "videoChannel"."accountId" = "account".id
123 ` 123 `
124 await utils.sequelize.query(query) 124 await utils.sequelize.query(query)
@@ -157,13 +157,13 @@ async function up (utils: {
157 } 157 }
158 158
159 { 159 {
160 const query1 = `UPDATE "actorFollow" 160 const query1 = `UPDATE "actorFollow"
161 SET "actorId" = 161 SET "actorId" =
162 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")` 162 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")`
163 await utils.sequelize.query(query1) 163 await utils.sequelize.query(query1)
164 164
165 const query2 = `UPDATE "actorFollow" 165 const query2 = `UPDATE "actorFollow"
166 SET "targetActorId" = 166 SET "targetActorId" =
167 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")` 167 (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")`
168 168
169 await utils.sequelize.query(query2) 169 await utils.sequelize.query(query2)
@@ -189,8 +189,8 @@ async function up (utils: {
189 await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey') 189 await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey')
190 } 190 }
191 191
192 const query = `UPDATE "videoShare" 192 const query = `UPDATE "videoShare"
193 SET "actorId" = 193 SET "actorId" =
194 (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")` 194 (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")`
195 await utils.sequelize.query(query) 195 await utils.sequelize.query(query)
196 196
diff --git a/server/initializers/migrations/0140-actor-url.ts b/server/initializers/migrations/0140-actor-url.ts
index 020499391..d790988ad 100644
--- a/server/initializers/migrations/0140-actor-url.ts
+++ b/server/initializers/migrations/0140-actor-url.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { WEBSERVER } from '../constants' 2import { WEBSERVER } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const toReplace = WEBSERVER.HOSTNAME + ':443' 9 const toReplace = WEBSERVER.HOSTNAME + ':443'
diff --git a/server/initializers/migrations/0145-delete-author.ts b/server/initializers/migrations/0145-delete-author.ts
index cb23d1cc2..6c9427997 100644
--- a/server/initializers/migrations/0145-delete-author.ts
+++ b/server/initializers/migrations/0145-delete-author.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.dropTable('Authors') 8 await utils.queryInterface.dropTable('Authors')
diff --git a/server/initializers/migrations/0150-avatar-cascade.ts b/server/initializers/migrations/0150-avatar-cascade.ts
index 821696717..fb3b25773 100644
--- a/server/initializers/migrations/0150-avatar-cascade.ts
+++ b/server/initializers/migrations/0150-avatar-cascade.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey') 8 await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey')
diff --git a/server/initializers/migrations/0155-video-comments-enabled.ts b/server/initializers/migrations/0155-video-comments-enabled.ts
index 6949d3a0c..691640b35 100644
--- a/server/initializers/migrations/0155-video-comments-enabled.ts
+++ b/server/initializers/migrations/0155-video-comments-enabled.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { Migration } from '../../models/migrations' 2import { Migration } from '../../models/migrations'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0160-account-route.ts b/server/initializers/migrations/0160-account-route.ts
index cab4c72f1..97469948b 100644
--- a/server/initializers/migrations/0160-account-route.ts
+++ b/server/initializers/migrations/0160-account-route.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0165-video-route.ts b/server/initializers/migrations/0165-video-route.ts
index 56d98bc69..aa7c75128 100644
--- a/server/initializers/migrations/0165-video-route.ts
+++ b/server/initializers/migrations/0165-video-route.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts
index a12b35da9..901a3c799 100644
--- a/server/initializers/migrations/0170-actor-follow-score.ts
+++ b/server/initializers/migrations/0170-actor-follow-score.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { ACTOR_FOLLOW_SCORE } from '../constants' 2import { ACTOR_FOLLOW_SCORE } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('server', 'score') 9 await utils.queryInterface.removeColumn('server', 'score')
diff --git a/server/initializers/migrations/0175-actor-follow-counts.ts b/server/initializers/migrations/0175-actor-follow-counts.ts
index 4fb524181..d7853f8dc 100644
--- a/server/initializers/migrations/0175-actor-follow-counts.ts
+++ b/server/initializers/migrations/0175-actor-follow-counts.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 const query = 'UPDATE "actor" SET ' + 8 const query = 'UPDATE "actor" SET ' +
diff --git a/server/initializers/migrations/0180-job-table-delete.ts b/server/initializers/migrations/0180-job-table-delete.ts
index df29145d0..fb48a0c9d 100644
--- a/server/initializers/migrations/0180-job-table-delete.ts
+++ b/server/initializers/migrations/0180-job-table-delete.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 await utils.queryInterface.dropTable('job') 8 await utils.queryInterface.dropTable('job')
diff --git a/server/initializers/migrations/0185-video-share-url.ts b/server/initializers/migrations/0185-video-share-url.ts
index f7eeb0878..f59931e0f 100644
--- a/server/initializers/migrations/0185-video-share-url.ts
+++ b/server/initializers/migrations/0185-video-share-url.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0190-video-comment-unique-url.ts b/server/initializers/migrations/0190-video-comment-unique-url.ts
index b196c9352..a8769ed41 100644
--- a/server/initializers/migrations/0190-video-comment-unique-url.ts
+++ b/server/initializers/migrations/0190-video-comment-unique-url.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0195-support.ts b/server/initializers/migrations/0195-support.ts
index 3b9eabe79..3f7c75dce 100644
--- a/server/initializers/migrations/0195-support.ts
+++ b/server/initializers/migrations/0195-support.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0200-video-published-at.ts b/server/initializers/migrations/0200-video-published-at.ts
index 1701ea07a..d8c7b42a7 100644
--- a/server/initializers/migrations/0200-video-published-at.ts
+++ b/server/initializers/migrations/0200-video-published-at.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0205-user-nsfw-policy.ts b/server/initializers/migrations/0205-user-nsfw-policy.ts
index d0f6e8962..9c2786f12 100644
--- a/server/initializers/migrations/0205-user-nsfw-policy.ts
+++ b/server/initializers/migrations/0205-user-nsfw-policy.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0210-video-language.ts b/server/initializers/migrations/0210-video-language.ts
index ca95c7527..ee4ce9266 100644
--- a/server/initializers/migrations/0210-video-language.ts
+++ b/server/initializers/migrations/0210-video-language.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { CONSTRAINTS_FIELDS } from '../constants' 2import { CONSTRAINTS_FIELDS } from '../constants'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0215-video-support-length.ts b/server/initializers/migrations/0215-video-support-length.ts
index ba395050f..26c0ca700 100644
--- a/server/initializers/migrations/0215-video-support-length.ts
+++ b/server/initializers/migrations/0215-video-support-length.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 { 8 {
diff --git a/server/initializers/migrations/0255-video-blacklist-reason.ts b/server/initializers/migrations/0255-video-blacklist-reason.ts
index 69d6efb9e..7de982f93 100644
--- a/server/initializers/migrations/0255-video-blacklist-reason.ts
+++ b/server/initializers/migrations/0255-video-blacklist-reason.ts
@@ -1,5 +1,4 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { VideoAbuseState } from '../../../shared/models/videos'
3 2
4async function up (utils: { 3async function up (utils: {
5 transaction: Sequelize.Transaction 4 transaction: Sequelize.Transaction
diff --git a/server/initializers/migrations/0285-description-support.ts b/server/initializers/migrations/0285-description-support.ts
index 85ef4ef39..aab3a938f 100644
--- a/server/initializers/migrations/0285-description-support.ts
+++ b/server/initializers/migrations/0285-description-support.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0290-account-video-rate-url.ts b/server/initializers/migrations/0290-account-video-rate-url.ts
index bdabf2929..b974b1a81 100644
--- a/server/initializers/migrations/0290-account-video-rate-url.ts
+++ b/server/initializers/migrations/0290-account-video-rate-url.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts
index dbf249f66..e1999b072 100644
--- a/server/initializers/migrations/0295-video-file-extname.ts
+++ b/server/initializers/migrations/0295-video-file-extname.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts
index aa5fc21fb..5e35e14ba 100644
--- a/server/initializers/migrations/0300-user-videos-history-enabled.ts
+++ b/server/initializers/migrations/0300-user-videos-history-enabled.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0305-fix-unfederated-videos.ts b/server/initializers/migrations/0305-fix-unfederated-videos.ts
index be206601f..9c5d56b7b 100644
--- a/server/initializers/migrations/0305-fix-unfederated-videos.ts
+++ b/server/initializers/migrations/0305-fix-unfederated-videos.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0310-drop-unused-video-indexes.ts b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
index d51f430c0..181858d3d 100644
--- a/server/initializers/migrations/0310-drop-unused-video-indexes.ts
+++ b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const indexNames = [ 9 const indexNames = [
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts
index 8284c58a0..0e3f4fbef 100644
--- a/server/initializers/migrations/0315-user-notifications.ts
+++ b/server/initializers/migrations/0315-user-notifications.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0320-blacklist-unfederate.ts b/server/initializers/migrations/0320-blacklist-unfederate.ts
index 6fb7bbb90..41de41c55 100644
--- a/server/initializers/migrations/0320-blacklist-unfederate.ts
+++ b/server/initializers/migrations/0320-blacklist-unfederate.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0325-video-abuse-fields.ts b/server/initializers/migrations/0325-video-abuse-fields.ts
index fca6d666f..d88724a20 100644
--- a/server/initializers/migrations/0325-video-abuse-fields.ts
+++ b/server/initializers/migrations/0325-video-abuse-fields.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0330-video-streaming-playlist.ts b/server/initializers/migrations/0330-video-streaming-playlist.ts
index c85a762ab..f75541a7f 100644
--- a/server/initializers/migrations/0330-video-streaming-playlist.ts
+++ b/server/initializers/migrations/0330-video-streaming-playlist.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0335-video-downloading-enabled.ts b/server/initializers/migrations/0335-video-downloading-enabled.ts
index e79466447..c745f1f02 100644
--- a/server/initializers/migrations/0335-video-downloading-enabled.ts
+++ b/server/initializers/migrations/0335-video-downloading-enabled.ts
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize'
2import { Migration } from '../../models/migrations' 2import { Migration } from '../../models/migrations'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize 7 sequelize: Sequelize.Sequelize
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts
index fe4f4a5f9..7cbc14ab5 100644
--- a/server/initializers/migrations/0340-add-originally-published-at.ts
+++ b/server/initializers/migrations/0340-add-originally-published-at.ts
@@ -1,8 +1,8 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize 6 sequelize: Sequelize.Sequelize
7}): Promise<void> { 7}): Promise<void> {
8 8
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts
index de69f5b9e..76813f93f 100644
--- a/server/initializers/migrations/0345-video-playlists.ts
+++ b/server/initializers/migrations/0345-video-playlists.ts
@@ -4,8 +4,8 @@ import * as uuidv4 from 'uuid/v4'
4import { WEBSERVER } from '../constants' 4import { WEBSERVER } from '../constants'
5 5
6async function up (utils: { 6async function up (utils: {
7 transaction: Sequelize.Transaction, 7 transaction: Sequelize.Transaction
8 queryInterface: Sequelize.QueryInterface, 8 queryInterface: Sequelize.QueryInterface
9 sequelize: Sequelize.Sequelize 9 sequelize: Sequelize.Sequelize
10}): Promise<void> { 10}): Promise<void> {
11 const transaction = utils.transaction 11 const transaction = utils.transaction
diff --git a/server/initializers/migrations/0350-video-blacklist-type.ts b/server/initializers/migrations/0350-video-blacklist-type.ts
index 4849020ef..f79ae5ec7 100644
--- a/server/initializers/migrations/0350-video-blacklist-type.ts
+++ b/server/initializers/migrations/0350-video-blacklist-type.ts
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize'
2import { VideoBlacklistType } from '../../../shared/models/videos' 2import { VideoBlacklistType } from '../../../shared/models/videos'
3 3
4async function up (utils: { 4async function up (utils: {
5 transaction: Sequelize.Transaction, 5 transaction: Sequelize.Transaction
6 queryInterface: Sequelize.QueryInterface, 6 queryInterface: Sequelize.QueryInterface
7 sequelize: Sequelize.Sequelize, 7 sequelize: Sequelize.Sequelize
8 db: any 8 db: any
9}): Promise<void> { 9}): Promise<void> {
10 { 10 {
diff --git a/server/initializers/migrations/0355-p2p-peer-version.ts b/server/initializers/migrations/0355-p2p-peer-version.ts
index 18f23d9b7..89af28d07 100644
--- a/server/initializers/migrations/0355-p2p-peer-version.ts
+++ b/server/initializers/migrations/0355-p2p-peer-version.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 9
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts
index 05caf8e1d..6f9a01a9c 100644
--- a/server/initializers/migrations/0360-notification-instance-follower.ts
+++ b/server/initializers/migrations/0360-notification-instance-follower.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0365-user-admin-flags.ts b/server/initializers/migrations/0365-user-admin-flags.ts
index 20553100a..b705387da 100644
--- a/server/initializers/migrations/0365-user-admin-flags.ts
+++ b/server/initializers/migrations/0365-user-admin-flags.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0370-thumbnail.ts b/server/initializers/migrations/0370-thumbnail.ts
index 384ca1a15..07c25436a 100644
--- a/server/initializers/migrations/0370-thumbnail.ts
+++ b/server/initializers/migrations/0370-thumbnail.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0375-account-description.ts b/server/initializers/migrations/0375-account-description.ts
index 1258563fd..f9af942e0 100644
--- a/server/initializers/migrations/0375-account-description.ts
+++ b/server/initializers/migrations/0375-account-description.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0380-cleanup-timestamps.ts b/server/initializers/migrations/0380-cleanup-timestamps.ts
index 2a9fd6f02..18ecfb997 100644
--- a/server/initializers/migrations/0380-cleanup-timestamps.ts
+++ b/server/initializers/migrations/0380-cleanup-timestamps.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 try { 9 try {
diff --git a/server/initializers/migrations/0385-remove-actor-uuid.ts b/server/initializers/migrations/0385-remove-actor-uuid.ts
index 032c0562b..eefbc386b 100644
--- a/server/initializers/migrations/0385-remove-actor-uuid.ts
+++ b/server/initializers/migrations/0385-remove-actor-uuid.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.queryInterface.removeColumn('actor', 'uuid') 9 await utils.queryInterface.removeColumn('actor', 'uuid')
diff --git a/server/initializers/migrations/0390-user-pending-email.ts b/server/initializers/migrations/0390-user-pending-email.ts
index 5ca871746..9cf0affa5 100644
--- a/server/initializers/migrations/0390-user-pending-email.ts
+++ b/server/initializers/migrations/0390-user-pending-email.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0395-user-video-languages.ts b/server/initializers/migrations/0395-user-video-languages.ts
index 278698bf4..9c24fbc9a 100644
--- a/server/initializers/migrations/0395-user-video-languages.ts
+++ b/server/initializers/migrations/0395-user-video-languages.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0400-user-theme.ts b/server/initializers/migrations/0400-user-theme.ts
index f74d76115..7addb1bb3 100644
--- a/server/initializers/migrations/0400-user-theme.ts
+++ b/server/initializers/migrations/0400-user-theme.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0405-plugin.ts b/server/initializers/migrations/0405-plugin.ts
index c55b81960..5c290b986 100644
--- a/server/initializers/migrations/0405-plugin.ts
+++ b/server/initializers/migrations/0405-plugin.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0410-video-playlist-element.ts b/server/initializers/migrations/0410-video-playlist-element.ts
index f536632a2..1b4692357 100644
--- a/server/initializers/migrations/0410-video-playlist-element.ts
+++ b/server/initializers/migrations/0410-video-playlist-element.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0415-thumbnail-auto-generated.ts b/server/initializers/migrations/0415-thumbnail-auto-generated.ts
index f822a4c05..98d563c88 100644
--- a/server/initializers/migrations/0415-thumbnail-auto-generated.ts
+++ b/server/initializers/migrations/0415-thumbnail-auto-generated.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0420-avatar-lazy.ts b/server/initializers/migrations/0420-avatar-lazy.ts
index 5fc57aac2..5c74819d2 100644
--- a/server/initializers/migrations/0420-avatar-lazy.ts
+++ b/server/initializers/migrations/0420-avatar-lazy.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts
index 4e5f9e6ab..720b99ccc 100644
--- a/server/initializers/migrations/0425-nullable-actor-fields.ts
+++ b/server/initializers/migrations/0425-nullable-actor-fields.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 const data = { 9 const data = {
diff --git a/server/initializers/migrations/0430-auto-follow-notification-setting.ts b/server/initializers/migrations/0430-auto-follow-notification-setting.ts
index 034bdd46d..1734828a4 100644
--- a/server/initializers/migrations/0430-auto-follow-notification-setting.ts
+++ b/server/initializers/migrations/0430-auto-follow-notification-setting.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0435-user-modals.ts b/server/initializers/migrations/0435-user-modals.ts
index 5c2aa85b5..737440e9b 100644
--- a/server/initializers/migrations/0435-user-modals.ts
+++ b/server/initializers/migrations/0435-user-modals.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0440-user-auto-play-next-video.ts b/server/initializers/migrations/0440-user-auto-play-next-video.ts
index f0baafe7b..f3f663f59 100644
--- a/server/initializers/migrations/0440-user-auto-play-next-video.ts
+++ b/server/initializers/migrations/0440-user-auto-play-next-video.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0445-shared-inbox-optional.ts b/server/initializers/migrations/0445-shared-inbox-optional.ts
index dad2d6569..ade1a2a57 100644
--- a/server/initializers/migrations/0445-shared-inbox-optional.ts
+++ b/server/initializers/migrations/0445-shared-inbox-optional.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0450-streaming-playlist-files.ts b/server/initializers/migrations/0450-streaming-playlist-files.ts
index 460dac8be..08e2e3989 100644
--- a/server/initializers/migrations/0450-streaming-playlist-files.ts
+++ b/server/initializers/migrations/0450-streaming-playlist-files.ts
@@ -1,15 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { join } from 'path'
3import { HLS_STREAMING_PLAYLIST_DIRECTORY, WEBSERVER } from '@server/initializers/constants'
4import { CONFIG } from '@server/initializers/config'
5import { pathExists, stat, writeFile } from 'fs-extra'
6import * as parseTorrent from 'parse-torrent'
7import { createTorrentPromise } from '@server/helpers/webtorrent'
8 2
9async function up (utils: { 3async function up (utils: {
10 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
11 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
12 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
13 db: any 7 db: any
14}): Promise<void> { 8}): Promise<void> {
15 { 9 {
@@ -42,8 +36,8 @@ async function up (utils: {
42 { 36 {
43 const query = 'insert into "videoFile" ' + 37 const query = 'insert into "videoFile" ' +
44 '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' + 38 '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' +
45 '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", "videoFile"."fps", ' + 39 '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", ' +
46 '"videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' + 40 '"videoFile"."fps", "videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' +
47 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)' 41 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)'
48 42
49 await utils.sequelize.query(query, { transaction: utils.transaction }) 43 await utils.sequelize.query(query, { transaction: utils.transaction })
diff --git a/server/initializers/migrations/0455-soft-delete-video-comments.ts b/server/initializers/migrations/0455-soft-delete-video-comments.ts
index bcfb97b56..00e56015f 100644
--- a/server/initializers/migrations/0455-soft-delete-video-comments.ts
+++ b/server/initializers/migrations/0455-soft-delete-video-comments.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0460-user-playlist-autoplay.ts b/server/initializers/migrations/0460-user-playlist-autoplay.ts
index 3067ac1a4..d6f5081ab 100644
--- a/server/initializers/migrations/0460-user-playlist-autoplay.ts
+++ b/server/initializers/migrations/0460-user-playlist-autoplay.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0465-thumbnail-file-url-length.ts b/server/initializers/migrations/0465-thumbnail-file-url-length.ts
index db8c85c29..84a4fa0ba 100644
--- a/server/initializers/migrations/0465-thumbnail-file-url-length.ts
+++ b/server/initializers/migrations/0465-thumbnail-file-url-length.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 { 9 {
diff --git a/server/initializers/migrations/0470-cleaup-indexes.ts b/server/initializers/migrations/0470-cleaup-indexes.ts
index 53e401c2b..7365c30f8 100644
--- a/server/initializers/migrations/0470-cleaup-indexes.ts
+++ b/server/initializers/migrations/0470-cleaup-indexes.ts
@@ -1,9 +1,9 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2 2
3async function up (utils: { 3async function up (utils: {
4 transaction: Sequelize.Transaction, 4 transaction: Sequelize.Transaction
5 queryInterface: Sequelize.QueryInterface, 5 queryInterface: Sequelize.QueryInterface
6 sequelize: Sequelize.Sequelize, 6 sequelize: Sequelize.Sequelize
7 db: any 7 db: any
8}): Promise<void> { 8}): Promise<void> {
9 await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;') 9 await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;')
diff --git a/server/initializers/migrations/0475-redundancy-expires-on.ts b/server/initializers/migrations/0475-redundancy-expires-on.ts
new file mode 100644
index 000000000..edbddba37
--- /dev/null
+++ b/server/initializers/migrations/0475-redundancy-expires-on.ts
@@ -0,0 +1,27 @@
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 {
10 const data = {
11 type: Sequelize.DATE,
12 allowNull: true,
13 defaultValue: null
14 }
15
16 await utils.queryInterface.changeColumn('videoRedundancy', 'expiresOn', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrations/0480-caption-file-url.ts b/server/initializers/migrations/0480-caption-file-url.ts
new file mode 100644
index 000000000..1f88206d3
--- /dev/null
+++ b/server/initializers/migrations/0480-caption-file-url.ts
@@ -0,0 +1,27 @@
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 {
10 const data = {
11 type: Sequelize.STRING,
12 allowNull: true,
13 defaultValue: null
14 }
15
16 await utils.queryInterface.addColumn('videoCaption', 'fileUrl', data)
17 }
18}
19
20function down (options) {
21 throw new Error('Not implemented.')
22}
23
24export {
25 up,
26 down
27}
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts
index 1cb0116b7..77203ae24 100644
--- a/server/initializers/migrator.ts
+++ b/server/initializers/migrator.ts
@@ -20,7 +20,7 @@ async function migrate () {
20 } 20 }
21 21
22 const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options) 22 const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options)
23 if (rows && rows[0] && rows[0].migrationVersion) { 23 if (rows?.[0]?.migrationVersion) {
24 actualVersion = rows[0].migrationVersion 24 actualVersion = rows[0].migrationVersion
25 } 25 }
26 26
@@ -60,7 +60,7 @@ export {
60async function getMigrationScripts () { 60async function getMigrationScripts () {
61 const files = await readdir(path.join(__dirname, 'migrations')) 61 const files = await readdir(path.join(__dirname, 'migrations'))
62 const filesToMigrate: { 62 const filesToMigrate: {
63 version: string, 63 version: string
64 script: string 64 script: string
65 }[] = [] 65 }[] = []
66 66
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index f802658cf..3f6edc070 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -1,6 +1,6 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { Transaction } from 'sequelize' 2import { Transaction } from 'sequelize'
3import * as url from 'url' 3import { URL } from 'url'
4import * as uuidv4 from 'uuid/v4' 4import * as uuidv4 from 'uuid/v4'
5import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' 5import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
6import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' 6import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
@@ -33,8 +33,7 @@ import {
33 MActorFull, 33 MActorFull,
34 MActorFullActor, 34 MActorFullActor,
35 MActorId, 35 MActorId,
36 MChannel, 36 MChannel
37 MChannelAccountDefault
38} from '../../typings/models' 37} from '../../typings/models'
39 38
40// Set account keys, this could be long so process after the account creation and do not block the client 39// Set account keys, this could be long so process after the account creation and do not block the client
@@ -121,13 +120,13 @@ async function getOrCreateActorAndServerAndModel (
121 120
122 if ((created === true || refreshed === true) && updateCollections === true) { 121 if ((created === true || refreshed === true) && updateCollections === true) {
123 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } 122 const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' }
124 await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 123 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })
125 } 124 }
126 125
127 // We created a new account: fetch the playlists 126 // We created a new account: fetch the playlists
128 if (created === true && actor.Account && accountPlaylistsUrl) { 127 if (created === true && actor.Account && accountPlaylistsUrl) {
129 const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } 128 const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' }
130 await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) 129 await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })
131 } 130 }
132 131
133 return actorRefreshed 132 return actorRefreshed
@@ -215,7 +214,7 @@ async function fetchActorTotalItems (url: string) {
215 } 214 }
216} 215}
217 216
218async function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { 217function getAvatarInfoIfExists (actorJSON: ActivityPubActor) {
219 if ( 218 if (
220 actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && 219 actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
221 isActivityPubUrlValid(actorJSON.icon.url) 220 isActivityPubUrlValid(actorJSON.icon.url)
@@ -271,7 +270,10 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel
271 270
272 if (statusCode === 404) { 271 if (statusCode === 404) {
273 logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) 272 logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
274 actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy() 273 actor.Account
274 ? await actor.Account.destroy()
275 : await actor.VideoChannel.destroy()
276
275 return { actor: undefined, refreshed: false } 277 return { actor: undefined, refreshed: false }
276 } 278 }
277 279
@@ -337,14 +339,14 @@ function saveActorAndServerAndModelIfNotExist (
337 ownerActor?: MActorFullActor, 339 ownerActor?: MActorFullActor,
338 t?: Transaction 340 t?: Transaction
339): Bluebird<MActorFullActor> | Promise<MActorFullActor> { 341): Bluebird<MActorFullActor> | Promise<MActorFullActor> {
340 let actor = result.actor 342 const actor = result.actor
341 343
342 if (t !== undefined) return save(t) 344 if (t !== undefined) return save(t)
343 345
344 return sequelizeTypescript.transaction(t => save(t)) 346 return sequelizeTypescript.transaction(t => save(t))
345 347
346 async function save (t: Transaction) { 348 async function save (t: Transaction) {
347 const actorHost = url.parse(actor.url).host 349 const actorHost = new URL(actor.url).host
348 350
349 const serverOptions = { 351 const serverOptions = {
350 where: { 352 where: {
@@ -402,7 +404,7 @@ type FetchRemoteActorResult = {
402 support?: string 404 support?: string
403 playlists?: string 405 playlists?: string
404 avatar?: { 406 avatar?: {
405 name: string, 407 name: string
406 fileUrl: string 408 fileUrl: string
407 } 409 }
408 attributedTo: ActivityPubAttributedTo[] 410 attributedTo: ActivityPubAttributedTo[]
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts
index 65b2dcb49..8252e95e9 100644
--- a/server/lib/activitypub/cache-file.ts
+++ b/server/lib/activitypub/cache-file.ts
@@ -13,7 +13,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
13 if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) 13 if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url)
14 14
15 return { 15 return {
16 expiresOn: new Date(cacheFileObject.expires), 16 expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
17 url: cacheFileObject.id, 17 url: cacheFileObject.id,
18 fileUrl: url.href, 18 fileUrl: url.href,
19 strategy: null, 19 strategy: null,
@@ -30,7 +30,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
30 if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) 30 if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`)
31 31
32 return { 32 return {
33 expiresOn: new Date(cacheFileObject.expires), 33 expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
34 url: cacheFileObject.id, 34 url: cacheFileObject.id,
35 fileUrl: url.href, 35 fileUrl: url.href,
36 strategy: null, 36 strategy: null,
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts
index 9e469e3e6..eeafdf4ba 100644
--- a/server/lib/activitypub/crawl.ts
+++ b/server/lib/activitypub/crawl.ts
@@ -3,7 +3,7 @@ import { doRequest } from '../../helpers/requests'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import * as Bluebird from 'bluebird' 4import * as Bluebird from 'bluebird'
5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' 5import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub'
6import { parse } from 'url' 6import { URL } from 'url'
7 7
8type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) 8type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>)
9type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) 9type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>)
@@ -24,7 +24,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>
24 const response = await doRequest<ActivityPubOrderedCollection<T>>(options) 24 const response = await doRequest<ActivityPubOrderedCollection<T>>(options)
25 const firstBody = response.body 25 const firstBody = response.body
26 26
27 let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT 27 const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT
28 let i = 0 28 let i = 0
29 let nextLink = firstBody.first 29 let nextLink = firstBody.first
30 while (nextLink && i < limit) { 30 while (nextLink && i < limit) {
@@ -32,7 +32,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>
32 32
33 if (typeof nextLink === 'string') { 33 if (typeof nextLink === 'string') {
34 // Don't crawl ourselves 34 // Don't crawl ourselves
35 const remoteHost = parse(nextLink).host 35 const remoteHost = new URL(nextLink).host
36 if (remoteHost === WEBSERVER.HOST) continue 36 if (remoteHost === WEBSERVER.HOST) continue
37 37
38 options.uri = nextLink 38 options.uri = nextLink
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts
index 1abf43cd4..a1c95504e 100644
--- a/server/lib/activitypub/follow.ts
+++ b/server/lib/activitypub/follow.ts
@@ -27,7 +27,6 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) {
27 } 27 }
28 28
29 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 29 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
30 .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err))
31 } 30 }
32} 31}
33 32
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 9f0225b64..c4c6b849b 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { MActor, MActorFollowActors } from '../../../typings/models' 6import { MActor, MActorFollowActors } from '../../../typings/models'
7 7
8async function sendAccept (actorFollow: MActorFollowActors) { 8function sendAccept (actorFollow: MActorFollowActors) {
9 const follower = actorFollow.ActorFollower 9 const follower = actorFollow.ActorFollower
10 const me = actorFollow.ActorFollowing 10 const me = actorFollow.ActorFollowing
11 11
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index a0f33852c..d03b358f1 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -28,7 +28,7 @@ async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare,
28 logger.info('Creating job to send announce %s.', videoShare.url) 28 logger.info('Creating job to send announce %s.', videoShare.url)
29 29
30 const followersException = [ byActor ] 30 const followersException = [ byActor ]
31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) 31 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce')
32} 32}
33 33
34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { 34function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 1709d8348..3585d704a 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -130,10 +130,10 @@ export {
130// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
131 131
132async function sendVideoRelatedCreateActivity (options: { 132async function sendVideoRelatedCreateActivity (options: {
133 byActor: MActorLight, 133 byActor: MActorLight
134 video: MVideoAccountLight, 134 video: MVideoAccountLight
135 url: string, 135 url: string
136 object: any, 136 object: any
137 transaction?: Transaction 137 transaction?: Transaction
138}) { 138}) {
139 const activityBuilder = (audience: ActivityAudience) => { 139 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index 6e41f241f..600469c71 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -6,7 +6,7 @@ import { sendVideoRelatedActivity } from './utils'
6import { audiencify, getAudience } from '../audience' 6import { audiencify, getAudience } from '../audience'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' 7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
8 8
9async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
10 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index da7638a7b..e4e523631 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -7,7 +7,7 @@ import { Transaction } from 'sequelize'
7import { MActor, MVideoFullLight } from '../../../typings/models' 7import { MActor, MVideoFullLight } from '../../../typings/models'
8import { MVideoAbuseVideo } from '../../../typings/models/video' 8import { MVideoAbuseVideo } from '../../../typings/models/video'
9 9
10async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { 10function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) {
11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user 11 if (!video.VideoChannel.Account.Actor.serverId) return // Local user
12 12
13 const url = getVideoAbuseActivityPubUrl(videoAbuse) 13 const url = getVideoAbuseActivityPubUrl(videoAbuse)
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index e84a6f98b..5db252325 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' 7import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models'
8 8
9async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
10 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 4258a3c36..643c468a9 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { MActor } from '../../../typings/models' 6import { MActor } from '../../../typings/models'
7 7
8async function sendReject (follower: MActor, following: MActor) { 8function sendReject (follower: MActor, following: MActor) {
9 if (!follower.serverId) { // This should never happen 9 if (!follower.serverId) { // This should never happen
10 logger.warn('Do not sending reject to local follower.') 10 logger.warn('Do not sending reject to local follower.')
11 return 11 return
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index e9ab5b3c5..33f1d4921 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -28,7 +28,7 @@ import {
28 MVideoShare 28 MVideoShare
29} from '../../../typings/models' 29} from '../../../typings/models'
30 30
31async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { 31function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
32 const me = actorFollow.ActorFollower 32 const me = actorFollow.ActorFollower
33 const following = actorFollow.ActorFollowing 33 const following = actorFollow.ActorFollowing
34 34
@@ -118,10 +118,10 @@ function undoActivityData (
118} 118}
119 119
120async function sendUndoVideoRelatedActivity (options: { 120async function sendUndoVideoRelatedActivity (options: {
121 byActor: MActor, 121 byActor: MActor
122 video: MVideoAccountLight, 122 video: MVideoAccountLight
123 url: string, 123 url: string
124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, 124 activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce
125 transaction: Transaction 125 transaction: Transaction
126}) { 126}) {
127 const activityBuilder = (audience: ActivityAudience) => { 127 const activityBuilder = (audience: ActivityAudience) => {
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 9c76671b5..cb500bd34 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -8,7 +8,6 @@ import { getUpdateActivityPubUrl } from '../url'
8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' 8import { broadcastToFollowers, sendVideoRelatedActivity } from './utils'
9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' 9import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience'
10import { logger } from '../../../helpers/logger' 10import { logger } from '../../../helpers/logger'
11import { VideoCaptionModel } from '../../../models/video/video-caption'
12import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 11import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
13import { getServerActor } from '../../../helpers/utils' 12import { getServerActor } from '../../../helpers/utils'
14import { 13import {
@@ -29,7 +28,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction
29 28
30 logger.info('Creating job to update video %s.', video.url) 29 logger.info('Creating job to update video %s.', video.url)
31 30
32 const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor 31 const byActor = overrodeByActor || video.VideoChannel.Account.Actor
33 32
34 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 33 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
35 34
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts
index 8809417f9..47482b9a9 100644
--- a/server/lib/activitypub/send/send-view.ts
+++ b/server/lib/activitypub/send/send-view.ts
@@ -16,7 +16,7 @@ async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Tran
16 return buildViewActivity(url, byActor, video, audience) 16 return buildViewActivity(url, byActor, video, audience)
17 } 17 }
18 18
19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) 19 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t, contextType: 'View' })
20} 20}
21 21
22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { 22function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView {
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts
index 77b723479..0d67bb3d6 100644
--- a/server/lib/activitypub/send/utils.ts
+++ b/server/lib/activitypub/send/utils.ts
@@ -8,13 +8,15 @@ import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAud
8import { getServerActor } from '../../../helpers/utils' 8import { getServerActor } from '../../../helpers/utils'
9import { afterCommitIfTransaction } from '../../../helpers/database-utils' 9import { afterCommitIfTransaction } from '../../../helpers/database-utils'
10import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' 10import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
11import { ContextType } from '@server/helpers/activitypub'
11 12
12async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 13async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
13 byActor: MActorLight, 14 byActor: MActorLight
14 video: MVideoAccountLight, 15 video: MVideoAccountLight
15 transaction?: Transaction 16 transaction?: Transaction
17 contextType?: ContextType
16}) { 18}) {
17 const { byActor, video, transaction } = options 19 const { byActor, video, transaction, contextType } = options
18 20
19 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) 21 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
20 22
@@ -24,7 +26,7 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
24 const activity = activityBuilder(audience) 26 const activity = activityBuilder(audience)
25 27
26 return afterCommitIfTransaction(transaction, () => { 28 return afterCommitIfTransaction(transaction, () => {
27 return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()) 29 return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox(), contextType)
28 }) 30 })
29 } 31 }
30 32
@@ -34,7 +36,7 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
34 36
35 const actorsException = [ byActor ] 37 const actorsException = [ byActor ]
36 38
37 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException) 39 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
38} 40}
39 41
40async function forwardVideoRelatedActivity ( 42async function forwardVideoRelatedActivity (
@@ -90,11 +92,12 @@ async function broadcastToFollowers (
90 byActor: MActorId, 92 byActor: MActorId,
91 toFollowersOf: MActorId[], 93 toFollowersOf: MActorId[],
92 t: Transaction, 94 t: Transaction,
93 actorsException: MActorWithInboxes[] = [] 95 actorsException: MActorWithInboxes[] = [],
96 contextType?: ContextType
94) { 97) {
95 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 98 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
96 99
97 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 100 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
98} 101}
99 102
100async function broadcastToActors ( 103async function broadcastToActors (
@@ -102,13 +105,14 @@ async function broadcastToActors (
102 byActor: MActorId, 105 byActor: MActorId,
103 toActors: MActor[], 106 toActors: MActor[],
104 t?: Transaction, 107 t?: Transaction,
105 actorsException: MActorWithInboxes[] = [] 108 actorsException: MActorWithInboxes[] = [],
109 contextType?: ContextType
106) { 110) {
107 const uris = await computeUris(toActors, actorsException) 111 const uris = await computeUris(toActors, actorsException)
108 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) 112 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
109} 113}
110 114
111function broadcastTo (uris: string[], data: any, byActor: MActorId) { 115function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
112 if (uris.length === 0) return undefined 116 if (uris.length === 0) return undefined
113 117
114 logger.debug('Creating broadcast job.', { uris }) 118 logger.debug('Creating broadcast job.', { uris })
@@ -116,19 +120,21 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId) {
116 const payload = { 120 const payload = {
117 uris, 121 uris,
118 signatureActorId: byActor.id, 122 signatureActorId: byActor.id,
119 body: data 123 body: data,
124 contextType
120 } 125 }
121 126
122 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) 127 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
123} 128}
124 129
125function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { 130function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
126 logger.debug('Creating unicast job.', { uri: toActorUrl }) 131 logger.debug('Creating unicast job.', { uri: toActorUrl })
127 132
128 const payload = { 133 const payload = {
129 uri: toActorUrl, 134 uri: toActorUrl,
130 signatureActorId: byActor.id, 135 signatureActorId: byActor.id,
131 body: data 136 body: data,
137 contextType
132 } 138 }
133 139
134 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) 140 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index d5c078a29..8642d2432 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -10,9 +10,9 @@ import { checkUrlsSameHost } from '../../helpers/activitypub'
10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' 10import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video'
11 11
12type ResolveThreadParams = { 12type ResolveThreadParams = {
13 url: string, 13 url: string
14 comments?: MCommentOwner[], 14 comments?: MCommentOwner[]
15 isVideo?: boolean, 15 isVideo?: boolean
16 commentCreated?: boolean 16 commentCreated?: boolean
17} 17}
18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> 18type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }>
@@ -28,7 +28,7 @@ async function resolveThread (params: ResolveThreadParams): ResolveThreadResult
28 if (params.commentCreated === undefined) params.commentCreated = false 28 if (params.commentCreated === undefined) params.commentCreated = false
29 if (params.comments === undefined) params.comments = [] 29 if (params.comments === undefined) params.comments = []
30 30
31 // Already have this comment? 31 // Already have this comment?
32 if (isVideo !== true) { 32 if (isVideo !== true) {
33 const result = await resolveCommentFromDB(params) 33 const result = await resolveCommentFromDB(params)
34 if (result) return result 34 if (result) return result
@@ -87,7 +87,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
87 87
88 let resultComment: MCommentOwnerVideo 88 let resultComment: MCommentOwnerVideo
89 if (comments.length !== 0) { 89 if (comments.length !== 0) {
90 const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo 90 const firstReply = comments[comments.length - 1] as MCommentOwnerVideo
91 firstReply.inReplyToCommentId = null 91 firstReply.inReplyToCommentId = null
92 firstReply.originCommentId = null 92 firstReply.originCommentId = null
93 firstReply.videoId = video.id 93 firstReply.videoId = video.id
@@ -97,9 +97,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) {
97 comments[comments.length - 1] = await firstReply.save() 97 comments[comments.length - 1] = await firstReply.save()
98 98
99 for (let i = comments.length - 2; i >= 0; i--) { 99 for (let i = comments.length - 2; i >= 0; i--) {
100 const comment = comments[ i ] as MCommentOwnerVideo 100 const comment = comments[i] as MCommentOwnerVideo
101 comment.originCommentId = firstReply.id 101 comment.originCommentId = firstReply.id
102 comment.inReplyToCommentId = comments[ i + 1 ].id 102 comment.inReplyToCommentId = comments[i + 1].id
103 comment.videoId = video.id 103 comment.videoId = video.id
104 comment.changed('updatedAt', true) 104 comment.changed('updatedAt', true)
105 comment.Video = video 105 comment.Video = video
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts
index 6bd46bb58..79ccfbc7e 100644
--- a/server/lib/activitypub/video-rates.ts
+++ b/server/lib/activitypub/video-rates.ts
@@ -58,8 +58,6 @@ async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateTy
58 const field = rate === 'like' ? 'likes' : 'dislikes' 58 const field = rate === 'like' ? 'likes' : 'dislikes'
59 await video.increment(field, { by: rateCounts }) 59 await video.increment(field, { by: rateCounts })
60 } 60 }
61
62 return
63} 61}
64 62
65async function sendVideoRateChange ( 63async function sendVideoRateChange (
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index ade93150f..9e43caa20 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -6,7 +6,8 @@ import {
6 ActivityHashTagObject, 6 ActivityHashTagObject,
7 ActivityMagnetUrlObject, 7 ActivityMagnetUrlObject,
8 ActivityPlaylistSegmentHashesObject, 8 ActivityPlaylistSegmentHashesObject,
9 ActivityPlaylistUrlObject, ActivityTagObject, 9 ActivityPlaylistUrlObject,
10 ActivityTagObject,
10 ActivityUrlObject, 11 ActivityUrlObject,
11 ActivityVideoUrlObject, 12 ActivityVideoUrlObject,
12 VideoState 13 VideoState
@@ -17,14 +18,14 @@ import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validat
17import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' 18import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos'
18import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' 19import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
19import { logger } from '../../helpers/logger' 20import { logger } from '../../helpers/logger'
20import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 21import { doRequest } from '../../helpers/requests'
21import { 22import {
22 ACTIVITY_PUB, 23 ACTIVITY_PUB,
23 MIMETYPES, 24 MIMETYPES,
24 P2P_MEDIA_LOADER_PEER_VERSION, 25 P2P_MEDIA_LOADER_PEER_VERSION,
25 PREVIEWS_SIZE, 26 PREVIEWS_SIZE,
26 REMOTE_SCHEME, 27 REMOTE_SCHEME,
27 STATIC_PATHS 28 STATIC_PATHS, THUMBNAILS_SIZE
28} from '../../initializers/constants' 29} from '../../initializers/constants'
29import { TagModel } from '../../models/video/tag' 30import { TagModel } from '../../models/video/tag'
30import { VideoModel } from '../../models/video/video' 31import { VideoModel } from '../../models/video/video'
@@ -40,7 +41,7 @@ import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub
40import { createRates } from './video-rates' 41import { createRates } from './video-rates'
41import { addVideoShares, shareVideoByServerAndChannel } from './share' 42import { addVideoShares, shareVideoByServerAndChannel } from './share'
42import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' 43import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
43import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 44import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
44import { Notifier } from '../notifier' 45import { Notifier } from '../notifier'
45import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' 46import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist'
46import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' 47import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
@@ -71,6 +72,7 @@ import {
71 MVideoThumbnail 72 MVideoThumbnail
72} from '../../typings/models' 73} from '../../typings/models'
73import { MThumbnail } from '../../typings/models/video/thumbnail' 74import { MThumbnail } from '../../typings/models/video/thumbnail'
75import { maxBy, minBy } from 'lodash'
74 76
75async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { 77async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) {
76 const video = videoArg as MVideoAP 78 const video = videoArg as MVideoAP
@@ -131,19 +133,6 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
131 return body.description ? body.description : '' 133 return body.description ? body.description : ''
132} 134}
133 135
134function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) {
135 const url = buildRemoteBaseUrl(video, path)
136
137 // We need to provide a callback, if no we could have an uncaught exception
138 return doRequestAndSaveToFile({ uri: url }, destPath)
139}
140
141function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) {
142 const host = video.VideoChannel.Account.Actor.Server.host
143
144 return REMOTE_SCHEME.HTTP + '://' + host + path
145}
146
147function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { 136function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) {
148 const channel = videoObject.attributedTo.find(a => a.type === 'Group') 137 const channel = videoObject.attributedTo.find(a => a.type === 'Group')
149 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) 138 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url)
@@ -173,7 +162,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
173 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) 162 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate)
174 163
175 await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) 164 await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner)
176 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) 165 .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.likes }))
177 } else { 166 } else {
178 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) 167 jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' })
179 } 168 }
@@ -183,7 +172,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
183 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) 172 const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate)
184 173
185 await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) 174 await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner)
186 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) 175 .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.dislikes }))
187 } else { 176 } else {
188 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) 177 jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' })
189 } 178 }
@@ -193,7 +182,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
193 const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) 182 const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate)
194 183
195 await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) 184 await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner)
196 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) 185 .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err, rootUrl: fetchedVideo.shares }))
197 } else { 186 } else {
198 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) 187 jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' })
199 } 188 }
@@ -203,30 +192,30 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo
203 const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) 192 const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate)
204 193
205 await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) 194 await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner)
206 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) 195 .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err, rootUrl: fetchedVideo.comments }))
207 } else { 196 } else {
208 jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) 197 jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' })
209 } 198 }
210 199
211 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) 200 await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }))
212} 201}
213 202
214function getOrCreateVideoAndAccountAndChannel (options: { 203function getOrCreateVideoAndAccountAndChannel (options: {
215 videoObject: { id: string } | string, 204 videoObject: { id: string } | string
216 syncParam?: SyncParam, 205 syncParam?: SyncParam
217 fetchType?: 'all', 206 fetchType?: 'all'
218 allowRefresh?: boolean 207 allowRefresh?: boolean
219}): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> 208}): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }>
220function getOrCreateVideoAndAccountAndChannel (options: { 209function getOrCreateVideoAndAccountAndChannel (options: {
221 videoObject: { id: string } | string, 210 videoObject: { id: string } | string
222 syncParam?: SyncParam, 211 syncParam?: SyncParam
223 fetchType?: VideoFetchByUrlType, 212 fetchType?: VideoFetchByUrlType
224 allowRefresh?: boolean 213 allowRefresh?: boolean
225}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> 214}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }>
226async function getOrCreateVideoAndAccountAndChannel (options: { 215async function getOrCreateVideoAndAccountAndChannel (options: {
227 videoObject: { id: string } | string, 216 videoObject: { id: string } | string
228 syncParam?: SyncParam, 217 syncParam?: SyncParam
229 fetchType?: VideoFetchByUrlType, 218 fetchType?: VideoFetchByUrlType
230 allowRefresh?: boolean // true by default 219 allowRefresh?: boolean // true by default
231}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { 220}): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> {
232 // Default params 221 // Default params
@@ -246,8 +235,14 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
246 syncParam 235 syncParam
247 } 236 }
248 237
249 if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) 238 if (syncParam.refreshVideo === true) {
250 else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } }) 239 videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
240 } else {
241 await JobQueue.Instance.createJobWithPromise({
242 type: 'activitypub-refresher',
243 payload: { type: 'video', url: videoFromDatabase.url }
244 })
245 }
251 } 246 }
252 247
253 return { video: videoFromDatabase, created: false } 248 return { video: videoFromDatabase, created: false }
@@ -266,10 +261,10 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
266} 261}
267 262
268async function updateVideoFromAP (options: { 263async function updateVideoFromAP (options: {
269 video: MVideoAccountLightBlacklistAllFiles, 264 video: MVideoAccountLightBlacklistAllFiles
270 videoObject: VideoTorrentObject, 265 videoObject: VideoTorrentObject
271 account: MAccountIdActor, 266 account: MAccountIdActor
272 channel: MChannelDefault, 267 channel: MChannelDefault
273 overrideTo?: string[] 268 overrideTo?: string[]
274}) { 269}) {
275 const { video, videoObject, account, channel, overrideTo } = options 270 const { video, videoObject, account, channel, overrideTo } = options
@@ -284,7 +279,7 @@ async function updateVideoFromAP (options: {
284 let thumbnailModel: MThumbnail 279 let thumbnailModel: MThumbnail
285 280
286 try { 281 try {
287 thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 282 thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
288 } catch (err) { 283 } catch (err) {
289 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) 284 logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
290 } 285 }
@@ -300,7 +295,7 @@ async function updateVideoFromAP (options: {
300 throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) 295 throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url)
301 } 296 }
302 297
303 const to = overrideTo ? overrideTo : videoObject.to 298 const to = overrideTo || videoObject.to
304 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) 299 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to)
305 video.name = videoData.name 300 video.name = videoData.name
306 video.uuid = videoData.uuid 301 video.uuid = videoData.uuid
@@ -327,8 +322,7 @@ async function updateVideoFromAP (options: {
327 322
328 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) 323 if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t)
329 324
330 // FIXME: use icon URL instead 325 const previewUrl = videoUpdated.getPreview().getFileUrl(videoUpdated)
331 const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename))
332 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 326 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
333 await videoUpdated.addAndSaveThumbnail(previewModel, t) 327 await videoUpdated.addAndSaveThumbnail(previewModel, t)
334 328
@@ -391,7 +385,7 @@ async function updateVideoFromAP (options: {
391 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) 385 await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
392 386
393 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 387 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
394 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) 388 return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t)
395 }) 389 })
396 await Promise.all(videoCaptionsPromises) 390 await Promise.all(videoCaptionsPromises)
397 } 391 }
@@ -424,8 +418,8 @@ async function updateVideoFromAP (options: {
424} 418}
425 419
426async function refreshVideoIfNeeded (options: { 420async function refreshVideoIfNeeded (options: {
427 video: MVideoThumbnail, 421 video: MVideoThumbnail
428 fetchedType: VideoFetchByUrlType, 422 fetchedType: VideoFetchByUrlType
429 syncParam: SyncParam 423 syncParam: SyncParam
430}): Promise<MVideoThumbnail> { 424}): Promise<MVideoThumbnail> {
431 if (!options.video.isOutdated()) return options.video 425 if (!options.video.isOutdated()) return options.video
@@ -483,7 +477,6 @@ export {
483 federateVideoIfNeeded, 477 federateVideoIfNeeded,
484 fetchRemoteVideo, 478 fetchRemoteVideo,
485 getOrCreateVideoAndAccountAndChannel, 479 getOrCreateVideoAndAccountAndChannel,
486 fetchRemoteVideoStaticFile,
487 fetchRemoteVideoDescription, 480 fetchRemoteVideoDescription,
488 getOrCreateVideoChannelFromVideoObject 481 getOrCreateVideoChannelFromVideoObject
489} 482}
@@ -519,7 +512,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
519 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) 512 const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
520 const video = VideoModel.build(videoData) as MVideoThumbnail 513 const video = VideoModel.build(videoData) as MVideoThumbnail
521 514
522 const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) 515 const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
523 516
524 let thumbnailModel: MThumbnail 517 let thumbnailModel: MThumbnail
525 if (waitThumbnail === true) { 518 if (waitThumbnail === true) {
@@ -534,9 +527,12 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
534 527
535 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) 528 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
536 529
537 // FIXME: use icon URL instead 530 const previewIcon = getPreviewFromIcons(videoObject)
538 const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) 531 const previewUrl = previewIcon
539 const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) 532 ? previewIcon.url
533 : buildRemoteVideoBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName()))
534 const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
535
540 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) 536 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
541 537
542 // Process files 538 // Process files
@@ -567,7 +563,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
567 563
568 // Process captions 564 // Process captions
569 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { 565 const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
570 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) 566 return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t)
571 }) 567 })
572 await Promise.all(videoCaptionsPromises) 568 await Promise.all(videoCaptionsPromises)
573 569
@@ -592,13 +588,13 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc
592 thumbnailModel = videoCreated.id 588 thumbnailModel = videoCreated.id
593 589
594 return thumbnailModel.save() 590 return thumbnailModel.save()
595 }) 591 }).catch(err => logger.error('Cannot create miniature from url.', { err }))
596 } 592 }
597 593
598 return { autoBlacklisted, videoCreated } 594 return { autoBlacklisted, videoCreated }
599} 595}
600 596
601async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { 597function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) {
602 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED 598 const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED
603 const duration = videoObject.duration.replace(/[^\d]+/, '') 599 const duration = videoObject.duration.replace(/[^\d]+/, '')
604 600
@@ -639,7 +635,6 @@ async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, vide
639 createdAt: new Date(videoObject.published), 635 createdAt: new Date(videoObject.published),
640 publishedAt: new Date(videoObject.published), 636 publishedAt: new Date(videoObject.published),
641 originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, 637 originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null,
642 // FIXME: updatedAt does not seems to be considered by Sequelize
643 updatedAt: new Date(videoObject.updated), 638 updatedAt: new Date(videoObject.updated),
644 views: videoObject.views, 639 views: videoObject.views,
645 likes: 0, 640 likes: 0,
@@ -672,7 +667,7 @@ function videoFileActivityUrlToDBAttributes (
672 667
673 const mediaType = fileUrl.mediaType 668 const mediaType = fileUrl.mediaType
674 const attribute = { 669 const attribute = {
675 extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ], 670 extname: MIMETYPES.VIDEO.MIMETYPE_EXT[mediaType],
676 infoHash: parsed.infoHash, 671 infoHash: parsed.infoHash,
677 resolution: fileUrl.height, 672 resolution: fileUrl.height,
678 size: fileUrl.size, 673 size: fileUrl.size,
@@ -722,3 +717,19 @@ function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObjec
722 717
723 return attributes 718 return attributes
724} 719}
720
721function getThumbnailFromIcons (videoObject: VideoTorrentObject) {
722 let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth)
723 // Fallback if there are not valid icons
724 if (validIcons.length === 0) validIcons = videoObject.icon
725
726 return minBy(validIcons, 'width')
727}
728
729function getPreviewFromIcons (videoObject: VideoTorrentObject) {
730 const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth)
731
732 // FIXME: don't put a fallback here for compatibility with PeerTube <2.2
733
734 return maxBy(validIcons, 'width')
735}
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts
index 1d8a08ed0..572bd03bd 100644
--- a/server/lib/client-html.ts
+++ b/server/lib/client-html.ts
@@ -17,7 +17,7 @@ import { MAccountActor, MChannelActor, MVideo } from '../typings/models'
17 17
18export class ClientHtml { 18export class ClientHtml {
19 19
20 private static htmlCache: { [ path: string ]: string } = {} 20 private static htmlCache: { [path: string]: string } = {}
21 21
22 static invalidCache () { 22 static invalidCache () {
23 logger.info('Cleaning HTML cache.') 23 logger.info('Cleaning HTML cache.')
@@ -94,7 +94,7 @@ export class ClientHtml {
94 94
95 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { 95 private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) {
96 const path = ClientHtml.getIndexPath(req, res, paramLang) 96 const path = ClientHtml.getIndexPath(req, res, paramLang)
97 if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ] 97 if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
98 98
99 const buffer = await readFile(path) 99 const buffer = await readFile(path)
100 100
@@ -104,7 +104,7 @@ export class ClientHtml {
104 html = ClientHtml.addCustomCSS(html) 104 html = ClientHtml.addCustomCSS(html)
105 html = await ClientHtml.addAsyncPluginCSS(html) 105 html = await ClientHtml.addAsyncPluginCSS(html)
106 106
107 ClientHtml.htmlCache[ path ] = html 107 ClientHtml.htmlCache[path] = html
108 108
109 return html 109 return html
110 } 110 }
@@ -214,21 +214,21 @@ export class ClientHtml {
214 const schemaTags = { 214 const schemaTags = {
215 '@context': 'http://schema.org', 215 '@context': 'http://schema.org',
216 '@type': 'VideoObject', 216 '@type': 'VideoObject',
217 name: videoNameEscaped, 217 'name': videoNameEscaped,
218 description: videoDescriptionEscaped, 218 'description': videoDescriptionEscaped,
219 thumbnailUrl: previewUrl, 219 'thumbnailUrl': previewUrl,
220 uploadDate: video.createdAt.toISOString(), 220 'uploadDate': video.createdAt.toISOString(),
221 duration: getActivityStreamDuration(video.duration), 221 'duration': getActivityStreamDuration(video.duration),
222 contentUrl: videoUrl, 222 'contentUrl': videoUrl,
223 embedUrl: embedUrl, 223 'embedUrl': embedUrl,
224 interactionCount: video.views 224 'interactionCount': video.views
225 } 225 }
226 226
227 let tagsString = '' 227 let tagsString = ''
228 228
229 // Opengraph 229 // Opengraph
230 Object.keys(openGraphMetaTags).forEach(tagName => { 230 Object.keys(openGraphMetaTags).forEach(tagName => {
231 const tagValue = openGraphMetaTags[ tagName ] 231 const tagValue = openGraphMetaTags[tagName]
232 232
233 tagsString += `<meta property="${tagName}" content="${tagValue}" />` 233 tagsString += `<meta property="${tagName}" content="${tagValue}" />`
234 }) 234 })
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 7484524a4..9ce6186b1 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -32,7 +32,8 @@ class Emailer {
32 private initialized = false 32 private initialized = false
33 private transporter: Transporter 33 private transporter: Transporter
34 34
35 private constructor () {} 35 private constructor () {
36 }
36 37
37 init () { 38 init () {
38 // Already initialized 39 // Already initialized
@@ -97,12 +98,12 @@ class Emailer {
97 const channelName = video.VideoChannel.getDisplayName() 98 const channelName = video.VideoChannel.getDisplayName()
98 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 99 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
99 100
100 const text = `Hi dear user,\n\n` + 101 const text = 'Hi dear user,\n\n' +
101 `Your subscription ${channelName} just published a new video: ${video.name}` + 102 `Your subscription ${channelName} just published a new video: ${video.name}` +
102 `\n\n` + 103 '\n\n' +
103 `You can view it on ${videoUrl} ` + 104 `You can view it on ${videoUrl} ` +
104 `\n\n` + 105 '\n\n' +
105 `Cheers,\n` + 106 'Cheers,\n' +
106 `${CONFIG.EMAIL.BODY.SIGNATURE}` 107 `${CONFIG.EMAIL.BODY.SIGNATURE}`
107 108
108 const emailPayload: EmailPayload = { 109 const emailPayload: EmailPayload = {
@@ -118,10 +119,10 @@ class Emailer {
118 const followerName = actorFollow.ActorFollower.Account.getDisplayName() 119 const followerName = actorFollow.ActorFollower.Account.getDisplayName()
119 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() 120 const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
120 121
121 const text = `Hi dear user,\n\n` + 122 const text = 'Hi dear user,\n\n' +
122 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + 123 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
123 `\n\n` + 124 '\n\n' +
124 `Cheers,\n` + 125 'Cheers,\n' +
125 `${CONFIG.EMAIL.BODY.SIGNATURE}` 126 `${CONFIG.EMAIL.BODY.SIGNATURE}`
126 127
127 const emailPayload: EmailPayload = { 128 const emailPayload: EmailPayload = {
@@ -136,10 +137,10 @@ class Emailer {
136 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { 137 addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) {
137 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' 138 const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : ''
138 139
139 const text = `Hi dear admin,\n\n` + 140 const text = 'Hi dear admin,\n\n' +
140 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + 141 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
141 `\n\n` + 142 '\n\n' +
142 `Cheers,\n` + 143 'Cheers,\n' +
143 `${CONFIG.EMAIL.BODY.SIGNATURE}` 144 `${CONFIG.EMAIL.BODY.SIGNATURE}`
144 145
145 const emailPayload: EmailPayload = { 146 const emailPayload: EmailPayload = {
@@ -152,10 +153,10 @@ class Emailer {
152 } 153 }
153 154
154 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { 155 addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) {
155 const text = `Hi dear admin,\n\n` + 156 const text = 'Hi dear admin,\n\n' +
156 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + 157 `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` +
157 `\n\n` + 158 '\n\n' +
158 `Cheers,\n` + 159 'Cheers,\n' +
159 `${CONFIG.EMAIL.BODY.SIGNATURE}` 160 `${CONFIG.EMAIL.BODY.SIGNATURE}`
160 161
161 const emailPayload: EmailPayload = { 162 const emailPayload: EmailPayload = {
@@ -170,12 +171,12 @@ class Emailer {
170 myVideoPublishedNotification (to: string[], video: MVideo) { 171 myVideoPublishedNotification (to: string[], video: MVideo) {
171 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() 172 const videoUrl = WEBSERVER.URL + video.getWatchStaticPath()
172 173
173 const text = `Hi dear user,\n\n` + 174 const text = 'Hi dear user,\n\n' +
174 `Your video ${video.name} has been published.` + 175 `Your video ${video.name} has been published.` +
175 `\n\n` + 176 '\n\n' +
176 `You can view it on ${videoUrl} ` + 177 `You can view it on ${videoUrl} ` +
177 `\n\n` + 178 '\n\n' +
178 `Cheers,\n` + 179 'Cheers,\n' +
179 `${CONFIG.EMAIL.BODY.SIGNATURE}` 180 `${CONFIG.EMAIL.BODY.SIGNATURE}`
180 181
181 const emailPayload: EmailPayload = { 182 const emailPayload: EmailPayload = {
@@ -190,12 +191,12 @@ class Emailer {
190 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { 191 myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) {
191 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() 192 const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
192 193
193 const text = `Hi dear user,\n\n` + 194 const text = 'Hi dear user,\n\n' +
194 `Your video import ${videoImport.getTargetIdentifier()} is finished.` + 195 `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
195 `\n\n` + 196 '\n\n' +
196 `You can view the imported video on ${videoUrl} ` + 197 `You can view the imported video on ${videoUrl} ` +
197 `\n\n` + 198 '\n\n' +
198 `Cheers,\n` + 199 'Cheers,\n' +
199 `${CONFIG.EMAIL.BODY.SIGNATURE}` 200 `${CONFIG.EMAIL.BODY.SIGNATURE}`
200 201
201 const emailPayload: EmailPayload = { 202 const emailPayload: EmailPayload = {
@@ -210,12 +211,12 @@ class Emailer {
210 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { 211 myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) {
211 const importUrl = WEBSERVER.URL + '/my-account/video-imports' 212 const importUrl = WEBSERVER.URL + '/my-account/video-imports'
212 213
213 const text = `Hi dear user,\n\n` + 214 const text = 'Hi dear user,\n\n' +
214 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + 215 `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
215 `\n\n` + 216 '\n\n' +
216 `See your videos import dashboard for more information: ${importUrl}` + 217 `See your videos import dashboard for more information: ${importUrl}` +
217 `\n\n` + 218 '\n\n' +
218 `Cheers,\n` + 219 'Cheers,\n' +
219 `${CONFIG.EMAIL.BODY.SIGNATURE}` 220 `${CONFIG.EMAIL.BODY.SIGNATURE}`
220 221
221 const emailPayload: EmailPayload = { 222 const emailPayload: EmailPayload = {
@@ -232,12 +233,12 @@ class Emailer {
232 const video = comment.Video 233 const video = comment.Video
233 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 234 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
234 235
235 const text = `Hi dear user,\n\n` + 236 const text = 'Hi dear user,\n\n' +
236 `A new comment has been posted by ${accountName} on your video ${video.name}` + 237 `A new comment has been posted by ${accountName} on your video ${video.name}` +
237 `\n\n` + 238 '\n\n' +
238 `You can view it on ${commentUrl} ` + 239 `You can view it on ${commentUrl} ` +
239 `\n\n` + 240 '\n\n' +
240 `Cheers,\n` + 241 'Cheers,\n' +
241 `${CONFIG.EMAIL.BODY.SIGNATURE}` 242 `${CONFIG.EMAIL.BODY.SIGNATURE}`
242 243
243 const emailPayload: EmailPayload = { 244 const emailPayload: EmailPayload = {
@@ -254,12 +255,12 @@ class Emailer {
254 const video = comment.Video 255 const video = comment.Video
255 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() 256 const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath()
256 257
257 const text = `Hi dear user,\n\n` + 258 const text = 'Hi dear user,\n\n' +
258 `${accountName} mentioned you on video ${video.name}` + 259 `${accountName} mentioned you on video ${video.name}` +
259 `\n\n` + 260 '\n\n' +
260 `You can view the comment on ${commentUrl} ` + 261 `You can view the comment on ${commentUrl} ` +
261 `\n\n` + 262 '\n\n' +
262 `Cheers,\n` + 263 'Cheers,\n' +
263 `${CONFIG.EMAIL.BODY.SIGNATURE}` 264 `${CONFIG.EMAIL.BODY.SIGNATURE}`
264 265
265 const emailPayload: EmailPayload = { 266 const emailPayload: EmailPayload = {
@@ -274,9 +275,9 @@ class Emailer {
274 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { 275 addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) {
275 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() 276 const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
276 277
277 const text = `Hi,\n\n` + 278 const text = 'Hi,\n\n' +
278 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + 279 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
279 `Cheers,\n` + 280 'Cheers,\n' +
280 `${CONFIG.EMAIL.BODY.SIGNATURE}` 281 `${CONFIG.EMAIL.BODY.SIGNATURE}`
281 282
282 const emailPayload: EmailPayload = { 283 const emailPayload: EmailPayload = {
@@ -292,14 +293,14 @@ class Emailer {
292 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' 293 const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list'
293 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() 294 const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
294 295
295 const text = `Hi,\n\n` + 296 const text = 'Hi,\n\n' +
296 `A recently added video was auto-blacklisted and requires moderator review before publishing.` + 297 'A recently added video was auto-blacklisted and requires moderator review before publishing.' +
297 `\n\n` + 298 '\n\n' +
298 `You can view it and take appropriate action on ${videoUrl}` + 299 `You can view it and take appropriate action on ${videoUrl}` +
299 `\n\n` + 300 '\n\n' +
300 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + 301 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
301 `\n\n` + 302 '\n\n' +
302 `Cheers,\n` + 303 'Cheers,\n' +
303 `${CONFIG.EMAIL.BODY.SIGNATURE}` 304 `${CONFIG.EMAIL.BODY.SIGNATURE}`
304 305
305 const emailPayload: EmailPayload = { 306 const emailPayload: EmailPayload = {
@@ -312,9 +313,9 @@ class Emailer {
312 } 313 }
313 314
314 addNewUserRegistrationNotification (to: string[], user: MUser) { 315 addNewUserRegistrationNotification (to: string[], user: MUser) {
315 const text = `Hi,\n\n` + 316 const text = 'Hi,\n\n' +
316 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + 317 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
317 `Cheers,\n` + 318 'Cheers,\n' +
318 `${CONFIG.EMAIL.BODY.SIGNATURE}` 319 `${CONFIG.EMAIL.BODY.SIGNATURE}`
319 320
320 const emailPayload: EmailPayload = { 321 const emailPayload: EmailPayload = {
@@ -367,11 +368,11 @@ class Emailer {
367 } 368 }
368 369
369 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { 370 addPasswordResetEmailJob (to: string, resetPasswordUrl: string) {
370 const text = `Hi dear user,\n\n` + 371 const text = 'Hi dear user,\n\n' +
371 `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + 372 `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` +
372 `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + 373 `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` +
373 `If you are not the person who initiated this request, please ignore this email.\n\n` + 374 'If you are not the person who initiated this request, please ignore this email.\n\n' +
374 `Cheers,\n` + 375 'Cheers,\n' +
375 `${CONFIG.EMAIL.BODY.SIGNATURE}` 376 `${CONFIG.EMAIL.BODY.SIGNATURE}`
376 377
377 const emailPayload: EmailPayload = { 378 const emailPayload: EmailPayload = {
@@ -384,11 +385,11 @@ class Emailer {
384 } 385 }
385 386
386 addVerifyEmailJob (to: string, verifyEmailUrl: string) { 387 addVerifyEmailJob (to: string, verifyEmailUrl: string) {
387 const text = `Welcome to PeerTube,\n\n` + 388 const text = 'Welcome to PeerTube,\n\n' +
388 `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + 389 `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` +
389 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + 390 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
390 `If you are not the person who initiated this request, please ignore this email.\n\n` + 391 'If you are not the person who initiated this request, please ignore this email.\n\n' +
391 `Cheers,\n` + 392 'Cheers,\n' +
392 `${CONFIG.EMAIL.BODY.SIGNATURE}` 393 `${CONFIG.EMAIL.BODY.SIGNATURE}`
393 394
394 const emailPayload: EmailPayload = { 395 const emailPayload: EmailPayload = {
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts
index 440c3fde8..26ab3bd0d 100644
--- a/server/lib/files-cache/videos-caption-cache.ts
+++ b/server/lib/files-cache/videos-caption-cache.ts
@@ -5,7 +5,7 @@ import { VideoCaptionModel } from '../../models/video/video-caption'
5import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' 5import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
6import { CONFIG } from '../../initializers/config' 6import { CONFIG } from '../../initializers/config'
7import { logger } from '../../helpers/logger' 7import { logger } from '../../helpers/logger'
8import { fetchRemoteVideoStaticFile } from '../activitypub' 8import { doRequestAndSaveToFile } from '@server/helpers/requests'
9 9
10type GetPathParam = { videoId: string, language: string } 10type GetPathParam = { videoId: string, language: string }
11 11
@@ -46,11 +46,10 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
46 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) 46 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
47 if (!video) return undefined 47 if (!video) return undefined
48 48
49 // FIXME: use URL 49 const remoteUrl = videoCaption.getFileUrl(video)
50 const remoteStaticPath = videoCaption.getCaptionStaticPath()
51 const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) 50 const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
52 51
53 await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) 52 await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
54 53
55 return { isOwned: false, path: destPath } 54 return { isOwned: false, path: destPath }
56 } 55 }
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts
index 3da6bb138..d0d4fc5b5 100644
--- a/server/lib/files-cache/videos-preview-cache.ts
+++ b/server/lib/files-cache/videos-preview-cache.ts
@@ -1,9 +1,8 @@
1import { join } from 'path' 1import { join } from 'path'
2import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' 2import { FILES_CACHE } from '../../initializers/constants'
3import { VideoModel } from '../../models/video/video' 3import { VideoModel } from '../../models/video/video'
4import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' 4import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
5import { CONFIG } from '../../initializers/config' 5import { doRequestAndSaveToFile } from '@server/helpers/requests'
6import { fetchRemoteVideoStaticFile } from '../activitypub'
7 6
8class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { 7class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
9 8
@@ -32,11 +31,11 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
32 31
33 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') 32 if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.')
34 33
35 // FIXME: use URL 34 const preview = video.getPreview()
36 const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) 35 const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename)
37 const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename)
38 36
39 await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) 37 const remoteUrl = preview.getFileUrl(video)
38 await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
40 39
41 return { isOwned: false, path: destPath } 40 return { isOwned: false, path: destPath }
42 } 41 }
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
index 0ff7b44a0..7d9dd61e9 100644
--- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts
@@ -5,11 +5,13 @@ import { doRequest } from '../../../helpers/requests'
5import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' 5import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
6import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' 6import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants'
7import { ActorFollowScoreCache } from '../../files-cache' 7import { ActorFollowScoreCache } from '../../files-cache'
8import { ContextType } from '@server/helpers/activitypub'
8 9
9export type ActivitypubHttpBroadcastPayload = { 10export type ActivitypubHttpBroadcastPayload = {
10 uris: string[] 11 uris: string[]
11 signatureActorId?: number 12 signatureActorId?: number
12 body: any 13 body: any
14 contextType?: ContextType
13} 15}
14 16
15async function processActivityPubHttpBroadcast (job: Bull.Job) { 17async function processActivityPubHttpBroadcast (job: Bull.Job) {
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
index c70ce3be9..6b71e2891 100644
--- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts
+++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts
@@ -4,11 +4,13 @@ import { doRequest } from '../../../helpers/requests'
4import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' 4import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
5import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' 5import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants'
6import { ActorFollowScoreCache } from '../../files-cache' 6import { ActorFollowScoreCache } from '../../files-cache'
7import { ContextType } from '@server/helpers/activitypub'
7 8
8export type ActivitypubHttpUnicastPayload = { 9export type ActivitypubHttpUnicastPayload = {
9 uri: string 10 uri: string
10 signatureActorId?: number 11 signatureActorId?: number
11 body: any 12 body: any
13 contextType?: ContextType
12} 14}
13 15
14async function processActivityPubHttpUnicast (job: Bull.Job) { 16async function processActivityPubHttpUnicast (job: Bull.Job) {
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
index d3bde6e6a..54b35840d 100644
--- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
+++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts
@@ -1,11 +1,11 @@
1import { buildSignedActivity } from '../../../../helpers/activitypub' 1import { buildSignedActivity, ContextType } from '../../../../helpers/activitypub'
2import { getServerActor } from '../../../../helpers/utils' 2import { getServerActor } from '../../../../helpers/utils'
3import { ActorModel } from '../../../../models/activitypub/actor' 3import { ActorModel } from '../../../../models/activitypub/actor'
4import { sha256 } from '../../../../helpers/core-utils' 4import { sha256 } from '../../../../helpers/core-utils'
5import { HTTP_SIGNATURE } from '../../../../initializers/constants' 5import { HTTP_SIGNATURE } from '../../../../initializers/constants'
6import { MActor } from '../../../../typings/models' 6import { MActor } from '../../../../typings/models'
7 7
8type Payload = { body: any, signatureActorId?: number } 8type Payload = { body: any, contextType?: ContextType, signatureActorId?: number }
9 9
10async function computeBody (payload: Payload) { 10async function computeBody (payload: Payload) {
11 let body = payload.body 11 let body = payload.body
@@ -13,7 +13,7 @@ async function computeBody (payload: Payload) {
13 if (payload.signatureActorId) { 13 if (payload.signatureActorId) {
14 const actorSignature = await ActorModel.load(payload.signatureActorId) 14 const actorSignature = await ActorModel.load(payload.signatureActorId)
15 if (!actorSignature) throw new Error('Unknown signature actor id.') 15 if (!actorSignature) throw new Error('Unknown signature actor id.')
16 body = await buildSignedActivity(actorSignature, payload.body) 16 body = await buildSignedActivity(actorSignature, payload.body, payload.contextType)
17 } 17 }
18 18
19 return body 19 return body
@@ -42,7 +42,7 @@ async function buildSignedRequestOptions (payload: Payload) {
42 42
43function buildGlobalHeaders (body: any) { 43function buildGlobalHeaders (body: any) {
44 return { 44 return {
45 'Digest': buildDigest(body) 45 Digest: buildDigest(body)
46 } 46 }
47} 47}
48 48
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 99c991e72..be9e7d181 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -11,7 +11,7 @@ import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
11import { getVideoFilePath } from '@server/lib/video-paths' 11import { getVideoFilePath } from '@server/lib/video-paths'
12 12
13export type VideoFileImportPayload = { 13export type VideoFileImportPayload = {
14 videoUUID: string, 14 videoUUID: string
15 filePath: string 15 filePath: string
16} 16}
17 17
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 1fca17584..09f225cec 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -221,7 +221,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
221 isNewVideo: true 221 isNewVideo: true
222 } 222 }
223 223
224 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 224 await JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput })
225 } 225 }
226 226
227 } catch (err) { 227 } catch (err) {
diff --git a/server/lib/job-queue/handlers/video-redundancy.ts b/server/lib/job-queue/handlers/video-redundancy.ts
new file mode 100644
index 000000000..319d7090e
--- /dev/null
+++ b/server/lib/job-queue/handlers/video-redundancy.ts
@@ -0,0 +1,20 @@
1import * as Bull from 'bull'
2import { logger } from '../../../helpers/logger'
3import { VideosRedundancyScheduler } from '@server/lib/schedulers/videos-redundancy-scheduler'
4
5export type VideoRedundancyPayload = {
6 videoId: number
7}
8
9async function processVideoRedundancy (job: Bull.Job) {
10 const payload = job.data as VideoRedundancyPayload
11 logger.info('Processing video redundancy in job %d.', job.id)
12
13 return VideosRedundancyScheduler.Instance.createManualRedundancy(payload.videoId)
14}
15
16// ---------------------------------------------------------------------------
17
18export {
19 processVideoRedundancy
20}
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 39b9fac98..c020057c9 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -6,7 +6,6 @@ import { JobQueue } from '../job-queue'
6import { federateVideoIfNeeded } from '../../activitypub' 6import { federateVideoIfNeeded } from '../../activitypub'
7import { retryTransactionWrapper } from '../../../helpers/database-utils' 7import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { sequelizeTypescript } from '../../../initializers' 8import { sequelizeTypescript } from '../../../initializers'
9import * as Bluebird from 'bluebird'
10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' 9import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' 10import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 11import { Notifier } from '../../notifier'
@@ -40,8 +39,11 @@ interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
40 type: 'optimize' 39 type: 'optimize'
41} 40}
42 41
43export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload 42export type VideoTranscodingPayload =
44 | OptimizeTranscodingPayload | MergeAudioTranscodingPayload 43 HLSTranscodingPayload
44 | NewResolutionTranscodingPayload
45 | OptimizeTranscodingPayload
46 | MergeAudioTranscodingPayload
45 47
46async function processVideoTranscoding (job: Bull.Job) { 48async function processVideoTranscoding (job: Bull.Job) {
47 const payload = job.data as VideoTranscodingPayload 49 const payload = job.data as VideoTranscodingPayload
@@ -105,7 +107,7 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
105 107
106 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 108 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
107 // Maybe the video changed in database, refresh it 109 // Maybe the video changed in database, refresh it
108 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) 110 const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
109 // Video does not exist anymore 111 // Video does not exist anymore
110 if (!videoDatabase) return undefined 112 if (!videoDatabase) return undefined
111 113
@@ -122,8 +124,6 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
122 await createHlsJobIfEnabled(hlsPayload) 124 await createHlsJobIfEnabled(hlsPayload)
123 125
124 if (resolutionsEnabled.length !== 0) { 126 if (resolutionsEnabled.length !== 0) {
125 const tasks: (Bluebird<Bull.Job<any>> | Promise<Bull.Job<any>>)[] = []
126
127 for (const resolution of resolutionsEnabled) { 127 for (const resolution of resolutionsEnabled) {
128 let dataInput: VideoTranscodingPayload 128 let dataInput: VideoTranscodingPayload
129 129
@@ -143,12 +143,9 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O
143 } 143 }
144 } 144 }
145 145
146 const p = JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 146 JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
147 tasks.push(p)
148 } 147 }
149 148
150 await Promise.all(tasks)
151
152 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) 149 logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
153 } else { 150 } else {
154 // No transcoding to do, it's now published 151 // No transcoding to do, it's now published
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts
index 73fa5ed04..2258cd029 100644
--- a/server/lib/job-queue/handlers/video-views.ts
+++ b/server/lib/job-queue/handlers/video-views.ts
@@ -23,6 +23,8 @@ async function processVideosViews () {
23 for (const videoId of videoIds) { 23 for (const videoId of videoIds) {
24 try { 24 try {
25 const views = await Redis.Instance.getVideoViews(videoId, hour) 25 const views = await Redis.Instance.getVideoViews(videoId, hour)
26 await Redis.Instance.deleteVideoViews(videoId, hour)
27
26 if (views) { 28 if (views) {
27 logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) 29 logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour)
28 30
@@ -52,8 +54,6 @@ async function processVideosViews () {
52 logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) 54 logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err })
53 } 55 }
54 } 56 }
55
56 await Redis.Instance.deleteVideoViews(videoId, hour)
57 } catch (err) { 57 } catch (err) {
58 logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) 58 logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err })
59 } 59 }
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts
index ec601e9ea..14acace7d 100644
--- a/server/lib/job-queue/job-queue.ts
+++ b/server/lib/job-queue/job-queue.ts
@@ -13,6 +13,7 @@ import { processVideoImport, VideoImportPayload } from './handlers/video-import'
13import { processVideosViews } from './handlers/video-views' 13import { processVideosViews } from './handlers/video-views'
14import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' 14import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher'
15import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' 15import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import'
16import { processVideoRedundancy, VideoRedundancyPayload } from '@server/lib/job-queue/handlers/video-redundancy'
16 17
17type CreateJobArgument = 18type CreateJobArgument =
18 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | 19 { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } |
@@ -24,20 +25,21 @@ type CreateJobArgument =
24 { type: 'email', payload: EmailPayload } | 25 { type: 'email', payload: EmailPayload } |
25 { type: 'video-import', payload: VideoImportPayload } | 26 { type: 'video-import', payload: VideoImportPayload } |
26 { type: 'activitypub-refresher', payload: RefreshPayload } | 27 { type: 'activitypub-refresher', payload: RefreshPayload } |
27 { type: 'videos-views', payload: {} } 28 { type: 'videos-views', payload: {} } |
29 { type: 'video-redundancy', payload: VideoRedundancyPayload }
28 30
29const handlers: { [ id in (JobType | 'video-file') ]: (job: Bull.Job) => Promise<any>} = { 31const handlers: { [id in JobType]: (job: Bull.Job) => Promise<any> } = {
30 'activitypub-http-broadcast': processActivityPubHttpBroadcast, 32 'activitypub-http-broadcast': processActivityPubHttpBroadcast,
31 'activitypub-http-unicast': processActivityPubHttpUnicast, 33 'activitypub-http-unicast': processActivityPubHttpUnicast,
32 'activitypub-http-fetcher': processActivityPubHttpFetcher, 34 'activitypub-http-fetcher': processActivityPubHttpFetcher,
33 'activitypub-follow': processActivityPubFollow, 35 'activitypub-follow': processActivityPubFollow,
34 'video-file-import': processVideoFileImport, 36 'video-file-import': processVideoFileImport,
35 'video-transcoding': processVideoTranscoding, 37 'video-transcoding': processVideoTranscoding,
36 'video-file': processVideoTranscoding, // TODO: remove it (changed in 1.3)
37 'email': processEmail, 38 'email': processEmail,
38 'video-import': processVideoImport, 39 'video-import': processVideoImport,
39 'videos-views': processVideosViews, 40 'videos-views': processVideosViews,
40 'activitypub-refresher': refreshAPObject 41 'activitypub-refresher': refreshAPObject,
42 'video-redundancy': processVideoRedundancy
41} 43}
42 44
43const jobTypes: JobType[] = [ 45const jobTypes: JobType[] = [
@@ -50,20 +52,22 @@ const jobTypes: JobType[] = [
50 'video-file-import', 52 'video-file-import',
51 'video-import', 53 'video-import',
52 'videos-views', 54 'videos-views',
53 'activitypub-refresher' 55 'activitypub-refresher',
56 'video-redundancy'
54] 57]
55 58
56class JobQueue { 59class JobQueue {
57 60
58 private static instance: JobQueue 61 private static instance: JobQueue
59 62
60 private queues: { [ id in JobType ]?: Bull.Queue } = {} 63 private queues: { [id in JobType]?: Bull.Queue } = {}
61 private initialized = false 64 private initialized = false
62 private jobRedisPrefix: string 65 private jobRedisPrefix: string
63 66
64 private constructor () {} 67 private constructor () {
68 }
65 69
66 async init () { 70 init () {
67 // Already initialized 71 // Already initialized
68 if (this.initialized === true) return 72 if (this.initialized === true) return
69 this.initialized = true 73 this.initialized = true
@@ -105,11 +109,16 @@ class JobQueue {
105 } 109 }
106 } 110 }
107 111
108 createJob (obj: CreateJobArgument) { 112 createJob (obj: CreateJobArgument): void {
113 this.createJobWithPromise(obj)
114 .catch(err => logger.error('Cannot create job.', { err, obj }))
115 }
116
117 createJobWithPromise (obj: CreateJobArgument) {
109 const queue = this.queues[obj.type] 118 const queue = this.queues[obj.type]
110 if (queue === undefined) { 119 if (queue === undefined) {
111 logger.error('Unknown queue %s: cannot create job.', obj.type) 120 logger.error('Unknown queue %s: cannot create job.', obj.type)
112 throw Error('Unknown queue, cannot create job') 121 return
113 } 122 }
114 123
115 const jobArgs: Bull.JobOptions = { 124 const jobArgs: Bull.JobOptions = {
@@ -122,10 +131,10 @@ class JobQueue {
122 } 131 }
123 132
124 async listForApi (options: { 133 async listForApi (options: {
125 state: JobState, 134 state: JobState
126 start: number, 135 start: number
127 count: number, 136 count: number
128 asc?: boolean, 137 asc?: boolean
129 jobType: JobType 138 jobType: JobType
130 }): Promise<Bull.Job[]> { 139 }): Promise<Bull.Job[]> {
131 const { state, start, count, asc, jobType } = options 140 const { state, start, count, asc, jobType } = options
@@ -133,16 +142,14 @@ class JobQueue {
133 142
134 const filteredJobTypes = this.filterJobTypes(jobType) 143 const filteredJobTypes = this.filterJobTypes(jobType)
135 144
136 // TODO: optimize
137 for (const jobType of filteredJobTypes) { 145 for (const jobType of filteredJobTypes) {
138 const queue = this.queues[ jobType ] 146 const queue = this.queues[jobType]
139 if (queue === undefined) { 147 if (queue === undefined) {
140 logger.error('Unknown queue %s to list jobs.', jobType) 148 logger.error('Unknown queue %s to list jobs.', jobType)
141 continue 149 continue
142 } 150 }
143 151
144 // FIXME: Bull queue typings does not have getJobs method 152 const jobs = await queue.getJobs([ state ], 0, start + count, asc)
145 const jobs = await (queue as any).getJobs(state, 0, start + count, asc)
146 results = results.concat(jobs) 153 results = results.concat(jobs)
147 } 154 }
148 155
@@ -164,7 +171,7 @@ class JobQueue {
164 const filteredJobTypes = this.filterJobTypes(jobType) 171 const filteredJobTypes = this.filterJobTypes(jobType)
165 172
166 for (const type of filteredJobTypes) { 173 for (const type of filteredJobTypes) {
167 const queue = this.queues[ type ] 174 const queue = this.queues[type]
168 if (queue === undefined) { 175 if (queue === undefined) {
169 logger.error('Unknown queue %s to count jobs.', type) 176 logger.error('Unknown queue %s to count jobs.', type)
170 continue 177 continue
@@ -172,7 +179,7 @@ class JobQueue {
172 179
173 const counts = await queue.getJobCounts() 180 const counts = await queue.getJobCounts()
174 181
175 total += counts[ state ] 182 total += counts[state]
176 } 183 }
177 184
178 return total 185 return total
@@ -188,7 +195,7 @@ class JobQueue {
188 private addRepeatableJobs () { 195 private addRepeatableJobs () {
189 this.queues['videos-views'].add({}, { 196 this.queues['videos-views'].add({}, {
190 repeat: REPEAT_JOBS['videos-views'] 197 repeat: REPEAT_JOBS['videos-views']
191 }) 198 }).catch(err => logger.error('Cannot add repeatable job.', { err }))
192 } 199 }
193 200
194 private filterJobTypes (jobType?: JobType) { 201 private filterJobTypes (jobType?: JobType) {
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts
index b609f4585..55f7a985d 100644
--- a/server/lib/moderation.ts
+++ b/server/lib/moderation.ts
@@ -15,41 +15,41 @@ export type AcceptResult = {
15 15
16// Can be filtered by plugins 16// Can be filtered by plugins
17function isLocalVideoAccepted (object: { 17function isLocalVideoAccepted (object: {
18 videoBody: VideoCreate, 18 videoBody: VideoCreate
19 videoFile: Express.Multer.File & { duration?: number }, 19 videoFile: Express.Multer.File & { duration?: number }
20 user: UserModel 20 user: UserModel
21}): AcceptResult { 21}): AcceptResult {
22 return { accepted: true } 22 return { accepted: true }
23} 23}
24 24
25function isLocalVideoThreadAccepted (_object: { 25function isLocalVideoThreadAccepted (_object: {
26 commentBody: VideoCommentCreate, 26 commentBody: VideoCommentCreate
27 video: VideoModel, 27 video: VideoModel
28 user: UserModel 28 user: UserModel
29}): AcceptResult { 29}): AcceptResult {
30 return { accepted: true } 30 return { accepted: true }
31} 31}
32 32
33function isLocalVideoCommentReplyAccepted (_object: { 33function isLocalVideoCommentReplyAccepted (_object: {
34 commentBody: VideoCommentCreate, 34 commentBody: VideoCommentCreate
35 parentComment: VideoCommentModel, 35 parentComment: VideoCommentModel
36 video: VideoModel, 36 video: VideoModel
37 user: UserModel 37 user: UserModel
38}): AcceptResult { 38}): AcceptResult {
39 return { accepted: true } 39 return { accepted: true }
40} 40}
41 41
42function isRemoteVideoAccepted (_object: { 42function isRemoteVideoAccepted (_object: {
43 activity: ActivityCreate, 43 activity: ActivityCreate
44 videoAP: VideoTorrentObject, 44 videoAP: VideoTorrentObject
45 byActor: ActorModel 45 byActor: ActorModel
46}): AcceptResult { 46}): AcceptResult {
47 return { accepted: true } 47 return { accepted: true }
48} 48}
49 49
50function isRemoteVideoCommentAccepted (_object: { 50function isRemoteVideoCommentAccepted (_object: {
51 activity: ActivityCreate, 51 activity: ActivityCreate
52 commentAP: VideoCommentObject, 52 commentAP: VideoCommentObject
53 byActor: ActorModel 53 byActor: ActorModel
54}): AcceptResult { 54}): AcceptResult {
55 return { accepted: true } 55 return { accepted: true }
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
index 679b9bcf6..63197eee1 100644
--- a/server/lib/notifier.ts
+++ b/server/lib/notifier.ts
@@ -6,7 +6,6 @@ import { UserModel } from '../models/account/user'
6import { PeerTubeSocket } from './peertube-socket' 6import { PeerTubeSocket } from './peertube-socket'
7import { CONFIG } from '../initializers/config' 7import { CONFIG } from '../initializers/config'
8import { VideoPrivacy, VideoState } from '../../shared/models/videos' 8import { VideoPrivacy, VideoState } from '../../shared/models/videos'
9import * as Bluebird from 'bluebird'
10import { AccountBlocklistModel } from '../models/account/account-blocklist' 9import { AccountBlocklistModel } from '../models/account/account-blocklist'
11import { 10import {
12 MCommentOwnerVideo, 11 MCommentOwnerVideo,
@@ -17,7 +16,8 @@ import {
17 MVideoFullLight 16 MVideoFullLight
18} from '../typings/models/video' 17} from '../typings/models/video'
19import { 18import {
20 MUser, MUserAccount, 19 MUser,
20 MUserAccount,
21 MUserDefault, 21 MUserDefault,
22 MUserNotifSettingAccount, 22 MUserNotifSettingAccount,
23 MUserWithNotificationSetting, 23 MUserWithNotificationSetting,
@@ -32,14 +32,15 @@ class Notifier {
32 32
33 private static instance: Notifier 33 private static instance: Notifier
34 34
35 private constructor () {} 35 private constructor () {
36 }
36 37
37 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { 38 notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void {
38 // Only notify on public and published videos which are not blacklisted 39 // Only notify on public and published videos which are not blacklisted
39 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return 40 if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return
40 41
41 this.notifySubscribersOfNewVideo(video) 42 this.notifySubscribersOfNewVideo(video)
42 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) 43 .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
43 } 44 }
44 45
45 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { 46 notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void {
@@ -63,7 +64,9 @@ class Notifier {
63 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return 64 if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return
64 65
65 this.notifyOwnedVideoHasBeenPublished(video) 66 this.notifyOwnedVideoHasBeenPublished(video)
66 .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length 67 .catch(err => {
68 logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })
69 })
67 } 70 }
68 71
69 notifyOnNewComment (comment: MCommentOwnerVideo): void { 72 notifyOnNewComment (comment: MCommentOwnerVideo): void {
@@ -76,17 +79,17 @@ class Notifier {
76 79
77 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { 80 notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void {
78 this.notifyModeratorsOfNewVideoAbuse(videoAbuse) 81 this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
79 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) 82 .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
80 } 83 }
81 84
82 notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { 85 notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void {
83 this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) 86 this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist)
84 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) 87 .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err }))
85 } 88 }
86 89
87 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { 90 notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void {
88 this.notifyVideoOwnerOfBlacklist(videoBlacklist) 91 this.notifyVideoOwnerOfBlacklist(videoBlacklist)
89 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) 92 .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
90 } 93 }
91 94
92 notifyOnVideoUnblacklist (video: MVideoFullLight): void { 95 notifyOnVideoUnblacklist (video: MVideoFullLight): void {
@@ -96,7 +99,7 @@ class Notifier {
96 99
97 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { 100 notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void {
98 this.notifyOwnerVideoImportIsFinished(videoImport, success) 101 this.notifyOwnerVideoImportIsFinished(videoImport, success)
99 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) 102 .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
100 } 103 }
101 104
102 notifyOnNewUserRegistration (user: MUserDefault): void { 105 notifyOnNewUserRegistration (user: MUserDefault): void {
@@ -106,14 +109,14 @@ class Notifier {
106 109
107 notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { 110 notifyOfNewUserFollow (actorFollow: MActorFollowFull): void {
108 this.notifyUserOfNewActorFollow(actorFollow) 111 this.notifyUserOfNewActorFollow(actorFollow)
109 .catch(err => { 112 .catch(err => {
110 logger.error( 113 logger.error(
111 'Cannot notify owner of channel %s of a new follow by %s.', 114 'Cannot notify owner of channel %s of a new follow by %s.',
112 actorFollow.ActorFollowing.VideoChannel.getDisplayName(), 115 actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
113 actorFollow.ActorFollower.Account.getDisplayName(), 116 actorFollow.ActorFollower.Account.getDisplayName(),
114 { err } 117 { err }
115 ) 118 )
116 }) 119 })
117 } 120 }
118 121
119 notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { 122 notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void {
@@ -548,10 +551,10 @@ class Notifier {
548 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) 551 return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
549 } 552 }
550 553
551 private async notify <T extends MUserWithNotificationSetting> (options: { 554 private async notify<T extends MUserWithNotificationSetting> (options: {
552 users: T[], 555 users: T[]
553 notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, 556 notificationCreator: (user: T) => Promise<UserNotificationModelForApi>
554 emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, 557 emailSender: (emails: string[]) => void
555 settingGetter: (user: T) => UserNotificationSettingValue 558 settingGetter: (user: T) => UserNotificationSettingValue
556 }) { 559 }) {
557 const emails: string[] = [] 560 const emails: string[] = []
@@ -569,7 +572,7 @@ class Notifier {
569 } 572 }
570 573
571 if (emails.length !== 0) { 574 if (emails.length !== 0) {
572 await options.emailSender(emails) 575 options.emailSender(emails)
573 } 576 }
574 } 577 }
575 578
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts
index 25b4f3c61..dcdfba28c 100644
--- a/server/lib/plugins/plugin-index.ts
+++ b/server/lib/plugins/plugin-index.ts
@@ -31,7 +31,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
31 31
32 logger.debug('Got result from PeerTube index.', { body }) 32 logger.debug('Got result from PeerTube index.', { body })
33 33
34 await addInstanceInformation(body) 34 addInstanceInformation(body)
35 35
36 return body as ResultList<PeerTubePluginIndex> 36 return body as ResultList<PeerTubePluginIndex>
37 } catch (err) { 37 } catch (err) {
@@ -40,7 +40,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList)
40 } 40 }
41} 41}
42 42
43async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { 43function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) {
44 for (const d of result.data) { 44 for (const d of result.data) {
45 d.installed = PluginManager.Instance.isRegistered(d.npmName) 45 d.installed = PluginManager.Instance.isRegistered(d.npmName)
46 d.name = PluginModel.normalizePluginName(d.npmName) 46 d.name = PluginModel.normalizePluginName(d.npmName)
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts
index 7ebdabd34..73f7a71ce 100644
--- a/server/lib/plugins/plugin-manager.ts
+++ b/server/lib/plugins/plugin-manager.ts
@@ -55,30 +55,30 @@ export interface HookInformationValue {
55} 55}
56 56
57type AlterableVideoConstant = 'language' | 'licence' | 'category' 57type AlterableVideoConstant = 'language' | 'licence' | 'category'
58type VideoConstant = { [ key in number | string ]: string } 58type VideoConstant = { [key in number | string]: string }
59type UpdatedVideoConstant = { 59type UpdatedVideoConstant = {
60 [ name in AlterableVideoConstant ]: { 60 [name in AlterableVideoConstant]: {
61 [ npmName: string ]: { 61 [npmName: string]: {
62 added: { key: number | string, label: string }[], 62 added: { key: number | string, label: string }[]
63 deleted: { key: number | string, label: string }[] 63 deleted: { key: number | string, label: string }[]
64 } 64 }
65 } 65 }
66} 66}
67 67
68type PluginLocalesTranslations = { 68type PluginLocalesTranslations = {
69 [ locale: string ]: PluginTranslation 69 [locale: string]: PluginTranslation
70} 70}
71 71
72export class PluginManager implements ServerHook { 72export class PluginManager implements ServerHook {
73 73
74 private static instance: PluginManager 74 private static instance: PluginManager
75 75
76 private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} 76 private registeredPlugins: { [name: string]: RegisteredPlugin } = {}
77 private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} 77 private settings: { [name: string]: RegisterServerSettingOptions[] } = {}
78 private hooks: { [ name: string ]: HookInformationValue[] } = {} 78 private hooks: { [name: string]: HookInformationValue[] } = {}
79 private translations: PluginLocalesTranslations = {} 79 private translations: PluginLocalesTranslations = {}
80 80
81 private updatedVideoConstants: UpdatedVideoConstant = { 81 private readonly updatedVideoConstants: UpdatedVideoConstant = {
82 language: {}, 82 language: {},
83 licence: {}, 83 licence: {},
84 category: {} 84 category: {}
@@ -133,7 +133,7 @@ export class PluginManager implements ServerHook {
133 133
134 // ###################### Hooks ###################### 134 // ###################### Hooks ######################
135 135
136 async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { 136 async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
137 if (!this.hooks[hookName]) return Promise.resolve(result) 137 if (!this.hooks[hookName]) return Promise.resolve(result)
138 138
139 const hookType = getHookType(hookName) 139 const hookType = getHookType(hookName)
@@ -312,7 +312,7 @@ export class PluginManager implements ServerHook {
312 clientScripts[c.script] = c 312 clientScripts[c.script] = c
313 } 313 }
314 314
315 this.registeredPlugins[ npmName ] = { 315 this.registeredPlugins[npmName] = {
316 npmName, 316 npmName,
317 name: plugin.name, 317 name: plugin.name,
318 type: plugin.type, 318 type: plugin.type,
@@ -438,7 +438,7 @@ export class PluginManager implements ServerHook {
438 const plugins: RegisteredPlugin[] = [] 438 const plugins: RegisteredPlugin[] = []
439 439
440 for (const npmName of Object.keys(this.registeredPlugins)) { 440 for (const npmName of Object.keys(this.registeredPlugins)) {
441 const plugin = this.registeredPlugins[ npmName ] 441 const plugin = this.registeredPlugins[npmName]
442 if (plugin.type !== type) continue 442 if (plugin.type !== type) continue
443 443
444 plugins.push(plugin) 444 plugins.push(plugin)
@@ -518,11 +518,11 @@ export class PluginManager implements ServerHook {
518 } 518 }
519 } 519 }
520 520
521 private addConstant <T extends string | number> (parameters: { 521 private addConstant<T extends string | number> (parameters: {
522 npmName: string, 522 npmName: string
523 type: AlterableVideoConstant, 523 type: AlterableVideoConstant
524 obj: VideoConstant, 524 obj: VideoConstant
525 key: T, 525 key: T
526 label: string 526 label: string
527 }) { 527 }) {
528 const { npmName, type, obj, key, label } = parameters 528 const { npmName, type, obj, key, label } = parameters
@@ -545,10 +545,10 @@ export class PluginManager implements ServerHook {
545 return true 545 return true
546 } 546 }
547 547
548 private deleteConstant <T extends string | number> (parameters: { 548 private deleteConstant<T extends string | number> (parameters: {
549 npmName: string, 549 npmName: string
550 type: AlterableVideoConstant, 550 type: AlterableVideoConstant
551 obj: VideoConstant, 551 obj: VideoConstant
552 key: T 552 key: T
553 }) { 553 }) {
554 const { npmName, type, obj, key } = parameters 554 const { npmName, type, obj, key } = parameters
@@ -604,7 +604,7 @@ export class PluginManager implements ServerHook {
604 const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) 604 const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType)
605 if (!packageJSONValid) { 605 if (!packageJSONValid) {
606 const formattedFields = badFields.map(f => `"${f}"`) 606 const formattedFields = badFields.map(f => `"${f}"`)
607 .join(', ') 607 .join(', ')
608 608
609 throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) 609 throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`)
610 } 610 }
diff --git a/server/lib/redis.ts b/server/lib/redis.ts
index f77d0b62c..0c5dbdd3e 100644
--- a/server/lib/redis.ts
+++ b/server/lib/redis.ts
@@ -12,7 +12,7 @@ import {
12import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
13 13
14type CachedRoute = { 14type CachedRoute = {
15 body: string, 15 body: string
16 contentType?: string 16 contentType?: string
17 statusCode?: string 17 statusCode?: string
18} 18}
@@ -24,7 +24,8 @@ class Redis {
24 private client: RedisClient 24 private client: RedisClient
25 private prefix: string 25 private prefix: string
26 26
27 private constructor () {} 27 private constructor () {
28 }
28 29
29 init () { 30 init () {
30 // Already initialized 31 // Already initialized
@@ -49,9 +50,9 @@ class Redis {
49 return Object.assign({}, 50 return Object.assign({},
50 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, 51 (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {},
51 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, 52 (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {},
52 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) ? 53 (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT)
53 { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } : 54 ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT }
54 { path: CONFIG.REDIS.SOCKET } 55 : { path: CONFIG.REDIS.SOCKET }
55 ) 56 )
56 } 57 }
57 58
@@ -63,7 +64,7 @@ class Redis {
63 return this.prefix 64 return this.prefix
64 } 65 }
65 66
66 /************* Forgot password *************/ 67 /* ************ Forgot password ************ */
67 68
68 async setResetPasswordVerificationString (userId: number) { 69 async setResetPasswordVerificationString (userId: number) {
69 const generatedString = await generateRandomString(32) 70 const generatedString = await generateRandomString(32)
@@ -77,7 +78,7 @@ class Redis {
77 return this.getValue(this.generateResetPasswordKey(userId)) 78 return this.getValue(this.generateResetPasswordKey(userId))
78 } 79 }
79 80
80 /************* Email verification *************/ 81 /* ************ Email verification ************ */
81 82
82 async setVerifyEmailVerificationString (userId: number) { 83 async setVerifyEmailVerificationString (userId: number) {
83 const generatedString = await generateRandomString(32) 84 const generatedString = await generateRandomString(32)
@@ -91,7 +92,7 @@ class Redis {
91 return this.getValue(this.generateVerifyEmailKey(userId)) 92 return this.getValue(this.generateVerifyEmailKey(userId))
92 } 93 }
93 94
94 /************* Contact form per IP *************/ 95 /* ************ Contact form per IP ************ */
95 96
96 async setContactFormIp (ip: string) { 97 async setContactFormIp (ip: string) {
97 return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) 98 return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME)
@@ -101,7 +102,7 @@ class Redis {
101 return this.exists(this.generateContactFormKey(ip)) 102 return this.exists(this.generateContactFormKey(ip))
102 } 103 }
103 104
104 /************* Views per IP *************/ 105 /* ************ Views per IP ************ */
105 106
106 setIPVideoView (ip: string, videoUUID: string) { 107 setIPVideoView (ip: string, videoUUID: string) {
107 return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) 108 return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME)
@@ -111,7 +112,7 @@ class Redis {
111 return this.exists(this.generateViewKey(ip, videoUUID)) 112 return this.exists(this.generateViewKey(ip, videoUUID))
112 } 113 }
113 114
114 /************* API cache *************/ 115 /* ************ API cache ************ */
115 116
116 async getCachedRoute (req: express.Request) { 117 async getCachedRoute (req: express.Request) {
117 const cached = await this.getObject(this.generateCachedRouteKey(req)) 118 const cached = await this.getObject(this.generateCachedRouteKey(req))
@@ -120,17 +121,17 @@ class Redis {
120 } 121 }
121 122
122 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { 123 setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) {
123 const cached: CachedRoute = Object.assign({}, { 124 const cached: CachedRoute = Object.assign(
124 body: body.toString() 125 {},
125 }, 126 { body: body.toString() },
126 (contentType) ? { contentType } : null, 127 (contentType) ? { contentType } : null,
127 (statusCode) ? { statusCode: statusCode.toString() } : null 128 (statusCode) ? { statusCode: statusCode.toString() } : null
128 ) 129 )
129 130
130 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) 131 return this.setObject(this.generateCachedRouteKey(req), cached, lifetime)
131 } 132 }
132 133
133 /************* Video views *************/ 134 /* ************ Video views ************ */
134 135
135 addVideoView (videoId: number) { 136 addVideoView (videoId: number) {
136 const keyIncr = this.generateVideoViewKey(videoId) 137 const keyIncr = this.generateVideoViewKey(videoId)
@@ -173,7 +174,7 @@ class Redis {
173 ]) 174 ])
174 } 175 }
175 176
176 /************* Keys generation *************/ 177 /* ************ Keys generation ************ */
177 178
178 generateCachedRouteKey (req: express.Request) { 179 generateCachedRouteKey (req: express.Request) {
179 return req.method + '-' + req.originalUrl 180 return req.method + '-' + req.originalUrl
@@ -207,7 +208,7 @@ class Redis {
207 return 'contact-form-' + ip 208 return 'contact-form-' + ip
208 } 209 }
209 210
210 /************* Redis helpers *************/ 211 /* ************ Redis helpers ************ */
211 212
212 private getValue (key: string) { 213 private getValue (key: string) {
213 return new Promise<string>((res, rej) => { 214 return new Promise<string>((res, rej) => {
@@ -265,7 +266,7 @@ class Redis {
265 }) 266 })
266 } 267 }
267 268
268 private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) { 269 private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) {
269 return new Promise<void>((res, rej) => { 270 return new Promise<void>((res, rej) => {
270 this.client.hmset(this.prefix + key, obj, (err, ok) => { 271 this.client.hmset(this.prefix + key, obj, (err, ok) => {
271 if (err) return rej(err) 272 if (err) return rej(err)
@@ -282,7 +283,7 @@ class Redis {
282 } 283 }
283 284
284 private getObject (key: string) { 285 private getObject (key: string) {
285 return new Promise<{ [ id: string ]: string }>((res, rej) => { 286 return new Promise<{ [id: string]: string }>((res, rej) => {
286 this.client.hgetall(this.prefix + key, (err, value) => { 287 this.client.hgetall(this.prefix + key, (err, value) => {
287 if (err) return rej(err) 288 if (err) return rej(err)
288 289
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts
index 1b4ecd7c0..78d84e02e 100644
--- a/server/lib/redundancy.ts
+++ b/server/lib/redundancy.ts
@@ -13,10 +13,10 @@ async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?
13 await videoRedundancy.destroy({ transaction: t }) 13 await videoRedundancy.destroy({ transaction: t })
14} 14}
15 15
16async function removeRedundancyOf (serverId: number) { 16async function removeRedundanciesOfServer (serverId: number) {
17 const videosRedundancy = await VideoRedundancyModel.listLocalOfServer(serverId) 17 const redundancies = await VideoRedundancyModel.listLocalOfServer(serverId)
18 18
19 for (const redundancy of videosRedundancy) { 19 for (const redundancy of redundancies) {
20 await removeVideoRedundancy(redundancy) 20 await removeVideoRedundancy(redundancy)
21 } 21 }
22} 22}
@@ -24,6 +24,6 @@ async function removeRedundancyOf (serverId: number) {
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
25 25
26export { 26export {
27 removeRedundancyOf, 27 removeRedundanciesOfServer,
28 removeVideoRedundancy 28 removeVideoRedundancy
29} 29}
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts
index dd326bc1e..d700a99f0 100644
--- a/server/lib/schedulers/auto-follow-index-instances.ts
+++ b/server/lib/schedulers/auto-follow-index-instances.ts
@@ -57,8 +57,7 @@ export class AutoFollowIndexInstances extends AbstractScheduler {
57 isAutoFollow: true 57 isAutoFollow: true
58 } 58 }
59 59
60 await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) 60 JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
61 .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err))
62 } 61 }
63 } 62 }
64 63
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts
index 7ff41e639..014993e94 100644
--- a/server/lib/schedulers/plugins-check-scheduler.ts
+++ b/server/lib/schedulers/plugins-check-scheduler.ts
@@ -43,7 +43,7 @@ export class PluginsCheckScheduler extends AbstractScheduler {
43 const results = await getLatestPluginsVersion(npmNames) 43 const results = await getLatestPluginsVersion(npmNames)
44 44
45 for (const result of results) { 45 for (const result of results) {
46 const plugin = pluginIndex[ result.npmName ] 46 const plugin = pluginIndex[result.npmName]
47 if (!result.latestVersion) continue 47 if (!result.latestVersion) continue
48 48
49 if ( 49 if (
diff --git a/server/lib/schedulers/remove-old-views-scheduler.ts b/server/lib/schedulers/remove-old-views-scheduler.ts
index 39fbb9163..5ae87fe50 100644
--- a/server/lib/schedulers/remove-old-views-scheduler.ts
+++ b/server/lib/schedulers/remove-old-views-scheduler.ts
@@ -1,9 +1,7 @@
1import { logger } from '../../helpers/logger' 1import { logger } from '../../helpers/logger'
2import { AbstractScheduler } from './abstract-scheduler' 2import { AbstractScheduler } from './abstract-scheduler'
3import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 3import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
4import { UserVideoHistoryModel } from '../../models/account/user-video-history'
5import { CONFIG } from '../../initializers/config' 4import { CONFIG } from '../../initializers/config'
6import { isTestInstance } from '../../helpers/core-utils'
7import { VideoViewModel } from '../../models/video/video-views' 5import { VideoViewModel } from '../../models/video/video-views'
8 6
9export class RemoveOldViewsScheduler extends AbstractScheduler { 7export class RemoveOldViewsScheduler extends AbstractScheduler {
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts
index 350a335d3..956780a77 100644
--- a/server/lib/schedulers/update-videos-scheduler.ts
+++ b/server/lib/schedulers/update-videos-scheduler.ts
@@ -4,7 +4,6 @@ import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-upda
4import { retryTransactionWrapper } from '../../helpers/database-utils' 4import { retryTransactionWrapper } from '../../helpers/database-utils'
5import { federateVideoIfNeeded } from '../activitypub' 5import { federateVideoIfNeeded } from '../activitypub'
6import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' 6import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants'
7import { VideoPrivacy } from '../../../shared/models/videos'
8import { Notifier } from '../notifier' 7import { Notifier } from '../notifier'
9import { sequelizeTypescript } from '../../initializers/database' 8import { sequelizeTypescript } from '../../initializers/database'
10import { MVideoFullLight } from '@server/typings/models' 9import { MVideoFullLight } from '@server/typings/models'
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts
index c1c91b656..e33a4133a 100644
--- a/server/lib/schedulers/videos-redundancy-scheduler.ts
+++ b/server/lib/schedulers/videos-redundancy-scheduler.ts
@@ -1,7 +1,7 @@
1import { AbstractScheduler } from './abstract-scheduler' 1import { AbstractScheduler } from './abstract-scheduler'
2import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' 2import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { VideosRedundancy } from '../../../shared/models/redundancy' 4import { VideosRedundancyStrategy } from '../../../shared/models/redundancy'
5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 5import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
6import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' 6import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent'
7import { join } from 'path' 7import { join } from 'path'
@@ -25,11 +25,12 @@ import {
25 MVideoWithAllFiles 25 MVideoWithAllFiles
26} from '@server/typings/models' 26} from '@server/typings/models'
27import { getVideoFilename } from '../video-paths' 27import { getVideoFilename } from '../video-paths'
28import { VideoModel } from '@server/models/video/video'
28 29
29type CandidateToDuplicate = { 30type CandidateToDuplicate = {
30 redundancy: VideosRedundancy, 31 redundancy: VideosRedundancyStrategy
31 video: MVideoWithAllFiles, 32 video: MVideoWithAllFiles
32 files: MVideoFile[], 33 files: MVideoFile[]
33 streamingPlaylists: MStreamingPlaylistFiles[] 34 streamingPlaylists: MStreamingPlaylistFiles[]
34} 35}
35 36
@@ -41,7 +42,7 @@ function isMVideoRedundancyFileVideo (
41 42
42export class VideosRedundancyScheduler extends AbstractScheduler { 43export class VideosRedundancyScheduler extends AbstractScheduler {
43 44
44 private static instance: AbstractScheduler 45 private static instance: VideosRedundancyScheduler
45 46
46 protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL 47 protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL
47 48
@@ -49,6 +50,22 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
49 super() 50 super()
50 } 51 }
51 52
53 async createManualRedundancy (videoId: number) {
54 const videoToDuplicate = await VideoModel.loadWithFiles(videoId)
55
56 if (!videoToDuplicate) {
57 logger.warn('Video to manually duplicate %d does not exist anymore.', videoId)
58 return
59 }
60
61 return this.createVideoRedundancies({
62 video: videoToDuplicate,
63 redundancy: null,
64 files: videoToDuplicate.VideoFiles,
65 streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists
66 })
67 }
68
52 protected async internalExecute () { 69 protected async internalExecute () {
53 for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { 70 for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) {
54 logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) 71 logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy)
@@ -94,7 +111,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
94 for (const redundancyModel of expired) { 111 for (const redundancyModel of expired) {
95 try { 112 try {
96 const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) 113 const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy)
97 const candidate = { 114 const candidate: CandidateToDuplicate = {
98 redundancy: redundancyConfig, 115 redundancy: redundancyConfig,
99 video: null, 116 video: null,
100 files: [], 117 files: [],
@@ -140,7 +157,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
140 } 157 }
141 } 158 }
142 159
143 private findVideoToDuplicate (cache: VideosRedundancy) { 160 private findVideoToDuplicate (cache: VideosRedundancyStrategy) {
144 if (cache.strategy === 'most-views') { 161 if (cache.strategy === 'most-views') {
145 return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) 162 return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR)
146 } 163 }
@@ -187,13 +204,21 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
187 } 204 }
188 } 205 }
189 206
190 private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { 207 private async createVideoFileRedundancy (redundancy: VideosRedundancyStrategy | null, video: MVideoAccountLight, fileArg: MVideoFile) {
208 let strategy = 'manual'
209 let expiresOn: Date = null
210
211 if (redundancy) {
212 strategy = redundancy.strategy
213 expiresOn = this.buildNewExpiration(redundancy.minLifetime)
214 }
215
191 const file = fileArg as MVideoFileVideo 216 const file = fileArg as MVideoFileVideo
192 file.Video = video 217 file.Video = video
193 218
194 const serverActor = await getServerActor() 219 const serverActor = await getServerActor()
195 220
196 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) 221 logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy)
197 222
198 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() 223 const { baseUrlHttp, baseUrlWs } = video.getBaseUrls()
199 const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) 224 const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs)
@@ -204,10 +229,10 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
204 await move(tmpPath, destPath, { overwrite: true }) 229 await move(tmpPath, destPath, { overwrite: true })
205 230
206 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ 231 const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({
207 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 232 expiresOn,
208 url: getVideoCacheFileActivityPubUrl(file), 233 url: getVideoCacheFileActivityPubUrl(file),
209 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), 234 fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL),
210 strategy: redundancy.strategy, 235 strategy,
211 videoFileId: file.id, 236 videoFileId: file.id,
212 actorId: serverActor.id 237 actorId: serverActor.id
213 }) 238 })
@@ -220,25 +245,33 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
220 } 245 }
221 246
222 private async createStreamingPlaylistRedundancy ( 247 private async createStreamingPlaylistRedundancy (
223 redundancy: VideosRedundancy, 248 redundancy: VideosRedundancyStrategy,
224 video: MVideoAccountLight, 249 video: MVideoAccountLight,
225 playlistArg: MStreamingPlaylist 250 playlistArg: MStreamingPlaylist
226 ) { 251 ) {
252 let strategy = 'manual'
253 let expiresOn: Date = null
254
255 if (redundancy) {
256 strategy = redundancy.strategy
257 expiresOn = this.buildNewExpiration(redundancy.minLifetime)
258 }
259
227 const playlist = playlistArg as MStreamingPlaylistVideo 260 const playlist = playlistArg as MStreamingPlaylistVideo
228 playlist.Video = video 261 playlist.Video = video
229 262
230 const serverActor = await getServerActor() 263 const serverActor = await getServerActor()
231 264
232 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, redundancy.strategy) 265 logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy)
233 266
234 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) 267 const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid)
235 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) 268 await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT)
236 269
237 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ 270 const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({
238 expiresOn: this.buildNewExpiration(redundancy.minLifetime), 271 expiresOn,
239 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), 272 url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist),
240 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), 273 fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL),
241 strategy: redundancy.strategy, 274 strategy,
242 videoStreamingPlaylistId: playlist.id, 275 videoStreamingPlaylistId: playlist.id,
243 actorId: serverActor.id 276 actorId: serverActor.id
244 }) 277 })
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index a99f71629..8dbd41771 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -69,7 +69,7 @@ function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile,
69function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { 69function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
70 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) 70 const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
71 71
72 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 72 const thumbnail = existingThumbnail || new ThumbnailModel()
73 73
74 thumbnail.filename = filename 74 thumbnail.filename = filename
75 thumbnail.height = height 75 thumbnail.height = height
@@ -142,18 +142,18 @@ function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, si
142} 142}
143 143
144async function createThumbnailFromFunction (parameters: { 144async function createThumbnailFromFunction (parameters: {
145 thumbnailCreator: () => Promise<any>, 145 thumbnailCreator: () => Promise<any>
146 filename: string, 146 filename: string
147 height: number, 147 height: number
148 width: number, 148 width: number
149 type: ThumbnailType, 149 type: ThumbnailType
150 automaticallyGenerated?: boolean, 150 automaticallyGenerated?: boolean
151 fileUrl?: string, 151 fileUrl?: string
152 existingThumbnail?: MThumbnail 152 existingThumbnail?: MThumbnail
153}) { 153}) {
154 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters 154 const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
155 155
156 const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() 156 const thumbnail = existingThumbnail || new ThumbnailModel()
157 157
158 thumbnail.filename = filename 158 thumbnail.filename = filename
159 thumbnail.height = height 159 thumbnail.height = height
diff --git a/server/lib/user.ts b/server/lib/user.ts
index c45438d95..88e60a7df 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -18,9 +18,9 @@ import { MUser, MUserDefault, MUserId } from '../typings/models/user'
18type ChannelNames = { name: string, displayName: string } 18type ChannelNames = { name: string, displayName: string }
19 19
20async function createUserAccountAndChannelAndPlaylist (parameters: { 20async function createUserAccountAndChannelAndPlaylist (parameters: {
21 userToCreate: MUser, 21 userToCreate: MUser
22 userDisplayName?: string, 22 userDisplayName?: string
23 channelNames?: ChannelNames, 23 channelNames?: ChannelNames
24 validateUser?: boolean 24 validateUser?: boolean
25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { 25}): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> {
26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters 26 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
@@ -63,11 +63,11 @@ async function createUserAccountAndChannelAndPlaylist (parameters: {
63} 63}
64 64
65async function createLocalAccountWithoutKeys (parameters: { 65async function createLocalAccountWithoutKeys (parameters: {
66 name: string, 66 name: string
67 displayName?: string, 67 displayName?: string
68 userId: number | null, 68 userId: number | null
69 applicationId: number | null, 69 applicationId: number | null
70 t: Transaction | undefined, 70 t: Transaction | undefined
71 type?: ActivityPubActorType 71 type?: ActivityPubActorType
72}) { 72}) {
73 const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters 73 const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts
index 1dd45b76d..3b90b1b94 100644
--- a/server/lib/video-blacklist.ts
+++ b/server/lib/video-blacklist.ts
@@ -9,15 +9,15 @@ import { Notifier } from './notifier'
9import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' 9import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models'
10 10
11async function autoBlacklistVideoIfNeeded (parameters: { 11async function autoBlacklistVideoIfNeeded (parameters: {
12 video: MVideoWithBlacklistLight, 12 video: MVideoWithBlacklistLight
13 user?: MUser, 13 user?: MUser
14 isRemote: boolean, 14 isRemote: boolean
15 isNew: boolean, 15 isNew: boolean
16 notify?: boolean, 16 notify?: boolean
17 transaction?: Transaction 17 transaction?: Transaction
18}) { 18}) {
19 const { video, user, isRemote, isNew, notify = true, transaction } = parameters 19 const { video, user, isRemote, isNew, notify = true, transaction } = parameters
20 const doAutoBlacklist = await Hooks.wrapPromiseFun( 20 const doAutoBlacklist = await Hooks.wrapFun(
21 autoBlacklistNeeded, 21 autoBlacklistNeeded,
22 { video, user, isRemote, isNew }, 22 { video, user, isRemote, isNew },
23 'filter:video.auto-blacklist.result' 23 'filter:video.auto-blacklist.result'
@@ -49,10 +49,10 @@ async function autoBlacklistVideoIfNeeded (parameters: {
49 return true 49 return true
50} 50}
51 51
52async function autoBlacklistNeeded (parameters: { 52function autoBlacklistNeeded (parameters: {
53 video: MVideoWithBlacklistLight, 53 video: MVideoWithBlacklistLight
54 isRemote: boolean, 54 isRemote: boolean
55 isNew: boolean, 55 isNew: boolean
56 user?: MUser 56 user?: MUser
57}) { 57}) {
58 const { user, video, isRemote, isNew } = parameters 58 const { user, video, isRemote, isNew } = parameters
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 41eab456b..14829c9d6 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -6,8 +6,7 @@ import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUr
6import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' 7import { MAccountId, MChannelDefault, MChannelId } from '../typings/models'
8 8
9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & 9type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & { Account?: T }
10 { Account?: T }
11 10
12async function createLocalVideoChannel <T extends MAccountId> ( 11async function createLocalVideoChannel <T extends MAccountId> (
13 videoChannelInfo: VideoChannelCreate, 12 videoChannelInfo: VideoChannelCreate,
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts
index b8074e6d2..fe83d23e7 100644
--- a/server/lib/video-comment.ts
+++ b/server/lib/video-comment.ts
@@ -7,9 +7,9 @@ import { sendCreateVideoComment } from './activitypub/send'
7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' 7import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models'
8 8
9async function createVideoComment (obj: { 9async function createVideoComment (obj: {
10 text: string, 10 text: string
11 inReplyToComment: MComment | null, 11 inReplyToComment: MComment | null
12 video: MVideoFullLight, 12 video: MVideoFullLight
13 account: MAccountDefault 13 account: MAccountDefault
14}, t: Sequelize.Transaction) { 14}, t: Sequelize.Transaction) {
15 let originCommentId: number | null = null 15 let originCommentId: number | null = null
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts
index c6d8466ac..ab7d04d25 100644
--- a/server/middlewares/activitypub.ts
+++ b/server/middlewares/activitypub.ts
@@ -1,10 +1,12 @@
1import { NextFunction, Request, Response } from 'express' 1import { NextFunction, Request, Response } from 'express'
2import { ActivityPubSignature } from '../../shared' 2import { ActivityDelete, ActivityPubSignature } from '../../shared'
3import { logger } from '../helpers/logger' 3import { logger } from '../helpers/logger'
4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' 4import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto'
5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' 5import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants'
6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' 6import { getOrCreateActorAndServerAndModel } from '../lib/activitypub'
7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' 7import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger'
8import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor'
9import { getAPId } from '@server/helpers/activitypub'
8 10
9async function checkSignature (req: Request, res: Response, next: NextFunction) { 11async function checkSignature (req: Request, res: Response, next: NextFunction) {
10 try { 12 try {
@@ -15,7 +17,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
15 17
16 // Forwarded activity 18 // Forwarded activity
17 const bodyActor = req.body.actor 19 const bodyActor = req.body.actor
18 const bodyActorId = bodyActor && bodyActor.id ? bodyActor.id : bodyActor 20 const bodyActorId = getAPId(bodyActor)
19 if (bodyActorId && bodyActorId !== actor.url) { 21 if (bodyActorId && bodyActorId !== actor.url) {
20 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res) 22 const jsonLDSignatureChecked = await checkJsonLDSignature(req, res)
21 if (jsonLDSignatureChecked !== true) return 23 if (jsonLDSignatureChecked !== true) return
@@ -23,7 +25,13 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
23 25
24 return next() 26 return next()
25 } catch (err) { 27 } catch (err) {
26 logger.error('Error in ActivityPub signature checker.', err) 28 const activity: ActivityDelete = req.body
29 if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) {
30 logger.debug('Handling signature error on actor delete activity', { err })
31 return res.sendStatus(204)
32 }
33
34 logger.warn('Error in ActivityPub signature checker.', { err })
27 return res.sendStatus(403) 35 return res.sendStatus(403)
28 } 36 }
29} 37}
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts
index d11d70790..f5de69603 100644
--- a/server/middlewares/csp.ts
+++ b/server/middlewares/csp.ts
@@ -3,20 +3,20 @@ import { CONFIG } from '../initializers/config'
3 3
4const baseDirectives = Object.assign({}, 4const baseDirectives = Object.assign({},
5 { 5 {
6 defaultSrc: ["'none'"], // by default, not specifying default-src = '*' 6 defaultSrc: [ '\'none\'' ], // by default, not specifying default-src = '*'
7 connectSrc: ['*', 'data:'], 7 connectSrc: [ '*', 'data:' ],
8 mediaSrc: ["'self'", 'https:', 'blob:'], 8 mediaSrc: [ '\'self\'', 'https:', 'blob:' ],
9 fontSrc: ["'self'", 'data:'], 9 fontSrc: [ '\'self\'', 'data:' ],
10 imgSrc: ["'self'", 'data:', 'blob:'], 10 imgSrc: [ '\'self\'', 'data:', 'blob:' ],
11 scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'", 'blob:'], 11 scriptSrc: [ '\'self\' \'unsafe-inline\' \'unsafe-eval\'', 'blob:' ],
12 styleSrc: ["'self' 'unsafe-inline'"], 12 styleSrc: [ '\'self\' \'unsafe-inline\'' ],
13 objectSrc: ["'none'"], // only define to allow plugins, else let defaultSrc 'none' block it 13 objectSrc: [ '\'none\'' ], // only define to allow plugins, else let defaultSrc 'none' block it
14 formAction: ["'self'"], 14 formAction: [ '\'self\'' ],
15 frameAncestors: ["'none'"], 15 frameAncestors: [ '\'none\'' ],
16 baseUri: ["'self'"], 16 baseUri: [ '\'self\'' ],
17 manifestSrc: ["'self'"], 17 manifestSrc: [ '\'self\'' ],
18 frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed 18 frameSrc: [ '\'self\'' ], // instead of deprecated child-src / self because of test-embed
19 workerSrc: ["'self'", 'blob:'] // instead of deprecated child-src 19 workerSrc: [ '\'self\'', 'blob:' ] // instead of deprecated child-src
20 }, 20 },
21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, 21 CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {},
22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} 22 CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {}
@@ -29,7 +29,7 @@ const baseCSP = helmet.contentSecurityPolicy({
29}) 29})
30 30
31const embedCSP = helmet.contentSecurityPolicy({ 31const embedCSP = helmet.contentSecurityPolicy({
32 directives: Object.assign({}, baseDirectives, { frameAncestors: ['*'] }), 32 directives: Object.assign({}, baseDirectives, { frameAncestors: [ '*' ] }),
33 browserSniff: false, // assumes a modern browser, but allows CDN in front 33 browserSniff: false, // assumes a modern browser, but allows CDN in front
34 reportOnly: CONFIG.CSP.REPORT_ONLY 34 reportOnly: CONFIG.CSP.REPORT_ONLY
35}) 35})
diff --git a/server/middlewares/dnt.ts b/server/middlewares/dnt.ts
index 607def855..dd88005dd 100644
--- a/server/middlewares/dnt.ts
+++ b/server/middlewares/dnt.ts
@@ -1,6 +1,3 @@
1import * as ipaddr from 'ipaddr.js'
2import { format } from 'util'
3
4const advertiseDoNotTrack = (_, res, next) => { 1const advertiseDoNotTrack = (_, res, next) => {
5 res.setHeader('Tk', 'N') 2 res.setHeader('Tk', 'N')
6 return next() 3 return next()
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts
index 749f5cccd..9eef03bb4 100644
--- a/server/middlewares/oauth.ts
+++ b/server/middlewares/oauth.ts
@@ -51,6 +51,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) {
51 51
52 return next() 52 return next()
53 }) 53 })
54 .catch(err => logger.error('Cannot get access token.', { err }))
54} 55}
55 56
56function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { 57function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) {
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts
index 8c27e8237..fcbb2902c 100644
--- a/server/middlewares/sort.ts
+++ b/server/middlewares/sort.ts
@@ -1,20 +1,14 @@
1import * as express from 'express' 1import * as express from 'express'
2import { SortType } from '../models/utils' 2import { SortType } from '../models/utils'
3 3
4function setDefaultSort (req: express.Request, res: express.Response, next: express.NextFunction) { 4const setDefaultSort = setDefaultSortFactory('-createdAt')
5 if (!req.query.sort) req.query.sort = '-createdAt'
6
7 return next()
8}
9 5
10function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { 6const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name')
11 if (!req.query.sort) req.query.sort = '-match'
12 7
13 return next() 8const setDefaultSearchSort = setDefaultSortFactory('-match')
14}
15 9
16function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) { 10function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) {
17 let newSort: SortType = { sortModel: undefined, sortValue: '' } 11 const newSort: SortType = { sortModel: undefined, sortValue: '' }
18 12
19 if (!req.query.sort) req.query.sort = '-createdAt' 13 if (!req.query.sort) req.query.sort = '-createdAt'
20 14
@@ -39,5 +33,16 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex
39export { 33export {
40 setDefaultSort, 34 setDefaultSort,
41 setDefaultSearchSort, 35 setDefaultSearchSort,
36 setDefaultVideoRedundanciesSort,
42 setBlacklistSort 37 setBlacklistSort
43} 38}
39
40// ---------------------------------------------------------------------------
41
42function setDefaultSortFactory (sort: string) {
43 return (req: express.Request, res: express.Response, next: express.NextFunction) => {
44 if (!req.query.sort) req.query.sort = sort
45
46 return next()
47 }
48}
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/avatar.ts
index 8623d07e8..2acb97483 100644
--- a/server/middlewares/validators/avatar.ts
+++ b/server/middlewares/validators/avatar.ts
@@ -8,8 +8,8 @@ import { cleanUpReqFiles } from '../../helpers/express-utils'
8 8
9const updateAvatarValidator = [ 9const updateAvatarValidator = [
10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage( 10 body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage(
11 'This file is not supported or too large. Please, make sure it is of the following type : ' 11 'This file is not supported or too large. Please, make sure it is of the following type : ' +
12 + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ') 12 CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ')
13 ), 13 ),
14 14
15 (req: express.Request, res: express.Response, next: express.NextFunction) => { 15 (req: express.Request, res: express.Response, next: express.NextFunction) => {
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts
index 2d1f61947..ceab646c0 100644
--- a/server/middlewares/validators/config.ts
+++ b/server/middlewares/validators/config.ts
@@ -55,7 +55,7 @@ const customConfigUpdateValidator = [
55 55
56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), 56 body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'),
57 57
58 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 58 (req: express.Request, res: express.Response, next: express.NextFunction) => {
59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) 59 logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body })
60 60
61 if (areValidationErrors(req, res)) return 61 if (areValidationErrors(req, res)) return
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index 29f6c87be..f34c2b174 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -22,13 +22,13 @@ function setFeedFormatContentType (req: express.Request, res: express.Response,
22 22
23 let acceptableContentTypes: string[] 23 let acceptableContentTypes: string[]
24 if (format === 'atom' || format === 'atom1') { 24 if (format === 'atom' || format === 'atom1') {
25 acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml'] 25 acceptableContentTypes = [ 'application/atom+xml', 'application/xml', 'text/xml' ]
26 } else if (format === 'json' || format === 'json1') { 26 } else if (format === 'json' || format === 'json1') {
27 acceptableContentTypes = ['application/json'] 27 acceptableContentTypes = [ 'application/json' ]
28 } else if (format === 'rss' || format === 'rss2') { 28 } else if (format === 'rss' || format === 'rss2') {
29 acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml'] 29 acceptableContentTypes = [ 'application/rss+xml', 'application/xml', 'text/xml' ]
30 } else { 30 } else {
31 acceptableContentTypes = ['application/xml', 'text/xml'] 31 acceptableContentTypes = [ 'application/xml', 'text/xml' ]
32 } 32 }
33 33
34 if (req.accepts(acceptableContentTypes)) { 34 if (req.accepts(acceptableContentTypes)) {
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts
index 8098e3a44..8cd3bc33d 100644
--- a/server/middlewares/validators/redundancy.ts
+++ b/server/middlewares/validators/redundancy.ts
@@ -1,12 +1,13 @@
1import * as express from 'express' 1import * as express from 'express'
2import { body, param } from 'express-validator' 2import { body, param, query } from 'express-validator'
3import { exists, isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' 3import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc'
4import { logger } from '../../helpers/logger' 4import { logger } from '../../helpers/logger'
5import { areValidationErrors } from './utils' 5import { areValidationErrors } from './utils'
6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 6import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
7import { isHostValid } from '../../helpers/custom-validators/servers' 7import { isHostValid } from '../../helpers/custom-validators/servers'
8import { ServerModel } from '../../models/server/server' 8import { ServerModel } from '../../models/server/server'
9import { doesVideoExist } from '../../helpers/middlewares' 9import { doesVideoExist } from '../../helpers/middlewares'
10import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies'
10 11
11const videoFileRedundancyGetValidator = [ 12const videoFileRedundancyGetValidator = [
12 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
@@ -101,10 +102,77 @@ const updateServerRedundancyValidator = [
101 } 102 }
102] 103]
103 104
105const listVideoRedundanciesValidator = [
106 query('target')
107 .custom(isVideoRedundancyTarget).withMessage('Should have a valid video redundancies target'),
108
109 (req: express.Request, res: express.Response, next: express.NextFunction) => {
110 logger.debug('Checking listVideoRedundanciesValidator parameters', { parameters: req.query })
111
112 if (areValidationErrors(req, res)) return
113
114 return next()
115 }
116]
117
118const addVideoRedundancyValidator = [
119 body('videoId')
120 .custom(isIdValid)
121 .withMessage('Should have a valid video id'),
122
123 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
124 logger.debug('Checking addVideoRedundancyValidator parameters', { parameters: req.query })
125
126 if (areValidationErrors(req, res)) return
127
128 if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
129
130 if (res.locals.onlyVideo.remote === false) {
131 return res.status(400)
132 .json({ error: 'Cannot create a redundancy on a local video' })
133 .end()
134 }
135
136 const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid)
137 if (alreadyExists) {
138 return res.status(409)
139 .json({ error: 'This video is already duplicated by your instance.' })
140 }
141
142 return next()
143 }
144]
145
146const removeVideoRedundancyValidator = [
147 param('redundancyId')
148 .custom(isIdValid)
149 .withMessage('Should have a valid redundancy id'),
150
151 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
152 logger.debug('Checking removeVideoRedundancyValidator parameters', { parameters: req.query })
153
154 if (areValidationErrors(req, res)) return
155
156 const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10))
157 if (!redundancy) {
158 return res.status(404)
159 .json({ error: 'Video redundancy not found' })
160 .end()
161 }
162
163 res.locals.videoRedundancy = redundancy
164
165 return next()
166 }
167]
168
104// --------------------------------------------------------------------------- 169// ---------------------------------------------------------------------------
105 170
106export { 171export {
107 videoFileRedundancyGetValidator, 172 videoFileRedundancyGetValidator,
108 videoPlaylistRedundancyGetValidator, 173 videoPlaylistRedundancyGetValidator,
109 updateServerRedundancyValidator 174 updateServerRedundancyValidator,
175 listVideoRedundanciesValidator,
176 addVideoRedundancyValidator,
177 removeVideoRedundancyValidator
110} 178}
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts
index c75e701d6..b76dab722 100644
--- a/server/middlewares/validators/sort.ts
+++ b/server/middlewares/validators/sort.ts
@@ -23,6 +23,7 @@ const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUM
23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) 23const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS)
24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) 24const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS)
25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) 25const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS)
26const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES)
26 27
27const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) 28const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
28const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) 29const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
@@ -45,6 +46,7 @@ const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COL
45const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) 46const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS)
46const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) 47const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS)
47const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS) 48const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS)
49const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COLUMNS)
48 50
49// --------------------------------------------------------------------------- 51// ---------------------------------------------------------------------------
50 52
@@ -69,5 +71,6 @@ export {
69 serversBlocklistSortValidator, 71 serversBlocklistSortValidator,
70 userNotificationsSortValidator, 72 userNotificationsSortValidator,
71 videoPlaylistsSortValidator, 73 videoPlaylistsSortValidator,
74 videoRedundanciesSortValidator,
72 pluginsSortValidator 75 pluginsSortValidator
73} 76}
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index c78c67a8c..5d52b5804 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -36,7 +36,6 @@ import { doesVideoExist } from '../../helpers/middlewares'
36import { UserRole } from '../../../shared/models/users' 36import { UserRole } from '../../../shared/models/users'
37import { MUserDefault } from '@server/typings/models' 37import { MUserDefault } from '@server/typings/models'
38import { Hooks } from '@server/lib/plugins/hooks' 38import { Hooks } from '@server/lib/plugins/hooks'
39import { isLocalVideoAccepted } from '@server/lib/moderation'
40 39
41const usersAddValidator = [ 40const usersAddValidator = [
42 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 41 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -149,7 +148,7 @@ const usersBlockingValidator = [
149] 148]
150 149
151const deleteMeValidator = [ 150const deleteMeValidator = [
152 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 151 (req: express.Request, res: express.Response, next: express.NextFunction) => {
153 const user = res.locals.oauth.token.User 152 const user = res.locals.oauth.token.User
154 if (user.username === 'root') { 153 if (user.username === 'root') {
155 return res.status(400) 154 return res.status(400)
@@ -303,7 +302,7 @@ const ensureUserRegistrationAllowed = [
303] 302]
304 303
305const ensureUserRegistrationAllowedForIP = [ 304const ensureUserRegistrationAllowedForIP = [
306 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 305 (req: express.Request, res: express.Response, next: express.NextFunction) => {
307 const allowed = isSignupAllowedForCurrentIP(req.ip) 306 const allowed = isSignupAllowedForCurrentIP(req.ip)
308 307
309 if (allowed === false) { 308 if (allowed === false) {
@@ -410,7 +409,7 @@ const userAutocompleteValidator = [
410] 409]
411 410
412const ensureAuthUserOwnsAccountValidator = [ 411const ensureAuthUserOwnsAccountValidator = [
413 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 412 (req: express.Request, res: express.Response, next: express.NextFunction) => {
414 const user = res.locals.oauth.token.User 413 const user = res.locals.oauth.token.User
415 414
416 if (res.locals.account.id !== user.Account.id) { 415 if (res.locals.account.id !== user.Account.id) {
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts
index 7b0cd6f66..872d9c2ab 100644
--- a/server/middlewares/validators/videos/video-captions.ts
+++ b/server/middlewares/validators/videos/video-captions.ts
@@ -13,10 +13,12 @@ const addVideoCaptionValidator = [
13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), 13 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'),
14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), 14 param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'),
15 body('captionfile') 15 body('captionfile')
16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( 16 .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile'))
17 `This caption file is not supported or too large. Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` 17 .withMessage(
18 + Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ') 18 'This caption file is not supported or too large. ' +
19 ), 19 `Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` +
20 Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ')
21 ),
20 22
21 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
22 logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) 24 logger.debug('Checking addVideoCaption parameters', { parameters: req.body })
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts
index 77c5f940d..da2fafb10 100644
--- a/server/middlewares/validators/videos/video-comments.ts
+++ b/server/middlewares/validators/videos/video-comments.ts
@@ -50,7 +50,7 @@ const addVideoCommentThreadValidator = [
50 if (areValidationErrors(req, res)) return 50 if (areValidationErrors(req, res)) return
51 if (!await doesVideoExist(req.params.videoId, res)) return 51 if (!await doesVideoExist(req.params.videoId, res)) return
52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return 52 if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return
53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return 53 if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return
54 54
55 return next() 55 return next()
56 } 56 }
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts
index 318dad100..5dc5db533 100644
--- a/server/middlewares/validators/videos/video-imports.ts
+++ b/server/middlewares/validators/videos/video-imports.ts
@@ -22,10 +22,11 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([
22 .optional() 22 .optional()
23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), 23 .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'),
24 body('torrentfile') 24 body('torrentfile')
25 .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( 25 .custom((value, { req }) => isVideoImportTorrentFile(req.files))
26 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' 26 .withMessage(
27 + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') 27 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' +
28 ), 28 CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ')
29 ),
29 body('name') 30 body('name')
30 .optional() 31 .optional()
31 .custom(isVideoNameValid).withMessage('Should have a valid name'), 32 .custom(isVideoNameValid).withMessage('Should have a valid name'),
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index 1d67e8666..6b15c5464 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -384,10 +384,11 @@ export {
384function getCommonPlaylistEditAttributes () { 384function getCommonPlaylistEditAttributes () {
385 return [ 385 return [
386 body('thumbnailfile') 386 body('thumbnailfile')
387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 387 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile'))
388 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 388 .withMessage(
389 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') 389 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
390 ), 390 CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
391 ),
391 392
392 body('description') 393 body('description')
393 .optional() 394 .optional()
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts
index 4021cfecc..cbc144f69 100644
--- a/server/middlewares/validators/videos/video-rates.ts
+++ b/server/middlewares/validators/videos/video-rates.ts
@@ -24,7 +24,7 @@ const videoUpdateRateValidator = [
24 } 24 }
25] 25]
26 26
27const getAccountVideoRateValidator = function (rateType: VideoRateType) { 27const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) {
28 return [ 28 return [
29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), 29 param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'),
30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), 30 param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -51,7 +51,7 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) {
51const videoRatingValidator = [ 51const videoRatingValidator = [
52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), 52 query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'),
53 53
54 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 54 (req: express.Request, res: express.Response, next: express.NextFunction) => {
55 logger.debug('Checking rating parameter', { parameters: req.params }) 55 logger.debug('Checking rating parameter', { parameters: req.params })
56 56
57 if (areValidationErrors(req, res)) return 57 if (areValidationErrors(req, res)) return
@@ -64,6 +64,6 @@ const videoRatingValidator = [
64 64
65export { 65export {
66 videoUpdateRateValidator, 66 videoUpdateRateValidator,
67 getAccountVideoRateValidator, 67 getAccountVideoRateValidatorFactory,
68 videoRatingValidator 68 videoRatingValidator
69} 69}
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 6733d9dec..11dd02706 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -49,8 +49,8 @@ import { getVideoWithAttributes } from '../../../helpers/video'
49const videosAddValidator = getCommonVideoEditAttributes().concat([ 49const videosAddValidator = getCommonVideoEditAttributes().concat([
50 body('videofile') 50 body('videofile')
51 .custom((value, { req }) => isVideoFile(req.files)).withMessage( 51 .custom((value, { req }) => isVideoFile(req.files)).withMessage(
52 'This file is not supported or too large. Please, make sure it is of the following type: ' 52 'This file is not supported or too large. Please, make sure it is of the following type: ' +
53 + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') 53 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ')
54 ), 54 ),
55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), 55 body('name').custom(isVideoNameValid).withMessage('Should have a valid name'),
56 body('channelId') 56 body('channelId')
@@ -245,19 +245,15 @@ const videosTerminateChangeOwnershipValidator = [
245 // Check if the user who did the request is able to change the ownership of the video 245 // Check if the user who did the request is able to change the ownership of the video
246 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return 246 if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return
247 247
248 return next()
249 },
250 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
251 const videoChangeOwnership = res.locals.videoChangeOwnership 248 const videoChangeOwnership = res.locals.videoChangeOwnership
252 249
253 if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { 250 if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) {
254 return next()
255 } else {
256 res.status(403) 251 res.status(403)
257 .json({ error: 'Ownership already accepted or refused' }) 252 .json({ error: 'Ownership already accepted or refused' })
258
259 return 253 return
260 } 254 }
255
256 return next()
261 } 257 }
262] 258]
263 259
@@ -284,14 +280,14 @@ function getCommonVideoEditAttributes () {
284 return [ 280 return [
285 body('thumbnailfile') 281 body('thumbnailfile')
286 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( 282 .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage(
287 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' 283 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' +
288 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 284 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
289 ), 285 ),
290 body('previewfile') 286 body('previewfile')
291 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( 287 .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage(
292 'This preview file is not supported or too large. Please, make sure it is of the following type: ' 288 'This preview file is not supported or too large. Please, make sure it is of the following type: ' +
293 + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') 289 CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ')
294 ), 290 ),
295 291
296 body('category') 292 body('category')
297 .optional() 293 .optional()
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts
index d50e6527f..5fe864f8b 100644
--- a/server/middlewares/validators/webfinger.ts
+++ b/server/middlewares/validators/webfinger.ts
@@ -18,15 +18,14 @@ const webfingerValidator = [
18 const nameWithHost = getHostWithPort(req.query.resource.substr(5)) 18 const nameWithHost = getHostWithPort(req.query.resource.substr(5))
19 const [ name ] = nameWithHost.split('@') 19 const [ name ] = nameWithHost.split('@')
20 20
21 // FIXME: we don't need the full actor 21 const actor = await ActorModel.loadLocalUrlByName(name)
22 const actor = await ActorModel.loadLocalByName(name)
23 if (!actor) { 22 if (!actor) {
24 return res.status(404) 23 return res.status(404)
25 .send({ error: 'Actor not found' }) 24 .send({ error: 'Actor not found' })
26 .end() 25 .end()
27 } 26 }
28 27
29 res.locals.actorFull = actor 28 res.locals.actorUrl = actor
30 return next() 29 return next()
31 } 30 }
32] 31]
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts
index 6ebe32556..e2f66d733 100644
--- a/server/models/account/account-blocklist.ts
+++ b/server/models/account/account-blocklist.ts
@@ -80,7 +80,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
80 attributes: [ 'accountId', 'id' ], 80 attributes: [ 'accountId', 'id' ],
81 where: { 81 where: {
82 accountId: { 82 accountId: {
83 [Op.in]: accountIds // FIXME: sequelize ANY seems broken 83 [Op.in]: accountIds
84 }, 84 },
85 targetAccountId 85 targetAccountId
86 }, 86 },
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts
index c593595b2..8aeb486d1 100644
--- a/server/models/account/account-video-rate.ts
+++ b/server/models/account/account-video-rate.ts
@@ -99,7 +99,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { 99 static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> {
100 const options: FindOptions = { 100 const options: FindOptions = {
101 where: { 101 where: {
102 [ Op.or]: [ 102 [Op.or]: [
103 { 103 {
104 accountId, 104 accountId,
105 videoId 105 videoId
@@ -116,10 +116,10 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
116 } 116 }
117 117
118 static listByAccountForApi (options: { 118 static listByAccountForApi (options: {
119 start: number, 119 start: number
120 count: number, 120 count: number
121 sort: string, 121 sort: string
122 type?: string, 122 type?: string
123 accountId: number 123 accountId: number
124 }) { 124 }) {
125 const query: FindOptions = { 125 const query: FindOptions = {
@@ -135,7 +135,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> {
135 required: true, 135 required: true,
136 include: [ 136 include: [
137 { 137 {
138 model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), 138 model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }),
139 required: true 139 required: true
140 } 140 }
141 ] 141 ]
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 8a0ffeb63..0905a0fb2 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -53,7 +53,7 @@ export type SummaryOptions = {
53 ] 53 ]
54})) 54}))
55@Scopes(() => ({ 55@Scopes(() => ({
56 [ ScopeNames.SUMMARY ]: (options: SummaryOptions = {}) => { 56 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
57 const whereActor = options.whereActor || undefined 57 const whereActor = options.whereActor || undefined
58 58
59 const serverInclude: IncludeOptions = { 59 const serverInclude: IncludeOptions = {
@@ -254,15 +254,15 @@ export class AccountModel extends Model<AccountModel> {
254 254
255 const query = { 255 const query = {
256 where: { 256 where: {
257 [ Op.or ]: [ 257 [Op.or]: [
258 { 258 {
259 userId: { 259 userId: {
260 [ Op.ne ]: null 260 [Op.ne]: null
261 } 261 }
262 }, 262 },
263 { 263 {
264 applicationId: { 264 applicationId: {
265 [ Op.ne ]: null 265 [Op.ne]: null
266 } 266 }
267 } 267 }
268 ] 268 ]
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
index a05f30175..5a725187a 100644
--- a/server/models/account/user-notification.ts
+++ b/server/models/account/user-notification.ts
@@ -363,7 +363,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
363 where: { 363 where: {
364 userId, 364 userId,
365 id: { 365 id: {
366 [Op.in]: notificationIds // FIXME: sequelize ANY seems broken 366 [Op.in]: notificationIds
367 } 367 }
368 } 368 }
369 } 369 }
@@ -379,7 +379,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> {
379 379
380 toFormattedJSON (this: UserNotificationModelForApi): UserNotification { 380 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
381 const video = this.Video 381 const video = this.Video
382 ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) 382 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) })
383 : undefined 383 : undefined
384 384
385 const videoImport = this.VideoImport ? { 385 const videoImport = this.VideoImport ? {
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 4c2c5e278..fb4c15aef 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -1,4 +1,4 @@
1import { FindOptions, literal, Op, QueryTypes, where, fn, col } from 'sequelize' 1import { FindOptions, literal, Op, QueryTypes, where, fn, col, WhereOptions } from 'sequelize'
2import { 2import {
3 AfterDestroy, 3 AfterDestroy,
4 AfterUpdate, 4 AfterUpdate,
@@ -101,7 +101,7 @@ enum ScopeNames {
101 required: true, 101 required: true,
102 where: { 102 where: {
103 type: { 103 type: {
104 [ Op.ne ]: VideoPlaylistType.REGULAR 104 [Op.ne]: VideoPlaylistType.REGULAR
105 } 105 }
106 } 106 }
107 } 107 }
@@ -186,7 +186,10 @@ export class UserModel extends Model<UserModel> {
186 186
187 @AllowNull(false) 187 @AllowNull(false)
188 @Default(true) 188 @Default(true)
189 @Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')) 189 @Is(
190 'UserAutoPlayNextVideoPlaylist',
191 value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')
192 )
190 @Column 193 @Column
191 autoPlayNextVideoPlaylist: boolean 194 autoPlayNextVideoPlaylist: boolean
192 195
@@ -308,7 +311,8 @@ export class UserModel extends Model<UserModel> {
308 } 311 }
309 312
310 static listForApi (start: number, count: number, sort: string, search?: string) { 313 static listForApi (start: number, count: number, sort: string, search?: string) {
311 let where = undefined 314 let where: WhereOptions
315
312 if (search) { 316 if (search) {
313 where = { 317 where = {
314 [Op.or]: [ 318 [Op.or]: [
@@ -319,7 +323,7 @@ export class UserModel extends Model<UserModel> {
319 }, 323 },
320 { 324 {
321 username: { 325 username: {
322 [ Op.iLike ]: '%' + search + '%' 326 [Op.iLike]: '%' + search + '%'
323 } 327 }
324 } 328 }
325 ] 329 ]
@@ -332,14 +336,14 @@ export class UserModel extends Model<UserModel> {
332 [ 336 [
333 literal( 337 literal(
334 '(' + 338 '(' +
335 'SELECT COALESCE(SUM("size"), 0) ' + 339 'SELECT COALESCE(SUM("size"), 0) ' +
336 'FROM (' + 340 'FROM (' +
337 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + 341 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
338 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 342 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
339 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 343 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
340 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 344 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
341 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' + 345 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' +
342 ') t' + 346 ') t' +
343 ')' 347 ')'
344 ), 348 ),
345 'videoQuotaUsed' 349 'videoQuotaUsed'
@@ -353,18 +357,18 @@ export class UserModel extends Model<UserModel> {
353 } 357 }
354 358
355 return UserModel.findAndCountAll(query) 359 return UserModel.findAndCountAll(query)
356 .then(({ rows, count }) => { 360 .then(({ rows, count }) => {
357 return { 361 return {
358 data: rows, 362 data: rows,
359 total: count 363 total: count
360 } 364 }
361 }) 365 })
362 } 366 }
363 367
364 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { 368 static listWithRight (right: UserRight): Bluebird<MUserDefault[]> {
365 const roles = Object.keys(USER_ROLE_LABELS) 369 const roles = Object.keys(USER_ROLE_LABELS)
366 .map(k => parseInt(k, 10) as UserRole) 370 .map(k => parseInt(k, 10) as UserRole)
367 .filter(role => hasUserRight(role, right)) 371 .filter(role => hasUserRight(role, right))
368 372
369 const query = { 373 const query = {
370 where: { 374 where: {
@@ -390,7 +394,7 @@ export class UserModel extends Model<UserModel> {
390 required: true, 394 required: true,
391 include: [ 395 include: [
392 { 396 {
393 attributes: [ ], 397 attributes: [],
394 model: ActorModel.unscoped(), 398 model: ActorModel.unscoped(),
395 required: true, 399 required: true,
396 where: { 400 where: {
@@ -398,7 +402,7 @@ export class UserModel extends Model<UserModel> {
398 }, 402 },
399 include: [ 403 include: [
400 { 404 {
401 attributes: [ ], 405 attributes: [],
402 as: 'ActorFollowings', 406 as: 'ActorFollowings',
403 model: ActorFollowModel.unscoped(), 407 model: ActorFollowModel.unscoped(),
404 required: true, 408 required: true,
@@ -433,7 +437,7 @@ export class UserModel extends Model<UserModel> {
433 static loadByUsername (username: string): Bluebird<MUserDefault> { 437 static loadByUsername (username: string): Bluebird<MUserDefault> {
434 const query = { 438 const query = {
435 where: { 439 where: {
436 username: { [ Op.iLike ]: username } 440 username: { [Op.iLike]: username }
437 } 441 }
438 } 442 }
439 443
@@ -443,7 +447,7 @@ export class UserModel extends Model<UserModel> {
443 static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> { 447 static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> {
444 const query = { 448 const query = {
445 where: { 449 where: {
446 username: { [ Op.iLike ]: username } 450 username: { [Op.iLike]: username }
447 } 451 }
448 } 452 }
449 453
@@ -465,7 +469,7 @@ export class UserModel extends Model<UserModel> {
465 469
466 const query = { 470 const query = {
467 where: { 471 where: {
468 [ Op.or ]: [ 472 [Op.or]: [
469 where(fn('lower', col('username')), fn('lower', username)), 473 where(fn('lower', col('username')), fn('lower', username)),
470 474
471 { email } 475 { email }
@@ -592,7 +596,7 @@ export class UserModel extends Model<UserModel> {
592 const query = { 596 const query = {
593 where: { 597 where: {
594 username: { 598 username: {
595 [ Op.like ]: `%${search}%` 599 [Op.like]: `%${search}%`
596 } 600 }
597 }, 601 },
598 limit: 10 602 limit: 10
@@ -652,7 +656,7 @@ export class UserModel extends Model<UserModel> {
652 videoLanguages: this.videoLanguages, 656 videoLanguages: this.videoLanguages,
653 657
654 role: this.role, 658 role: this.role,
655 roleLabel: USER_ROLE_LABELS[ this.role ], 659 roleLabel: USER_ROLE_LABELS[this.role],
656 660
657 videoQuota: this.videoQuota, 661 videoQuota: this.videoQuota,
658 videoQuotaDaily: this.videoQuotaDaily, 662 videoQuotaDaily: this.videoQuotaDaily,
@@ -686,13 +690,13 @@ export class UserModel extends Model<UserModel> {
686 690
687 if (Array.isArray(this.Account.VideoChannels) === true) { 691 if (Array.isArray(this.Account.VideoChannels) === true) {
688 json.videoChannels = this.Account.VideoChannels 692 json.videoChannels = this.Account.VideoChannels
689 .map(c => c.toFormattedJSON()) 693 .map(c => c.toFormattedJSON())
690 .sort((v1, v2) => { 694 .sort((v1, v2) => {
691 if (v1.createdAt < v2.createdAt) return -1 695 if (v1.createdAt < v2.createdAt) return -1
692 if (v1.createdAt === v2.createdAt) return 0 696 if (v1.createdAt === v2.createdAt) return 0
693 697
694 return 1 698 return 1
695 }) 699 })
696 } 700 }
697 701
698 return json 702 return json
@@ -702,7 +706,7 @@ export class UserModel extends Model<UserModel> {
702 const formatted = this.toFormattedJSON() 706 const formatted = this.toFormattedJSON()
703 707
704 const specialPlaylists = this.Account.VideoPlaylists 708 const specialPlaylists = this.Account.VideoPlaylists
705 .map(p => ({ id: p.id, name: p.name, type: p.type })) 709 .map(p => ({ id: p.id, name: p.name, type: p.type }))
706 710
707 return Object.assign(formatted, { specialPlaylists }) 711 return Object.assign(formatted, { specialPlaylists })
708 } 712 }
@@ -729,12 +733,12 @@ export class UserModel extends Model<UserModel> {
729 733
730 return 'SELECT SUM("size") AS "total" ' + 734 return 'SELECT SUM("size") AS "total" ' +
731 'FROM (' + 735 'FROM (' +
732 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + 736 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' +
733 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + 737 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' +
734 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 738 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
735 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + 739 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' +
736 'WHERE "account"."userId" = $userId ' + andWhere + 740 'WHERE "account"."userId" = $userId ' + andWhere +
737 'GROUP BY "video"."id"' + 741 'GROUP BY "video"."id"' +
738 ') t' 742 ') t'
739 } 743 }
740 744
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts
index f21d2b8a2..27643704e 100644
--- a/server/models/activitypub/actor-follow.ts
+++ b/server/models/activitypub/actor-follow.ts
@@ -1,5 +1,5 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { values, difference } from 'lodash' 2import { difference, values } from 'lodash'
3import { 3import {
4 AfterCreate, 4 AfterCreate,
5 AfterDestroy, 5 AfterDestroy,
@@ -23,7 +23,7 @@ import { logger } from '../../helpers/logger'
23import { getServerActor } from '../../helpers/utils' 23import { getServerActor } from '../../helpers/utils'
24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' 24import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants'
25import { ServerModel } from '../server/server' 25import { ServerModel } from '../server/server'
26import { createSafeIn, getSort, getFollowsSort } from '../utils' 26import { createSafeIn, getFollowsSort, getSort } from '../utils'
27import { ActorModel, unusedActorAttributesForAPI } from './actor' 27import { ActorModel, unusedActorAttributesForAPI } from './actor'
28import { VideoChannelModel } from '../video/video-channel' 28import { VideoChannelModel } from '../video/video-channel'
29import { AccountModel } from '../account/account' 29import { AccountModel } from '../account/account'
@@ -36,7 +36,6 @@ import {
36 MActorFollowSubscriptions 36 MActorFollowSubscriptions
37} from '@server/typings/models' 37} from '@server/typings/models'
38import { ActivityPubActorType } from '@shared/models' 38import { ActivityPubActorType } from '@shared/models'
39import { afterCommitIfTransaction } from '@server/helpers/database-utils'
40 39
41@Table({ 40@Table({
42 tableName: 'actorFollow', 41 tableName: 'actorFollow',
@@ -226,7 +225,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
226 225
227 return ActorFollowModel.findOne(query) 226 return ActorFollowModel.findOne(query)
228 .then(result => { 227 .then(result => {
229 if (result && result.ActorFollowing.VideoChannel) { 228 if (result?.ActorFollowing.VideoChannel) {
230 result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing 229 result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing
231 } 230 }
232 231
@@ -239,24 +238,24 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
239 .map(t => { 238 .map(t => {
240 if (t.host) { 239 if (t.host) {
241 return { 240 return {
242 [ Op.and ]: [ 241 [Op.and]: [
243 { 242 {
244 '$preferredUsername$': t.name 243 $preferredUsername$: t.name
245 }, 244 },
246 { 245 {
247 '$host$': t.host 246 $host$: t.host
248 } 247 }
249 ] 248 ]
250 } 249 }
251 } 250 }
252 251
253 return { 252 return {
254 [ Op.and ]: [ 253 [Op.and]: [
255 { 254 {
256 '$preferredUsername$': t.name 255 $preferredUsername$: t.name
257 }, 256 },
258 { 257 {
259 '$serverId$': null 258 $serverId$: null
260 } 259 }
261 ] 260 ]
262 } 261 }
@@ -265,9 +264,9 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
265 const query = { 264 const query = {
266 attributes: [], 265 attributes: [],
267 where: { 266 where: {
268 [ Op.and ]: [ 267 [Op.and]: [
269 { 268 {
270 [ Op.or ]: whereTab 269 [Op.or]: whereTab
271 }, 270 },
272 { 271 {
273 actorId 272 actorId
@@ -295,12 +294,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
295 } 294 }
296 295
297 static listFollowingForApi (options: { 296 static listFollowingForApi (options: {
298 id: number, 297 id: number
299 start: number, 298 start: number
300 count: number, 299 count: number
301 sort: string, 300 sort: string
302 state?: FollowState, 301 state?: FollowState
303 actorType?: ActivityPubActorType, 302 actorType?: ActivityPubActorType
304 search?: string 303 search?: string
305 }) { 304 }) {
306 const { id, start, count, sort, search, state, actorType } = options 305 const { id, start, count, sort, search, state, actorType } = options
@@ -312,7 +311,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
312 if (search) { 311 if (search) {
313 Object.assign(followingServerWhere, { 312 Object.assign(followingServerWhere, {
314 host: { 313 host: {
315 [ Op.iLike ]: '%' + search + '%' 314 [Op.iLike]: '%' + search + '%'
316 } 315 }
317 }) 316 })
318 } 317 }
@@ -362,12 +361,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
362 } 361 }
363 362
364 static listFollowersForApi (options: { 363 static listFollowersForApi (options: {
365 actorId: number, 364 actorId: number
366 start: number, 365 start: number
367 count: number, 366 count: number
368 sort: string, 367 sort: string
369 state?: FollowState, 368 state?: FollowState
370 actorType?: ActivityPubActorType, 369 actorType?: ActivityPubActorType
371 search?: string 370 search?: string
372 }) { 371 }) {
373 const { actorId, start, count, sort, search, state, actorType } = options 372 const { actorId, start, count, sort, search, state, actorType } = options
@@ -379,7 +378,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
379 if (search) { 378 if (search) {
380 Object.assign(followerServerWhere, { 379 Object.assign(followerServerWhere, {
381 host: { 380 host: {
382 [ Op.iLike ]: '%' + search + '%' 381 [Op.iLike]: '%' + search + '%'
383 } 382 }
384 }) 383 })
385 } 384 }
@@ -631,7 +630,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
631 630
632 const tasks: Bluebird<any>[] = [] 631 const tasks: Bluebird<any>[] = []
633 632
634 for (let selection of selections) { 633 for (const selection of selections) {
635 let query = 'SELECT ' + selection + ' FROM "actor" ' + 634 let query = 'SELECT ' + selection + ' FROM "actor" ' +
636 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + 635 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' +
637 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + 636 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' +
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 007647ced..00e8dc954 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -16,7 +16,7 @@ import {
16 Table, 16 Table,
17 UpdatedAt 17 UpdatedAt
18} from 'sequelize-typescript' 18} from 'sequelize-typescript'
19import { ActivityPubActorType } from '../../../shared/models/activitypub' 19import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub'
20import { Avatar } from '../../../shared/models/avatars/avatar.model' 20import { Avatar } from '../../../shared/models/avatars/avatar.model'
21import { activityPubContextify } from '../../helpers/activitypub' 21import { activityPubContextify } from '../../helpers/activitypub'
22import { 22import {
@@ -43,7 +43,7 @@ import {
43 MActorFull, 43 MActorFull,
44 MActorHost, 44 MActorHost,
45 MActorServer, 45 MActorServer,
46 MActorSummaryFormattable, 46 MActorSummaryFormattable, MActorUrl,
47 MActorWithInboxes 47 MActorWithInboxes
48} from '../../typings/models' 48} from '../../typings/models'
49import * as Bluebird from 'bluebird' 49import * as Bluebird from 'bluebird'
@@ -276,7 +276,8 @@ export class ActorModel extends Model<ActorModel> {
276 }) 276 })
277 VideoChannel: VideoChannelModel 277 VideoChannel: VideoChannelModel
278 278
279 private static cache: { [ id: string ]: any } = {} 279 private static localNameCache: { [ id: string ]: any } = {}
280 private static localUrlCache: { [ id: string ]: any } = {}
280 281
281 static load (id: number): Bluebird<MActor> { 282 static load (id: number): Bluebird<MActor> {
282 return ActorModel.unscoped().findByPk(id) 283 return ActorModel.unscoped().findByPk(id)
@@ -334,7 +335,7 @@ export class ActorModel extends Model<ActorModel> {
334 const query = { 335 const query = {
335 where: { 336 where: {
336 followersUrl: { 337 followersUrl: {
337 [ Op.in ]: followersUrls 338 [Op.in]: followersUrls
338 } 339 }
339 }, 340 },
340 transaction 341 transaction
@@ -345,8 +346,8 @@ export class ActorModel extends Model<ActorModel> {
345 346
346 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { 347 static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> {
347 // The server actor never change, so we can easily cache it 348 // The server actor never change, so we can easily cache it
348 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.cache[preferredUsername]) { 349 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localNameCache[preferredUsername]) {
349 return Bluebird.resolve(ActorModel.cache[preferredUsername]) 350 return Bluebird.resolve(ActorModel.localNameCache[preferredUsername])
350 } 351 }
351 352
352 const query = { 353 const query = {
@@ -361,7 +362,33 @@ export class ActorModel extends Model<ActorModel> {
361 .findOne(query) 362 .findOne(query)
362 .then(actor => { 363 .then(actor => {
363 if (preferredUsername === SERVER_ACTOR_NAME) { 364 if (preferredUsername === SERVER_ACTOR_NAME) {
364 ActorModel.cache[ preferredUsername ] = actor 365 ActorModel.localNameCache[preferredUsername] = actor
366 }
367
368 return actor
369 })
370 }
371
372 static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> {
373 // The server actor never change, so we can easily cache it
374 if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localUrlCache[preferredUsername]) {
375 return Bluebird.resolve(ActorModel.localUrlCache[preferredUsername])
376 }
377
378 const query = {
379 attributes: [ 'url' ],
380 where: {
381 preferredUsername,
382 serverId: null
383 },
384 transaction
385 }
386
387 return ActorModel.unscoped()
388 .findOne(query)
389 .then(actor => {
390 if (preferredUsername === SERVER_ACTOR_NAME) {
391 ActorModel.localUrlCache[preferredUsername] = actor
365 } 392 }
366 393
367 return actor 394 return actor
@@ -473,9 +500,11 @@ export class ActorModel extends Model<ActorModel> {
473 } 500 }
474 501
475 toActivityPubObject (this: MActorAP, name: string) { 502 toActivityPubObject (this: MActorAP, name: string) {
476 let icon = undefined 503 let icon: ActivityIconObject
504
477 if (this.avatarId) { 505 if (this.avatarId) {
478 const extension = extname(this.Avatar.filename) 506 const extension = extname(this.Avatar.filename)
507
479 icon = { 508 icon = {
480 type: 'Image', 509 type: 'Image',
481 mediaType: extension === '.png' ? 'image/png' : 'image/jpeg', 510 mediaType: extension === '.png' ? 'image/png' : 'image/jpeg',
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts
index b680be237..d2101ce86 100644
--- a/server/models/oauth/oauth-token.ts
+++ b/server/models/oauth/oauth-token.ts
@@ -23,10 +23,10 @@ import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token'
23 23
24export type OAuthTokenInfo = { 24export type OAuthTokenInfo = {
25 refreshToken: string 25 refreshToken: string
26 refreshTokenExpiresAt: Date, 26 refreshTokenExpiresAt: Date
27 client: { 27 client: {
28 id: number 28 id: number
29 }, 29 }
30 user: { 30 user: {
31 id: number 31 id: number
32 } 32 }
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts
index 8c9a7eabf..1b63d3818 100644
--- a/server/models/redundancy/video-redundancy.ts
+++ b/server/models/redundancy/video-redundancy.ts
@@ -13,13 +13,13 @@ import {
13 UpdatedAt 13 UpdatedAt
14} from 'sequelize-typescript' 14} from 'sequelize-typescript'
15import { ActorModel } from '../activitypub/actor' 15import { ActorModel } from '../activitypub/actor'
16import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' 16import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils'
17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' 17import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
18import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' 18import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants'
19import { VideoFileModel } from '../video/video-file' 19import { VideoFileModel } from '../video/video-file'
20import { getServerActor } from '../../helpers/utils' 20import { getServerActor } from '../../helpers/utils'
21import { VideoModel } from '../video/video' 21import { VideoModel } from '../video/video'
22import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' 22import { VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../shared/models/redundancy'
23import { logger } from '../../helpers/logger' 23import { logger } from '../../helpers/logger'
24import { CacheFileObject, VideoPrivacy } from '../../../shared' 24import { CacheFileObject, VideoPrivacy } from '../../../shared'
25import { VideoChannelModel } from '../video/video-channel' 25import { VideoChannelModel } from '../video/video-channel'
@@ -27,17 +27,23 @@ import { ServerModel } from '../server/server'
27import { sample } from 'lodash' 27import { sample } from 'lodash'
28import { isTestInstance } from '../../helpers/core-utils' 28import { isTestInstance } from '../../helpers/core-utils'
29import * as Bluebird from 'bluebird' 29import * as Bluebird from 'bluebird'
30import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' 30import { col, FindOptions, fn, literal, Op, Transaction, WhereOptions } from 'sequelize'
31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' 31import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist'
32import { CONFIG } from '../../initializers/config' 32import { CONFIG } from '../../initializers/config'
33import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' 33import { MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models'
34import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model'
35import {
36 FileRedundancyInformation,
37 StreamingPlaylistRedundancyInformation,
38 VideoRedundancy
39} from '@shared/models/redundancy/video-redundancy.model'
34 40
35export enum ScopeNames { 41export enum ScopeNames {
36 WITH_VIDEO = 'WITH_VIDEO' 42 WITH_VIDEO = 'WITH_VIDEO'
37} 43}
38 44
39@Scopes(() => ({ 45@Scopes(() => ({
40 [ ScopeNames.WITH_VIDEO ]: { 46 [ScopeNames.WITH_VIDEO]: {
41 include: [ 47 include: [
42 { 48 {
43 model: VideoFileModel, 49 model: VideoFileModel,
@@ -86,7 +92,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
86 @UpdatedAt 92 @UpdatedAt
87 updatedAt: Date 93 updatedAt: Date
88 94
89 @AllowNull(false) 95 @AllowNull(true)
90 @Column 96 @Column
91 expiresOn: Date 97 expiresOn: Date
92 98
@@ -161,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
161 logger.info('Removing duplicated video streaming playlist %s.', videoUUID) 167 logger.info('Removing duplicated video streaming playlist %s.', videoUUID)
162 168
163 videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) 169 videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true)
164 .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) 170 .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err }))
165 } 171 }
166 172
167 return undefined 173 return undefined
@@ -193,6 +199,15 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
193 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) 199 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
194 } 200 }
195 201
202 static loadByIdWithVideo (id: number, transaction?: Transaction): Bluebird<MVideoRedundancyVideo> {
203 const query = {
204 where: { id },
205 transaction
206 }
207
208 return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query)
209 }
210
196 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { 211 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> {
197 const query = { 212 const query = {
198 where: { 213 where: {
@@ -215,12 +230,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
215 }, 230 },
216 include: [ 231 include: [
217 { 232 {
218 attributes: [ ], 233 attributes: [],
219 model: VideoFileModel, 234 model: VideoFileModel,
220 required: true, 235 required: true,
221 include: [ 236 include: [
222 { 237 {
223 attributes: [ ], 238 attributes: [],
224 model: VideoModel, 239 model: VideoModel,
225 required: true, 240 required: true,
226 where: { 241 where: {
@@ -233,7 +248,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
233 } 248 }
234 249
235 return VideoRedundancyModel.findOne(query) 250 return VideoRedundancyModel.findOne(query)
236 .then(r => !!r) 251 .then(r => !!r)
237 } 252 }
238 253
239 static async getVideoSample (p: Bluebird<VideoModel[]>) { 254 static async getVideoSample (p: Bluebird<VideoModel[]>) {
@@ -295,7 +310,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
295 where: { 310 where: {
296 privacy: VideoPrivacy.PUBLIC, 311 privacy: VideoPrivacy.PUBLIC,
297 views: { 312 views: {
298 [ Op.gte ]: minViews 313 [Op.gte]: minViews
299 } 314 }
300 }, 315 },
301 include: [ 316 include: [
@@ -318,7 +333,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
318 actorId: actor.id, 333 actorId: actor.id,
319 strategy, 334 strategy,
320 createdAt: { 335 createdAt: {
321 [ Op.lt ]: expiredDate 336 [Op.lt]: expiredDate
322 } 337 }
323 } 338 }
324 } 339 }
@@ -377,7 +392,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
377 where: { 392 where: {
378 actorId: actor.id, 393 actorId: actor.id,
379 expiresOn: { 394 expiresOn: {
380 [ Op.lt ]: new Date() 395 [Op.lt]: new Date()
381 } 396 }
382 } 397 }
383 } 398 }
@@ -394,7 +409,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
394 [Op.ne]: actor.id 409 [Op.ne]: actor.id
395 }, 410 },
396 expiresOn: { 411 expiresOn: {
397 [ Op.lt ]: new Date() 412 [Op.lt]: new Date(),
413 [Op.ne]: null
398 } 414 }
399 } 415 }
400 } 416 }
@@ -447,7 +463,112 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
447 return VideoRedundancyModel.findAll(query) 463 return VideoRedundancyModel.findAll(query)
448 } 464 }
449 465
450 static async getStats (strategy: VideoRedundancyStrategy) { 466 static listForApi (options: {
467 start: number
468 count: number
469 sort: string
470 target: VideoRedundanciesTarget
471 strategy?: string
472 }) {
473 const { start, count, sort, target, strategy } = options
474 const redundancyWhere: WhereOptions = {}
475 const videosWhere: WhereOptions = {}
476 let redundancySqlSuffix = ''
477
478 if (target === 'my-videos') {
479 Object.assign(videosWhere, { remote: false })
480 } else if (target === 'remote-videos') {
481 Object.assign(videosWhere, { remote: true })
482 Object.assign(redundancyWhere, { strategy: { [Op.ne]: null } })
483 redundancySqlSuffix = ' AND "videoRedundancy"."strategy" IS NOT NULL'
484 }
485
486 if (strategy) {
487 Object.assign(redundancyWhere, { strategy: strategy })
488 }
489
490 const videoFilterWhere = {
491 [Op.and]: [
492 {
493 [Op.or]: [
494 {
495 id: {
496 [Op.in]: literal(
497 '(' +
498 'SELECT "videoId" FROM "videoFile" ' +
499 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoFileId" = "videoFile".id' +
500 redundancySqlSuffix +
501 ')'
502 )
503 }
504 },
505 {
506 id: {
507 [Op.in]: literal(
508 '(' +
509 'select "videoId" FROM "videoStreamingPlaylist" ' +
510 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' +
511 redundancySqlSuffix +
512 ')'
513 )
514 }
515 }
516 ]
517 },
518
519 videosWhere
520 ]
521 }
522
523 // /!\ On video model /!\
524 const findOptions = {
525 offset: start,
526 limit: count,
527 order: getSort(sort),
528 include: [
529 {
530 required: false,
531 model: VideoFileModel.unscoped(),
532 include: [
533 {
534 model: VideoRedundancyModel.unscoped(),
535 required: false,
536 where: redundancyWhere
537 }
538 ]
539 },
540 {
541 required: false,
542 model: VideoStreamingPlaylistModel.unscoped(),
543 include: [
544 {
545 model: VideoRedundancyModel.unscoped(),
546 required: false,
547 where: redundancyWhere
548 },
549 {
550 model: VideoFileModel.unscoped(),
551 required: false
552 }
553 ]
554 }
555 ],
556 where: videoFilterWhere
557 }
558
559 // /!\ On video model /!\
560 const countOptions = {
561 where: videoFilterWhere
562 }
563
564 return Promise.all([
565 VideoModel.findAll(findOptions),
566
567 VideoModel.count(countOptions)
568 ]).then(([ data, total ]) => ({ total, data }))
569 }
570
571 static async getStats (strategy: VideoRedundancyStrategyWithManual) {
451 const actor = await getServerActor() 572 const actor = await getServerActor()
452 573
453 const query: FindOptions = { 574 const query: FindOptions = {
@@ -471,11 +592,58 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
471 } 592 }
472 593
473 return VideoRedundancyModel.findOne(query) 594 return VideoRedundancyModel.findOne(query)
474 .then((r: any) => ({ 595 .then((r: any) => ({
475 totalUsed: parseAggregateResult(r.totalUsed), 596 totalUsed: parseAggregateResult(r.totalUsed),
476 totalVideos: r.totalVideos, 597 totalVideos: r.totalVideos,
477 totalVideoFiles: r.totalVideoFiles 598 totalVideoFiles: r.totalVideoFiles
478 })) 599 }))
600 }
601
602 static toFormattedJSONStatic (video: MVideoForRedundancyAPI): VideoRedundancy {
603 const filesRedundancies: FileRedundancyInformation[] = []
604 const streamingPlaylistsRedundancies: StreamingPlaylistRedundancyInformation[] = []
605
606 for (const file of video.VideoFiles) {
607 for (const redundancy of file.RedundancyVideos) {
608 filesRedundancies.push({
609 id: redundancy.id,
610 fileUrl: redundancy.fileUrl,
611 strategy: redundancy.strategy,
612 createdAt: redundancy.createdAt,
613 updatedAt: redundancy.updatedAt,
614 expiresOn: redundancy.expiresOn,
615 size: file.size
616 })
617 }
618 }
619
620 for (const playlist of video.VideoStreamingPlaylists) {
621 const size = playlist.VideoFiles.reduce((a, b) => a + b.size, 0)
622
623 for (const redundancy of playlist.RedundancyVideos) {
624 streamingPlaylistsRedundancies.push({
625 id: redundancy.id,
626 fileUrl: redundancy.fileUrl,
627 strategy: redundancy.strategy,
628 createdAt: redundancy.createdAt,
629 updatedAt: redundancy.updatedAt,
630 expiresOn: redundancy.expiresOn,
631 size
632 })
633 }
634 }
635
636 return {
637 id: video.id,
638 name: video.name,
639 url: video.url,
640 uuid: video.uuid,
641
642 redundancies: {
643 files: filesRedundancies,
644 streamingPlaylists: streamingPlaylistsRedundancies
645 }
646 }
479 } 647 }
480 648
481 getVideo () { 649 getVideo () {
@@ -494,7 +662,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
494 id: this.url, 662 id: this.url,
495 type: 'CacheFile' as 'CacheFile', 663 type: 'CacheFile' as 'CacheFile',
496 object: this.VideoStreamingPlaylist.Video.url, 664 object: this.VideoStreamingPlaylist.Video.url,
497 expires: this.expiresOn.toISOString(), 665 expires: this.expiresOn ? this.expiresOn.toISOString() : null,
498 url: { 666 url: {
499 type: 'Link', 667 type: 'Link',
500 mediaType: 'application/x-mpegURL', 668 mediaType: 'application/x-mpegURL',
@@ -507,10 +675,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
507 id: this.url, 675 id: this.url,
508 type: 'CacheFile' as 'CacheFile', 676 type: 'CacheFile' as 'CacheFile',
509 object: this.VideoFile.Video.url, 677 object: this.VideoFile.Video.url,
510 expires: this.expiresOn.toISOString(), 678 expires: this.expiresOn ? this.expiresOn.toISOString() : null,
511 url: { 679 url: {
512 type: 'Link', 680 type: 'Link',
513 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, 681 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any,
514 href: this.fileUrl, 682 href: this.fileUrl,
515 height: this.VideoFile.resolution, 683 height: this.VideoFile.resolution,
516 size: this.VideoFile.size, 684 size: this.VideoFile.size,
@@ -525,7 +693,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
525 693
526 const notIn = literal( 694 const notIn = literal(
527 '(' + 695 '(' +
528 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + 696 `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` +
529 ')' 697 ')'
530 ) 698 )
531 699
@@ -535,7 +703,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
535 required: true, 703 required: true,
536 where: { 704 where: {
537 id: { 705 id: {
538 [ Op.notIn ]: notIn 706 [Op.notIn]: notIn
539 } 707 }
540 } 708 }
541 } 709 }
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts
index d094da1f5..95774a467 100644
--- a/server/models/server/plugin.ts
+++ b/server/models/server/plugin.ts
@@ -189,10 +189,10 @@ export class PluginModel extends Model<PluginModel> {
189 } 189 }
190 190
191 static listForApi (options: { 191 static listForApi (options: {
192 pluginType?: PluginType, 192 pluginType?: PluginType
193 uninstalled?: boolean, 193 uninstalled?: boolean
194 start: number, 194 start: number
195 count: number, 195 count: number
196 sort: string 196 sort: string
197 }) { 197 }) {
198 const { uninstalled = false } = options 198 const { uninstalled = false } = options
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts
index b88df4fd5..883ae47ab 100644
--- a/server/models/server/server-blocklist.ts
+++ b/server/models/server/server-blocklist.ts
@@ -81,7 +81,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> {
81 attributes: [ 'accountId', 'id' ], 81 attributes: [ 'accountId', 'id' ],
82 where: { 82 where: {
83 accountId: { 83 accountId: {
84 [Op.in]: accountIds // FIXME: sequelize ANY seems broken 84 [Op.in]: accountIds
85 }, 85 },
86 targetServerId 86 targetServerId
87 }, 87 },
diff --git a/server/models/utils.ts b/server/models/utils.ts
index f89b80011..f7afb8d4b 100644
--- a/server/models/utils.ts
+++ b/server/models/utils.ts
@@ -67,7 +67,7 @@ function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): Or
67function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { 67function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] {
68 const [ firstSort ] = getSort(value) 68 const [ firstSort ] = getSort(value)
69 69
70 if (model) return [ [ literal(`"${model}.${firstSort[ 0 ]}" ${firstSort[ 1 ]}`) ], lastSort ] as any[] // FIXME: typings 70 if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as any[] // FIXME: typings
71 return [ firstSort, lastSort ] 71 return [ firstSort, lastSort ]
72} 72}
73 73
@@ -139,7 +139,7 @@ function buildServerIdsFollowedBy (actorId: any) {
139 'SELECT "actor"."serverId" FROM "actorFollow" ' + 139 'SELECT "actor"."serverId" FROM "actorFollow" ' +
140 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + 140 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' +
141 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + 141 'WHERE "actorFollow"."actorId" = ' + actorIdNumber +
142 ')' 142 ')'
143} 143}
144 144
145function buildWhereIdOrUUID (id: number | string) { 145function buildWhereIdOrUUID (id: number | string) {
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index 3b011b1d2..e396784d2 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -19,6 +19,8 @@ import { CONFIG } from '../../initializers/config'
19import { VideoModel } from './video' 19import { VideoModel } from './video'
20import { VideoPlaylistModel } from './video-playlist' 20import { VideoPlaylistModel } from './video-playlist'
21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' 21import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
22import { MVideoAccountLight } from '@server/typings/models'
23import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
22 24
23@Table({ 25@Table({
24 tableName: 'thumbnail', 26 tableName: 'thumbnail',
@@ -90,7 +92,7 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
90 @UpdatedAt 92 @UpdatedAt
91 updatedAt: Date 93 updatedAt: Date
92 94
93 private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { 95 private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
94 [ThumbnailType.MINIATURE]: { 96 [ThumbnailType.MINIATURE]: {
95 label: 'miniature', 97 label: 'miniature',
96 directory: CONFIG.STORAGE.THUMBNAILS_DIR, 98 directory: CONFIG.STORAGE.THUMBNAILS_DIR,
@@ -126,11 +128,14 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
126 return videoUUID + '.jpg' 128 return videoUUID + '.jpg'
127 } 129 }
128 130
129 getFileUrl (isLocal: boolean) { 131 getFileUrl (video: MVideoAccountLight) {
130 if (isLocal === false) return this.fileUrl 132 const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename
131 133
132 const staticPath = ThumbnailModel.types[this.type].staticPath 134 if (video.isOwned()) return WEBSERVER.URL + staticPath
133 return WEBSERVER.URL + staticPath + this.filename 135 if (this.fileUrl) return this.fileUrl
136
137 // Fallback if we don't have a file URL
138 return buildRemoteVideoBaseUrl(video, staticPath)
134 } 139 }
135 140
136 getPath () { 141 getPath () {
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts
index 3636db18d..da8c1577c 100644
--- a/server/models/video/video-abuse.ts
+++ b/server/models/video/video-abuse.ts
@@ -87,9 +87,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
87 } 87 }
88 88
89 static listForApi (parameters: { 89 static listForApi (parameters: {
90 start: number, 90 start: number
91 count: number, 91 count: number
92 sort: string, 92 sort: string
93 serverAccountId: number 93 serverAccountId: number
94 user?: MUserAccountId 94 user?: MUserAccountId
95 }) { 95 }) {
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts
index eeb2a4afd..59d3e1050 100644
--- a/server/models/video/video-caption.ts
+++ b/server/models/video/video-caption.ts
@@ -5,6 +5,7 @@ import {
5 BelongsTo, 5 BelongsTo,
6 Column, 6 Column,
7 CreatedAt, 7 CreatedAt,
8 DataType,
8 ForeignKey, 9 ForeignKey,
9 Is, 10 Is,
10 Model, 11 Model,
@@ -16,13 +17,14 @@ import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
16import { VideoModel } from './video' 17import { VideoModel } from './video'
17import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' 18import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
18import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' 19import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
19import { LAZY_STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' 20import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
20import { join } from 'path' 21import { join } from 'path'
21import { logger } from '../../helpers/logger' 22import { logger } from '../../helpers/logger'
22import { remove } from 'fs-extra' 23import { remove } from 'fs-extra'
23import { CONFIG } from '../../initializers/config' 24import { CONFIG } from '../../initializers/config'
24import * as Bluebird from 'bluebird' 25import * as Bluebird from 'bluebird'
25import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' 26import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models'
27import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
26 28
27export enum ScopeNames { 29export enum ScopeNames {
28 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' 30 WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
@@ -64,6 +66,10 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
64 @Column 66 @Column
65 language: string 67 language: string
66 68
69 @AllowNull(true)
70 @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max))
71 fileUrl: string
72
67 @ForeignKey(() => VideoModel) 73 @ForeignKey(() => VideoModel)
68 @Column 74 @Column
69 videoId: number 75 videoId: number
@@ -114,13 +120,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
114 return VideoCaptionModel.findOne(query) 120 return VideoCaptionModel.findOne(query)
115 } 121 }
116 122
117 static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { 123 static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) {
118 const values = { 124 const values = {
119 videoId, 125 videoId,
120 language 126 language,
127 fileUrl
121 } 128 }
122 129
123 return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings 130 return VideoCaptionModel.upsert(values, { transaction, returning: true })
124 .then(([ caption ]) => caption) 131 .then(([ caption ]) => caption)
125 } 132 }
126 133
@@ -175,4 +182,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> {
175 removeCaptionFile (this: MVideoCaptionFormattable) { 182 removeCaptionFile (this: MVideoCaptionFormattable) {
176 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) 183 return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
177 } 184 }
185
186 getFileUrl (video: MVideoAccountLight) {
187 if (!this.Video) this.Video = video as VideoModel
188
189 if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath()
190 if (this.fileUrl) return this.fileUrl
191
192 // Fallback if we don't have a file URL
193 return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath())
194 }
178} 195}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index e10adcb3a..835216671 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -30,7 +30,7 @@ import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttr
30import { VideoModel } from './video' 30import { VideoModel } from './video'
31import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' 31import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants'
32import { ServerModel } from '../server/server' 32import { ServerModel } from '../server/server'
33import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' 33import { FindOptions, Op } from 'sequelize'
34import { AvatarModel } from '../avatar/avatar' 34import { AvatarModel } from '../avatar/avatar'
35import { VideoPlaylistModel } from './video-playlist' 35import { VideoPlaylistModel } from './video-playlist'
36import * as Bluebird from 'bluebird' 36import * as Bluebird from 'bluebird'
@@ -43,18 +43,6 @@ import {
43 MChannelSummaryFormattable 43 MChannelSummaryFormattable
44} from '../../typings/models/video' 44} from '../../typings/models/video'
45 45
46// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
47const indexes: ModelIndexesOptions[] = [
48 buildTrigramSearchIndex('video_channel_name_trigram', 'name'),
49
50 {
51 fields: [ 'accountId' ]
52 },
53 {
54 fields: [ 'actorId' ]
55 }
56]
57
58export enum ScopeNames { 46export enum ScopeNames {
59 FOR_API = 'FOR_API', 47 FOR_API = 'FOR_API',
60 WITH_ACCOUNT = 'WITH_ACCOUNT', 48 WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -133,7 +121,7 @@ export type SummaryOptions = {
133 }, 121 },
134 { 122 {
135 serverId: { 123 serverId: {
136 [ Op.in ]: Sequelize.literal(inQueryInstanceFollow) 124 [Op.in]: Sequelize.literal(inQueryInstanceFollow)
137 } 125 }
138 } 126 }
139 ] 127 ]
@@ -176,7 +164,16 @@ export type SummaryOptions = {
176})) 164}))
177@Table({ 165@Table({
178 tableName: 'videoChannel', 166 tableName: 'videoChannel',
179 indexes 167 indexes: [
168 buildTrigramSearchIndex('video_channel_name_trigram', 'name'),
169
170 {
171 fields: [ 'accountId' ]
172 },
173 {
174 fields: [ 'actorId' ]
175 }
176 ]
180}) 177})
181export class VideoChannelModel extends Model<VideoChannelModel> { 178export class VideoChannelModel extends Model<VideoChannelModel> {
182 179
@@ -351,9 +348,9 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
351 } 348 }
352 349
353 static listByAccount (options: { 350 static listByAccount (options: {
354 accountId: number, 351 accountId: number
355 start: number, 352 start: number
356 count: number, 353 count: number
357 sort: string 354 sort: string
358 }) { 355 }) {
359 const query = { 356 const query = {
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts
index fb4d16b4d..b33c33d5e 100644
--- a/server/models/video/video-comment.ts
+++ b/server/models/video/video-comment.ts
@@ -257,10 +257,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
257 } 257 }
258 258
259 static async listThreadsForApi (parameters: { 259 static async listThreadsForApi (parameters: {
260 videoId: number, 260 videoId: number
261 start: number, 261 start: number
262 count: number, 262 count: number
263 sort: string, 263 sort: string
264 user?: MUserAccountId 264 user?: MUserAccountId
265 }) { 265 }) {
266 const { videoId, start, count, sort, user } = parameters 266 const { videoId, start, count, sort, user } = parameters
@@ -300,8 +300,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
300 } 300 }
301 301
302 static async listThreadCommentsForApi (parameters: { 302 static async listThreadCommentsForApi (parameters: {
303 videoId: number, 303 videoId: number
304 threadId: number, 304 threadId: number
305 user?: MUserAccountId 305 user?: MUserAccountId
306 }) { 306 }) {
307 const { videoId, threadId, user } = parameters 307 const { videoId, threadId, user } = parameters
@@ -314,7 +314,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
314 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, 314 order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order,
315 where: { 315 where: {
316 videoId, 316 videoId,
317 [ Op.or ]: [ 317 [Op.or]: [
318 { id: threadId }, 318 { id: threadId },
319 { originCommentId: threadId } 319 { originCommentId: threadId }
320 ], 320 ],
@@ -346,7 +346,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
346 order: [ [ 'createdAt', order ] ] as Order, 346 order: [ [ 'createdAt', order ] ] as Order,
347 where: { 347 where: {
348 id: { 348 id: {
349 [ Op.in ]: Sequelize.literal('(' + 349 [Op.in]: Sequelize.literal('(' +
350 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + 350 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' +
351 `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + 351 `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` +
352 'UNION ' + 352 'UNION ' +
@@ -355,7 +355,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
355 ') ' + 355 ') ' +
356 'SELECT id FROM children' + 356 'SELECT id FROM children' +
357 ')'), 357 ')'),
358 [ Op.ne ]: comment.id 358 [Op.ne]: comment.id
359 } 359 }
360 }, 360 },
361 transaction: t 361 transaction: t
@@ -461,7 +461,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
461 } 461 }
462 462
463 isDeleted () { 463 isDeleted () {
464 return null !== this.deletedAt 464 return this.deletedAt !== null
465 } 465 }
466 466
467 extractMentions () { 467 extractMentions () {
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts
index 67395e5c0..1fa66fd63 100644
--- a/server/models/video/video-format-utils.ts
+++ b/server/models/video/video-format-utils.ts
@@ -27,12 +27,13 @@ import { generateMagnetUri } from '@server/helpers/webtorrent'
27export type VideoFormattingJSONOptions = { 27export type VideoFormattingJSONOptions = {
28 completeDescription?: boolean 28 completeDescription?: boolean
29 additionalAttributes: { 29 additionalAttributes: {
30 state?: boolean, 30 state?: boolean
31 waitTranscoding?: boolean, 31 waitTranscoding?: boolean
32 scheduledUpdate?: boolean, 32 scheduledUpdate?: boolean
33 blacklistInfo?: boolean 33 blacklistInfo?: boolean
34 } 34 }
35} 35}
36
36function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { 37function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
37 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined 38 const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
38 39
@@ -181,12 +182,10 @@ function videoFilesModelToFormattedJSON (
181): VideoFile[] { 182): VideoFile[] {
182 return videoFiles 183 return videoFiles
183 .map(videoFile => { 184 .map(videoFile => {
184 let resolutionLabel = videoFile.resolution + 'p'
185
186 return { 185 return {
187 resolution: { 186 resolution: {
188 id: videoFile.resolution, 187 id: videoFile.resolution,
189 label: resolutionLabel 188 label: videoFile.resolution + 'p'
190 }, 189 },
191 magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), 190 magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs),
192 size: videoFile.size, 191 size: videoFile.size,
@@ -214,7 +213,7 @@ function addVideoFilesInAPAcc (
214 for (const file of files) { 213 for (const file of files) {
215 acc.push({ 214 acc.push({
216 type: 'Link', 215 type: 'Link',
217 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, 216 mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any,
218 href: model.getVideoFileUrl(file, baseUrlHttp), 217 href: model.getVideoFileUrl(file, baseUrlHttp),
219 height: file.resolution, 218 height: file.resolution,
220 size: file.size, 219 size: file.size,
@@ -282,10 +281,8 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
282 addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) 281 addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || [])
283 282
284 for (const playlist of (video.VideoStreamingPlaylists || [])) { 283 for (const playlist of (video.VideoStreamingPlaylists || [])) {
285 let tag: ActivityTagObject[] 284 const tag = playlist.p2pMediaLoaderInfohashes
286 285 .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) as ActivityTagObject[]
287 tag = playlist.p2pMediaLoaderInfohashes
288 .map(i => ({ type: 'Infohash' as 'Infohash', name: i }))
289 tag.push({ 286 tag.push({
290 type: 'Link', 287 type: 'Link',
291 name: 'sha256', 288 name: 'sha256',
@@ -308,11 +305,12 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
308 for (const caption of video.VideoCaptions) { 305 for (const caption of video.VideoCaptions) {
309 subtitleLanguage.push({ 306 subtitleLanguage.push({
310 identifier: caption.language, 307 identifier: caption.language,
311 name: VideoCaptionModel.getLanguageLabel(caption.language) 308 name: VideoCaptionModel.getLanguageLabel(caption.language),
309 url: caption.getFileUrl(video)
312 }) 310 })
313 } 311 }
314 312
315 const miniature = video.getMiniature() 313 const icons = [ video.getMiniature(), video.getPreview() ]
316 314
317 return { 315 return {
318 type: 'Video' as 'Video', 316 type: 'Video' as 'Video',
@@ -337,13 +335,13 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject {
337 content: video.getTruncatedDescription(), 335 content: video.getTruncatedDescription(),
338 support: video.support, 336 support: video.support,
339 subtitleLanguage, 337 subtitleLanguage,
340 icon: { 338 icon: icons.map(i => ({
341 type: 'Image', 339 type: 'Image',
342 url: miniature.getFileUrl(video.isOwned()), 340 url: i.getFileUrl(video),
343 mediaType: 'image/jpeg', 341 mediaType: 'image/jpeg',
344 width: miniature.width, 342 width: i.width,
345 height: miniature.height 343 height: i.height
346 }, 344 })),
347 url, 345 url,
348 likes: getVideoLikesActivityPubUrl(video), 346 likes: getVideoLikesActivityPubUrl(video),
349 dislikes: getVideoDislikesActivityPubUrl(video), 347 dislikes: getVideoDislikesActivityPubUrl(video),
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts
index f2d71357f..4ba16f5fd 100644
--- a/server/models/video/video-playlist-element.ts
+++ b/server/models/video/video-playlist-element.ts
@@ -120,10 +120,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
120 } 120 }
121 121
122 static listForApi (options: { 122 static listForApi (options: {
123 start: number, 123 start: number
124 count: number, 124 count: number
125 videoPlaylistId: number, 125 videoPlaylistId: number
126 serverAccount: AccountModel, 126 serverAccount: AccountModel
127 user?: MUserAccountId 127 user?: MUserAccountId
128 }) { 128 }) {
129 const accountIds = [ options.serverAccount.id ] 129 const accountIds = [ options.serverAccount.id ]
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts
index bcdda36e5..4ca17ebec 100644
--- a/server/models/video/video-playlist.ts
+++ b/server/models/video/video-playlist.ts
@@ -68,12 +68,12 @@ type AvailableForListOptions = {
68 type?: VideoPlaylistType 68 type?: VideoPlaylistType
69 accountId?: number 69 accountId?: number
70 videoChannelId?: number 70 videoChannelId?: number
71 listMyPlaylists?: boolean, 71 listMyPlaylists?: boolean
72 search?: string 72 search?: string
73} 73}
74 74
75@Scopes(() => ({ 75@Scopes(() => ({
76 [ ScopeNames.WITH_THUMBNAIL ]: { 76 [ScopeNames.WITH_THUMBNAIL]: {
77 include: [ 77 include: [
78 { 78 {
79 model: ThumbnailModel, 79 model: ThumbnailModel,
@@ -81,7 +81,7 @@ type AvailableForListOptions = {
81 } 81 }
82 ] 82 ]
83 }, 83 },
84 [ ScopeNames.WITH_VIDEOS_LENGTH ]: { 84 [ScopeNames.WITH_VIDEOS_LENGTH]: {
85 attributes: { 85 attributes: {
86 include: [ 86 include: [
87 [ 87 [
@@ -91,7 +91,7 @@ type AvailableForListOptions = {
91 ] 91 ]
92 } 92 }
93 } as FindOptions, 93 } as FindOptions,
94 [ ScopeNames.WITH_ACCOUNT ]: { 94 [ScopeNames.WITH_ACCOUNT]: {
95 include: [ 95 include: [
96 { 96 {
97 model: AccountModel, 97 model: AccountModel,
@@ -99,7 +99,7 @@ type AvailableForListOptions = {
99 } 99 }
100 ] 100 ]
101 }, 101 },
102 [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { 102 [ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY]: {
103 include: [ 103 include: [
104 { 104 {
105 model: AccountModel.scope(AccountScopeNames.SUMMARY), 105 model: AccountModel.scope(AccountScopeNames.SUMMARY),
@@ -111,7 +111,7 @@ type AvailableForListOptions = {
111 } 111 }
112 ] 112 ]
113 }, 113 },
114 [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { 114 [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: {
115 include: [ 115 include: [
116 { 116 {
117 model: AccountModel, 117 model: AccountModel,
@@ -123,7 +123,7 @@ type AvailableForListOptions = {
123 } 123 }
124 ] 124 ]
125 }, 125 },
126 [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { 126 [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => {
127 127
128 let whereActor: WhereOptions = {} 128 let whereActor: WhereOptions = {}
129 129
@@ -138,13 +138,13 @@ type AvailableForListOptions = {
138 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) 138 const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId)
139 139
140 whereActor = { 140 whereActor = {
141 [ Op.or ]: [ 141 [Op.or]: [
142 { 142 {
143 serverId: null 143 serverId: null
144 }, 144 },
145 { 145 {
146 serverId: { 146 serverId: {
147 [ Op.in ]: literal(inQueryInstanceFollow) 147 [Op.in]: literal(inQueryInstanceFollow)
148 } 148 }
149 } 149 }
150 ] 150 ]
@@ -172,7 +172,7 @@ type AvailableForListOptions = {
172 if (options.search) { 172 if (options.search) {
173 whereAnd.push({ 173 whereAnd.push({
174 name: { 174 name: {
175 [ Op.iLike ]: '%' + options.search + '%' 175 [Op.iLike]: '%' + options.search + '%'
176 } 176 }
177 }) 177 })
178 } 178 }
@@ -299,13 +299,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
299 299
300 static listForApi (options: { 300 static listForApi (options: {
301 followerActorId: number 301 followerActorId: number
302 start: number, 302 start: number
303 count: number, 303 count: number
304 sort: string, 304 sort: string
305 type?: VideoPlaylistType, 305 type?: VideoPlaylistType
306 accountId?: number, 306 accountId?: number
307 videoChannelId?: number, 307 videoChannelId?: number
308 listMyPlaylists?: boolean, 308 listMyPlaylists?: boolean
309 search?: string 309 search?: string
310 }) { 310 }) {
311 const query = { 311 const query = {
@@ -369,7 +369,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
369 model: VideoPlaylistElementModel.unscoped(), 369 model: VideoPlaylistElementModel.unscoped(),
370 where: { 370 where: {
371 videoId: { 371 videoId: {
372 [Op.in]: videoIds // FIXME: sequelize ANY seems broken 372 [Op.in]: videoIds
373 } 373 }
374 }, 374 },
375 required: true 375 required: true
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index eacffe186..1ec8d717e 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1,18 +1,7 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { maxBy, minBy } from 'lodash' 2import { maxBy, minBy } from 'lodash'
3import { join } from 'path' 3import { join } from 'path'
4import { 4import { CountOptions, FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize'
5 CountOptions,
6 FindOptions,
7 IncludeOptions,
8 ModelIndexesOptions,
9 Op,
10 QueryTypes,
11 ScopeOptions,
12 Sequelize,
13 Transaction,
14 WhereOptions
15} from 'sequelize'
16import { 5import {
17 AllowNull, 6 AllowNull,
18 BeforeDestroy, 7 BeforeDestroy,
@@ -136,8 +125,7 @@ import {
136 MVideoThumbnailBlacklist, 125 MVideoThumbnailBlacklist,
137 MVideoWithAllFiles, 126 MVideoWithAllFiles,
138 MVideoWithFile, 127 MVideoWithFile,
139 MVideoWithRights, 128 MVideoWithRights
140 MStreamingPlaylistFiles
141} from '../../typings/models' 129} from '../../typings/models'
142import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file' 130import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/models/video/video-file'
143import { MThumbnail } from '../../typings/models/video/thumbnail' 131import { MThumbnail } from '../../typings/models/video/thumbnail'
@@ -145,74 +133,6 @@ import { VideoFile } from '@shared/models/videos/video-file.model'
145import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' 133import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths'
146import validator from 'validator' 134import validator from 'validator'
147 135
148// FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
149const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [
150 buildTrigramSearchIndex('video_name_trigram', 'name'),
151
152 { fields: [ 'createdAt' ] },
153 {
154 fields: [
155 { name: 'publishedAt', order: 'DESC' },
156 { name: 'id', order: 'ASC' }
157 ]
158 },
159 { fields: [ 'duration' ] },
160 { fields: [ 'views' ] },
161 { fields: [ 'channelId' ] },
162 {
163 fields: [ 'originallyPublishedAt' ],
164 where: {
165 originallyPublishedAt: {
166 [Op.ne]: null
167 }
168 }
169 },
170 {
171 fields: [ 'category' ], // We don't care videos with an unknown category
172 where: {
173 category: {
174 [Op.ne]: null
175 }
176 }
177 },
178 {
179 fields: [ 'licence' ], // We don't care videos with an unknown licence
180 where: {
181 licence: {
182 [Op.ne]: null
183 }
184 }
185 },
186 {
187 fields: [ 'language' ], // We don't care videos with an unknown language
188 where: {
189 language: {
190 [Op.ne]: null
191 }
192 }
193 },
194 {
195 fields: [ 'nsfw' ], // Most of the videos are not NSFW
196 where: {
197 nsfw: true
198 }
199 },
200 {
201 fields: [ 'remote' ], // Only index local videos
202 where: {
203 remote: false
204 }
205 },
206 {
207 fields: [ 'uuid' ],
208 unique: true
209 },
210 {
211 fields: [ 'url' ],
212 unique: true
213 }
214]
215
216export enum ScopeNames { 136export enum ScopeNames {
217 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', 137 AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS',
218 FOR_API = 'FOR_API', 138 FOR_API = 'FOR_API',
@@ -267,7 +187,7 @@ export type AvailableForListIDsOptions = {
267} 187}
268 188
269@Scopes(() => ({ 189@Scopes(() => ({
270 [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { 190 [ScopeNames.FOR_API]: (options: ForAPIOptions) => {
271 const query: FindOptions = { 191 const query: FindOptions = {
272 include: [ 192 include: [
273 { 193 {
@@ -292,7 +212,7 @@ export type AvailableForListIDsOptions = {
292 if (options.ids) { 212 if (options.ids) {
293 query.where = { 213 query.where = {
294 id: { 214 id: {
295 [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken 215 [Op.in]: options.ids
296 } 216 }
297 } 217 }
298 } 218 }
@@ -316,7 +236,7 @@ export type AvailableForListIDsOptions = {
316 236
317 return query 237 return query
318 }, 238 },
319 [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { 239 [ScopeNames.AVAILABLE_FOR_LIST_IDS]: (options: AvailableForListIDsOptions) => {
320 const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] 240 const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : []
321 241
322 const query: FindOptions = { 242 const query: FindOptions = {
@@ -327,11 +247,11 @@ export type AvailableForListIDsOptions = {
327 const attributesType = options.attributesType || 'id' 247 const attributesType = options.attributesType || 'id'
328 248
329 if (attributesType === 'id') query.attributes = [ 'id' ] 249 if (attributesType === 'id') query.attributes = [ 'id' ]
330 else if (attributesType === 'none') query.attributes = [ ] 250 else if (attributesType === 'none') query.attributes = []
331 251
332 whereAnd.push({ 252 whereAnd.push({
333 id: { 253 id: {
334 [ Op.notIn ]: Sequelize.literal( 254 [Op.notIn]: Sequelize.literal(
335 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' 255 '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")'
336 ) 256 )
337 } 257 }
@@ -340,7 +260,7 @@ export type AvailableForListIDsOptions = {
340 if (options.serverAccountId) { 260 if (options.serverAccountId) {
341 whereAnd.push({ 261 whereAnd.push({
342 channelId: { 262 channelId: {
343 [ Op.notIn ]: Sequelize.literal( 263 [Op.notIn]: Sequelize.literal(
344 '(' + 264 '(' +
345 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + 265 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' +
346 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + 266 buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) +
@@ -353,15 +273,14 @@ export type AvailableForListIDsOptions = {
353 273
354 // Only list public/published videos 274 // Only list public/published videos
355 if (!options.filter || options.filter !== 'all-local') { 275 if (!options.filter || options.filter !== 'all-local') {
356
357 const publishWhere = { 276 const publishWhere = {
358 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding 277 // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding
359 [ Op.or ]: [ 278 [Op.or]: [
360 { 279 {
361 state: VideoState.PUBLISHED 280 state: VideoState.PUBLISHED
362 }, 281 },
363 { 282 {
364 [ Op.and ]: { 283 [Op.and]: {
365 state: VideoState.TO_TRANSCODE, 284 state: VideoState.TO_TRANSCODE,
366 waitTranscoding: false 285 waitTranscoding: false
367 } 286 }
@@ -448,7 +367,7 @@ export type AvailableForListIDsOptions = {
448 [Op.or]: [ 367 [Op.or]: [
449 { 368 {
450 id: { 369 id: {
451 [ Op.in ]: Sequelize.literal( 370 [Op.in]: Sequelize.literal(
452 '(' + 371 '(' +
453 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + 372 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' +
454 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 373 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
@@ -459,7 +378,7 @@ export type AvailableForListIDsOptions = {
459 }, 378 },
460 { 379 {
461 id: { 380 id: {
462 [ Op.in ]: Sequelize.literal( 381 [Op.in]: Sequelize.literal(
463 '(' + 382 '(' +
464 'SELECT "video"."id" AS "id" FROM "video" ' + 383 'SELECT "video"."id" AS "id" FROM "video" ' +
465 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + 384 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' +
@@ -479,7 +398,7 @@ export type AvailableForListIDsOptions = {
479 if (options.withFiles === true) { 398 if (options.withFiles === true) {
480 whereAnd.push({ 399 whereAnd.push({
481 id: { 400 id: {
482 [ Op.in ]: Sequelize.literal( 401 [Op.in]: Sequelize.literal(
483 '(SELECT "videoId" FROM "videoFile")' 402 '(SELECT "videoId" FROM "videoFile")'
484 ) 403 )
485 } 404 }
@@ -493,7 +412,7 @@ export type AvailableForListIDsOptions = {
493 412
494 whereAnd.push({ 413 whereAnd.push({
495 id: { 414 id: {
496 [ Op.in ]: Sequelize.literal( 415 [Op.in]: Sequelize.literal(
497 '(' + 416 '(' +
498 'SELECT "videoId" FROM "videoTag" ' + 417 'SELECT "videoId" FROM "videoTag" ' +
499 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 418 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -509,7 +428,7 @@ export type AvailableForListIDsOptions = {
509 428
510 whereAnd.push({ 429 whereAnd.push({
511 id: { 430 id: {
512 [ Op.in ]: Sequelize.literal( 431 [Op.in]: Sequelize.literal(
513 '(' + 432 '(' +
514 'SELECT "videoId" FROM "videoTag" ' + 433 'SELECT "videoId" FROM "videoTag" ' +
515 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + 434 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
@@ -529,7 +448,7 @@ export type AvailableForListIDsOptions = {
529 if (options.categoryOneOf) { 448 if (options.categoryOneOf) {
530 whereAnd.push({ 449 whereAnd.push({
531 category: { 450 category: {
532 [ Op.or ]: options.categoryOneOf 451 [Op.or]: options.categoryOneOf
533 } 452 }
534 }) 453 })
535 } 454 }
@@ -537,7 +456,7 @@ export type AvailableForListIDsOptions = {
537 if (options.licenceOneOf) { 456 if (options.licenceOneOf) {
538 whereAnd.push({ 457 whereAnd.push({
539 licence: { 458 licence: {
540 [ Op.or ]: options.licenceOneOf 459 [Op.or]: options.licenceOneOf
541 } 460 }
542 }) 461 })
543 } 462 }
@@ -552,12 +471,12 @@ export type AvailableForListIDsOptions = {
552 [Op.or]: [ 471 [Op.or]: [
553 { 472 {
554 language: { 473 language: {
555 [ Op.or ]: videoLanguages 474 [Op.or]: videoLanguages
556 } 475 }
557 }, 476 },
558 { 477 {
559 id: { 478 id: {
560 [ Op.in ]: Sequelize.literal( 479 [Op.in]: Sequelize.literal(
561 '(' + 480 '(' +
562 'SELECT "videoId" FROM "videoCaption" ' + 481 'SELECT "videoId" FROM "videoCaption" ' +
563 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + 482 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' +
@@ -591,12 +510,12 @@ export type AvailableForListIDsOptions = {
591 } 510 }
592 511
593 query.where = { 512 query.where = {
594 [ Op.and ]: whereAnd 513 [Op.and]: whereAnd
595 } 514 }
596 515
597 return query 516 return query
598 }, 517 },
599 [ ScopeNames.WITH_THUMBNAILS ]: { 518 [ScopeNames.WITH_THUMBNAILS]: {
600 include: [ 519 include: [
601 { 520 {
602 model: ThumbnailModel, 521 model: ThumbnailModel,
@@ -604,7 +523,7 @@ export type AvailableForListIDsOptions = {
604 } 523 }
605 ] 524 ]
606 }, 525 },
607 [ ScopeNames.WITH_USER_ID ]: { 526 [ScopeNames.WITH_USER_ID]: {
608 include: [ 527 include: [
609 { 528 {
610 attributes: [ 'accountId' ], 529 attributes: [ 'accountId' ],
@@ -620,7 +539,7 @@ export type AvailableForListIDsOptions = {
620 } 539 }
621 ] 540 ]
622 }, 541 },
623 [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { 542 [ScopeNames.WITH_ACCOUNT_DETAILS]: {
624 include: [ 543 include: [
625 { 544 {
626 model: VideoChannelModel.unscoped(), 545 model: VideoChannelModel.unscoped(),
@@ -672,10 +591,10 @@ export type AvailableForListIDsOptions = {
672 } 591 }
673 ] 592 ]
674 }, 593 },
675 [ ScopeNames.WITH_TAGS ]: { 594 [ScopeNames.WITH_TAGS]: {
676 include: [ TagModel ] 595 include: [ TagModel ]
677 }, 596 },
678 [ ScopeNames.WITH_BLACKLISTED ]: { 597 [ScopeNames.WITH_BLACKLISTED]: {
679 include: [ 598 include: [
680 { 599 {
681 attributes: [ 'id', 'reason', 'unfederated' ], 600 attributes: [ 'id', 'reason', 'unfederated' ],
@@ -684,7 +603,7 @@ export type AvailableForListIDsOptions = {
684 } 603 }
685 ] 604 ]
686 }, 605 },
687 [ ScopeNames.WITH_WEBTORRENT_FILES ]: (withRedundancies = false) => { 606 [ScopeNames.WITH_WEBTORRENT_FILES]: (withRedundancies = false) => {
688 let subInclude: any[] = [] 607 let subInclude: any[] = []
689 608
690 if (withRedundancies === true) { 609 if (withRedundancies === true) {
@@ -708,7 +627,7 @@ export type AvailableForListIDsOptions = {
708 ] 627 ]
709 } 628 }
710 }, 629 },
711 [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { 630 [ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => {
712 const subInclude: IncludeOptions[] = [ 631 const subInclude: IncludeOptions[] = [
713 { 632 {
714 model: VideoFileModel.unscoped(), 633 model: VideoFileModel.unscoped(),
@@ -735,7 +654,7 @@ export type AvailableForListIDsOptions = {
735 ] 654 ]
736 } 655 }
737 }, 656 },
738 [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { 657 [ScopeNames.WITH_SCHEDULED_UPDATE]: {
739 include: [ 658 include: [
740 { 659 {
741 model: ScheduleVideoUpdateModel.unscoped(), 660 model: ScheduleVideoUpdateModel.unscoped(),
@@ -743,7 +662,7 @@ export type AvailableForListIDsOptions = {
743 } 662 }
744 ] 663 ]
745 }, 664 },
746 [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { 665 [ScopeNames.WITH_USER_HISTORY]: (userId: number) => {
747 return { 666 return {
748 include: [ 667 include: [
749 { 668 {
@@ -760,7 +679,72 @@ export type AvailableForListIDsOptions = {
760})) 679}))
761@Table({ 680@Table({
762 tableName: 'video', 681 tableName: 'video',
763 indexes 682 indexes: [
683 buildTrigramSearchIndex('video_name_trigram', 'name'),
684
685 { fields: [ 'createdAt' ] },
686 {
687 fields: [
688 { name: 'publishedAt', order: 'DESC' },
689 { name: 'id', order: 'ASC' }
690 ]
691 },
692 { fields: [ 'duration' ] },
693 { fields: [ 'views' ] },
694 { fields: [ 'channelId' ] },
695 {
696 fields: [ 'originallyPublishedAt' ],
697 where: {
698 originallyPublishedAt: {
699 [Op.ne]: null
700 }
701 }
702 },
703 {
704 fields: [ 'category' ], // We don't care videos with an unknown category
705 where: {
706 category: {
707 [Op.ne]: null
708 }
709 }
710 },
711 {
712 fields: [ 'licence' ], // We don't care videos with an unknown licence
713 where: {
714 licence: {
715 [Op.ne]: null
716 }
717 }
718 },
719 {
720 fields: [ 'language' ], // We don't care videos with an unknown language
721 where: {
722 language: {
723 [Op.ne]: null
724 }
725 }
726 },
727 {
728 fields: [ 'nsfw' ], // Most of the videos are not NSFW
729 where: {
730 nsfw: true
731 }
732 },
733 {
734 fields: [ 'remote' ], // Only index local videos
735 where: {
736 remote: false
737 }
738 },
739 {
740 fields: [ 'uuid' ],
741 unique: true
742 },
743 {
744 fields: [ 'url' ],
745 unique: true
746 }
747 ]
764}) 748})
765export class VideoModel extends Model<VideoModel> { 749export class VideoModel extends Model<VideoModel> {
766 750
@@ -1031,7 +1015,7 @@ export class VideoModel extends Model<VideoModel> {
1031 }, 1015 },
1032 onDelete: 'cascade', 1016 onDelete: 'cascade',
1033 hooks: true, 1017 hooks: true,
1034 [ 'separate' as any ]: true 1018 ['separate' as any]: true
1035 }) 1019 })
1036 VideoCaptions: VideoCaptionModel[] 1020 VideoCaptions: VideoCaptionModel[]
1037 1021
@@ -1127,16 +1111,16 @@ export class VideoModel extends Model<VideoModel> {
1127 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings 1111 order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings
1128 where: { 1112 where: {
1129 id: { 1113 id: {
1130 [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') 1114 [Op.in]: Sequelize.literal('(' + rawQuery + ')')
1131 }, 1115 },
1132 [ Op.or ]: [ 1116 [Op.or]: [
1133 { privacy: VideoPrivacy.PUBLIC }, 1117 { privacy: VideoPrivacy.PUBLIC },
1134 { privacy: VideoPrivacy.UNLISTED } 1118 { privacy: VideoPrivacy.UNLISTED }
1135 ] 1119 ]
1136 }, 1120 },
1137 include: [ 1121 include: [
1138 { 1122 {
1139 attributes: [ 'language' ], 1123 attributes: [ 'language', 'fileUrl' ],
1140 model: VideoCaptionModel.unscoped(), 1124 model: VideoCaptionModel.unscoped(),
1141 required: false 1125 required: false
1142 }, 1126 },
@@ -1146,10 +1130,10 @@ export class VideoModel extends Model<VideoModel> {
1146 required: false, 1130 required: false,
1147 // We only want videos shared by this actor 1131 // We only want videos shared by this actor
1148 where: { 1132 where: {
1149 [ Op.and ]: [ 1133 [Op.and]: [
1150 { 1134 {
1151 id: { 1135 id: {
1152 [ Op.not ]: null 1136 [Op.not]: null
1153 } 1137 }
1154 }, 1138 },
1155 { 1139 {
@@ -1199,8 +1183,8 @@ export class VideoModel extends Model<VideoModel> {
1199 // totals: totalVideos + totalVideoShares 1183 // totals: totalVideos + totalVideoShares
1200 let totalVideos = 0 1184 let totalVideos = 0
1201 let totalVideoShares = 0 1185 let totalVideoShares = 0
1202 if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) 1186 if (totals[0]) totalVideos = parseInt(totals[0].total, 10)
1203 if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) 1187 if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10)
1204 1188
1205 const total = totalVideos + totalVideoShares 1189 const total = totalVideos + totalVideoShares
1206 return { 1190 return {
@@ -1243,7 +1227,7 @@ export class VideoModel extends Model<VideoModel> {
1243 baseQuery = Object.assign(baseQuery, { 1227 baseQuery = Object.assign(baseQuery, {
1244 where: { 1228 where: {
1245 name: { 1229 name: {
1246 [ Op.iLike ]: '%' + search + '%' 1230 [Op.iLike]: '%' + search + '%'
1247 } 1231 }
1248 } 1232 }
1249 }) 1233 })
@@ -1273,25 +1257,25 @@ export class VideoModel extends Model<VideoModel> {
1273 } 1257 }
1274 1258
1275 static async listForApi (options: { 1259 static async listForApi (options: {
1276 start: number, 1260 start: number
1277 count: number, 1261 count: number
1278 sort: string, 1262 sort: string
1279 nsfw: boolean, 1263 nsfw: boolean
1280 includeLocalVideos: boolean, 1264 includeLocalVideos: boolean
1281 withFiles: boolean, 1265 withFiles: boolean
1282 categoryOneOf?: number[], 1266 categoryOneOf?: number[]
1283 licenceOneOf?: number[], 1267 licenceOneOf?: number[]
1284 languageOneOf?: string[], 1268 languageOneOf?: string[]
1285 tagsOneOf?: string[], 1269 tagsOneOf?: string[]
1286 tagsAllOf?: string[], 1270 tagsAllOf?: string[]
1287 filter?: VideoFilter, 1271 filter?: VideoFilter
1288 accountId?: number, 1272 accountId?: number
1289 videoChannelId?: number, 1273 videoChannelId?: number
1290 followerActorId?: number 1274 followerActorId?: number
1291 videoPlaylistId?: number, 1275 videoPlaylistId?: number
1292 trendingDays?: number, 1276 trendingDays?: number
1293 user?: MUserAccountId, 1277 user?: MUserAccountId
1294 historyOfUser?: MUserId, 1278 historyOfUser?: MUserId
1295 countVideos?: boolean 1279 countVideos?: boolean
1296 }) { 1280 }) {
1297 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { 1281 if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
@@ -1357,7 +1341,7 @@ export class VideoModel extends Model<VideoModel> {
1357 tagsAllOf?: string[] 1341 tagsAllOf?: string[]
1358 durationMin?: number // seconds 1342 durationMin?: number // seconds
1359 durationMax?: number // seconds 1343 durationMax?: number // seconds
1360 user?: MUserAccountId, 1344 user?: MUserAccountId
1361 filter?: VideoFilter 1345 filter?: VideoFilter
1362 }) { 1346 }) {
1363 const whereAnd = [] 1347 const whereAnd = []
@@ -1365,8 +1349,8 @@ export class VideoModel extends Model<VideoModel> {
1365 if (options.startDate || options.endDate) { 1349 if (options.startDate || options.endDate) {
1366 const publishedAtRange = {} 1350 const publishedAtRange = {}
1367 1351
1368 if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate 1352 if (options.startDate) publishedAtRange[Op.gte] = options.startDate
1369 if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate 1353 if (options.endDate) publishedAtRange[Op.lte] = options.endDate
1370 1354
1371 whereAnd.push({ publishedAt: publishedAtRange }) 1355 whereAnd.push({ publishedAt: publishedAtRange })
1372 } 1356 }
@@ -1374,8 +1358,8 @@ export class VideoModel extends Model<VideoModel> {
1374 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { 1358 if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) {
1375 const originallyPublishedAtRange = {} 1359 const originallyPublishedAtRange = {}
1376 1360
1377 if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate 1361 if (options.originallyPublishedStartDate) originallyPublishedAtRange[Op.gte] = options.originallyPublishedStartDate
1378 if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate 1362 if (options.originallyPublishedEndDate) originallyPublishedAtRange[Op.lte] = options.originallyPublishedEndDate
1379 1363
1380 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) 1364 whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange })
1381 } 1365 }
@@ -1383,8 +1367,8 @@ export class VideoModel extends Model<VideoModel> {
1383 if (options.durationMin || options.durationMax) { 1367 if (options.durationMin || options.durationMax) {
1384 const durationRange = {} 1368 const durationRange = {}
1385 1369
1386 if (options.durationMin) durationRange[ Op.gte ] = options.durationMin 1370 if (options.durationMin) durationRange[Op.gte] = options.durationMin
1387 if (options.durationMax) durationRange[ Op.lte ] = options.durationMax 1371 if (options.durationMax) durationRange[Op.lte] = options.durationMax
1388 1372
1389 whereAnd.push({ duration: durationRange }) 1373 whereAnd.push({ duration: durationRange })
1390 } 1374 }
@@ -1395,7 +1379,7 @@ export class VideoModel extends Model<VideoModel> {
1395 if (options.search) { 1379 if (options.search) {
1396 const trigramSearch = { 1380 const trigramSearch = {
1397 id: { 1381 id: {
1398 [ Op.in ]: Sequelize.literal( 1382 [Op.in]: Sequelize.literal(
1399 '(' + 1383 '(' +
1400 'SELECT "video"."id" FROM "video" ' + 1384 'SELECT "video"."id" FROM "video" ' +
1401 'WHERE ' + 1385 'WHERE ' +
@@ -1593,8 +1577,8 @@ export class VideoModel extends Model<VideoModel> {
1593 } 1577 }
1594 1578
1595 static loadForGetAPI (parameters: { 1579 static loadForGetAPI (parameters: {
1596 id: number | string, 1580 id: number | string
1597 t?: Transaction, 1581 t?: Transaction
1598 userId?: number 1582 userId?: number
1599 }): Bluebird<MVideoDetails> { 1583 }): Bluebird<MVideoDetails> {
1600 const { id, t, userId } = parameters 1584 const { id, t, userId } = parameters
@@ -1660,9 +1644,9 @@ export class VideoModel extends Model<VideoModel> {
1660 static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { 1644 static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) {
1661 // Instances only share videos 1645 // Instances only share videos
1662 const query = 'SELECT 1 FROM "videoShare" ' + 1646 const query = 'SELECT 1 FROM "videoShare" ' +
1663 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + 1647 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' +
1664 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + 1648 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' +
1665 'LIMIT 1' 1649 'LIMIT 1'
1666 1650
1667 const options = { 1651 const options = {
1668 type: QueryTypes.SELECT as QueryTypes.SELECT, 1652 type: QueryTypes.SELECT as QueryTypes.SELECT,
@@ -1694,7 +1678,7 @@ export class VideoModel extends Model<VideoModel> {
1694 } 1678 }
1695 1679
1696 return VideoModel.findAll(query) 1680 return VideoModel.findAll(query)
1697 .then(videos => videos.map(v => v.id)) 1681 .then(videos => videos.map(v => v.id))
1698 } 1682 }
1699 1683
1700 // threshold corresponds to how many video the field should have to be returned 1684 // threshold corresponds to how many video the field should have to be returned
@@ -1714,14 +1698,14 @@ export class VideoModel extends Model<VideoModel> {
1714 limit: count, 1698 limit: count,
1715 group: field, 1699 group: field,
1716 having: Sequelize.where( 1700 having: Sequelize.where(
1717 Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } 1701 Sequelize.fn('COUNT', Sequelize.col(field)), { [Op.gte]: threshold }
1718 ), 1702 ),
1719 order: [ (this.sequelize as any).random() ] 1703 order: [ (this.sequelize as any).random() ]
1720 } 1704 }
1721 1705
1722 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) 1706 return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] })
1723 .findAll(query) 1707 .findAll(query)
1724 .then(rows => rows.map(r => r[ field ])) 1708 .then(rows => rows.map(r => r[field]))
1725 } 1709 }
1726 1710
1727 static buildTrendingQuery (trendingDays: number) { 1711 static buildTrendingQuery (trendingDays: number) {
@@ -1732,7 +1716,7 @@ export class VideoModel extends Model<VideoModel> {
1732 required: false, 1716 required: false,
1733 where: { 1717 where: {
1734 startDate: { 1718 startDate: {
1735 [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) 1719 [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays)
1736 } 1720 }
1737 } 1721 }
1738 } 1722 }
@@ -1815,23 +1799,23 @@ export class VideoModel extends Model<VideoModel> {
1815 } 1799 }
1816 1800
1817 static getCategoryLabel (id: number) { 1801 static getCategoryLabel (id: number) {
1818 return VIDEO_CATEGORIES[ id ] || 'Misc' 1802 return VIDEO_CATEGORIES[id] || 'Misc'
1819 } 1803 }
1820 1804
1821 static getLicenceLabel (id: number) { 1805 static getLicenceLabel (id: number) {
1822 return VIDEO_LICENCES[ id ] || 'Unknown' 1806 return VIDEO_LICENCES[id] || 'Unknown'
1823 } 1807 }
1824 1808
1825 static getLanguageLabel (id: string) { 1809 static getLanguageLabel (id: string) {
1826 return VIDEO_LANGUAGES[ id ] || 'Unknown' 1810 return VIDEO_LANGUAGES[id] || 'Unknown'
1827 } 1811 }
1828 1812
1829 static getPrivacyLabel (id: number) { 1813 static getPrivacyLabel (id: number) {
1830 return VIDEO_PRIVACIES[ id ] || 'Unknown' 1814 return VIDEO_PRIVACIES[id] || 'Unknown'
1831 } 1815 }
1832 1816
1833 static getStateLabel (id: number) { 1817 static getStateLabel (id: number) {
1834 return VIDEO_STATES[ id ] || 'Unknown' 1818 return VIDEO_STATES[id] || 'Unknown'
1835 } 1819 }
1836 1820
1837 isBlacklisted () { 1821 isBlacklisted () {
@@ -1843,7 +1827,7 @@ export class VideoModel extends Model<VideoModel> {
1843 this.VideoChannel.Account.isBlocked() 1827 this.VideoChannel.Account.isBlocked()
1844 } 1828 }
1845 1829
1846 getQualityFileBy <T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { 1830 getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) {
1847 if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { 1831 if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) {
1848 const file = fun(this.VideoFiles, file => file.resolution) 1832 const file = fun(this.VideoFiles, file => file.resolution)
1849 1833
@@ -1861,15 +1845,15 @@ export class VideoModel extends Model<VideoModel> {
1861 return undefined 1845 return undefined
1862 } 1846 }
1863 1847
1864 getMaxQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { 1848 getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo {
1865 return this.getQualityFileBy(maxBy) 1849 return this.getQualityFileBy(maxBy)
1866 } 1850 }
1867 1851
1868 getMinQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { 1852 getMinQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo {
1869 return this.getQualityFileBy(minBy) 1853 return this.getQualityFileBy(minBy)
1870 } 1854 }
1871 1855
1872 getWebTorrentFile <T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { 1856 getWebTorrentFile<T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo {
1873 if (Array.isArray(this.VideoFiles) === false) return undefined 1857 if (Array.isArray(this.VideoFiles) === false) return undefined
1874 1858
1875 const file = this.VideoFiles.find(f => f.resolution === resolution) 1859 const file = this.VideoFiles.find(f => f.resolution === resolution)
@@ -1992,8 +1976,8 @@ export class VideoModel extends Model<VideoModel> {
1992 } 1976 }
1993 1977
1994 this.VideoStreamingPlaylists = this.VideoStreamingPlaylists 1978 this.VideoStreamingPlaylists = this.VideoStreamingPlaylists
1995 .filter(s => s.type !== VideoStreamingPlaylistType.HLS) 1979 .filter(s => s.type !== VideoStreamingPlaylistType.HLS)
1996 .concat(toAdd) 1980 .concat(toAdd)
1997 } 1981 }
1998 1982
1999 removeFile (videoFile: MVideoFile, isRedundancy = false) { 1983 removeFile (videoFile: MVideoFile, isRedundancy = false) {
@@ -2014,7 +1998,7 @@ export class VideoModel extends Model<VideoModel> {
2014 await remove(directoryPath) 1998 await remove(directoryPath)
2015 1999
2016 if (isRedundancy !== true) { 2000 if (isRedundancy !== true) {
2017 let streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo 2001 const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo
2018 streamingPlaylistWithFiles.Video = this 2002 streamingPlaylistWithFiles.Video = this
2019 2003
2020 if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { 2004 if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) {
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts
index 34c6be49b..d16f05108 100644
--- a/server/tests/api/activitypub/client.ts
+++ b/server/tests/api/activitypub/client.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -6,8 +6,6 @@ import {
6 cleanupTests, 6 cleanupTests,
7 doubleFollow, 7 doubleFollow,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 flushTests,
10 killallServers,
11 makeActivityPubGetRequest, 9 makeActivityPubGetRequest,
12 ServerInfo, 10 ServerInfo,
13 setAccessTokensToServers, 11 setAccessTokensToServers,
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts
index 3d54c2042..35fd94eed 100644
--- a/server/tests/api/activitypub/fetch.ts
+++ b/server/tests/api/activitypub/fetch.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -8,9 +8,7 @@ import {
8 createUser, 8 createUser,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests,
12 getVideosListSort, 11 getVideosListSort,
13 killallServers,
14 ServerInfo, 12 ServerInfo,
15 setAccessTokensToServers, 13 setAccessTokensToServers,
16 setActorField, 14 setActorField,
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts
index 8c00ba3d6..60d95b823 100644
--- a/server/tests/api/activitypub/helpers.ts
+++ b/server/tests/api/activitypub/helpers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index aa4bc6c0f..232c5d823 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -43,32 +43,32 @@ describe('Test AP refresher', function () {
43 await setDefaultVideoChannel(servers) 43 await setDefaultVideoChannel(servers)
44 44
45 { 45 {
46 videoUUID1 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video1' })).uuid 46 videoUUID1 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video1' })).uuid
47 videoUUID2 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video2' })).uuid 47 videoUUID2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video2' })).uuid
48 videoUUID3 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video3' })).uuid 48 videoUUID3 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video3' })).uuid
49 } 49 }
50 50
51 { 51 {
52 const a1 = await generateUserAccessToken(servers[ 1 ], 'user1') 52 const a1 = await generateUserAccessToken(servers[1], 'user1')
53 await uploadVideo(servers[ 1 ].url, a1, { name: 'video4' }) 53 await uploadVideo(servers[1].url, a1, { name: 'video4' })
54 54
55 const a2 = await generateUserAccessToken(servers[ 1 ], 'user2') 55 const a2 = await generateUserAccessToken(servers[1], 'user2')
56 await uploadVideo(servers[ 1 ].url, a2, { name: 'video5' }) 56 await uploadVideo(servers[1].url, a2, { name: 'video5' })
57 } 57 }
58 58
59 { 59 {
60 const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } 60 const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
61 const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) 61 const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
62 playlistUUID1 = res.body.videoPlaylist.uuid 62 playlistUUID1 = res.body.videoPlaylist.uuid
63 } 63 }
64 64
65 { 65 {
66 const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } 66 const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id }
67 const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) 67 const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs })
68 playlistUUID2 = res.body.videoPlaylist.uuid 68 playlistUUID2 = res.body.videoPlaylist.uuid
69 } 69 }
70 70
71 await doubleFollow(servers[ 0 ], servers[ 1 ]) 71 await doubleFollow(servers[0], servers[1])
72 }) 72 })
73 73
74 describe('Videos refresher', function () { 74 describe('Videos refresher', function () {
@@ -79,34 +79,34 @@ describe('Test AP refresher', function () {
79 await wait(10000) 79 await wait(10000)
80 80
81 // Change UUID so the remote server returns a 404 81 // Change UUID so the remote server returns a 404
82 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') 82 await setVideoField(servers[1].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
83 83
84 await getVideo(servers[ 0 ].url, videoUUID1) 84 await getVideo(servers[0].url, videoUUID1)
85 await getVideo(servers[ 0 ].url, videoUUID2) 85 await getVideo(servers[0].url, videoUUID2)
86 86
87 await waitJobs(servers) 87 await waitJobs(servers)
88 88
89 await getVideo(servers[ 0 ].url, videoUUID1, 404) 89 await getVideo(servers[0].url, videoUUID1, 404)
90 await getVideo(servers[ 0 ].url, videoUUID2, 200) 90 await getVideo(servers[0].url, videoUUID2, 200)
91 }) 91 })
92 92
93 it('Should not update a remote video if the remote instance is down', async function () { 93 it('Should not update a remote video if the remote instance is down', async function () {
94 this.timeout(70000) 94 this.timeout(70000)
95 95
96 killallServers([ servers[ 1 ] ]) 96 killallServers([ servers[1] ])
97 97
98 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') 98 await setVideoField(servers[1].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
99 99
100 // Video will need a refresh 100 // Video will need a refresh
101 await wait(10000) 101 await wait(10000)
102 102
103 await getVideo(servers[ 0 ].url, videoUUID3) 103 await getVideo(servers[0].url, videoUUID3)
104 // The refresh should fail 104 // The refresh should fail
105 await waitJobs([ servers[ 0 ] ]) 105 await waitJobs([ servers[0] ])
106 106
107 await reRunServer(servers[ 1 ]) 107 await reRunServer(servers[1])
108 108
109 await getVideo(servers[ 0 ].url, videoUUID3, 200) 109 await getVideo(servers[0].url, videoUUID3, 200)
110 }) 110 })
111 }) 111 })
112 112
@@ -118,16 +118,16 @@ describe('Test AP refresher', function () {
118 await wait(10000) 118 await wait(10000)
119 119
120 // Change actor name so the remote server returns a 404 120 // Change actor name so the remote server returns a 404
121 const to = 'http://localhost:' + servers[ 1 ].port + '/accounts/user2' 121 const to = 'http://localhost:' + servers[1].port + '/accounts/user2'
122 await setActorField(servers[ 1 ].internalServerNumber, to, 'preferredUsername', 'toto') 122 await setActorField(servers[1].internalServerNumber, to, 'preferredUsername', 'toto')
123 123
124 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port) 124 await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port)
125 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port) 125 await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port)
126 126
127 await waitJobs(servers) 127 await waitJobs(servers)
128 128
129 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port, 200) 129 await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port, 200)
130 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port, 404) 130 await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port, 404)
131 }) 131 })
132 }) 132 })
133 133
@@ -139,15 +139,15 @@ describe('Test AP refresher', function () {
139 await wait(10000) 139 await wait(10000)
140 140
141 // Change UUID so the remote server returns a 404 141 // Change UUID so the remote server returns a 404
142 await setPlaylistField(servers[ 1 ].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') 142 await setPlaylistField(servers[1].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
143 143
144 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1) 144 await getVideoPlaylist(servers[0].url, playlistUUID1)
145 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2) 145 await getVideoPlaylist(servers[0].url, playlistUUID2)
146 146
147 await waitJobs(servers) 147 await waitJobs(servers)
148 148
149 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1, 200) 149 await getVideoPlaylist(servers[0].url, playlistUUID1, 200)
150 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2, 404) 150 await getVideoPlaylist(servers[0].url, playlistUUID2, 404)
151 }) 151 })
152 }) 152 })
153 153
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index dc960c5c3..7e58bf065 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -1,15 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import { cleanupTests, closeAllSequelize, flushAndRunMultipleServers, ServerInfo, setActorField } from '../../../../shared/extra-utils'
6 cleanupTests,
7 closeAllSequelize,
8 flushAndRunMultipleServers,
9 killallServers,
10 ServerInfo,
11 setActorField
12} from '../../../../shared/extra-utils'
13import { HTTP_SIGNATURE } from '../../../initializers/constants' 6import { HTTP_SIGNATURE } from '../../../initializers/constants'
14import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' 7import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
15import * as chai from 'chai' 8import * as chai from 'chai'
@@ -33,7 +26,7 @@ function getAnnounceWithoutContext (server2: ServerInfo) {
33 if (Array.isArray(json[key])) { 26 if (Array.isArray(json[key])) {
34 result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`)) 27 result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`))
35 } else { 28 } else {
36 result[ key ] = json[ key ].replace(':9002', `:${server2.port}`) 29 result[key] = json[key].replace(':9002', `:${server2.port}`)
37 } 30 }
38 } 31 }
39 32
diff --git a/server/tests/api/check-params/accounts.ts b/server/tests/api/check-params/accounts.ts
index 4f79685bd..c29af7cd7 100644
--- a/server/tests/api/check-params/accounts.ts
+++ b/server/tests/api/check-params/accounts.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts
index 0661676ce..fb459f756 100644
--- a/server/tests/api/check-params/blocklist.ts
+++ b/server/tests/api/check-params/blocklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index 443fbcb60..f1a79806b 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts
index b3051945e..b2126b9b0 100644
--- a/server/tests/api/check-params/contact-form.ts
+++ b/server/tests/api/check-params/contact-form.ts
@@ -1,22 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import { cleanupTests, flushAndRunServer, immutableAssign, killallServers, reRunServer, ServerInfo } from '../../../../shared/extra-utils'
6 flushTests,
7 immutableAssign,
8 killallServers,
9 reRunServer,
10 flushAndRunServer,
11 ServerInfo,
12 setAccessTokensToServers, cleanupTests
13} from '../../../../shared/extra-utils'
14import {
15 checkBadCountPagination,
16 checkBadSortPagination,
17 checkBadStartPagination
18} from '../../../../shared/extra-utils/requests/check-api-params'
19import { getAccount } from '../../../../shared/extra-utils/users/accounts'
20import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' 6import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form'
21import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 7import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
22 8
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts
index 8dad26723..5fac73485 100644
--- a/server/tests/api/check-params/debug.ts
+++ b/server/tests/api/check-params/debug.ts
@@ -1,15 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, cleanupTests 11 userLogin
13} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
14import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' 13import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests'
15 14
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts
index be2a603a3..2c2224a45 100644
--- a/server/tests/api/check-params/follows.ts
+++ b/server/tests/api/check-params/follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts
index 22e237964..8f4af8d16 100644
--- a/server/tests/api/check-params/jobs.ts
+++ b/server/tests/api/check-params/jobs.ts
@@ -1,16 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, 11 userLogin
13 cleanupTests
14} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
15import { 13import {
16 checkBadCountPagination, 14 checkBadCountPagination,
diff --git a/server/tests/api/check-params/logs.ts b/server/tests/api/check-params/logs.ts
index f9d96bcc0..719da54e6 100644
--- a/server/tests/api/check-params/logs.ts
+++ b/server/tests/api/check-params/logs.ts
@@ -1,16 +1,14 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 createUser, 7 createUser,
7 flushTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
10 ServerInfo, 9 ServerInfo,
11 setAccessTokensToServers, 10 setAccessTokensToServers,
12 userLogin, 11 userLogin
13 cleanupTests
14} from '../../../../shared/extra-utils' 12} from '../../../../shared/extra-utils'
15import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' 13import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests'
16 14
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts
index 9553bce17..cf80b35c2 100644
--- a/server/tests/api/check-params/plugins.ts
+++ b/server/tests/api/check-params/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts
index 6471da840..b2370a094 100644
--- a/server/tests/api/check-params/redundancy.ts
+++ b/server/tests/api/check-params/redundancy.ts
@@ -1,23 +1,27 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 checkBadCountPagination,
7 checkBadSortPagination,
8 checkBadStartPagination,
6 cleanupTests, 9 cleanupTests,
7 createUser, 10 createUser,
8 doubleFollow, 11 doubleFollow,
9 flushAndRunMultipleServers, 12 flushAndRunMultipleServers, makeDeleteRequest,
10 flushTests, 13 makeGetRequest, makePostBodyRequest,
11 killallServers,
12 makePutBodyRequest, 14 makePutBodyRequest,
13 ServerInfo, 15 ServerInfo,
14 setAccessTokensToServers, 16 setAccessTokensToServers, uploadVideoAndGetId,
15 userLogin 17 userLogin, waitJobs
16} from '../../../../shared/extra-utils' 18} from '../../../../shared/extra-utils'
17 19
18describe('Test server redundancy API validators', function () { 20describe('Test server redundancy API validators', function () {
19 let servers: ServerInfo[] 21 let servers: ServerInfo[]
20 let userAccessToken = null 22 let userAccessToken = null
23 let videoIdLocal: number
24 let videoIdRemote: number
21 25
22 // --------------------------------------------------------------- 26 // ---------------------------------------------------------------
23 27
@@ -34,11 +38,136 @@ describe('Test server redundancy API validators', function () {
34 password: 'password' 38 password: 'password'
35 } 39 }
36 40
37 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 41 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
38 userAccessToken = await userLogin(servers[0], user) 42 userAccessToken = await userLogin(servers[0], user)
43
44 videoIdLocal = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video' })).id
45 videoIdRemote = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video' })).id
46
47 await waitJobs(servers)
48 })
49
50 describe('When listing redundancies', function () {
51 const path = '/api/v1/server/redundancy/videos'
52
53 let url: string
54 let token: string
55
56 before(function () {
57 url = servers[0].url
58 token = servers[0].accessToken
59 })
60
61 it('Should fail with an invalid token', async function () {
62 await makeGetRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 })
63 })
64
65 it('Should fail if the user is not an administrator', async function () {
66 await makeGetRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 })
67 })
68
69 it('Should fail with a bad start pagination', async function () {
70 await checkBadStartPagination(url, path, servers[0].accessToken)
71 })
72
73 it('Should fail with a bad count pagination', async function () {
74 await checkBadCountPagination(url, path, servers[0].accessToken)
75 })
76
77 it('Should fail with an incorrect sort', async function () {
78 await checkBadSortPagination(url, path, servers[0].accessToken)
79 })
80
81 it('Should fail with a bad target', async function () {
82 await makeGetRequest({ url, path, token, query: { target: 'bad target' } })
83 })
84
85 it('Should fail without target', async function () {
86 await makeGetRequest({ url, path, token })
87 })
88
89 it('Should succeed with the correct params', async function () {
90 await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, statusCodeExpected: 200 })
91 })
92 })
93
94 describe('When manually adding a redundancy', function () {
95 const path = '/api/v1/server/redundancy/videos'
96
97 let url: string
98 let token: string
99
100 before(function () {
101 url = servers[0].url
102 token = servers[0].accessToken
103 })
104
105 it('Should fail with an invalid token', async function () {
106 await makePostBodyRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 })
107 })
108
109 it('Should fail if the user is not an administrator', async function () {
110 await makePostBodyRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 })
111 })
112
113 it('Should fail without a video id', async function () {
114 await makePostBodyRequest({ url, path, token })
115 })
116
117 it('Should fail with an incorrect video id', async function () {
118 await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } })
119 })
120
121 it('Should fail with a not found video id', async function () {
122 await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, statusCodeExpected: 404 })
123 })
124
125 it('Should fail with a local a video id', async function () {
126 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } })
127 })
128
129 it('Should succeed with the correct params', async function () {
130 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 204 })
131 })
132
133 it('Should fail if the video is already duplicated', async function () {
134 this.timeout(30000)
135
136 await waitJobs(servers)
137
138 await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 409 })
139 })
140 })
141
142 describe('When manually removing a redundancy', function () {
143 const path = '/api/v1/server/redundancy/videos/'
144
145 let url: string
146 let token: string
147
148 before(function () {
149 url = servers[0].url
150 token = servers[0].accessToken
151 })
152
153 it('Should fail with an invalid token', async function () {
154 await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', statusCodeExpected: 401 })
155 })
156
157 it('Should fail if the user is not an administrator', async function () {
158 await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, statusCodeExpected: 403 })
159 })
160
161 it('Should fail with an incorrect video id', async function () {
162 await makeDeleteRequest({ url, path: path + 'toto', token })
163 })
164
165 it('Should fail with a not found video redundancy', async function () {
166 await makeDeleteRequest({ url, path: path + '454545', token, statusCodeExpected: 404 })
167 })
39 }) 168 })
40 169
41 describe('When updating redundancy', function () { 170 describe('When updating server redundancy', function () {
42 const path = '/api/v1/server/redundancy' 171 const path = '/api/v1/server/redundancy'
43 172
44 it('Should fail with an invalid token', async function () { 173 it('Should fail with an invalid token', async function () {
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts
index 8ad9d98bf..f8d0cd4ec 100644
--- a/server/tests/api/check-params/search.ts
+++ b/server/tests/api/check-params/search.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts
index d15753aed..457adfaab 100644
--- a/server/tests/api/check-params/services.ts
+++ b/server/tests/api/check-params/services.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
index 3b06be7ef..2048fa667 100644
--- a/server/tests/api/check-params/user-notifications.ts
+++ b/server/tests/api/check-params/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as io from 'socket.io-client' 4import * as io from 'socket.io-client'
diff --git a/server/tests/api/check-params/user-subscriptions.ts b/server/tests/api/check-params/user-subscriptions.ts
index fa36c4078..1edba4d64 100644
--- a/server/tests/api/check-params/user-subscriptions.ts
+++ b/server/tests/api/check-params/user-subscriptions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5d5af284c..f448bb2a6 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
@@ -50,6 +50,7 @@ describe('Test users API validators', function () {
50 let serverWithRegistrationDisabled: ServerInfo 50 let serverWithRegistrationDisabled: ServerInfo
51 let userAccessToken = '' 51 let userAccessToken = ''
52 let moderatorAccessToken = '' 52 let moderatorAccessToken = ''
53 // eslint-disable-next-line @typescript-eslint/no-unused-vars
53 let channelId: number 54 let channelId: number
54 55
55 // --------------------------------------------------------------- 56 // ---------------------------------------------------------------
@@ -120,7 +121,7 @@ describe('Test users API validators', function () {
120 121
121 { 122 {
122 const res = await getMyUserInformation(server.url, server.accessToken) 123 const res = await getMyUserInformation(server.url, server.accessToken)
123 channelId = res.body.videoChannels[ 0 ].id 124 channelId = res.body.videoChannels[0].id
124 } 125 }
125 126
126 { 127 {
@@ -529,7 +530,7 @@ describe('Test users API validators', function () {
529 it('Should fail without an incorrect input file', async function () { 530 it('Should fail without an incorrect input file', async function () {
530 const fields = {} 531 const fields = {}
531 const attaches = { 532 const attaches = {
532 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') 533 avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
533 } 534 }
534 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 535 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
535 }) 536 })
@@ -537,7 +538,7 @@ describe('Test users API validators', function () {
537 it('Should fail with a big file', async function () { 538 it('Should fail with a big file', async function () {
538 const fields = {} 539 const fields = {}
539 const attaches = { 540 const attaches = {
540 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 541 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
541 } 542 }
542 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) 543 await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches })
543 }) 544 })
@@ -545,7 +546,7 @@ describe('Test users API validators', function () {
545 it('Should fail with an unauthenticated user', async function () { 546 it('Should fail with an unauthenticated user', async function () {
546 const fields = {} 547 const fields = {}
547 const attaches = { 548 const attaches = {
548 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 549 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
549 } 550 }
550 await makeUploadRequest({ 551 await makeUploadRequest({
551 url: server.url, 552 url: server.url,
@@ -559,7 +560,7 @@ describe('Test users API validators', function () {
559 it('Should succeed with the correct params', async function () { 560 it('Should succeed with the correct params', async function () {
560 const fields = {} 561 const fields = {}
561 const attaches = { 562 const attaches = {
562 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 563 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
563 } 564 }
564 await makeUploadRequest({ 565 await makeUploadRequest({
565 url: server.url, 566 url: server.url,
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts
index bf29f8d4d..bea2177f3 100644
--- a/server/tests/api/check-params/video-abuses.ts
+++ b/server/tests/api/check-params/video-abuses.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -126,6 +126,7 @@ describe('Test video abuses API validators', function () {
126 126
127 describe('When updating a video abuse', function () { 127 describe('When updating a video abuse', function () {
128 const basePath = '/api/v1/videos/' 128 const basePath = '/api/v1/videos/'
129 // eslint-disable-next-line @typescript-eslint/no-unused-vars
129 let path: string 130 let path: string
130 131
131 before(() => { 132 before(() => {
@@ -163,6 +164,7 @@ describe('Test video abuses API validators', function () {
163 164
164 describe('When deleting a video abuse', function () { 165 describe('When deleting a video abuse', function () {
165 const basePath = '/api/v1/videos/' 166 const basePath = '/api/v1/videos/'
167 // eslint-disable-next-line @typescript-eslint/no-unused-vars
166 let path: string 168 let path: string
167 169
168 before(() => { 170 before(() => {
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts
index 6466888fb..145f43980 100644
--- a/server/tests/api/check-params/video-blacklist.ts
+++ b/server/tests/api/check-params/video-blacklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4 4
@@ -7,25 +7,24 @@ import {
7 createUser, 7 createUser,
8 doubleFollow, 8 doubleFollow,
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 flushTests,
11 getBlacklistedVideosList, 10 getBlacklistedVideosList,
12 getVideo, 11 getVideo,
13 getVideoWithToken, 12 getVideoWithToken,
14 killallServers,
15 makePostBodyRequest, 13 makePostBodyRequest,
16 makePutBodyRequest, 14 makePutBodyRequest,
17 removeVideoFromBlacklist, 15 removeVideoFromBlacklist,
18 ServerInfo, 16 ServerInfo,
19 setAccessTokensToServers, 17 setAccessTokensToServers,
20 uploadVideo, 18 uploadVideo,
21 userLogin, waitJobs 19 userLogin,
20 waitJobs
22} from '../../../../shared/extra-utils' 21} from '../../../../shared/extra-utils'
23import { 22import {
24 checkBadCountPagination, 23 checkBadCountPagination,
25 checkBadSortPagination, 24 checkBadSortPagination,
26 checkBadStartPagination 25 checkBadStartPagination
27} from '../../../../shared/extra-utils/requests/check-api-params' 26} from '../../../../shared/extra-utils/requests/check-api-params'
28import { VideoDetails, VideoBlacklistType } from '../../../../shared/models/videos' 27import { VideoBlacklistType, VideoDetails } from '../../../../shared/models/videos'
29import { expect } from 'chai' 28import { expect } from 'chai'
30 29
31describe('Test video blacklist API validators', function () { 30describe('Test video blacklist API validators', function () {
@@ -48,14 +47,14 @@ describe('Test video blacklist API validators', function () {
48 { 47 {
49 const username = 'user1' 48 const username = 'user1'
50 const password = 'my super password' 49 const password = 'my super password'
51 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) 50 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password })
52 userAccessToken1 = await userLogin(servers[0], { username, password }) 51 userAccessToken1 = await userLogin(servers[0], { username, password })
53 } 52 }
54 53
55 { 54 {
56 const username = 'user2' 55 const username = 'user2'
57 const password = 'my super password' 56 const password = 'my super password'
58 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) 57 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password })
59 userAccessToken2 = await userLogin(servers[0], { username, password }) 58 userAccessToken2 = await userLogin(servers[0], { username, password })
60 } 59 }
61 60
@@ -120,7 +119,7 @@ describe('Test video blacklist API validators', function () {
120 119
121 it('Should succeed with the correct params', async function () { 120 it('Should succeed with the correct params', async function () {
122 const path = basePath + servers[0].video.uuid + '/blacklist' 121 const path = basePath + servers[0].video.uuid + '/blacklist'
123 const fields = { } 122 const fields = {}
124 123
125 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 }) 124 await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 })
126 }) 125 })
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts
index 6ddc20d69..a5f5c3322 100644
--- a/server/tests/api/check-params/video-captions.ts
+++ b/server/tests/api/check-params/video-captions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -50,7 +50,7 @@ describe('Test video captions API validator', function () {
50 describe('When adding video caption', function () { 50 describe('When adding video caption', function () {
51 const fields = { } 51 const fields = { }
52 const attaches = { 52 const attaches = {
53 'captionfile': join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt') 53 captionfile: join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt')
54 } 54 }
55 55
56 it('Should fail without a valid uuid', async function () { 56 it('Should fail without a valid uuid', async function () {
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index de88298d1..2795ad7d5 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
@@ -243,7 +243,7 @@ describe('Test video channels API validator', function () {
243 it('Should fail with an incorrect input file', async function () { 243 it('Should fail with an incorrect input file', async function () {
244 const fields = {} 244 const fields = {}
245 const attaches = { 245 const attaches = {
246 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') 246 avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4')
247 } 247 }
248 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) 248 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
249 }) 249 })
@@ -251,7 +251,7 @@ describe('Test video channels API validator', function () {
251 it('Should fail with a big file', async function () { 251 it('Should fail with a big file', async function () {
252 const fields = {} 252 const fields = {}
253 const attaches = { 253 const attaches = {
254 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 254 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
255 } 255 }
256 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) 256 await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches })
257 }) 257 })
@@ -259,7 +259,7 @@ describe('Test video channels API validator', function () {
259 it('Should fail with an unauthenticated user', async function () { 259 it('Should fail with an unauthenticated user', async function () {
260 const fields = {} 260 const fields = {}
261 const attaches = { 261 const attaches = {
262 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 262 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
263 } 263 }
264 await makeUploadRequest({ 264 await makeUploadRequest({
265 url: server.url, 265 url: server.url,
@@ -273,7 +273,7 @@ describe('Test video channels API validator', function () {
273 it('Should succeed with the correct params', async function () { 273 it('Should succeed with the correct params', async function () {
274 const fields = {} 274 const fields = {}
275 const attaches = { 275 const attaches = {
276 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 276 avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
277 } 277 }
278 await makeUploadRequest({ 278 await makeUploadRequest({
279 url: server.url, 279 url: server.url,
@@ -324,7 +324,7 @@ describe('Test video channels API validator', function () {
324 }) 324 })
325 325
326 it('Should fail with an unknown video channel id', async function () { 326 it('Should fail with an unknown video channel id', async function () {
327 await deleteVideoChannel(server.url, server.accessToken,'super_channel2', 404) 327 await deleteVideoChannel(server.url, server.accessToken, 'super_channel2', 404)
328 }) 328 })
329 329
330 it('Should succeed with the correct parameters', async function () { 330 it('Should succeed with the correct parameters', async function () {
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts
index 5cf90bacc..e67cc01fa 100644
--- a/server/tests/api/check-params/video-comments.ts
+++ b/server/tests/api/check-params/video-comments.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts
index 231d5cc85..dbea39c48 100644
--- a/server/tests/api/check-params/video-imports.ts
+++ b/server/tests/api/check-params/video-imports.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { omit } from 'lodash' 3import { omit } from 'lodash'
4import 'mocha' 4import 'mocha'
@@ -29,6 +29,7 @@ describe('Test video imports API validator', function () {
29 const path = '/api/v1/videos/imports' 29 const path = '/api/v1/videos/imports'
30 let server: ServerInfo 30 let server: ServerInfo
31 let userAccessToken = '' 31 let userAccessToken = ''
32 // eslint-disable-next-line @typescript-eslint/no-unused-vars
32 let accountName: string 33 let accountName: string
33 let channelId: number 34 let channelId: number
34 35
@@ -48,7 +49,7 @@ describe('Test video imports API validator', function () {
48 49
49 { 50 {
50 const res = await getMyUserInformation(server.url, server.accessToken) 51 const res = await getMyUserInformation(server.url, server.accessToken)
51 channelId = res.body.videoChannels[ 0 ].id 52 channelId = res.body.videoChannels[0].id
52 accountName = res.body.account.name + '@' + res.body.account.host 53 accountName = res.body.account.name + '@' + res.body.account.host
53 } 54 }
54 }) 55 })
@@ -196,7 +197,7 @@ describe('Test video imports API validator', function () {
196 it('Should fail with an incorrect thumbnail file', async function () { 197 it('Should fail with an incorrect thumbnail file', async function () {
197 const fields = baseCorrectParams 198 const fields = baseCorrectParams
198 const attaches = { 199 const attaches = {
199 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 200 thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
200 } 201 }
201 202
202 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 203 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -205,7 +206,7 @@ describe('Test video imports API validator', function () {
205 it('Should fail with a big thumbnail file', async function () { 206 it('Should fail with a big thumbnail file', async function () {
206 const fields = baseCorrectParams 207 const fields = baseCorrectParams
207 const attaches = { 208 const attaches = {
208 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 209 thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
209 } 210 }
210 211
211 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 212 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -214,7 +215,7 @@ describe('Test video imports API validator', function () {
214 it('Should fail with an incorrect preview file', async function () { 215 it('Should fail with an incorrect preview file', async function () {
215 const fields = baseCorrectParams 216 const fields = baseCorrectParams
216 const attaches = { 217 const attaches = {
217 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') 218 previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png')
218 } 219 }
219 220
220 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 221 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -223,7 +224,7 @@ describe('Test video imports API validator', function () {
223 it('Should fail with a big preview file', async function () { 224 it('Should fail with a big preview file', async function () {
224 const fields = baseCorrectParams 225 const fields = baseCorrectParams
225 const attaches = { 226 const attaches = {
226 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 227 previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
227 } 228 }
228 229
229 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 230 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -232,7 +233,7 @@ describe('Test video imports API validator', function () {
232 it('Should fail with an invalid torrent file', async function () { 233 it('Should fail with an invalid torrent file', async function () {
233 const fields = omit(baseCorrectParams, 'targetUrl') 234 const fields = omit(baseCorrectParams, 'targetUrl')
234 const attaches = { 235 const attaches = {
235 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') 236 torrentfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png')
236 } 237 }
237 238
238 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) 239 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches })
@@ -303,7 +304,7 @@ describe('Test video imports API validator', function () {
303 304
304 fields = omit(fields, 'magnetUri') 305 fields = omit(fields, 'magnetUri')
305 const attaches = { 306 const attaches = {
306 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent') 307 torrentfile: join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent')
307 } 308 }
308 309
309 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 }) 310 await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 })
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index df158f3b1..0410e737a 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
@@ -36,6 +36,7 @@ describe('Test video playlists API validator', function () {
36 let privatePlaylistUUID: string 36 let privatePlaylistUUID: string
37 let watchLaterPlaylistId: number 37 let watchLaterPlaylistId: number
38 let videoId: number 38 let videoId: number
39 // eslint-disable-next-line @typescript-eslint/no-unused-vars
39 let videoId2: number 40 let videoId2: number
40 let playlistElementId: number 41 let playlistElementId: number
41 42
@@ -449,7 +450,7 @@ describe('Test video playlists API validator', function () {
449 videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id 450 videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id
450 videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id 451 videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id
451 452
452 for (let id of [ videoId3, videoId4 ]) { 453 for (const id of [ videoId3, videoId4 ]) {
453 await addVideoInPlaylist({ 454 await addVideoInPlaylist({
454 url: server.url, 455 url: server.url,
455 token: server.accessToken, 456 token: server.accessToken,
@@ -476,7 +477,7 @@ describe('Test video playlists API validator', function () {
476 } 477 }
477 478
478 { 479 {
479 const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) 480 const params = getBase({}, { playlistId: 42, expectedStatus: 404 })
480 await reorderVideosPlaylist(params) 481 await reorderVideosPlaylist(params)
481 } 482 }
482 }) 483 })
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts
index 811756745..ec8654db2 100644
--- a/server/tests/api/check-params/videos-filter.ts
+++ b/server/tests/api/check-params/videos-filter.ts
@@ -1,10 +1,9 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
5 cleanupTests, 5 cleanupTests,
6 createUser, 6 createUser,
7 createVideoPlaylist,
8 flushAndRunServer, 7 flushAndRunServer,
9 makeGetRequest, 8 makeGetRequest,
10 ServerInfo, 9 ServerInfo,
@@ -13,7 +12,6 @@ import {
13 userLogin 12 userLogin
14} from '../../../../shared/extra-utils' 13} from '../../../../shared/extra-utils'
15import { UserRole } from '../../../../shared/models/users' 14import { UserRole } from '../../../../shared/models/users'
16import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
17 15
18async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { 16async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) {
19 const paths = [ 17 const paths = [
@@ -77,7 +75,7 @@ describe('Test videos filters', function () {
77 }) 75 })
78 76
79 it('Should succeed with a good filter', async function () { 77 it('Should succeed with a good filter', async function () {
80 await testEndpoints(server, server.accessToken,'local', 200) 78 await testEndpoints(server, server.accessToken, 'local', 200)
81 }) 79 })
82 80
83 it('Should fail to list all-local with a simple user', async function () { 81 it('Should fail to list all-local with a simple user', async function () {
diff --git a/server/tests/api/check-params/videos-history.ts b/server/tests/api/check-params/videos-history.ts
index 3739e3fad..941f62654 100644
--- a/server/tests/api/check-params/videos-history.ts
+++ b/server/tests/api/check-params/videos-history.ts
@@ -1,6 +1,5 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { 4import {
6 checkBadCountPagination, 5 checkBadCountPagination,
@@ -15,12 +14,10 @@ import {
15 uploadVideo 14 uploadVideo
16} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
17 16
18const expect = chai.expect
19
20describe('Test videos history API validator', function () { 17describe('Test videos history API validator', function () {
18 const myHistoryPath = '/api/v1/users/me/history/videos'
19 const myHistoryRemove = myHistoryPath + '/remove'
21 let watchingPath: string 20 let watchingPath: string
22 let myHistoryPath = '/api/v1/users/me/history/videos'
23 let myHistoryRemove = myHistoryPath + '/remove'
24 let server: ServerInfo 21 let server: ServerInfo
25 22
26 // --------------------------------------------------------------- 23 // ---------------------------------------------------------------
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts
index 16ef1c505..0d4665954 100644
--- a/server/tests/api/check-params/videos.ts
+++ b/server/tests/api/check-params/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { omit } from 'lodash' 4import { omit } from 'lodash'
@@ -56,8 +56,8 @@ describe('Test videos API validator', function () {
56 56
57 { 57 {
58 const res = await getMyUserInformation(server.url, server.accessToken) 58 const res = await getMyUserInformation(server.url, server.accessToken)
59 channelId = res.body.videoChannels[ 0 ].id 59 channelId = res.body.videoChannels[0].id
60 channelName = res.body.videoChannels[ 0 ].name 60 channelName = res.body.videoChannels[0].name
61 accountName = res.body.account.name + '@' + res.body.account.host 61 accountName = res.body.account.name + '@' + res.body.account.host
62 } 62 }
63 }) 63 })
@@ -182,7 +182,7 @@ describe('Test videos API validator', function () {
182 describe('When adding a video', function () { 182 describe('When adding a video', function () {
183 let baseCorrectParams 183 let baseCorrectParams
184 const baseCorrectAttaches = { 184 const baseCorrectAttaches = {
185 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.webm') 185 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.webm')
186 } 186 }
187 187
188 before(function () { 188 before(function () {
@@ -330,7 +330,7 @@ describe('Test videos API validator', function () {
330 }) 330 })
331 331
332 it('Should fail with a bad originally published at attribute', async function () { 332 it('Should fail with a bad originally published at attribute', async function () {
333 const fields = immutableAssign(baseCorrectParams, { 'originallyPublishedAt': 'toto' }) 333 const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' })
334 const attaches = baseCorrectAttaches 334 const attaches = baseCorrectAttaches
335 335
336 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 336 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -345,12 +345,12 @@ describe('Test videos API validator', function () {
345 it('Should fail with an incorrect input file', async function () { 345 it('Should fail with an incorrect input file', async function () {
346 const fields = baseCorrectParams 346 const fields = baseCorrectParams
347 let attaches = { 347 let attaches = {
348 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm') 348 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm')
349 } 349 }
350 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 350 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
351 351
352 attaches = { 352 attaches = {
353 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv') 353 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv')
354 } 354 }
355 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 355 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
356 }) 356 })
@@ -358,8 +358,8 @@ describe('Test videos API validator', function () {
358 it('Should fail with an incorrect thumbnail file', async function () { 358 it('Should fail with an incorrect thumbnail file', async function () {
359 const fields = baseCorrectParams 359 const fields = baseCorrectParams
360 const attaches = { 360 const attaches = {
361 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), 361 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'),
362 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 362 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
363 } 363 }
364 364
365 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 365 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -368,8 +368,8 @@ describe('Test videos API validator', function () {
368 it('Should fail with a big thumbnail file', async function () { 368 it('Should fail with a big thumbnail file', async function () {
369 const fields = baseCorrectParams 369 const fields = baseCorrectParams
370 const attaches = { 370 const attaches = {
371 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), 371 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'),
372 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 372 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
373 } 373 }
374 374
375 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 375 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -378,8 +378,8 @@ describe('Test videos API validator', function () {
378 it('Should fail with an incorrect preview file', async function () { 378 it('Should fail with an incorrect preview file', async function () {
379 const fields = baseCorrectParams 379 const fields = baseCorrectParams
380 const attaches = { 380 const attaches = {
381 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), 381 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'),
382 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 382 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
383 } 383 }
384 384
385 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 385 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -388,8 +388,8 @@ describe('Test videos API validator', function () {
388 it('Should fail with a big preview file', async function () { 388 it('Should fail with a big preview file', async function () {
389 const fields = baseCorrectParams 389 const fields = baseCorrectParams
390 const attaches = { 390 const attaches = {
391 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), 391 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'),
392 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') 392 videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4')
393 } 393 }
394 394
395 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) 395 await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
@@ -566,7 +566,7 @@ describe('Test videos API validator', function () {
566 it('Should fail with an incorrect thumbnail file', async function () { 566 it('Should fail with an incorrect thumbnail file', async function () {
567 const fields = baseCorrectParams 567 const fields = baseCorrectParams
568 const attaches = { 568 const attaches = {
569 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') 569 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png')
570 } 570 }
571 571
572 await makeUploadRequest({ 572 await makeUploadRequest({
@@ -582,7 +582,7 @@ describe('Test videos API validator', function () {
582 it('Should fail with a big thumbnail file', async function () { 582 it('Should fail with a big thumbnail file', async function () {
583 const fields = baseCorrectParams 583 const fields = baseCorrectParams
584 const attaches = { 584 const attaches = {
585 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') 585 thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png')
586 } 586 }
587 587
588 await makeUploadRequest({ 588 await makeUploadRequest({
@@ -598,7 +598,7 @@ describe('Test videos API validator', function () {
598 it('Should fail with an incorrect preview file', async function () { 598 it('Should fail with an incorrect preview file', async function () {
599 const fields = baseCorrectParams 599 const fields = baseCorrectParams
600 const attaches = { 600 const attaches = {
601 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') 601 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png')
602 } 602 }
603 603
604 await makeUploadRequest({ 604 await makeUploadRequest({
@@ -614,7 +614,7 @@ describe('Test videos API validator', function () {
614 it('Should fail with a big preview file', async function () { 614 it('Should fail with a big preview file', async function () {
615 const fields = baseCorrectParams 615 const fields = baseCorrectParams
616 const attaches = { 616 const attaches = {
617 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') 617 previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png')
618 } 618 }
619 619
620 await makeUploadRequest({ 620 await makeUploadRequest({
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index 15a34f5aa..2a632e16f 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -74,7 +74,7 @@ async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalPara
74 const name = 'remote video ' + uuidv4() 74 const name = 'remote video ' + uuidv4()
75 75
76 const data = Object.assign({ name }, additionalParams) 76 const data = Object.assign({ name }, additionalParams)
77 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data) 77 const res = await uploadVideo(servers[1].url, servers[1].accessToken, data)
78 78
79 await waitJobs(servers) 79 await waitJobs(servers)
80 80
@@ -85,7 +85,7 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam
85 const name = 'local video ' + uuidv4() 85 const name = 'local video ' + uuidv4()
86 86
87 const data = Object.assign({ name }, additionalParams) 87 const data = Object.assign({ name }, additionalParams)
88 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data) 88 const res = await uploadVideo(servers[0].url, servers[0].accessToken, data)
89 89
90 await waitJobs(servers) 90 await waitJobs(servers)
91 91
@@ -95,9 +95,9 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam
95describe('Test users notifications', function () { 95describe('Test users notifications', function () {
96 let servers: ServerInfo[] = [] 96 let servers: ServerInfo[] = []
97 let userAccessToken: string 97 let userAccessToken: string
98 let userNotifications: UserNotification[] = [] 98 const userNotifications: UserNotification[] = []
99 let adminNotifications: UserNotification[] = [] 99 const adminNotifications: UserNotification[] = []
100 let adminNotificationsServer2: UserNotification[] = [] 100 const adminNotificationsServer2: UserNotification[] = []
101 const emails: object[] = [] 101 const emails: object[] = []
102 let channelId: number 102 let channelId: number
103 103
@@ -142,8 +142,8 @@ describe('Test users notifications', function () {
142 password: 'super password' 142 password: 'super password'
143 } 143 }
144 await createUser({ 144 await createUser({
145 url: servers[ 0 ].url, 145 url: servers[0].url,
146 accessToken: servers[ 0 ].accessToken, 146 accessToken: servers[0].accessToken,
147 username: user.username, 147 username: user.username,
148 password: user.password, 148 password: user.password,
149 videoQuota: 10 * 1000 * 1000 149 videoQuota: 10 * 1000 * 1000
@@ -155,15 +155,15 @@ describe('Test users notifications', function () {
155 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings) 155 await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
156 156
157 { 157 {
158 const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken) 158 const socket = getUserNotificationSocket(servers[0].url, userAccessToken)
159 socket.on('new-notification', n => userNotifications.push(n)) 159 socket.on('new-notification', n => userNotifications.push(n))
160 } 160 }
161 { 161 {
162 const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken) 162 const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken)
163 socket.on('new-notification', n => adminNotifications.push(n)) 163 socket.on('new-notification', n => adminNotifications.push(n))
164 } 164 }
165 { 165 {
166 const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken) 166 const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken)
167 socket.on('new-notification', n => adminNotificationsServer2.push(n)) 167 socket.on('new-notification', n => adminNotificationsServer2.push(n))
168 } 168 }
169 169
@@ -190,7 +190,7 @@ describe('Test users notifications', function () {
190 190
191 await uploadVideoByLocalAccount(servers) 191 await uploadVideoByLocalAccount(servers)
192 192
193 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) 193 const notification = await getLastNotification(servers[0].url, userAccessToken)
194 expect(notification).to.be.undefined 194 expect(notification).to.be.undefined
195 195
196 expect(emails).to.have.lengthOf(0) 196 expect(emails).to.have.lengthOf(0)
@@ -221,7 +221,7 @@ describe('Test users notifications', function () {
221 this.timeout(20000) 221 this.timeout(20000)
222 222
223 // In 2 seconds 223 // In 2 seconds
224 let updateAt = new Date(new Date().getTime() + 2000) 224 const updateAt = new Date(new Date().getTime() + 2000)
225 225
226 const data = { 226 const data = {
227 privacy: VideoPrivacy.PRIVATE, 227 privacy: VideoPrivacy.PRIVATE,
@@ -240,7 +240,7 @@ describe('Test users notifications', function () {
240 this.timeout(50000) 240 this.timeout(50000)
241 241
242 // In 2 seconds 242 // In 2 seconds
243 let updateAt = new Date(new Date().getTime() + 2000) 243 const updateAt = new Date(new Date().getTime() + 2000)
244 244
245 const data = { 245 const data = {
246 privacy: VideoPrivacy.PRIVATE, 246 privacy: VideoPrivacy.PRIVATE,
@@ -259,7 +259,7 @@ describe('Test users notifications', function () {
259 it('Should not send a notification before the video is published', async function () { 259 it('Should not send a notification before the video is published', async function () {
260 this.timeout(20000) 260 this.timeout(20000)
261 261
262 let updateAt = new Date(new Date().getTime() + 1000000) 262 const updateAt = new Date(new Date().getTime() + 1000000)
263 263
264 const data = { 264 const data = {
265 privacy: VideoPrivacy.PRIVATE, 265 privacy: VideoPrivacy.PRIVATE,
@@ -386,7 +386,7 @@ describe('Test users notifications', function () {
386 it('Should not send a new comment notification if the account is muted', async function () { 386 it('Should not send a new comment notification if the account is muted', async function () {
387 this.timeout(10000) 387 this.timeout(10000)
388 388
389 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 389 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root')
390 390
391 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) 391 const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
392 const uuid = resVideo.body.video.uuid 392 const uuid = resVideo.body.video.uuid
@@ -397,7 +397,7 @@ describe('Test users notifications', function () {
397 await wait(500) 397 await wait(500)
398 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') 398 await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
399 399
400 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 400 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root')
401 }) 401 })
402 402
403 it('Should send a new comment notification after a local comment on my video', async function () { 403 it('Should send a new comment notification after a local comment on my video', async function () {
@@ -456,9 +456,9 @@ describe('Test users notifications', function () {
456 await waitJobs(servers) 456 await waitJobs(servers)
457 457
458 { 458 {
459 const resThread = await addVideoCommentThread(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, 'comment') 459 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment')
460 const threadId = resThread.body.comment.id 460 const threadId = resThread.body.comment.id
461 await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, threadId, 'reply') 461 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply')
462 } 462 }
463 463
464 await waitJobs(servers) 464 await waitJobs(servers)
@@ -530,7 +530,7 @@ describe('Test users notifications', function () {
530 it('Should not send a new mention notification if the account is muted', async function () { 530 it('Should not send a new mention notification if the account is muted', async function () {
531 this.timeout(10000) 531 this.timeout(10000)
532 532
533 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 533 await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root')
534 534
535 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) 535 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
536 const uuid = resVideo.body.video.uuid 536 const uuid = resVideo.body.video.uuid
@@ -541,7 +541,7 @@ describe('Test users notifications', function () {
541 await wait(500) 541 await wait(500)
542 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') 542 await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
543 543
544 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') 544 await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root')
545 }) 545 })
546 546
547 it('Should not send a new mention notification if the remote account mention a local account', async function () { 547 it('Should not send a new mention notification if the remote account mention a local account', async function () {
@@ -585,7 +585,7 @@ describe('Test users notifications', function () {
585 585
586 await waitJobs(servers) 586 await waitJobs(servers)
587 587
588 const text1 = `hello @user_1@localhost:${servers[ 0 ].port} 1` 588 const text1 = `hello @user_1@localhost:${servers[0].port} 1`
589 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1) 589 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1)
590 const server2ThreadId = resThread.body.comment.id 590 const server2ThreadId = resThread.body.comment.id
591 591
@@ -596,7 +596,7 @@ describe('Test users notifications', function () {
596 const server1ThreadId = resThread2.body.data[0].id 596 const server1ThreadId = resThread2.body.data[0].id
597 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') 597 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence')
598 598
599 const text2 = `@user_1@localhost:${servers[ 0 ].port} hello 2 @root@localhost:${servers[ 0 ].port}` 599 const text2 = `@user_1@localhost:${servers[0].port} hello 2 @root@localhost:${servers[0].port}`
600 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2) 600 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2)
601 601
602 await waitJobs(servers) 602 await waitJobs(servers)
@@ -611,7 +611,7 @@ describe('Test users notifications', function () {
611 }) 611 })
612 }) 612 })
613 613
614 describe('Video abuse for moderators notification' , function () { 614 describe('Video abuse for moderators notification', function () {
615 let baseParams: CheckerBaseParams 615 let baseParams: CheckerBaseParams
616 616
617 before(() => { 617 before(() => {
@@ -722,7 +722,7 @@ describe('Test users notifications', function () {
722 await uploadVideoByRemoteAccount(servers, { waitTranscoding: false }) 722 await uploadVideoByRemoteAccount(servers, { waitTranscoding: false })
723 await waitJobs(servers) 723 await waitJobs(servers)
724 724
725 const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) 725 const notification = await getLastNotification(servers[0].url, userAccessToken)
726 if (notification) { 726 if (notification) {
727 expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED) 727 expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
728 } 728 }
@@ -769,7 +769,7 @@ describe('Test users notifications', function () {
769 this.timeout(70000) 769 this.timeout(70000)
770 770
771 // In 2 seconds 771 // In 2 seconds
772 let updateAt = new Date(new Date().getTime() + 2000) 772 const updateAt = new Date(new Date().getTime() + 2000)
773 773
774 const data = { 774 const data = {
775 privacy: VideoPrivacy.PRIVATE, 775 privacy: VideoPrivacy.PRIVATE,
@@ -787,7 +787,7 @@ describe('Test users notifications', function () {
787 it('Should not send a notification before the video is published', async function () { 787 it('Should not send a notification before the video is published', async function () {
788 this.timeout(20000) 788 this.timeout(20000)
789 789
790 let updateAt = new Date(new Date().getTime() + 1000000) 790 const updateAt = new Date(new Date().getTime() + 1000000)
791 791
792 const data = { 792 const data = {
793 privacy: VideoPrivacy.PRIVATE, 793 privacy: VideoPrivacy.PRIVATE,
@@ -970,8 +970,8 @@ describe('Test users notifications', function () {
970 970
971 describe('New actor follow', function () { 971 describe('New actor follow', function () {
972 let baseParams: CheckerBaseParams 972 let baseParams: CheckerBaseParams
973 let myChannelName = 'super channel name' 973 const myChannelName = 'super channel name'
974 let myUserName = 'super user name' 974 const myUserName = 'super user name'
975 975
976 before(async () => { 976 before(async () => {
977 baseParams = { 977 baseParams = {
@@ -1143,7 +1143,7 @@ describe('Test users notifications', function () {
1143 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { 1143 it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () {
1144 this.timeout(20000) 1144 this.timeout(20000)
1145 1145
1146 let updateAt = new Date(new Date().getTime() + 1000000) 1146 const updateAt = new Date(new Date().getTime() + 1000000)
1147 1147
1148 const name = 'video with auto-blacklist and future schedule ' + uuidv4() 1148 const name = 'video with auto-blacklist and future schedule ' + uuidv4()
1149 1149
@@ -1176,7 +1176,7 @@ describe('Test users notifications', function () {
1176 this.timeout(20000) 1176 this.timeout(20000)
1177 1177
1178 // In 2 seconds 1178 // In 2 seconds
1179 let updateAt = new Date(new Date().getTime() + 2000) 1179 const updateAt = new Date(new Date().getTime() + 2000)
1180 1180
1181 const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() 1181 const name = 'video with schedule done and still auto-blacklisted ' + uuidv4()
1182 1182
@@ -1221,26 +1221,26 @@ describe('Test users notifications', function () {
1221 1221
1222 describe('Mark as read', function () { 1222 describe('Mark as read', function () {
1223 it('Should mark as read some notifications', async function () { 1223 it('Should mark as read some notifications', async function () {
1224 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3) 1224 const res = await getUserNotifications(servers[0].url, userAccessToken, 2, 3)
1225 const ids = res.body.data.map(n => n.id) 1225 const ids = res.body.data.map(n => n.id)
1226 1226
1227 await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids) 1227 await markAsReadNotifications(servers[0].url, userAccessToken, ids)
1228 }) 1228 })
1229 1229
1230 it('Should have the notifications marked as read', async function () { 1230 it('Should have the notifications marked as read', async function () {
1231 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10) 1231 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10)
1232 1232
1233 const notifications = res.body.data as UserNotification[] 1233 const notifications = res.body.data as UserNotification[]
1234 expect(notifications[ 0 ].read).to.be.false 1234 expect(notifications[0].read).to.be.false
1235 expect(notifications[ 1 ].read).to.be.false 1235 expect(notifications[1].read).to.be.false
1236 expect(notifications[ 2 ].read).to.be.true 1236 expect(notifications[2].read).to.be.true
1237 expect(notifications[ 3 ].read).to.be.true 1237 expect(notifications[3].read).to.be.true
1238 expect(notifications[ 4 ].read).to.be.true 1238 expect(notifications[4].read).to.be.true
1239 expect(notifications[ 5 ].read).to.be.false 1239 expect(notifications[5].read).to.be.false
1240 }) 1240 })
1241 1241
1242 it('Should only list read notifications', async function () { 1242 it('Should only list read notifications', async function () {
1243 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false) 1243 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, false)
1244 1244
1245 const notifications = res.body.data as UserNotification[] 1245 const notifications = res.body.data as UserNotification[]
1246 for (const notification of notifications) { 1246 for (const notification of notifications) {
@@ -1249,7 +1249,7 @@ describe('Test users notifications', function () {
1249 }) 1249 })
1250 1250
1251 it('Should only list unread notifications', async function () { 1251 it('Should only list unread notifications', async function () {
1252 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) 1252 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true)
1253 1253
1254 const notifications = res.body.data as UserNotification[] 1254 const notifications = res.body.data as UserNotification[]
1255 for (const notification of notifications) { 1255 for (const notification of notifications) {
@@ -1258,9 +1258,9 @@ describe('Test users notifications', function () {
1258 }) 1258 })
1259 1259
1260 it('Should mark as read all notifications', async function () { 1260 it('Should mark as read all notifications', async function () {
1261 await markAsReadAllNotifications(servers[ 0 ].url, userAccessToken) 1261 await markAsReadAllNotifications(servers[0].url, userAccessToken)
1262 1262
1263 const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) 1263 const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true)
1264 1264
1265 expect(res.body.total).to.equal(0) 1265 expect(res.body.total).to.equal(0)
1266 expect(res.body.data).to.have.lengthOf(0) 1266 expect(res.body.data).to.have.lengthOf(0)
diff --git a/server/tests/api/redundancy/index.ts b/server/tests/api/redundancy/index.ts
index 8e69b95a6..5359055b0 100644
--- a/server/tests/api/redundancy/index.ts
+++ b/server/tests/api/redundancy/index.ts
@@ -1 +1,2 @@
1import './redundancy' 1import './redundancy'
2import './manage-redundancy'
diff --git a/server/tests/api/redundancy/manage-redundancy.ts b/server/tests/api/redundancy/manage-redundancy.ts
new file mode 100644
index 000000000..4253124c8
--- /dev/null
+++ b/server/tests/api/redundancy/manage-redundancy.ts
@@ -0,0 +1,373 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import * as chai from 'chai'
4import 'mocha'
5import {
6 cleanupTests,
7 doubleFollow,
8 flushAndRunMultipleServers,
9 getLocalIdByUUID,
10 ServerInfo,
11 setAccessTokensToServers,
12 uploadVideo,
13 uploadVideoAndGetId,
14 waitUntilLog
15} from '../../../../shared/extra-utils'
16import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
17import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy, updateRedundancy } from '@shared/extra-utils/server/redundancy'
18import { VideoPrivacy, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
19
20const expect = chai.expect
21
22describe('Test manage videos redundancy', function () {
23 const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ]
24
25 let servers: ServerInfo[]
26 let video1Server2UUID: string
27 let video2Server2UUID: string
28 let redundanciesToRemove: number[] = []
29
30 before(async function () {
31 this.timeout(120000)
32
33 const config = {
34 transcoding: {
35 hls: {
36 enabled: true
37 }
38 },
39 redundancy: {
40 videos: {
41 check_interval: '1 second',
42 strategies: [
43 {
44 strategy: 'recently-added',
45 min_lifetime: '1 hour',
46 size: '10MB',
47 min_views: 0
48 }
49 ]
50 }
51 }
52 }
53 servers = await flushAndRunMultipleServers(3, config)
54
55 // Get the access tokens
56 await setAccessTokensToServers(servers)
57
58 {
59 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' })
60 video1Server2UUID = res.body.video.uuid
61 }
62
63 {
64 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' })
65 video2Server2UUID = res.body.video.uuid
66 }
67
68 await waitJobs(servers)
69
70 // Server 1 and server 2 follow each other
71 await doubleFollow(servers[0], servers[1])
72 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true)
73
74 await waitJobs(servers)
75 })
76
77 it('Should not have redundancies on server 3', async function () {
78 for (const target of targets) {
79 const res = await listVideoRedundancies({
80 url: servers[2].url,
81 accessToken: servers[2].accessToken,
82 target
83 })
84
85 expect(res.body.total).to.equal(0)
86 expect(res.body.data).to.have.lengthOf(0)
87 }
88 })
89
90 it('Should not have "remote-videos" redundancies on server 2', async function () {
91 this.timeout(120000)
92
93 await waitJobs(servers)
94 await waitUntilLog(servers[0], 'Duplicated ', 10)
95 await waitJobs(servers)
96
97 const res = await listVideoRedundancies({
98 url: servers[1].url,
99 accessToken: servers[1].accessToken,
100 target: 'remote-videos'
101 })
102
103 expect(res.body.total).to.equal(0)
104 expect(res.body.data).to.have.lengthOf(0)
105 })
106
107 it('Should have "my-videos" redundancies on server 2', async function () {
108 this.timeout(120000)
109
110 const res = await listVideoRedundancies({
111 url: servers[1].url,
112 accessToken: servers[1].accessToken,
113 target: 'my-videos'
114 })
115
116 expect(res.body.total).to.equal(2)
117
118 const videos = res.body.data as VideoRedundancy[]
119 expect(videos).to.have.lengthOf(2)
120
121 const videos1 = videos.find(v => v.uuid === video1Server2UUID)
122 const videos2 = videos.find(v => v.uuid === video2Server2UUID)
123
124 expect(videos1.name).to.equal('video 1 server 2')
125 expect(videos2.name).to.equal('video 2 server 2')
126
127 expect(videos1.redundancies.files).to.have.lengthOf(4)
128 expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
129
130 const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
131
132 for (const r of redundancies) {
133 expect(r.strategy).to.be.null
134 expect(r.fileUrl).to.exist
135 expect(r.createdAt).to.exist
136 expect(r.updatedAt).to.exist
137 expect(r.expiresOn).to.exist
138 }
139 })
140
141 it('Should not have "my-videos" redundancies on server 1', async function () {
142 const res = await listVideoRedundancies({
143 url: servers[0].url,
144 accessToken: servers[0].accessToken,
145 target: 'my-videos'
146 })
147
148 expect(res.body.total).to.equal(0)
149 expect(res.body.data).to.have.lengthOf(0)
150 })
151
152 it('Should have "remote-videos" redundancies on server 1', async function () {
153 this.timeout(120000)
154
155 const res = await listVideoRedundancies({
156 url: servers[0].url,
157 accessToken: servers[0].accessToken,
158 target: 'remote-videos'
159 })
160
161 expect(res.body.total).to.equal(2)
162
163 const videos = res.body.data as VideoRedundancy[]
164 expect(videos).to.have.lengthOf(2)
165
166 const videos1 = videos.find(v => v.uuid === video1Server2UUID)
167 const videos2 = videos.find(v => v.uuid === video2Server2UUID)
168
169 expect(videos1.name).to.equal('video 1 server 2')
170 expect(videos2.name).to.equal('video 2 server 2')
171
172 expect(videos1.redundancies.files).to.have.lengthOf(4)
173 expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1)
174
175 const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists)
176
177 for (const r of redundancies) {
178 expect(r.strategy).to.equal('recently-added')
179 expect(r.fileUrl).to.exist
180 expect(r.createdAt).to.exist
181 expect(r.updatedAt).to.exist
182 expect(r.expiresOn).to.exist
183 }
184 })
185
186 it('Should correctly paginate and sort results', async function () {
187 {
188 const res = await listVideoRedundancies({
189 url: servers[0].url,
190 accessToken: servers[0].accessToken,
191 target: 'remote-videos',
192 sort: 'name',
193 start: 0,
194 count: 2
195 })
196
197 const videos = res.body.data
198 expect(videos[0].name).to.equal('video 1 server 2')
199 expect(videos[1].name).to.equal('video 2 server 2')
200 }
201
202 {
203 const res = await listVideoRedundancies({
204 url: servers[0].url,
205 accessToken: servers[0].accessToken,
206 target: 'remote-videos',
207 sort: '-name',
208 start: 0,
209 count: 2
210 })
211
212 const videos = res.body.data
213 expect(videos[0].name).to.equal('video 2 server 2')
214 expect(videos[1].name).to.equal('video 1 server 2')
215 }
216
217 {
218 const res = await listVideoRedundancies({
219 url: servers[0].url,
220 accessToken: servers[0].accessToken,
221 target: 'remote-videos',
222 sort: '-name',
223 start: 1,
224 count: 1
225 })
226
227 const videos = res.body.data
228 expect(videos[0].name).to.equal('video 1 server 2')
229 }
230 })
231
232 it('Should manually add a redundancy and list it', async function () {
233 this.timeout(120000)
234
235 const uuid = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3 server 2', privacy: VideoPrivacy.UNLISTED })).uuid
236 await waitJobs(servers)
237 const videoId = await getLocalIdByUUID(servers[0].url, uuid)
238
239 await addVideoRedundancy({
240 url: servers[0].url,
241 accessToken: servers[0].accessToken,
242 videoId
243 })
244
245 await waitJobs(servers)
246 await waitUntilLog(servers[0], 'Duplicated ', 15)
247 await waitJobs(servers)
248
249 {
250 const res = await listVideoRedundancies({
251 url: servers[0].url,
252 accessToken: servers[0].accessToken,
253 target: 'remote-videos',
254 sort: '-name',
255 start: 0,
256 count: 5
257 })
258
259 const videos = res.body.data
260 expect(videos[0].name).to.equal('video 3 server 2')
261
262 const video = videos[0]
263 expect(video.redundancies.files).to.have.lengthOf(4)
264 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
265
266 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
267
268 for (const r of redundancies) {
269 redundanciesToRemove.push(r.id)
270
271 expect(r.strategy).to.equal('manual')
272 expect(r.fileUrl).to.exist
273 expect(r.createdAt).to.exist
274 expect(r.updatedAt).to.exist
275 expect(r.expiresOn).to.be.null
276 }
277 }
278
279 const res = await listVideoRedundancies({
280 url: servers[1].url,
281 accessToken: servers[1].accessToken,
282 target: 'my-videos',
283 sort: '-name',
284 start: 0,
285 count: 5
286 })
287
288 const videos = res.body.data
289 expect(videos[0].name).to.equal('video 3 server 2')
290
291 const video = videos[0]
292 expect(video.redundancies.files).to.have.lengthOf(4)
293 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
294
295 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
296
297 for (const r of redundancies) {
298 expect(r.strategy).to.be.null
299 expect(r.fileUrl).to.exist
300 expect(r.createdAt).to.exist
301 expect(r.updatedAt).to.exist
302 expect(r.expiresOn).to.be.null
303 }
304 })
305
306 it('Should manually remove a redundancy and remove it from the list', async function () {
307 this.timeout(120000)
308
309 for (const redundancyId of redundanciesToRemove) {
310 await removeVideoRedundancy({
311 url: servers[0].url,
312 accessToken: servers[0].accessToken,
313 redundancyId
314 })
315 }
316
317 {
318 const res = await listVideoRedundancies({
319 url: servers[0].url,
320 accessToken: servers[0].accessToken,
321 target: 'remote-videos',
322 sort: '-name',
323 start: 0,
324 count: 5
325 })
326
327 const videos = res.body.data
328 expect(videos).to.have.lengthOf(2)
329
330 expect(videos[0].name).to.equal('video 2 server 2')
331
332 redundanciesToRemove = []
333 const video = videos[0]
334 expect(video.redundancies.files).to.have.lengthOf(4)
335 expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1)
336
337 const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists)
338
339 for (const r of redundancies) {
340 redundanciesToRemove.push(r.id)
341 }
342 }
343 })
344
345 it('Should remove another (auto) redundancy', async function () {
346 {
347 for (const redundancyId of redundanciesToRemove) {
348 await removeVideoRedundancy({
349 url: servers[0].url,
350 accessToken: servers[0].accessToken,
351 redundancyId
352 })
353 }
354
355 const res = await listVideoRedundancies({
356 url: servers[0].url,
357 accessToken: servers[0].accessToken,
358 target: 'remote-videos',
359 sort: '-name',
360 start: 0,
361 count: 5
362 })
363
364 const videos = res.body.data
365 expect(videos[0].name).to.equal('video 1 server 2')
366 expect(videos).to.have.lengthOf(1)
367 }
368 })
369
370 after(async function () {
371 await cleanupTests(servers)
372 })
373})
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts
index 1cdf93aa1..c5037a541 100644
--- a/server/tests/api/redundancy/redundancy.ts
+++ b/server/tests/api/redundancy/redundancy.ts
@@ -1,11 +1,12 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { VideoDetails } from '../../../../shared/models/videos' 5import { VideoDetails } from '../../../../shared/models/videos'
6import { 6import {
7 checkSegmentHash, 7 checkSegmentHash,
8 checkVideoFilesWereRemoved, cleanupTests, 8 checkVideoFilesWereRemoved,
9 cleanupTests,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
11 getFollowingListPaginationAndSort, 12 getFollowingListPaginationAndSort,
@@ -28,11 +29,16 @@ import {
28import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 29import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
29 30
30import * as magnetUtil from 'magnet-uri' 31import * as magnetUtil from 'magnet-uri'
31import { updateRedundancy } from '../../../../shared/extra-utils/server/redundancy' 32import {
33 addVideoRedundancy,
34 listVideoRedundancies,
35 removeVideoRedundancy,
36 updateRedundancy
37} from '../../../../shared/extra-utils/server/redundancy'
32import { ActorFollow } from '../../../../shared/models/actors' 38import { ActorFollow } from '../../../../shared/models/actors'
33import { readdir } from 'fs-extra' 39import { readdir } from 'fs-extra'
34import { join } from 'path' 40import { join } from 'path'
35import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' 41import { VideoRedundancy, VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../../shared/models/redundancy'
36import { getStats } from '../../../../shared/extra-utils/server/stats' 42import { getStats } from '../../../../shared/extra-utils/server/stats'
37import { ServerStats } from '../../../../shared/models/server/server-stats.model' 43import { ServerStats } from '../../../../shared/models/server/server-stats.model'
38 44
@@ -40,6 +46,7 @@ const expect = chai.expect
40 46
41let servers: ServerInfo[] = [] 47let servers: ServerInfo[] = []
42let video1Server2UUID: string 48let video1Server2UUID: string
49let video1Server2Id: number
43 50
44function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) { 51function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) {
45 const parsed = magnetUtil.decode(file.magnetUri) 52 const parsed = magnetUtil.decode(file.magnetUri)
@@ -52,7 +59,19 @@ function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: numbe
52 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) 59 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
53} 60}
54 61
55async function flushAndRunServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { 62async function flushAndRunServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}) {
63 const strategies: any[] = []
64
65 if (strategy !== null) {
66 strategies.push(
67 immutableAssign({
68 min_lifetime: '1 hour',
69 strategy: strategy,
70 size: '400KB'
71 }, additionalParams)
72 )
73 }
74
56 const config = { 75 const config = {
57 transcoding: { 76 transcoding: {
58 hls: { 77 hls: {
@@ -62,36 +81,32 @@ async function flushAndRunServers (strategy: VideoRedundancyStrategy, additional
62 redundancy: { 81 redundancy: {
63 videos: { 82 videos: {
64 check_interval: '5 seconds', 83 check_interval: '5 seconds',
65 strategies: [ 84 strategies
66 immutableAssign({
67 min_lifetime: '1 hour',
68 strategy: strategy,
69 size: '400KB'
70 }, additionalParams)
71 ]
72 } 85 }
73 } 86 }
74 } 87 }
88
75 servers = await flushAndRunMultipleServers(3, config) 89 servers = await flushAndRunMultipleServers(3, config)
76 90
77 // Get the access tokens 91 // Get the access tokens
78 await setAccessTokensToServers(servers) 92 await setAccessTokensToServers(servers)
79 93
80 { 94 {
81 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' }) 95 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' })
82 video1Server2UUID = res.body.video.uuid 96 video1Server2UUID = res.body.video.uuid
97 video1Server2Id = res.body.video.id
83 98
84 await viewVideo(servers[ 1 ].url, video1Server2UUID) 99 await viewVideo(servers[1].url, video1Server2UUID)
85 } 100 }
86 101
87 await waitJobs(servers) 102 await waitJobs(servers)
88 103
89 // Server 1 and server 2 follow each other 104 // Server 1 and server 2 follow each other
90 await doubleFollow(servers[ 0 ], servers[ 1 ]) 105 await doubleFollow(servers[0], servers[1])
91 // Server 1 and server 3 follow each other 106 // Server 1 and server 3 follow each other
92 await doubleFollow(servers[ 0 ], servers[ 2 ]) 107 await doubleFollow(servers[0], servers[2])
93 // Server 2 and server 3 follow each other 108 // Server 2 and server 3 follow each other
94 await doubleFollow(servers[ 1 ], servers[ 2 ]) 109 await doubleFollow(servers[1], servers[2])
95 110
96 await waitJobs(servers) 111 await waitJobs(servers)
97} 112}
@@ -100,7 +115,7 @@ async function check1WebSeed (videoUUID?: string) {
100 if (!videoUUID) videoUUID = video1Server2UUID 115 if (!videoUUID) videoUUID = video1Server2UUID
101 116
102 const webseeds = [ 117 const webseeds = [
103 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` 118 `http://localhost:${servers[1].port}/static/webseed/${videoUUID}`
104 ] 119 ]
105 120
106 for (const server of servers) { 121 for (const server of servers) {
@@ -118,8 +133,8 @@ async function check2Webseeds (videoUUID?: string) {
118 if (!videoUUID) videoUUID = video1Server2UUID 133 if (!videoUUID) videoUUID = video1Server2UUID
119 134
120 const webseeds = [ 135 const webseeds = [
121 `http://localhost:${servers[ 0 ].port}/static/redundancy/${videoUUID}`, 136 `http://localhost:${servers[0].port}/static/redundancy/${videoUUID}`,
122 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` 137 `http://localhost:${servers[1].port}/static/webseed/${videoUUID}`
123 ] 138 ]
124 139
125 for (const server of servers) { 140 for (const server of servers) {
@@ -216,41 +231,50 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
216 } 231 }
217} 232}
218 233
219async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { 234async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) {
235 let totalSize: number = null
236 let statsLength = 1
237
238 if (strategy !== 'manual') {
239 totalSize = 409600
240 statsLength = 2
241 }
242
220 const res = await getStats(servers[0].url) 243 const res = await getStats(servers[0].url)
221 const data: ServerStats = res.body 244 const data: ServerStats = res.body
222 245
223 expect(data.videosRedundancy).to.have.lengthOf(1) 246 expect(data.videosRedundancy).to.have.lengthOf(statsLength)
224 const stat = data.videosRedundancy[0]
225 247
248 const stat = data.videosRedundancy[0]
226 expect(stat.strategy).to.equal(strategy) 249 expect(stat.strategy).to.equal(strategy)
227 expect(stat.totalSize).to.equal(409600) 250 expect(stat.totalSize).to.equal(totalSize)
251
252 return stat
253}
254
255async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategyWithManual) {
256 const stat = await checkStatsGlobal(strategy)
257
228 expect(stat.totalUsed).to.be.at.least(1).and.below(409601) 258 expect(stat.totalUsed).to.be.at.least(1).and.below(409601)
229 expect(stat.totalVideoFiles).to.equal(4) 259 expect(stat.totalVideoFiles).to.equal(4)
230 expect(stat.totalVideos).to.equal(1) 260 expect(stat.totalVideos).to.equal(1)
231} 261}
232 262
233async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { 263async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategyWithManual) {
234 const res = await getStats(servers[0].url) 264 const stat = await checkStatsGlobal(strategy)
235 const data: ServerStats = res.body
236 265
237 expect(data.videosRedundancy).to.have.lengthOf(1)
238
239 const stat = data.videosRedundancy[0]
240 expect(stat.strategy).to.equal(strategy)
241 expect(stat.totalSize).to.equal(409600)
242 expect(stat.totalUsed).to.equal(0) 266 expect(stat.totalUsed).to.equal(0)
243 expect(stat.totalVideoFiles).to.equal(0) 267 expect(stat.totalVideoFiles).to.equal(0)
244 expect(stat.totalVideos).to.equal(0) 268 expect(stat.totalVideos).to.equal(0)
245} 269}
246 270
247async function enableRedundancyOnServer1 () { 271async function enableRedundancyOnServer1 () {
248 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) 272 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true)
249 273
250 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) 274 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' })
251 const follows: ActorFollow[] = res.body.data 275 const follows: ActorFollow[] = res.body.data
252 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) 276 const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`)
253 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) 277 const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`)
254 278
255 expect(server3).to.not.be.undefined 279 expect(server3).to.not.be.undefined
256 expect(server3.following.hostRedundancyAllowed).to.be.false 280 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -260,12 +284,12 @@ async function enableRedundancyOnServer1 () {
260} 284}
261 285
262async function disableRedundancyOnServer1 () { 286async function disableRedundancyOnServer1 () {
263 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, false) 287 await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, false)
264 288
265 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) 289 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' })
266 const follows: ActorFollow[] = res.body.data 290 const follows: ActorFollow[] = res.body.data
267 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) 291 const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`)
268 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) 292 const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`)
269 293
270 expect(server3).to.not.be.undefined 294 expect(server3).to.not.be.undefined
271 expect(server3.following.hostRedundancyAllowed).to.be.false 295 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -410,8 +434,8 @@ describe('Test videos redundancy', function () {
410 it('Should view 2 times the first video to have > min_views config', async function () { 434 it('Should view 2 times the first video to have > min_views config', async function () {
411 this.timeout(80000) 435 this.timeout(80000)
412 436
413 await viewVideo(servers[ 0 ].url, video1Server2UUID) 437 await viewVideo(servers[0].url, video1Server2UUID)
414 await viewVideo(servers[ 2 ].url, video1Server2UUID) 438 await viewVideo(servers[2].url, video1Server2UUID)
415 439
416 await wait(10000) 440 await wait(10000)
417 await waitJobs(servers) 441 await waitJobs(servers)
@@ -446,6 +470,74 @@ describe('Test videos redundancy', function () {
446 }) 470 })
447 }) 471 })
448 472
473 describe('With manual strategy', function () {
474 before(function () {
475 this.timeout(120000)
476
477 return flushAndRunServers(null)
478 })
479
480 it('Should have 1 webseed on the first video', async function () {
481 await check1WebSeed()
482 await check0PlaylistRedundancies()
483 await checkStatsWith1Webseed('manual')
484 })
485
486 it('Should create a redundancy on first video', async function () {
487 await addVideoRedundancy({
488 url: servers[0].url,
489 accessToken: servers[0].accessToken,
490 videoId: video1Server2Id
491 })
492 })
493
494 it('Should have 2 webseeds on the first video', async function () {
495 this.timeout(80000)
496
497 await waitJobs(servers)
498 await waitUntilLog(servers[0], 'Duplicated ', 5)
499 await waitJobs(servers)
500
501 await check2Webseeds()
502 await check1PlaylistRedundancies()
503 await checkStatsWith2Webseed('manual')
504 })
505
506 it('Should manually remove redundancies on server 1 and remove duplicated videos', async function () {
507 this.timeout(80000)
508
509 const res = await listVideoRedundancies({
510 url: servers[0].url,
511 accessToken: servers[0].accessToken,
512 target: 'remote-videos'
513 })
514
515 const videos = res.body.data as VideoRedundancy[]
516 expect(videos).to.have.lengthOf(1)
517
518 const video = videos[0]
519 for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) {
520 await removeVideoRedundancy({
521 url: servers[0].url,
522 accessToken: servers[0].accessToken,
523 redundancyId: r.id
524 })
525 }
526
527 await waitJobs(servers)
528 await wait(5000)
529
530 await check1WebSeed()
531 await check0PlaylistRedundancies()
532
533 await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ])
534 })
535
536 after(async function () {
537 await cleanupTests(servers)
538 })
539 })
540
449 describe('Test expiration', function () { 541 describe('Test expiration', function () {
450 const strategy = 'recently-added' 542 const strategy = 'recently-added'
451 543
@@ -528,7 +620,7 @@ describe('Test videos redundancy', function () {
528 await check1PlaylistRedundancies() 620 await check1PlaylistRedundancies()
529 await checkStatsWith2Webseed(strategy) 621 await checkStatsWith2Webseed(strategy)
530 622
531 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) 623 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' })
532 video2Server2UUID = res.body.video.uuid 624 video2Server2UUID = res.body.video.uuid
533 }) 625 })
534 626
@@ -560,8 +652,8 @@ describe('Test videos redundancy', function () {
560 652
561 await waitJobs(servers) 653 await waitJobs(servers)
562 654
563 killallServers([ servers[ 0 ] ]) 655 killallServers([ servers[0] ])
564 await reRunServer(servers[ 0 ], { 656 await reRunServer(servers[0], {
565 redundancy: { 657 redundancy: {
566 videos: { 658 videos: {
567 check_interval: '1 second', 659 check_interval: '1 second',
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts
index d5f0a5457..d7e3ed5be 100644
--- a/server/tests/api/search/search-activitypub-video-channels.ts
+++ b/server/tests/api/search/search-activitypub-video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -39,7 +39,7 @@ describe('Test ActivityPub video channels search', function () {
39 await setAccessTokensToServers(servers) 39 await setAccessTokensToServers(servers)
40 40
41 { 41 {
42 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'user1_server1', password: 'password' }) 42 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'user1_server1', password: 'password' })
43 const channel = { 43 const channel = {
44 name: 'channel1_server1', 44 name: 'channel1_server1',
45 displayName: 'Channel 1 server 1' 45 displayName: 'Channel 1 server 1'
@@ -49,7 +49,7 @@ describe('Test ActivityPub video channels search', function () {
49 49
50 { 50 {
51 const user = { username: 'user1_server2', password: 'password' } 51 const user = { username: 'user1_server2', password: 'password' }
52 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 52 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
53 userServer2Token = await userLogin(servers[1], user) 53 userServer2Token = await userLogin(servers[1], user)
54 54
55 const channel = { 55 const channel = {
@@ -70,8 +70,8 @@ describe('Test ActivityPub video channels search', function () {
70 this.timeout(15000) 70 this.timeout(15000)
71 71
72 { 72 {
73 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' 73 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server3'
74 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) 74 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
75 75
76 expect(res.body.total).to.equal(0) 76 expect(res.body.total).to.equal(0)
77 expect(res.body.data).to.be.an('array') 77 expect(res.body.data).to.be.an('array')
@@ -80,7 +80,7 @@ describe('Test ActivityPub video channels search', function () {
80 80
81 { 81 {
82 // Without token 82 // Without token
83 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 83 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
84 const res = await searchVideoChannel(servers[0].url, search) 84 const res = await searchVideoChannel(servers[0].url, search)
85 85
86 expect(res.body.total).to.equal(0) 86 expect(res.body.total).to.equal(0)
@@ -91,35 +91,35 @@ describe('Test ActivityPub video channels search', function () {
91 91
92 it('Should search a local video channel', async function () { 92 it('Should search a local video channel', async function () {
93 const searches = [ 93 const searches = [
94 'http://localhost:' + servers[ 0 ].port + '/video-channels/channel1_server1', 94 'http://localhost:' + servers[0].port + '/video-channels/channel1_server1',
95 'channel1_server1@localhost:' + servers[ 0 ].port 95 'channel1_server1@localhost:' + servers[0].port
96 ] 96 ]
97 97
98 for (const search of searches) { 98 for (const search of searches) {
99 const res = await searchVideoChannel(servers[ 0 ].url, search) 99 const res = await searchVideoChannel(servers[0].url, search)
100 100
101 expect(res.body.total).to.equal(1) 101 expect(res.body.total).to.equal(1)
102 expect(res.body.data).to.be.an('array') 102 expect(res.body.data).to.be.an('array')
103 expect(res.body.data).to.have.lengthOf(1) 103 expect(res.body.data).to.have.lengthOf(1)
104 expect(res.body.data[ 0 ].name).to.equal('channel1_server1') 104 expect(res.body.data[0].name).to.equal('channel1_server1')
105 expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 1') 105 expect(res.body.data[0].displayName).to.equal('Channel 1 server 1')
106 } 106 }
107 }) 107 })
108 108
109 it('Should search a remote video channel with URL or handle', async function () { 109 it('Should search a remote video channel with URL or handle', async function () {
110 const searches = [ 110 const searches = [
111 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2', 111 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2',
112 'channel1_server2@localhost:' + servers[ 1 ].port 112 'channel1_server2@localhost:' + servers[1].port
113 ] 113 ]
114 114
115 for (const search of searches) { 115 for (const search of searches) {
116 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) 116 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
117 117
118 expect(res.body.total).to.equal(1) 118 expect(res.body.total).to.equal(1)
119 expect(res.body.data).to.be.an('array') 119 expect(res.body.data).to.be.an('array')
120 expect(res.body.data).to.have.lengthOf(1) 120 expect(res.body.data).to.have.lengthOf(1)
121 expect(res.body.data[ 0 ].name).to.equal('channel1_server2') 121 expect(res.body.data[0].name).to.equal('channel1_server2')
122 expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 2') 122 expect(res.body.data[0].displayName).to.equal('Channel 1 server 2')
123 } 123 }
124 }) 124 })
125 125
@@ -137,13 +137,13 @@ describe('Test ActivityPub video channels search', function () {
137 137
138 await waitJobs(servers) 138 await waitJobs(servers)
139 139
140 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) 140 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[1].port, 0, 5)
141 expect(res.body.total).to.equal(0) 141 expect(res.body.total).to.equal(0)
142 expect(res.body.data).to.have.lengthOf(0) 142 expect(res.body.data).to.have.lengthOf(0)
143 }) 143 })
144 144
145 it('Should list video channel videos of server 2 with token', async function () { 145 it('Should list video channel videos of server 2 with token', async function () {
146 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) 146 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[1].port, 0, 5)
147 147
148 expect(res.body.total).to.equal(1) 148 expect(res.body.total).to.equal(1)
149 expect(res.body.data[0].name).to.equal('video 1 server 2') 149 expect(res.body.data[0].name).to.equal('video 1 server 2')
@@ -159,7 +159,7 @@ describe('Test ActivityPub video channels search', function () {
159 // Expire video channel 159 // Expire video channel
160 await wait(10000) 160 await wait(10000)
161 161
162 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 162 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
163 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 163 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
164 expect(res.body.total).to.equal(1) 164 expect(res.body.total).to.equal(1)
165 expect(res.body.data).to.have.lengthOf(1) 165 expect(res.body.data).to.have.lengthOf(1)
@@ -182,12 +182,12 @@ describe('Test ActivityPub video channels search', function () {
182 // Expire video channel 182 // Expire video channel
183 await wait(10000) 183 await wait(10000)
184 184
185 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 185 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
186 await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 186 await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
187 187
188 await waitJobs(servers) 188 await waitJobs(servers)
189 189
190 const videoChannelName = 'channel1_server2@localhost:' + servers[ 1 ].port 190 const videoChannelName = 'channel1_server2@localhost:' + servers[1].port
191 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt') 191 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt')
192 192
193 expect(res.body.total).to.equal(2) 193 expect(res.body.total).to.equal(2)
@@ -204,7 +204,7 @@ describe('Test ActivityPub video channels search', function () {
204 // Expire video 204 // Expire video
205 await wait(10000) 205 await wait(10000)
206 206
207 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' 207 const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2'
208 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 208 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
209 expect(res.body.total).to.equal(0) 209 expect(res.body.total).to.equal(0)
210 expect(res.body.data).to.have.lengthOf(0) 210 expect(res.body.data).to.have.lengthOf(0)
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts
index dbfefadda..c62dfca0d 100644
--- a/server/tests/api/search/search-activitypub-videos.ts
+++ b/server/tests/api/search/search-activitypub-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -34,12 +34,12 @@ describe('Test ActivityPub videos search', function () {
34 await setAccessTokensToServers(servers) 34 await setAccessTokensToServers(servers)
35 35
36 { 36 {
37 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1 on server 1' }) 37 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1 on server 1' })
38 videoServer1UUID = res.body.video.uuid 38 videoServer1UUID = res.body.video.uuid
39 } 39 }
40 40
41 { 41 {
42 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 on server 2' }) 42 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 on server 2' })
43 videoServer2UUID = res.body.video.uuid 43 videoServer2UUID = res.body.video.uuid
44 } 44 }
45 45
@@ -49,7 +49,7 @@ describe('Test ActivityPub videos search', function () {
49 it('Should not find a remote video', async function () { 49 it('Should not find a remote video', async function () {
50 { 50 {
51 const search = 'http://localhost:' + servers[1].port + '/videos/watch/43' 51 const search = 'http://localhost:' + servers[1].port + '/videos/watch/43'
52 const res = await searchVideoWithToken(servers[ 0 ].url, search, servers[ 0 ].accessToken) 52 const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
53 53
54 expect(res.body.total).to.equal(0) 54 expect(res.body.total).to.equal(0)
55 expect(res.body.data).to.be.an('array') 55 expect(res.body.data).to.be.an('array')
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index 7882d9373..4801fe04a 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -78,7 +78,7 @@ describe('Test videos search', function () {
78 const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined }) 78 const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined })
79 await uploadVideo(server.url, server.accessToken, attributes5) 79 await uploadVideo(server.url, server.accessToken, attributes5)
80 80
81 const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) 81 const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2' ] })
82 await uploadVideo(server.url, server.accessToken, attributes6) 82 await uploadVideo(server.url, server.accessToken, attributes6)
83 83
84 const attributes7 = immutableAssign(attributes1, { 84 const attributes7 = immutableAssign(attributes1, {
@@ -269,16 +269,16 @@ describe('Test videos search', function () {
269 { 269 {
270 const res = await advancedVideosSearch(server.url, query) 270 const res = await advancedVideosSearch(server.url, query)
271 expect(res.body.total).to.equal(2) 271 expect(res.body.total).to.equal(2)
272 expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') 272 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
273 expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') 273 expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4')
274 } 274 }
275 275
276 { 276 {
277 const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] })) 277 const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] }))
278 expect(res.body.total).to.equal(3) 278 expect(res.body.total).to.equal(3)
279 expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') 279 expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3')
280 expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') 280 expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4')
281 expect(res.body.data[ 2 ].name).to.equal('1111 2222 3333 - 5') 281 expect(res.body.data[2].name).to.equal('1111 2222 3333 - 5')
282 } 282 }
283 283
284 { 284 {
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts
index a06f578fc..5f48dc0eb 100644
--- a/server/tests/api/server/auto-follows.ts
+++ b/server/tests/api/server/auto-follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -59,9 +59,10 @@ async function server1Follows2 (servers: ServerInfo[]) {
59 59
60async function resetFollows (servers: ServerInfo[]) { 60async function resetFollows (servers: ServerInfo[]) {
61 try { 61 try {
62 await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ]) 62 await unfollow(servers[0].url, servers[0].accessToken, servers[1])
63 await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ]) 63 await unfollow(servers[1].url, servers[1].accessToken, servers[0])
64 } catch { /* empty */ } 64 } catch { /* empty */
65 }
65 66
66 await waitJobs(servers) 67 await waitJobs(servers)
67 68
@@ -163,8 +164,8 @@ describe('Test auto follows', function () {
163 await wait(5000) 164 await wait(5000)
164 await waitJobs(servers) 165 await waitJobs(servers)
165 166
166 await checkFollow(servers[ 0 ], servers[ 1 ], false) 167 await checkFollow(servers[0], servers[1], false)
167 await checkFollow(servers[ 1 ], servers[ 0 ], false) 168 await checkFollow(servers[1], servers[0], false)
168 }) 169 })
169 170
170 it('Should auto follow the index', async function () { 171 it('Should auto follow the index', async function () {
@@ -187,7 +188,7 @@ describe('Test auto follows', function () {
187 await wait(5000) 188 await wait(5000)
188 await waitJobs(servers) 189 await waitJobs(servers)
189 190
190 await checkFollow(servers[ 0 ], servers[ 1 ], true) 191 await checkFollow(servers[0], servers[1], true)
191 192
192 await resetFollows(servers) 193 await resetFollows(servers)
193 }) 194 })
@@ -200,8 +201,8 @@ describe('Test auto follows', function () {
200 await wait(5000) 201 await wait(5000)
201 await waitJobs(servers) 202 await waitJobs(servers)
202 203
203 await checkFollow(servers[ 0 ], servers[ 1 ], false) 204 await checkFollow(servers[0], servers[1], false)
204 await checkFollow(servers[ 0 ], servers[ 2 ], true) 205 await checkFollow(servers[0], servers[2], true)
205 }) 206 })
206 }) 207 })
207 208
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index cf99e5c0a..642525455 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -11,11 +11,14 @@ import {
11 getAbout, 11 getAbout,
12 getConfig, 12 getConfig,
13 getCustomConfig, 13 getCustomConfig,
14 killallServers, parallelTests, 14 killallServers,
15 parallelTests,
15 registerUser, 16 registerUser,
16 reRunServer, ServerInfo, 17 reRunServer,
18 ServerInfo,
17 setAccessTokensToServers, 19 setAccessTokensToServers,
18 updateCustomConfig, uploadVideo 20 updateCustomConfig,
21 uploadVideo
19} from '../../../../shared/extra-utils' 22} from '../../../../shared/extra-utils'
20import { ServerConfig } from '../../../../shared/models' 23import { ServerConfig } from '../../../../shared/models'
21 24
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts
index e4e895acb..bd1b0e38a 100644
--- a/server/tests/api/server/contact-form.ts
+++ b/server/tests/api/server/contact-form.ts
@@ -1,16 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, wait } from '../../../../shared/extra-utils'
6 flushTests,
7 killallServers,
8 flushAndRunServer,
9 ServerInfo,
10 setAccessTokensToServers,
11 wait,
12 cleanupTests
13} from '../../../../shared/extra-utils'
14import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 6import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
15import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 7import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
16import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' 8import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form'
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts
index c55a221f2..f18859e5d 100644
--- a/server/tests/api/server/email.ts
+++ b/server/tests/api/server/email.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts
index 46663bf7c..a73440286 100644
--- a/server/tests/api/server/follow-constraints.ts
+++ b/server/tests/api/server/follow-constraints.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -35,11 +35,11 @@ describe('Test follow constraints', function () {
35 await setAccessTokensToServers(servers) 35 await setAccessTokensToServers(servers)
36 36
37 { 37 {
38 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video server 1' }) 38 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' })
39 video1UUID = res.body.video.uuid 39 video1UUID = res.body.video.uuid
40 } 40 }
41 { 41 {
42 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video server 2' }) 42 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' })
43 video2UUID = res.body.video.uuid 43 video2UUID = res.body.video.uuid
44 } 44 }
45 45
@@ -47,7 +47,7 @@ describe('Test follow constraints', function () {
47 username: 'user1', 47 username: 'user1',
48 password: 'super_password' 48 password: 'super_password'
49 } 49 }
50 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 50 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
51 userAccessToken = await userLogin(servers[0], user) 51 userAccessToken = await userLogin(servers[0], user)
52 52
53 await doubleFollow(servers[0], servers[1]) 53 await doubleFollow(servers[0], servers[1])
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts
index 1984c9eb1..cee85cc4b 100644
--- a/server/tests/api/server/follows-moderation.ts
+++ b/server/tests/api/server/follows-moderation.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -24,7 +24,7 @@ const expect = chai.expect
24 24
25async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { 25async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') {
26 { 26 {
27 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 27 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
28 expect(res.body.total).to.equal(1) 28 expect(res.body.total).to.equal(1)
29 29
30 const follow = res.body.data[0] as ActorFollow 30 const follow = res.body.data[0] as ActorFollow
@@ -34,7 +34,7 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
34 } 34 }
35 35
36 { 36 {
37 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 37 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
38 expect(res.body.total).to.equal(1) 38 expect(res.body.total).to.equal(1)
39 39
40 const follow = res.body.data[0] as ActorFollow 40 const follow = res.body.data[0] as ActorFollow
@@ -46,12 +46,12 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
46 46
47async function checkNoFollowers (servers: ServerInfo[]) { 47async function checkNoFollowers (servers: ServerInfo[]) {
48 { 48 {
49 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 49 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
50 expect(res.body.total).to.equal(0) 50 expect(res.body.total).to.equal(0)
51 } 51 }
52 52
53 { 53 {
54 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 54 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
55 expect(res.body.total).to.equal(0) 55 expect(res.body.total).to.equal(0)
56 } 56 }
57} 57}
@@ -164,17 +164,17 @@ describe('Test follows moderation', function () {
164 await waitJobs(servers) 164 await waitJobs(servers)
165 165
166 { 166 {
167 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 167 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
168 expect(res.body.total).to.equal(2) 168 expect(res.body.total).to.equal(2)
169 } 169 }
170 170
171 { 171 {
172 const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) 172 const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' })
173 expect(res.body.total).to.equal(1) 173 expect(res.body.total).to.equal(1)
174 } 174 }
175 175
176 { 176 {
177 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) 177 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' })
178 expect(res.body.total).to.equal(1) 178 expect(res.body.total).to.equal(1)
179 } 179 }
180 180
@@ -184,7 +184,7 @@ describe('Test follows moderation', function () {
184 await checkServer1And2HasFollowers(servers) 184 await checkServer1And2HasFollowers(servers)
185 185
186 { 186 {
187 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) 187 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' })
188 expect(res.body.total).to.equal(0) 188 expect(res.body.total).to.equal(0)
189 } 189 }
190 }) 190 })
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index 4ffa9e791..b686af4e4 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -78,14 +78,14 @@ describe('Test follows', function () {
78 }) 78 })
79 79
80 it('Should have 2 followings on server 1', async function () { 80 it('Should have 2 followings on server 1', async function () {
81 let res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 1, sort: 'createdAt' }) 81 let res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 1, sort: 'createdAt' })
82 let follows = res.body.data 82 let follows = res.body.data
83 83
84 expect(res.body.total).to.equal(2) 84 expect(res.body.total).to.equal(2)
85 expect(follows).to.be.an('array') 85 expect(follows).to.be.an('array')
86 expect(follows.length).to.equal(1) 86 expect(follows.length).to.equal(1)
87 87
88 res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 1, count: 1, sort: 'createdAt' }) 88 res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 1, count: 1, sort: 'createdAt' })
89 follows = follows.concat(res.body.data) 89 follows = follows.concat(res.body.data)
90 90
91 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port) 91 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
@@ -101,7 +101,7 @@ describe('Test follows', function () {
101 const sort = 'createdAt' 101 const sort = 'createdAt'
102 const start = 0 102 const start = 0
103 const count = 1 103 const count = 1
104 const url = servers[ 0 ].url 104 const url = servers[0].url
105 105
106 { 106 {
107 const search = ':' + servers[1].port 107 const search = ':' + servers[1].port
@@ -112,7 +112,7 @@ describe('Test follows', function () {
112 112
113 expect(res.body.total).to.equal(1) 113 expect(res.body.total).to.equal(1)
114 expect(follows.length).to.equal(1) 114 expect(follows.length).to.equal(1)
115 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 1 ].port) 115 expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
116 } 116 }
117 117
118 { 118 {
@@ -170,9 +170,9 @@ describe('Test follows', function () {
170 170
171 it('Should have 1 followers on server 2 and 3', async function () { 171 it('Should have 1 followers on server 2 and 3', async function () {
172 for (const server of [ servers[1], servers[2] ]) { 172 for (const server of [ servers[1], servers[2] ]) {
173 let res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' }) 173 const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' })
174 174
175 let follows = res.body.data 175 const follows = res.body.data
176 expect(res.body.total).to.equal(1) 176 expect(res.body.total).to.equal(1)
177 expect(follows).to.be.an('array') 177 expect(follows).to.be.an('array')
178 expect(follows.length).to.equal(1) 178 expect(follows.length).to.equal(1)
@@ -181,7 +181,7 @@ describe('Test follows', function () {
181 }) 181 })
182 182
183 it('Should search/filter followers on server 2', async function () { 183 it('Should search/filter followers on server 2', async function () {
184 const url = servers[ 2 ].url 184 const url = servers[2].url
185 const start = 0 185 const start = 0
186 const count = 5 186 const count = 5
187 const sort = 'createdAt' 187 const sort = 'createdAt'
@@ -195,7 +195,7 @@ describe('Test follows', function () {
195 195
196 expect(res.body.total).to.equal(1) 196 expect(res.body.total).to.equal(1)
197 expect(follows.length).to.equal(1) 197 expect(follows.length).to.equal(1)
198 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 2 ].port) 198 expect(follows[0].following.host).to.equal('localhost:' + servers[2].port)
199 } 199 }
200 200
201 { 201 {
@@ -241,7 +241,7 @@ describe('Test follows', function () {
241 }) 241 })
242 242
243 it('Should have 0 followers on server 1', async function () { 243 it('Should have 0 followers on server 1', async function () {
244 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) 244 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' })
245 const follows = res.body.data 245 const follows = res.body.data
246 246
247 expect(res.body.total).to.equal(0) 247 expect(res.body.total).to.equal(0)
@@ -271,8 +271,8 @@ describe('Test follows', function () {
271 }) 271 })
272 272
273 it('Should not follow server 3 on server 1 anymore', async function () { 273 it('Should not follow server 3 on server 1 anymore', async function () {
274 const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 274 const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
275 let follows = res.body.data 275 const follows = res.body.data
276 276
277 expect(res.body.total).to.equal(1) 277 expect(res.body.total).to.equal(1)
278 expect(follows).to.be.an('array') 278 expect(follows).to.be.an('array')
@@ -282,9 +282,9 @@ describe('Test follows', function () {
282 }) 282 })
283 283
284 it('Should not have server 1 as follower on server 3 anymore', async function () { 284 it('Should not have server 1 as follower on server 3 anymore', async function () {
285 const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 1, sort: 'createdAt' }) 285 const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 1, sort: 'createdAt' })
286 286
287 let follows = res.body.data 287 const follows = res.body.data
288 expect(res.body.total).to.equal(0) 288 expect(res.body.total).to.equal(0)
289 expect(follows).to.be.an('array') 289 expect(follows).to.be.an('array')
290 expect(follows.length).to.equal(0) 290 expect(follows.length).to.equal(0)
@@ -336,59 +336,59 @@ describe('Test follows', function () {
336 tags: [ 'tag1', 'tag2', 'tag3' ] 336 tags: [ 'tag1', 'tag2', 'tag3' ]
337 } 337 }
338 338
339 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-2' }) 339 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-2' })
340 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-3' }) 340 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-3' })
341 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4Attributes) 341 await uploadVideo(servers[2].url, servers[2].accessToken, video4Attributes)
342 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-5' }) 342 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-5' })
343 await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-6' }) 343 await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-6' })
344 344
345 { 345 {
346 const user = { username: 'captain', password: 'password' } 346 const user = { username: 'captain', password: 'password' }
347 await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password }) 347 await createUser({ url: servers[2].url, accessToken: servers[2].accessToken, username: user.username, password: user.password })
348 const userAccessToken = await userLogin(servers[ 2 ], user) 348 const userAccessToken = await userLogin(servers[2], user)
349 349
350 const resVideos = await getVideosList(servers[ 2 ].url) 350 const resVideos = await getVideosList(servers[2].url)
351 video4 = resVideos.body.data.find(v => v.name === 'server3-4') 351 video4 = resVideos.body.data.find(v => v.name === 'server3-4')
352 352
353 { 353 {
354 await rateVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, 'like') 354 await rateVideo(servers[2].url, servers[2].accessToken, video4.id, 'like')
355 await rateVideo(servers[ 2 ].url, userAccessToken, video4.id, 'dislike') 355 await rateVideo(servers[2].url, userAccessToken, video4.id, 'dislike')
356 } 356 }
357 357
358 { 358 {
359 { 359 {
360 const text = 'my super first comment' 360 const text = 'my super first comment'
361 const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) 361 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
362 const threadId = res.body.comment.id 362 const threadId = res.body.comment.id
363 363
364 const text1 = 'my super answer to thread 1' 364 const text1 = 'my super answer to thread 1'
365 const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) 365 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
366 const childCommentId = childCommentRes.body.comment.id 366 const childCommentId = childCommentRes.body.comment.id
367 367
368 const text2 = 'my super answer to answer of thread 1' 368 const text2 = 'my super answer to answer of thread 1'
369 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2) 369 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text2)
370 370
371 const text3 = 'my second answer to thread 1' 371 const text3 = 'my second answer to thread 1'
372 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3) 372 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text3)
373 } 373 }
374 374
375 { 375 {
376 const text = 'will be deleted' 376 const text = 'will be deleted'
377 const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) 377 const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text)
378 const threadId = res.body.comment.id 378 const threadId = res.body.comment.id
379 379
380 const text1 = 'answer to deleted' 380 const text1 = 'answer to deleted'
381 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) 381 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1)
382 382
383 const text2 = 'will also be deleted' 383 const text2 = 'will also be deleted'
384 const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text2) 384 const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text2)
385 const childCommentId = childCommentRes.body.comment.id 385 const childCommentId = childCommentRes.body.comment.id
386 386
387 const text3 = 'my second answer to deleted' 387 const text3 = 'my second answer to deleted'
388 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text3) 388 await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text3)
389 389
390 await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId) 390 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, threadId)
391 await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId) 391 await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, childCommentId)
392 } 392 }
393 } 393 }
394 394
@@ -406,7 +406,7 @@ describe('Test follows', function () {
406 await waitJobs(servers) 406 await waitJobs(servers)
407 407
408 // Server 1 follows server 3 408 // Server 1 follows server 3
409 await follow(servers[ 0 ].url, [ servers[ 2 ].url ], servers[ 0 ].accessToken) 409 await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken)
410 410
411 await waitJobs(servers) 411 await waitJobs(servers)
412 }) 412 })
@@ -424,7 +424,7 @@ describe('Test follows', function () {
424 }) 424 })
425 425
426 it('Should have propagated videos', async function () { 426 it('Should have propagated videos', async function () {
427 const res = await getVideosList(servers[ 0 ].url) 427 const res = await getVideosList(servers[0].url)
428 expect(res.body.total).to.equal(7) 428 expect(res.body.total).to.equal(7)
429 429
430 const video2 = res.body.data.find(v => v.name === 'server3-2') 430 const video2 = res.body.data.find(v => v.name === 'server3-2')
@@ -470,7 +470,7 @@ describe('Test follows', function () {
470 } 470 }
471 ] 471 ]
472 } 472 }
473 await completeVideoCheck(servers[ 0 ].url, video4, checkAttributes) 473 await completeVideoCheck(servers[0].url, video4, checkAttributes)
474 }) 474 })
475 475
476 it('Should have propagated comments', async function () { 476 it('Should have propagated comments', async function () {
@@ -481,34 +481,34 @@ describe('Test follows', function () {
481 expect(res1.body.data).to.have.lengthOf(2) 481 expect(res1.body.data).to.have.lengthOf(2)
482 482
483 { 483 {
484 const comment: VideoComment = res1.body.data[ 0 ] 484 const comment: VideoComment = res1.body.data[0]
485 expect(comment.inReplyToCommentId).to.be.null 485 expect(comment.inReplyToCommentId).to.be.null
486 expect(comment.text).equal('my super first comment') 486 expect(comment.text).equal('my super first comment')
487 expect(comment.videoId).to.equal(video4.id) 487 expect(comment.videoId).to.equal(video4.id)
488 expect(comment.id).to.equal(comment.threadId) 488 expect(comment.id).to.equal(comment.threadId)
489 expect(comment.account.name).to.equal('root') 489 expect(comment.account.name).to.equal('root')
490 expect(comment.account.host).to.equal('localhost:' + servers[ 2 ].port) 490 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
491 expect(comment.totalReplies).to.equal(3) 491 expect(comment.totalReplies).to.equal(3)
492 expect(dateIsValid(comment.createdAt as string)).to.be.true 492 expect(dateIsValid(comment.createdAt as string)).to.be.true
493 expect(dateIsValid(comment.updatedAt as string)).to.be.true 493 expect(dateIsValid(comment.updatedAt as string)).to.be.true
494 494
495 const threadId = comment.threadId 495 const threadId = comment.threadId
496 496
497 const res2 = await getVideoThreadComments(servers[ 0 ].url, video4.id, threadId) 497 const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId)
498 498
499 const tree: VideoCommentThreadTree = res2.body 499 const tree: VideoCommentThreadTree = res2.body
500 expect(tree.comment.text).equal('my super first comment') 500 expect(tree.comment.text).equal('my super first comment')
501 expect(tree.children).to.have.lengthOf(2) 501 expect(tree.children).to.have.lengthOf(2)
502 502
503 const firstChild = tree.children[ 0 ] 503 const firstChild = tree.children[0]
504 expect(firstChild.comment.text).to.equal('my super answer to thread 1') 504 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
505 expect(firstChild.children).to.have.lengthOf(1) 505 expect(firstChild.children).to.have.lengthOf(1)
506 506
507 const childOfFirstChild = firstChild.children[ 0 ] 507 const childOfFirstChild = firstChild.children[0]
508 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') 508 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
509 expect(childOfFirstChild.children).to.have.lengthOf(0) 509 expect(childOfFirstChild.children).to.have.lengthOf(0)
510 510
511 const secondChild = tree.children[ 1 ] 511 const secondChild = tree.children[1]
512 expect(secondChild.comment.text).to.equal('my second answer to thread 1') 512 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
513 expect(secondChild.children).to.have.lengthOf(0) 513 expect(secondChild.children).to.have.lengthOf(0)
514 } 514 }
@@ -569,7 +569,7 @@ describe('Test follows', function () {
569 569
570 await waitJobs(servers) 570 await waitJobs(servers)
571 571
572 let res = await getVideosList(servers[ 0 ].url) 572 const res = await getVideosList(servers[0].url)
573 expect(res.body.total).to.equal(1) 573 expect(res.body.total).to.equal(1)
574 }) 574 })
575 575
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index 7e36067f1..2cf6e15ad 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,6 +8,7 @@ import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-c
8 8
9import { 9import {
10 cleanupTests, 10 cleanupTests,
11 closeAllSequelize,
11 completeVideoCheck, 12 completeVideoCheck,
12 flushAndRunMultipleServers, 13 flushAndRunMultipleServers,
13 getVideo, 14 getVideo,
@@ -17,11 +18,12 @@ import {
17 reRunServer, 18 reRunServer,
18 ServerInfo, 19 ServerInfo,
19 setAccessTokensToServers, 20 setAccessTokensToServers,
21 setActorFollowScores,
20 unfollow, 22 unfollow,
21 updateVideo, 23 updateVideo,
22 uploadVideo, uploadVideoAndGetId, 24 uploadVideo,
23 wait, 25 uploadVideoAndGetId,
24 setActorFollowScores, closeAllSequelize 26 wait
25} from '../../../../shared/extra-utils' 27} from '../../../../shared/extra-utils'
26import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' 28import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows'
27import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' 29import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -44,7 +46,7 @@ describe('Test handle downs', function () {
44 let missedVideo2: Video 46 let missedVideo2: Video
45 let unlistedVideo: Video 47 let unlistedVideo: Video
46 48
47 let videoIdsServer1: number[] = [] 49 const videoIdsServer1: number[] = []
48 50
49 const videoAttributes = { 51 const videoAttributes = {
50 name: 'my super name for server 1', 52 name: 'my super name for server 1',
@@ -137,7 +139,7 @@ describe('Test handle downs', function () {
137 139
138 // Remove server 2 follower 140 // Remove server 2 follower
139 for (let i = 0; i < 10; i++) { 141 for (let i = 0; i < 10; i++) {
140 await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 142 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
141 } 143 }
142 144
143 await waitJobs(servers[0]) 145 await waitJobs(servers[0])
@@ -145,14 +147,14 @@ describe('Test handle downs', function () {
145 // Kill server 3 147 // Kill server 3
146 killallServers([ servers[2] ]) 148 killallServers([ servers[2] ])
147 149
148 const resLastVideo1 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 150 const resLastVideo1 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
149 missedVideo1 = resLastVideo1.body.video 151 missedVideo1 = resLastVideo1.body.video
150 152
151 const resLastVideo2 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 153 const resLastVideo2 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
152 missedVideo2 = resLastVideo2.body.video 154 missedVideo2 = resLastVideo2.body.video
153 155
154 // Unlisted video 156 // Unlisted video
155 let resVideo = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, unlistedVideoAttributes) 157 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, unlistedVideoAttributes)
156 unlistedVideo = resVideo.body.video 158 unlistedVideo = resVideo.body.video
157 159
158 // Add comments to video 2 160 // Add comments to video 2
@@ -174,7 +176,7 @@ describe('Test handle downs', function () {
174 await wait(11000) 176 await wait(11000)
175 177
176 // Only server 3 is still a follower of server 1 178 // Only server 3 is still a follower of server 1
177 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 179 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
178 expect(res.body.data).to.be.an('array') 180 expect(res.body.data).to.be.an('array')
179 expect(res.body.data).to.have.lengthOf(1) 181 expect(res.body.data).to.have.lengthOf(1)
180 expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port) 182 expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port)
@@ -185,8 +187,8 @@ describe('Test handle downs', function () {
185 187
186 for (const state of states) { 188 for (const state of states) {
187 const res = await getJobsListPaginationAndSort({ 189 const res = await getJobsListPaginationAndSort({
188 url: servers[ 0 ].url, 190 url: servers[0].url,
189 accessToken: servers[ 0 ].accessToken, 191 accessToken: servers[0].accessToken,
190 state: state, 192 state: state,
191 start: 0, 193 start: 0,
192 count: 50, 194 count: 50,
@@ -209,7 +211,7 @@ describe('Test handle downs', function () {
209 211
210 await waitJobs(servers) 212 await waitJobs(servers)
211 213
212 const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) 214 const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' })
213 expect(res.body.data).to.be.an('array') 215 expect(res.body.data).to.be.an('array')
214 expect(res.body.data).to.have.lengthOf(2) 216 expect(res.body.data).to.have.lengthOf(2)
215 }) 217 })
@@ -221,8 +223,8 @@ describe('Test handle downs', function () {
221 expect(res1.body.data).to.be.an('array') 223 expect(res1.body.data).to.be.an('array')
222 expect(res1.body.data).to.have.lengthOf(11) 224 expect(res1.body.data).to.have.lengthOf(11)
223 225
224 await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, { }) 226 await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, {})
225 await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, { }) 227 await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, {})
226 228
227 await waitJobs(servers) 229 await waitJobs(servers)
228 230
@@ -313,14 +315,14 @@ describe('Test handle downs', function () {
313 this.timeout(120000) 315 this.timeout(120000)
314 316
315 for (let i = 0; i < 10; i++) { 317 for (let i = 0; i < 10; i++) {
316 const uuid = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'video ' + i })).uuid 318 const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video ' + i })).uuid
317 videoIdsServer1.push(uuid) 319 videoIdsServer1.push(uuid)
318 } 320 }
319 321
320 await waitJobs(servers) 322 await waitJobs(servers)
321 323
322 for (const id of videoIdsServer1) { 324 for (const id of videoIdsServer1) {
323 await getVideo(servers[ 1 ].url, id) 325 await getVideo(servers[1].url, id)
324 } 326 }
325 327
326 await waitJobs(servers) 328 await waitJobs(servers)
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts
index 58d8c8c10..19c8836b5 100644
--- a/server/tests/api/server/jobs.ts
+++ b/server/tests/api/server/jobs.ts
@@ -1,8 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 5import { cleanupTests, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index'
6import { doubleFollow } from '../../../../shared/extra-utils/server/follows' 6import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
7import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' 7import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs'
8import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers' 8import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers'
@@ -44,8 +44,8 @@ describe('Test jobs', function () {
44 it('Should list jobs with sort, pagination and job type', async function () { 44 it('Should list jobs with sort, pagination and job type', async function () {
45 { 45 {
46 const res = await getJobsListPaginationAndSort({ 46 const res = await getJobsListPaginationAndSort({
47 url: servers[ 1 ].url, 47 url: servers[1].url,
48 accessToken: servers[ 1 ].accessToken, 48 accessToken: servers[1].accessToken,
49 state: 'completed', 49 state: 'completed',
50 start: 1, 50 start: 1,
51 count: 2, 51 count: 2,
@@ -54,9 +54,9 @@ describe('Test jobs', function () {
54 expect(res.body.total).to.be.above(2) 54 expect(res.body.total).to.be.above(2)
55 expect(res.body.data).to.have.lengthOf(2) 55 expect(res.body.data).to.have.lengthOf(2)
56 56
57 let job: Job = res.body.data[ 0 ] 57 let job: Job = res.body.data[0]
58 // Skip repeat jobs 58 // Skip repeat jobs
59 if (job.type === 'videos-views') job = res.body.data[ 1 ] 59 if (job.type === 'videos-views') job = res.body.data[1]
60 60
61 expect(job.state).to.equal('completed') 61 expect(job.state).to.equal('completed')
62 expect(job.type.startsWith('activitypub-')).to.be.true 62 expect(job.type.startsWith('activitypub-')).to.be.true
@@ -67,8 +67,8 @@ describe('Test jobs', function () {
67 67
68 { 68 {
69 const res = await getJobsListPaginationAndSort({ 69 const res = await getJobsListPaginationAndSort({
70 url: servers[ 1 ].url, 70 url: servers[1].url,
71 accessToken: servers[ 1 ].accessToken, 71 accessToken: servers[1].accessToken,
72 state: 'completed', 72 state: 'completed',
73 start: 0, 73 start: 0,
74 count: 100, 74 count: 100,
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts
index d3c877408..b8714c7a1 100644
--- a/server/tests/api/server/logs.ts
+++ b/server/tests/api/server/logs.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/server/no-client.ts b/server/tests/api/server/no-client.ts
index 86edeb289..d0450aba0 100644
--- a/server/tests/api/server/no-client.ts
+++ b/server/tests/api/server/no-client.ts
@@ -9,7 +9,7 @@ describe('Start and stop server without web client routes', function () {
9 before(async function () { 9 before(async function () {
10 this.timeout(30000) 10 this.timeout(30000)
11 11
12 server = await flushAndRunServer(1, {}, ['--no-client']) 12 server = await flushAndRunServer(1, {}, [ '--no-client' ])
13 }) 13 })
14 14
15 it('Should fail getting the client', function () { 15 it('Should fail getting the client', function () {
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts
index b8a8a2fee..452d05012 100644
--- a/server/tests/api/server/plugins.ts
+++ b/server/tests/api/server/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -6,19 +6,28 @@ import {
6 cleanupTests, 6 cleanupTests,
7 closeAllSequelize, 7 closeAllSequelize,
8 flushAndRunServer, 8 flushAndRunServer,
9 getConfig, getMyUserInformation, getPluginPackageJSON, 9 getConfig,
10 getMyUserInformation,
10 getPlugin, 11 getPlugin,
12 getPluginPackageJSON,
11 getPluginRegisteredSettings, 13 getPluginRegisteredSettings,
12 getPluginsCSS, 14 getPluginsCSS,
13 installPlugin, killallServers, 15 getPublicSettings,
16 installPlugin,
17 killallServers,
14 listAvailablePlugins, 18 listAvailablePlugins,
15 listPlugins, reRunServer, 19 listPlugins,
20 reRunServer,
16 ServerInfo, 21 ServerInfo,
17 setAccessTokensToServers, 22 setAccessTokensToServers,
18 setPluginVersion, uninstallPlugin, 23 setPluginVersion,
19 updateCustomSubConfig, updateMyUser, updatePluginPackageJSON, updatePlugin, 24 uninstallPlugin,
25 updateCustomSubConfig,
26 updateMyUser,
27 updatePlugin,
28 updatePluginPackageJSON,
20 updatePluginSettings, 29 updatePluginSettings,
21 wait, getPublicSettings 30 wait
22} from '../../../../shared/extra-utils' 31} from '../../../../shared/extra-utils'
23import { PluginType } from '../../../../shared/models/plugins/plugin.type' 32import { PluginType } from '../../../../shared/models/plugins/plugin.type'
24import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' 33import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model'
@@ -88,7 +97,7 @@ describe('Test plugins', function () {
88 expect(res2.body.total).to.be.at.least(2) 97 expect(res2.body.total).to.be.at.least(2)
89 expect(data2).to.have.lengthOf(2) 98 expect(data2).to.have.lengthOf(2)
90 99
91 expect(data1[0].npmName).to.not.equal(data2[ 0 ].npmName) 100 expect(data1[0].npmName).to.not.equal(data2[0].npmName)
92 } 101 }
93 102
94 { 103 {
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts
index b6b33a884..d0d79c4f6 100644
--- a/server/tests/api/server/reverse-proxy.ts
+++ b/server/tests/api/server/reverse-proxy.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
index a01cd4b38..c207bb5f0 100644
--- a/server/tests/api/server/stats.ts
+++ b/server/tests/api/server/stats.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -9,13 +9,12 @@ import {
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 follow, 11 follow,
12 killallServers,
13 ServerInfo, 12 ServerInfo,
14 uploadVideo, 13 uploadVideo,
15 viewVideo, 14 viewVideo,
16 wait 15 wait
17} from '../../../../shared/extra-utils' 16} from '../../../../shared/extra-utils'
18import { flushTests, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 17import { setAccessTokensToServers } from '../../../../shared/extra-utils/index'
19import { getStats } from '../../../../shared/extra-utils/server/stats' 18import { getStats } from '../../../../shared/extra-utils/server/stats'
20import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' 19import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments'
21import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 20import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -36,7 +35,7 @@ describe('Test stats (excluding redundancy)', function () {
36 username: 'user1', 35 username: 'user1',
37 password: 'super_password' 36 password: 'super_password'
38 } 37 }
39 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 38 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
40 39
41 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) 40 const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
42 const videoUUID = resVideo.body.video.uuid 41 const videoUUID = resVideo.body.video.uuid
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts
index 9d7eec8ca..9d3a274d4 100644
--- a/server/tests/api/server/tracker.ts
+++ b/server/tests/api/server/tracker.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await,@typescript-eslint/no-floating-promises */
2 2
3import * as magnetUtil from 'magnet-uri' 3import * as magnetUtil from 'magnet-uri'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts
index 05e58017a..21b9ae4f8 100644
--- a/server/tests/api/users/blocklist.ts
+++ b/server/tests/api/users/blocklist.ts
@@ -1,21 +1,20 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { AccountBlock, ServerBlock, UserNotificationType, Video } from '../../../../shared/index' 5import { AccountBlock, ServerBlock, Video } from '../../../../shared/index'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, deleteVideoComment, 8 createUser,
9 deleteVideoComment,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
11 flushTests,
12 killallServers,
13 ServerInfo, 12 ServerInfo,
14 uploadVideo, 13 uploadVideo,
15 userLogin 14 userLogin
16} from '../../../../shared/extra-utils/index' 15} from '../../../../shared/extra-utils/index'
17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 16import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
18import { getVideosListWithToken, getVideosList } from '../../../../shared/extra-utils/videos/videos' 17import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos'
19import { 18import {
20 addVideoCommentReply, 19 addVideoCommentReply,
21 addVideoCommentThread, 20 addVideoCommentThread,
@@ -79,7 +78,7 @@ async function checkCommentNotification (
79 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text) 78 const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text)
80 const threadId = resComment.body.comment.id 79 const threadId = resComment.body.comment.id
81 80
82 await waitJobs([ mainServer, comment.server]) 81 await waitJobs([ mainServer, comment.server ])
83 82
84 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30) 83 const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30)
85 const commentNotifications = res.body.data 84 const commentNotifications = res.body.data
@@ -90,7 +89,7 @@ async function checkCommentNotification (
90 89
91 await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId) 90 await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId)
92 91
93 await waitJobs([ mainServer, comment.server]) 92 await waitJobs([ mainServer, comment.server ])
94} 93}
95 94
96describe('Test blocklist', function () { 95describe('Test blocklist', function () {
@@ -109,7 +108,7 @@ describe('Test blocklist', function () {
109 108
110 { 109 {
111 const user = { username: 'user1', password: 'password' } 110 const user = { username: 'user1', password: 'password' }
112 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 111 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
113 112
114 userToken1 = await userLogin(servers[0], user) 113 userToken1 = await userLogin(servers[0], user)
115 await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) 114 await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' })
@@ -117,14 +116,14 @@ describe('Test blocklist', function () {
117 116
118 { 117 {
119 const user = { username: 'moderator', password: 'password' } 118 const user = { username: 'moderator', password: 'password' }
120 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 119 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
121 120
122 userModeratorToken = await userLogin(servers[0], user) 121 userModeratorToken = await userLogin(servers[0], user)
123 } 122 }
124 123
125 { 124 {
126 const user = { username: 'user2', password: 'password' } 125 const user = { username: 'user2', password: 'password' }
127 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 126 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
128 127
129 userToken2 = await userLogin(servers[1], user) 128 userToken2 = await userLogin(servers[1], user)
130 await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) 129 await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' })
@@ -143,14 +142,14 @@ describe('Test blocklist', function () {
143 await doubleFollow(servers[0], servers[1]) 142 await doubleFollow(servers[0], servers[1])
144 143
145 { 144 {
146 const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1') 145 const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID1, 'comment root 1')
147 const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') 146 const resReply = await addVideoCommentReply(servers[0].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1')
148 await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') 147 await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1')
149 } 148 }
150 149
151 { 150 {
152 const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1') 151 const resComment = await addVideoCommentThread(servers[0].url, userToken1, videoUUID1, 'comment user 1')
153 await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') 152 await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1')
154 } 153 }
155 154
156 await waitJobs(servers) 155 await waitJobs(servers)
@@ -160,19 +159,19 @@ describe('Test blocklist', function () {
160 159
161 describe('When managing account blocklist', function () { 160 describe('When managing account blocklist', function () {
162 it('Should list all videos', function () { 161 it('Should list all videos', function () {
163 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 162 return checkAllVideos(servers[0].url, servers[0].accessToken)
164 }) 163 })
165 164
166 it('Should list the comments', function () { 165 it('Should list the comments', function () {
167 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 166 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
168 }) 167 })
169 168
170 it('Should block a remote account', async function () { 169 it('Should block a remote account', async function () {
171 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 170 await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
172 }) 171 })
173 172
174 it('Should hide its videos', async function () { 173 it('Should hide its videos', async function () {
175 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 174 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
176 175
177 const videos: Video[] = res.body.data 176 const videos: Video[] = res.body.data
178 expect(videos).to.have.lengthOf(3) 177 expect(videos).to.have.lengthOf(3)
@@ -182,11 +181,11 @@ describe('Test blocklist', function () {
182 }) 181 })
183 182
184 it('Should block a local account', async function () { 183 it('Should block a local account', async function () {
185 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 184 await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
186 }) 185 })
187 186
188 it('Should hide its videos', async function () { 187 it('Should hide its videos', async function () {
189 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 188 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
190 189
191 const videos: Video[] = res.body.data 190 const videos: Video[] = res.body.data
192 expect(videos).to.have.lengthOf(2) 191 expect(videos).to.have.lengthOf(2)
@@ -196,17 +195,17 @@ describe('Test blocklist', function () {
196 }) 195 })
197 196
198 it('Should hide its comments', async function () { 197 it('Should hide its comments', async function () {
199 const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', servers[ 0 ].accessToken) 198 const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', servers[0].accessToken)
200 199
201 const threads: VideoComment[] = resThreads.body.data 200 const threads: VideoComment[] = resThreads.body.data
202 expect(threads).to.have.lengthOf(1) 201 expect(threads).to.have.lengthOf(1)
203 expect(threads[ 0 ].totalReplies).to.equal(0) 202 expect(threads[0].totalReplies).to.equal(0)
204 203
205 const t = threads.find(t => t.text === 'comment user 1') 204 const t = threads.find(t => t.text === 'comment user 1')
206 expect(t).to.be.undefined 205 expect(t).to.be.undefined
207 206
208 for (const thread of threads) { 207 for (const thread of threads) {
209 const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, servers[ 0 ].accessToken) 208 const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, servers[0].accessToken)
210 209
211 const tree: VideoCommentThreadTree = res.body 210 const tree: VideoCommentThreadTree = res.body
212 expect(tree.children).to.have.lengthOf(0) 211 expect(tree.children).to.have.lengthOf(0)
@@ -217,37 +216,37 @@ describe('Test blocklist', function () {
217 this.timeout(20000) 216 this.timeout(20000)
218 217
219 { 218 {
220 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } 219 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
221 await checkCommentNotification(servers[ 0 ], comment, 'absence') 220 await checkCommentNotification(servers[0], comment, 'absence')
222 } 221 }
223 222
224 { 223 {
225 const comment = { 224 const comment = {
226 server: servers[ 0 ], 225 server: servers[0],
227 token: userToken1, 226 token: userToken1,
228 videoUUID: videoUUID2, 227 videoUUID: videoUUID2,
229 text: 'hello @root@localhost:' + servers[ 0 ].port 228 text: 'hello @root@localhost:' + servers[0].port
230 } 229 }
231 await checkCommentNotification(servers[ 0 ], comment, 'absence') 230 await checkCommentNotification(servers[0], comment, 'absence')
232 } 231 }
233 }) 232 })
234 233
235 it('Should list all the videos with another user', async function () { 234 it('Should list all the videos with another user', async function () {
236 return checkAllVideos(servers[ 0 ].url, userToken1) 235 return checkAllVideos(servers[0].url, userToken1)
237 }) 236 })
238 237
239 it('Should list all the comments with another user', async function () { 238 it('Should list all the comments with another user', async function () {
240 return checkAllComments(servers[ 0 ].url, userToken1, videoUUID1) 239 return checkAllComments(servers[0].url, userToken1, videoUUID1)
241 }) 240 })
242 241
243 it('Should list blocked accounts', async function () { 242 it('Should list blocked accounts', async function () {
244 { 243 {
245 const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 244 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
246 const blocks: AccountBlock[] = res.body.data 245 const blocks: AccountBlock[] = res.body.data
247 246
248 expect(res.body.total).to.equal(2) 247 expect(res.body.total).to.equal(2)
249 248
250 const block = blocks[ 0 ] 249 const block = blocks[0]
251 expect(block.byAccount.displayName).to.equal('root') 250 expect(block.byAccount.displayName).to.equal('root')
252 expect(block.byAccount.name).to.equal('root') 251 expect(block.byAccount.name).to.equal('root')
253 expect(block.blockedAccount.displayName).to.equal('user2') 252 expect(block.blockedAccount.displayName).to.equal('user2')
@@ -256,12 +255,12 @@ describe('Test blocklist', function () {
256 } 255 }
257 256
258 { 257 {
259 const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') 258 const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt')
260 const blocks: AccountBlock[] = res.body.data 259 const blocks: AccountBlock[] = res.body.data
261 260
262 expect(res.body.total).to.equal(2) 261 expect(res.body.total).to.equal(2)
263 262
264 const block = blocks[ 0 ] 263 const block = blocks[0]
265 expect(block.byAccount.displayName).to.equal('root') 264 expect(block.byAccount.displayName).to.equal('root')
266 expect(block.byAccount.name).to.equal('root') 265 expect(block.byAccount.name).to.equal('root')
267 expect(block.blockedAccount.displayName).to.equal('user1') 266 expect(block.blockedAccount.displayName).to.equal('user1')
@@ -271,11 +270,11 @@ describe('Test blocklist', function () {
271 }) 270 })
272 271
273 it('Should unblock the remote account', async function () { 272 it('Should unblock the remote account', async function () {
274 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 273 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
275 }) 274 })
276 275
277 it('Should display its videos', async function () { 276 it('Should display its videos', async function () {
278 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 277 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
279 278
280 const videos: Video[] = res.body.data 279 const videos: Video[] = res.body.data
281 expect(videos).to.have.lengthOf(3) 280 expect(videos).to.have.lengthOf(3)
@@ -285,48 +284,48 @@ describe('Test blocklist', function () {
285 }) 284 })
286 285
287 it('Should unblock the local account', async function () { 286 it('Should unblock the local account', async function () {
288 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 287 await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1')
289 }) 288 })
290 289
291 it('Should display its comments', function () { 290 it('Should display its comments', function () {
292 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 291 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
293 }) 292 })
294 293
295 it('Should have a notification from a non blocked account', async function () { 294 it('Should have a notification from a non blocked account', async function () {
296 this.timeout(20000) 295 this.timeout(20000)
297 296
298 { 297 {
299 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 298 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
300 await checkCommentNotification(servers[ 0 ], comment, 'presence') 299 await checkCommentNotification(servers[0], comment, 'presence')
301 } 300 }
302 301
303 { 302 {
304 const comment = { 303 const comment = {
305 server: servers[ 0 ], 304 server: servers[0],
306 token: userToken1, 305 token: userToken1,
307 videoUUID: videoUUID2, 306 videoUUID: videoUUID2,
308 text: 'hello @root@localhost:' + servers[ 0 ].port 307 text: 'hello @root@localhost:' + servers[0].port
309 } 308 }
310 await checkCommentNotification(servers[ 0 ], comment, 'presence') 309 await checkCommentNotification(servers[0], comment, 'presence')
311 } 310 }
312 }) 311 })
313 }) 312 })
314 313
315 describe('When managing server blocklist', function () { 314 describe('When managing server blocklist', function () {
316 it('Should list all videos', function () { 315 it('Should list all videos', function () {
317 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 316 return checkAllVideos(servers[0].url, servers[0].accessToken)
318 }) 317 })
319 318
320 it('Should list the comments', function () { 319 it('Should list the comments', function () {
321 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 320 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
322 }) 321 })
323 322
324 it('Should block a remote server', async function () { 323 it('Should block a remote server', async function () {
325 await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 324 await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
326 }) 325 })
327 326
328 it('Should hide its videos', async function () { 327 it('Should hide its videos', async function () {
329 const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) 328 const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken)
330 329
331 const videos: Video[] = res.body.data 330 const videos: Video[] = res.body.data
332 expect(videos).to.have.lengthOf(2) 331 expect(videos).to.have.lengthOf(2)
@@ -339,81 +338,81 @@ describe('Test blocklist', function () {
339 }) 338 })
340 339
341 it('Should list all the videos with another user', async function () { 340 it('Should list all the videos with another user', async function () {
342 return checkAllVideos(servers[ 0 ].url, userToken1) 341 return checkAllVideos(servers[0].url, userToken1)
343 }) 342 })
344 343
345 it('Should hide its comments', async function () { 344 it('Should hide its comments', async function () {
346 this.timeout(10000) 345 this.timeout(10000)
347 346
348 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') 347 const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2')
349 const threadId = resThreads.body.comment.id 348 const threadId = resThreads.body.comment.id
350 349
351 await waitJobs(servers) 350 await waitJobs(servers)
352 351
353 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 352 await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
354 353
355 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) 354 await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId)
356 }) 355 })
357 356
358 it('Should not have notifications from blocked server', async function () { 357 it('Should not have notifications from blocked server', async function () {
359 this.timeout(20000) 358 this.timeout(20000)
360 359
361 { 360 {
362 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } 361 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
363 await checkCommentNotification(servers[ 0 ], comment, 'absence') 362 await checkCommentNotification(servers[0], comment, 'absence')
364 } 363 }
365 364
366 { 365 {
367 const comment = { 366 const comment = {
368 server: servers[ 1 ], 367 server: servers[1],
369 token: userToken2, 368 token: userToken2,
370 videoUUID: videoUUID1, 369 videoUUID: videoUUID1,
371 text: 'hello @root@localhost:' + servers[ 0 ].port 370 text: 'hello @root@localhost:' + servers[0].port
372 } 371 }
373 await checkCommentNotification(servers[ 0 ], comment, 'absence') 372 await checkCommentNotification(servers[0], comment, 'absence')
374 } 373 }
375 }) 374 })
376 375
377 it('Should list blocked servers', async function () { 376 it('Should list blocked servers', async function () {
378 const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 377 const res = await getServerBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
379 const blocks: ServerBlock[] = res.body.data 378 const blocks: ServerBlock[] = res.body.data
380 379
381 expect(res.body.total).to.equal(1) 380 expect(res.body.total).to.equal(1)
382 381
383 const block = blocks[ 0 ] 382 const block = blocks[0]
384 expect(block.byAccount.displayName).to.equal('root') 383 expect(block.byAccount.displayName).to.equal('root')
385 expect(block.byAccount.name).to.equal('root') 384 expect(block.byAccount.name).to.equal('root')
386 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 385 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
387 }) 386 })
388 387
389 it('Should unblock the remote server', async function () { 388 it('Should unblock the remote server', async function () {
390 await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 389 await removeServerFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
391 }) 390 })
392 391
393 it('Should display its videos', function () { 392 it('Should display its videos', function () {
394 return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) 393 return checkAllVideos(servers[0].url, servers[0].accessToken)
395 }) 394 })
396 395
397 it('Should display its comments', function () { 396 it('Should display its comments', function () {
398 return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 397 return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
399 }) 398 })
400 399
401 it('Should have notification from unblocked server', async function () { 400 it('Should have notification from unblocked server', async function () {
402 this.timeout(20000) 401 this.timeout(20000)
403 402
404 { 403 {
405 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 404 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
406 await checkCommentNotification(servers[ 0 ], comment, 'presence') 405 await checkCommentNotification(servers[0], comment, 'presence')
407 } 406 }
408 407
409 { 408 {
410 const comment = { 409 const comment = {
411 server: servers[ 1 ], 410 server: servers[1],
412 token: userToken2, 411 token: userToken2,
413 videoUUID: videoUUID1, 412 videoUUID: videoUUID1,
414 text: 'hello @root@localhost:' + servers[ 0 ].port 413 text: 'hello @root@localhost:' + servers[0].port
415 } 414 }
416 await checkCommentNotification(servers[ 0 ], comment, 'presence') 415 await checkCommentNotification(servers[0], comment, 'presence')
417 } 416 }
418 }) 417 })
419 }) 418 })
@@ -423,24 +422,24 @@ describe('Test blocklist', function () {
423 422
424 describe('When managing account blocklist', function () { 423 describe('When managing account blocklist', function () {
425 it('Should list all videos', async function () { 424 it('Should list all videos', async function () {
426 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 425 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
427 await checkAllVideos(servers[ 0 ].url, token) 426 await checkAllVideos(servers[0].url, token)
428 } 427 }
429 }) 428 })
430 429
431 it('Should list the comments', async function () { 430 it('Should list the comments', async function () {
432 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 431 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
433 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 432 await checkAllComments(servers[0].url, token, videoUUID1)
434 } 433 }
435 }) 434 })
436 435
437 it('Should block a remote account', async function () { 436 it('Should block a remote account', async function () {
438 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 437 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
439 }) 438 })
440 439
441 it('Should hide its videos', async function () { 440 it('Should hide its videos', async function () {
442 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 441 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
443 const res = await getVideosListWithToken(servers[ 0 ].url, token) 442 const res = await getVideosListWithToken(servers[0].url, token)
444 443
445 const videos: Video[] = res.body.data 444 const videos: Video[] = res.body.data
446 expect(videos).to.have.lengthOf(3) 445 expect(videos).to.have.lengthOf(3)
@@ -451,12 +450,12 @@ describe('Test blocklist', function () {
451 }) 450 })
452 451
453 it('Should block a local account', async function () { 452 it('Should block a local account', async function () {
454 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 453 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user1')
455 }) 454 })
456 455
457 it('Should hide its videos', async function () { 456 it('Should hide its videos', async function () {
458 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 457 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
459 const res = await getVideosListWithToken(servers[ 0 ].url, token) 458 const res = await getVideosListWithToken(servers[0].url, token)
460 459
461 const videos: Video[] = res.body.data 460 const videos: Video[] = res.body.data
462 expect(videos).to.have.lengthOf(2) 461 expect(videos).to.have.lengthOf(2)
@@ -467,18 +466,18 @@ describe('Test blocklist', function () {
467 }) 466 })
468 467
469 it('Should hide its comments', async function () { 468 it('Should hide its comments', async function () {
470 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 469 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
471 const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', token) 470 const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', token)
472 471
473 const threads: VideoComment[] = resThreads.body.data 472 const threads: VideoComment[] = resThreads.body.data
474 expect(threads).to.have.lengthOf(1) 473 expect(threads).to.have.lengthOf(1)
475 expect(threads[ 0 ].totalReplies).to.equal(0) 474 expect(threads[0].totalReplies).to.equal(0)
476 475
477 const t = threads.find(t => t.text === 'comment user 1') 476 const t = threads.find(t => t.text === 'comment user 1')
478 expect(t).to.be.undefined 477 expect(t).to.be.undefined
479 478
480 for (const thread of threads) { 479 for (const thread of threads) {
481 const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, token) 480 const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, token)
482 481
483 const tree: VideoCommentThreadTree = res.body 482 const tree: VideoCommentThreadTree = res.body
484 expect(tree.children).to.have.lengthOf(0) 483 expect(tree.children).to.have.lengthOf(0)
@@ -490,29 +489,29 @@ describe('Test blocklist', function () {
490 this.timeout(20000) 489 this.timeout(20000)
491 490
492 { 491 {
493 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } 492 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' }
494 await checkCommentNotification(servers[ 0 ], comment, 'absence') 493 await checkCommentNotification(servers[0], comment, 'absence')
495 } 494 }
496 495
497 { 496 {
498 const comment = { 497 const comment = {
499 server: servers[ 1 ], 498 server: servers[1],
500 token: userToken2, 499 token: userToken2,
501 videoUUID: videoUUID1, 500 videoUUID: videoUUID1,
502 text: 'hello @root@localhost:' + servers[ 0 ].port 501 text: 'hello @root@localhost:' + servers[0].port
503 } 502 }
504 await checkCommentNotification(servers[ 0 ], comment, 'absence') 503 await checkCommentNotification(servers[0], comment, 'absence')
505 } 504 }
506 }) 505 })
507 506
508 it('Should list blocked accounts', async function () { 507 it('Should list blocked accounts', async function () {
509 { 508 {
510 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 509 const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
511 const blocks: AccountBlock[] = res.body.data 510 const blocks: AccountBlock[] = res.body.data
512 511
513 expect(res.body.total).to.equal(2) 512 expect(res.body.total).to.equal(2)
514 513
515 const block = blocks[ 0 ] 514 const block = blocks[0]
516 expect(block.byAccount.displayName).to.equal('peertube') 515 expect(block.byAccount.displayName).to.equal('peertube')
517 expect(block.byAccount.name).to.equal('peertube') 516 expect(block.byAccount.name).to.equal('peertube')
518 expect(block.blockedAccount.displayName).to.equal('user2') 517 expect(block.blockedAccount.displayName).to.equal('user2')
@@ -521,12 +520,12 @@ describe('Test blocklist', function () {
521 } 520 }
522 521
523 { 522 {
524 const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') 523 const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt')
525 const blocks: AccountBlock[] = res.body.data 524 const blocks: AccountBlock[] = res.body.data
526 525
527 expect(res.body.total).to.equal(2) 526 expect(res.body.total).to.equal(2)
528 527
529 const block = blocks[ 0 ] 528 const block = blocks[0]
530 expect(block.byAccount.displayName).to.equal('peertube') 529 expect(block.byAccount.displayName).to.equal('peertube')
531 expect(block.byAccount.name).to.equal('peertube') 530 expect(block.byAccount.name).to.equal('peertube')
532 expect(block.blockedAccount.displayName).to.equal('user1') 531 expect(block.blockedAccount.displayName).to.equal('user1')
@@ -536,12 +535,12 @@ describe('Test blocklist', function () {
536 }) 535 })
537 536
538 it('Should unblock the remote account', async function () { 537 it('Should unblock the remote account', async function () {
539 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) 538 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port)
540 }) 539 })
541 540
542 it('Should display its videos', async function () { 541 it('Should display its videos', async function () {
543 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 542 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
544 const res = await getVideosListWithToken(servers[ 0 ].url, token) 543 const res = await getVideosListWithToken(servers[0].url, token)
545 544
546 const videos: Video[] = res.body.data 545 const videos: Video[] = res.body.data
547 expect(videos).to.have.lengthOf(3) 546 expect(videos).to.have.lengthOf(3)
@@ -552,12 +551,12 @@ describe('Test blocklist', function () {
552 }) 551 })
553 552
554 it('Should unblock the local account', async function () { 553 it('Should unblock the local account', async function () {
555 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') 554 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user1')
556 }) 555 })
557 556
558 it('Should display its comments', async function () { 557 it('Should display its comments', async function () {
559 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 558 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
560 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 559 await checkAllComments(servers[0].url, token, videoUUID1)
561 } 560 }
562 }) 561 })
563 562
@@ -565,43 +564,43 @@ describe('Test blocklist', function () {
565 this.timeout(20000) 564 this.timeout(20000)
566 565
567 { 566 {
568 const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' } 567 const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' }
569 await checkCommentNotification(servers[ 0 ], comment, 'presence') 568 await checkCommentNotification(servers[0], comment, 'presence')
570 } 569 }
571 570
572 { 571 {
573 const comment = { 572 const comment = {
574 server: servers[ 1 ], 573 server: servers[1],
575 token: userToken2, 574 token: userToken2,
576 videoUUID: videoUUID1, 575 videoUUID: videoUUID1,
577 text: 'hello @root@localhost:' + servers[ 0 ].port 576 text: 'hello @root@localhost:' + servers[0].port
578 } 577 }
579 await checkCommentNotification(servers[ 0 ], comment, 'presence') 578 await checkCommentNotification(servers[0], comment, 'presence')
580 } 579 }
581 }) 580 })
582 }) 581 })
583 582
584 describe('When managing server blocklist', function () { 583 describe('When managing server blocklist', function () {
585 it('Should list all videos', async function () { 584 it('Should list all videos', async function () {
586 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 585 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
587 await checkAllVideos(servers[ 0 ].url, token) 586 await checkAllVideos(servers[0].url, token)
588 } 587 }
589 }) 588 })
590 589
591 it('Should list the comments', async function () { 590 it('Should list the comments', async function () {
592 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 591 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
593 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 592 await checkAllComments(servers[0].url, token, videoUUID1)
594 } 593 }
595 }) 594 })
596 595
597 it('Should block a remote server', async function () { 596 it('Should block a remote server', async function () {
598 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 597 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
599 }) 598 })
600 599
601 it('Should hide its videos', async function () { 600 it('Should hide its videos', async function () {
602 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 601 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
603 const res1 = await getVideosList(servers[ 0 ].url) 602 const res1 = await getVideosList(servers[0].url)
604 const res2 = await getVideosListWithToken(servers[ 0 ].url, token) 603 const res2 = await getVideosListWithToken(servers[0].url, token)
605 604
606 for (const res of [ res1, res2 ]) { 605 for (const res of [ res1, res2 ]) {
607 const videos: Video[] = res.body.data 606 const videos: Video[] = res.body.data
@@ -619,60 +618,60 @@ describe('Test blocklist', function () {
619 it('Should hide its comments', async function () { 618 it('Should hide its comments', async function () {
620 this.timeout(10000) 619 this.timeout(10000)
621 620
622 const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') 621 const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2')
623 const threadId = resThreads.body.comment.id 622 const threadId = resThreads.body.comment.id
624 623
625 await waitJobs(servers) 624 await waitJobs(servers)
626 625
627 await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) 626 await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1)
628 627
629 await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) 628 await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId)
630 }) 629 })
631 630
632 it('Should not have notification from blocked instances by instance', async function () { 631 it('Should not have notification from blocked instances by instance', async function () {
633 this.timeout(20000) 632 this.timeout(20000)
634 633
635 { 634 {
636 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } 635 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' }
637 await checkCommentNotification(servers[ 0 ], comment, 'absence') 636 await checkCommentNotification(servers[0], comment, 'absence')
638 } 637 }
639 638
640 { 639 {
641 const comment = { 640 const comment = {
642 server: servers[ 1 ], 641 server: servers[1],
643 token: userToken2, 642 token: userToken2,
644 videoUUID: videoUUID1, 643 videoUUID: videoUUID1,
645 text: 'hello @root@localhost:' + servers[ 0 ].port 644 text: 'hello @root@localhost:' + servers[0].port
646 } 645 }
647 await checkCommentNotification(servers[ 0 ], comment, 'absence') 646 await checkCommentNotification(servers[0], comment, 'absence')
648 } 647 }
649 }) 648 })
650 649
651 it('Should list blocked servers', async function () { 650 it('Should list blocked servers', async function () {
652 const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') 651 const res = await getServerBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt')
653 const blocks: ServerBlock[] = res.body.data 652 const blocks: ServerBlock[] = res.body.data
654 653
655 expect(res.body.total).to.equal(1) 654 expect(res.body.total).to.equal(1)
656 655
657 const block = blocks[ 0 ] 656 const block = blocks[0]
658 expect(block.byAccount.displayName).to.equal('peertube') 657 expect(block.byAccount.displayName).to.equal('peertube')
659 expect(block.byAccount.name).to.equal('peertube') 658 expect(block.byAccount.name).to.equal('peertube')
660 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) 659 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
661 }) 660 })
662 661
663 it('Should unblock the remote server', async function () { 662 it('Should unblock the remote server', async function () {
664 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 663 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
665 }) 664 })
666 665
667 it('Should list all videos', async function () { 666 it('Should list all videos', async function () {
668 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 667 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
669 await checkAllVideos(servers[ 0 ].url, token) 668 await checkAllVideos(servers[0].url, token)
670 } 669 }
671 }) 670 })
672 671
673 it('Should list the comments', async function () { 672 it('Should list the comments', async function () {
674 for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { 673 for (const token of [ userModeratorToken, servers[0].accessToken ]) {
675 await checkAllComments(servers[ 0 ].url, token, videoUUID1) 674 await checkAllComments(servers[0].url, token, videoUUID1)
676 } 675 }
677 }) 676 })
678 677
@@ -680,18 +679,18 @@ describe('Test blocklist', function () {
680 this.timeout(20000) 679 this.timeout(20000)
681 680
682 { 681 {
683 const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } 682 const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' }
684 await checkCommentNotification(servers[ 0 ], comment, 'presence') 683 await checkCommentNotification(servers[0], comment, 'presence')
685 } 684 }
686 685
687 { 686 {
688 const comment = { 687 const comment = {
689 server: servers[ 1 ], 688 server: servers[1],
690 token: userToken2, 689 token: userToken2,
691 videoUUID: videoUUID1, 690 videoUUID: videoUUID1,
692 text: 'hello @root@localhost:' + servers[ 0 ].port 691 text: 'hello @root@localhost:' + servers[0].port
693 } 692 }
694 await checkCommentNotification(servers[ 0 ], comment, 'presence') 693 await checkCommentNotification(servers[0], comment, 'presence')
695 } 694 }
696 }) 695 })
697 }) 696 })
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts
index 08017f89c..7d6b0c6a9 100644
--- a/server/tests/api/users/user-subscriptions.ts
+++ b/server/tests/api/users/user-subscriptions.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -13,16 +13,17 @@ import {
13 updateVideo, 13 updateVideo,
14 userLogin 14 userLogin
15} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
16import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' 16import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index'
17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 17import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
18import { Video, VideoChannel } from '../../../../shared/models/videos' 18import { Video, VideoChannel } from '../../../../shared/models/videos'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 19import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
20import { 20import {
21 addUserSubscription, 21 addUserSubscription,
22 areSubscriptionsExist,
23 getUserSubscription,
22 listUserSubscriptions, 24 listUserSubscriptions,
23 listUserSubscriptionVideos, 25 listUserSubscriptionVideos,
24 removeUserSubscription, 26 removeUserSubscription
25 getUserSubscription, areSubscriptionsExist
26} from '../../../../shared/extra-utils/users/user-subscriptions' 27} from '../../../../shared/extra-utils/users/user-subscriptions'
27 28
28const expect = chai.expect 29const expect = chai.expect
@@ -116,7 +117,7 @@ describe('Test users subscriptions', function () {
116 117
117 it('Should get subscription', async function () { 118 it('Should get subscription', async function () {
118 { 119 {
119 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:' + servers[2].port) 120 const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
120 const videoChannel: VideoChannel = res.body 121 const videoChannel: VideoChannel = res.body
121 122
122 expect(videoChannel.name).to.equal('user3_channel') 123 expect(videoChannel.name).to.equal('user3_channel')
@@ -127,7 +128,7 @@ describe('Test users subscriptions', function () {
127 } 128 }
128 129
129 { 130 {
130 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:' + servers[0].port) 131 const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
131 const videoChannel: VideoChannel = res.body 132 const videoChannel: VideoChannel = res.body
132 133
133 expect(videoChannel.name).to.equal('root_channel') 134 expect(videoChannel.name).to.equal('root_channel')
@@ -146,7 +147,7 @@ describe('Test users subscriptions', function () {
146 'user3_channel@localhost:' + servers[0].port 147 'user3_channel@localhost:' + servers[0].port
147 ] 148 ]
148 149
149 const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris) 150 const res = await areSubscriptionsExist(servers[0].url, users[0].accessToken, uris)
150 const body = res.body 151 const body = res.body
151 152
152 expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true 153 expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts
index 791418318..591ce4959 100644
--- a/server/tests/api/users/users-multiple-servers.ts
+++ b/server/tests/api/users/users-multiple-servers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -57,17 +57,17 @@ describe('Test users with multiple servers', function () {
57 password: 'password' 57 password: 'password'
58 } 58 }
59 const res = await createUser({ 59 const res = await createUser({
60 url: servers[ 0 ].url, 60 url: servers[0].url,
61 accessToken: servers[ 0 ].accessToken, 61 accessToken: servers[0].accessToken,
62 username: user.username, 62 username: user.username,
63 password: user.password 63 password: user.password
64 }) 64 })
65 userId = res.body.user.id 65 userId = res.body.user.id
66 userAccessToken = await userLogin(servers[ 0 ], user) 66 userAccessToken = await userLogin(servers[0], user)
67 } 67 }
68 68
69 { 69 {
70 const resVideo = await uploadVideo(servers[ 0 ].url, userAccessToken, {}) 70 const resVideo = await uploadVideo(servers[0].url, userAccessToken, {})
71 videoUUID = resVideo.body.video.uuid 71 videoUUID = resVideo.body.video.uuid
72 } 72 }
73 73
@@ -86,7 +86,6 @@ describe('Test users with multiple servers', function () {
86 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 86 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
87 user = res.body 87 user = res.body
88 88
89 const account: Account = user.account
90 expect(user.account.displayName).to.equal('my super display name') 89 expect(user.account.displayName).to.equal('my super display name')
91 90
92 await waitJobs(servers) 91 await waitJobs(servers)
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts
index 7cd61f539..675ebf690 100644
--- a/server/tests/api/users/users-verification.ts
+++ b/server/tests/api/users/users-verification.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index 24203a731..502eac0bb 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -1,8 +1,8 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { User, UserRole, Video, MyUser, VideoPlaylistType } from '../../../../shared/index' 5import { MyUser, User, UserRole, Video, VideoPlaylistType } from '../../../../shared/index'
6import { 6import {
7 blockUser, 7 blockUser,
8 cleanupTests, 8 cleanupTests,
@@ -18,7 +18,8 @@ import {
18 getUsersList, 18 getUsersList,
19 getUsersListPaginationAndSort, 19 getUsersListPaginationAndSort,
20 getVideoChannel, 20 getVideoChannel,
21 getVideosList, installPlugin, 21 getVideosList,
22 installPlugin,
22 login, 23 login,
23 makePutBodyRequest, 24 makePutBodyRequest,
24 rateVideo, 25 rateVideo,
@@ -121,13 +122,13 @@ describe('Test users', function () {
121 122
122 it('Should be able to login with an insensitive username', async function () { 123 it('Should be able to login with an insensitive username', async function () {
123 const user = { username: 'RoOt', password: server.user.password } 124 const user = { username: 'RoOt', password: server.user.password }
124 const res = await login(server.url, server.client, user, 200) 125 await login(server.url, server.client, user, 200)
125 126
126 const user2 = { username: 'rOoT', password: server.user.password } 127 const user2 = { username: 'rOoT', password: server.user.password }
127 const res2 = await login(server.url, server.client, user2, 200) 128 await login(server.url, server.client, user2, 200)
128 129
129 const user3 = { username: 'ROOt', password: server.user.password } 130 const user3 = { username: 'ROOt', password: server.user.password }
130 const res3 = await login(server.url, server.client, user3, 200) 131 await login(server.url, server.client, user3, 200)
131 }) 132 })
132 }) 133 })
133 134
@@ -137,7 +138,7 @@ describe('Test users', function () {
137 const videoAttributes = {} 138 const videoAttributes = {}
138 await uploadVideo(server.url, accessToken, videoAttributes) 139 await uploadVideo(server.url, accessToken, videoAttributes)
139 const res = await getVideosList(server.url) 140 const res = await getVideosList(server.url)
140 const video = res.body.data[ 0 ] 141 const video = res.body.data[0]
141 142
142 expect(video.account.name).to.equal('root') 143 expect(video.account.name).to.equal('root')
143 videoId = video.id 144 videoId = video.id
@@ -167,8 +168,8 @@ describe('Test users', function () {
167 const ratings = res.body 168 const ratings = res.body
168 169
169 expect(ratings.total).to.equal(1) 170 expect(ratings.total).to.equal(1)
170 expect(ratings.data[ 0 ].video.id).to.equal(videoId) 171 expect(ratings.data[0].video.id).to.equal(videoId)
171 expect(ratings.data[ 0 ].rating).to.equal('like') 172 expect(ratings.data[0].rating).to.equal('like')
172 }) 173 })
173 174
174 it('Should retrieve ratings list by rating type', async function () { 175 it('Should retrieve ratings list by rating type', async function () {
@@ -307,7 +308,7 @@ describe('Test users', function () {
307 const videos = res.body.data 308 const videos = res.body.data
308 expect(videos).to.have.lengthOf(1) 309 expect(videos).to.have.lengthOf(1)
309 310
310 const video: Video = videos[ 0 ] 311 const video: Video = videos[0]
311 expect(video.name).to.equal('super user video') 312 expect(video.name).to.equal('super user video')
312 expect(video.thumbnailPath).to.not.be.null 313 expect(video.thumbnailPath).to.not.be.null
313 expect(video.previewPath).to.not.be.null 314 expect(video.previewPath).to.not.be.null
@@ -344,12 +345,12 @@ describe('Test users', function () {
344 expect(users).to.be.an('array') 345 expect(users).to.be.an('array')
345 expect(users.length).to.equal(2) 346 expect(users.length).to.equal(2)
346 347
347 const user = users[ 0 ] 348 const user = users[0]
348 expect(user.username).to.equal('user_1') 349 expect(user.username).to.equal('user_1')
349 expect(user.email).to.equal('user_1@example.com') 350 expect(user.email).to.equal('user_1@example.com')
350 expect(user.nsfwPolicy).to.equal('display') 351 expect(user.nsfwPolicy).to.equal('display')
351 352
352 const rootUser = users[ 1 ] 353 const rootUser = users[1]
353 expect(rootUser.username).to.equal('root') 354 expect(rootUser.username).to.equal('root')
354 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') 355 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
355 expect(user.nsfwPolicy).to.equal('display') 356 expect(user.nsfwPolicy).to.equal('display')
@@ -367,7 +368,7 @@ describe('Test users', function () {
367 expect(total).to.equal(2) 368 expect(total).to.equal(2)
368 expect(users.length).to.equal(1) 369 expect(users.length).to.equal(1)
369 370
370 const user = users[ 0 ] 371 const user = users[0]
371 expect(user.username).to.equal('root') 372 expect(user.username).to.equal('root')
372 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') 373 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
373 expect(user.roleLabel).to.equal('Administrator') 374 expect(user.roleLabel).to.equal('Administrator')
@@ -383,7 +384,7 @@ describe('Test users', function () {
383 expect(total).to.equal(2) 384 expect(total).to.equal(2)
384 expect(users.length).to.equal(1) 385 expect(users.length).to.equal(1)
385 386
386 const user = users[ 0 ] 387 const user = users[0]
387 expect(user.username).to.equal('user_1') 388 expect(user.username).to.equal('user_1')
388 expect(user.email).to.equal('user_1@example.com') 389 expect(user.email).to.equal('user_1@example.com')
389 expect(user.nsfwPolicy).to.equal('display') 390 expect(user.nsfwPolicy).to.equal('display')
@@ -398,7 +399,7 @@ describe('Test users', function () {
398 expect(total).to.equal(2) 399 expect(total).to.equal(2)
399 expect(users.length).to.equal(1) 400 expect(users.length).to.equal(1)
400 401
401 const user = users[ 0 ] 402 const user = users[0]
402 expect(user.username).to.equal('user_1') 403 expect(user.username).to.equal('user_1')
403 expect(user.email).to.equal('user_1@example.com') 404 expect(user.email).to.equal('user_1@example.com')
404 expect(user.nsfwPolicy).to.equal('display') 405 expect(user.nsfwPolicy).to.equal('display')
@@ -413,13 +414,13 @@ describe('Test users', function () {
413 expect(total).to.equal(2) 414 expect(total).to.equal(2)
414 expect(users.length).to.equal(2) 415 expect(users.length).to.equal(2)
415 416
416 expect(users[ 0 ].username).to.equal('root') 417 expect(users[0].username).to.equal('root')
417 expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com') 418 expect(users[0].email).to.equal('admin' + server.internalServerNumber + '@example.com')
418 expect(users[ 0 ].nsfwPolicy).to.equal('display') 419 expect(users[0].nsfwPolicy).to.equal('display')
419 420
420 expect(users[ 1 ].username).to.equal('user_1') 421 expect(users[1].username).to.equal('user_1')
421 expect(users[ 1 ].email).to.equal('user_1@example.com') 422 expect(users[1].email).to.equal('user_1@example.com')
422 expect(users[ 1 ].nsfwPolicy).to.equal('display') 423 expect(users[1].nsfwPolicy).to.equal('display')
423 }) 424 })
424 425
425 it('Should search user by username', async function () { 426 it('Should search user by username', async function () {
@@ -429,7 +430,7 @@ describe('Test users', function () {
429 expect(res.body.total).to.equal(1) 430 expect(res.body.total).to.equal(1)
430 expect(users.length).to.equal(1) 431 expect(users.length).to.equal(1)
431 432
432 expect(users[ 0 ].username).to.equal('root') 433 expect(users[0].username).to.equal('root')
433 }) 434 })
434 435
435 it('Should search user by email', async function () { 436 it('Should search user by email', async function () {
@@ -440,8 +441,8 @@ describe('Test users', function () {
440 expect(res.body.total).to.equal(1) 441 expect(res.body.total).to.equal(1)
441 expect(users.length).to.equal(1) 442 expect(users.length).to.equal(1)
442 443
443 expect(users[ 0 ].username).to.equal('user_1') 444 expect(users[0].username).to.equal('user_1')
444 expect(users[ 0 ].email).to.equal('user_1@example.com') 445 expect(users[0].email).to.equal('user_1@example.com')
445 } 446 }
446 447
447 { 448 {
@@ -451,8 +452,8 @@ describe('Test users', function () {
451 expect(res.body.total).to.equal(2) 452 expect(res.body.total).to.equal(2)
452 expect(users.length).to.equal(2) 453 expect(users.length).to.equal(2)
453 454
454 expect(users[ 0 ].username).to.equal('root') 455 expect(users[0].username).to.equal('root')
455 expect(users[ 1 ].username).to.equal('user_1') 456 expect(users[1].username).to.equal('user_1')
456 } 457 }
457 }) 458 })
458 }) 459 })
@@ -691,7 +692,7 @@ describe('Test users', function () {
691 692
692 expect(res.body.total).to.equal(1) 693 expect(res.body.total).to.equal(1)
693 694
694 const video = res.body.data[ 0 ] 695 const video = res.body.data[0]
695 expect(video.account.name).to.equal('root') 696 expect(video.account.name).to.equal('root')
696 }) 697 })
697 }) 698 })
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts
index f12d730cc..ac7a0b89c 100644
--- a/server/tests/api/videos/audio-only.ts
+++ b/server/tests/api/videos/audio-only.ts
@@ -1,28 +1,21 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 checkDirectoryIsEmpty,
7 checkSegmentHash,
8 checkTmpIsEmpty,
9 cleanupTests, 6 cleanupTests,
10 doubleFollow, 7 doubleFollow,
11 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
12 getPlaylist, 9 getVideo,
13 getVideo, makeGetRequest, makeRawRequest, 10 root,
14 removeVideo, root,
15 ServerInfo, 11 ServerInfo,
16 setAccessTokensToServers, updateCustomSubConfig, 12 setAccessTokensToServers,
17 updateVideo,
18 uploadVideo, 13 uploadVideo,
19 waitJobs, webtorrentAdd 14 waitJobs
20} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
21import { VideoDetails } from '../../../../shared/models/videos' 16import { VideoDetails } from '../../../../shared/models/videos'
22import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
23import { join } from 'path' 17import { join } from 'path'
24import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' 18import { audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils'
25import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils'
26 19
27const expect = chai.expect 20const expect = chai.expect
28 21
@@ -87,14 +80,14 @@ describe('Test audio only video transcoding', function () {
87 80
88 it('0p transcoded video should not have video', async function () { 81 it('0p transcoded video should not have video', async function () {
89 const paths = [ 82 const paths = [
90 join(root(), 'test' + servers[ 0 ].internalServerNumber, 'videos', videoUUID + '-0.mp4'), 83 join(root(), 'test' + servers[0].internalServerNumber, 'videos', videoUUID + '-0.mp4'),
91 join(root(), 'test' + servers[ 0 ].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4') 84 join(root(), 'test' + servers[0].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4')
92 ] 85 ]
93 86
94 for (const path of paths) { 87 for (const path of paths) {
95 const { audioStream } = await audio.get(path) 88 const { audioStream } = await audio.get(path)
96 expect(audioStream[ 'codec_name' ]).to.be.equal('aac') 89 expect(audioStream['codec_name']).to.be.equal('aac')
97 expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) 90 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000)
98 91
99 const size = await getVideoStreamSize(path) 92 const size = await getVideoStreamSize(path)
100 expect(size.height).to.equal(0) 93 expect(size.height).to.equal(0)
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index fa3e250ec..e3029f1ae 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -63,9 +63,9 @@ describe('Test multiple servers', function () {
63 displayName: 'my channel', 63 displayName: 'my channel',
64 description: 'super channel' 64 description: 'super channel'
65 } 65 }
66 await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 66 await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
67 const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1) 67 const channelRes = await getVideoChannelsList(servers[0].url, 0, 1)
68 videoChannelId = channelRes.body.data[ 0 ].id 68 videoChannelId = channelRes.body.data[0].id
69 } 69 }
70 70
71 // Server 1 and server 2 follow each other 71 // Server 1 and server 2 follow each other
@@ -163,7 +163,7 @@ describe('Test multiple servers', function () {
163 username: 'user1', 163 username: 'user1',
164 password: 'super_password' 164 password: 'super_password'
165 } 165 }
166 await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) 166 await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password })
167 const userAccessToken = await userLogin(servers[1], user) 167 const userAccessToken = await userLogin(servers[1], user)
168 168
169 const videoAttributes = { 169 const videoAttributes = {
@@ -762,12 +762,12 @@ describe('Test multiple servers', function () {
762 762
763 { 763 {
764 const text = 'my super first comment' 764 const text = 'my super first comment'
765 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, text) 765 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, text)
766 } 766 }
767 767
768 { 768 {
769 const text = 'my super second comment' 769 const text = 'my super second comment'
770 await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, text) 770 await addVideoCommentThread(servers[2].url, servers[2].accessToken, videoUUID, text)
771 } 771 }
772 772
773 await waitJobs(servers) 773 await waitJobs(servers)
@@ -777,7 +777,7 @@ describe('Test multiple servers', function () {
777 const threadId = res.body.data.find(c => c.text === 'my super first comment').id 777 const threadId = res.body.data.find(c => c.text === 'my super first comment').id
778 778
779 const text = 'my super answer to thread 1' 779 const text = 'my super answer to thread 1'
780 await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, videoUUID, threadId, text) 780 await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text)
781 } 781 }
782 782
783 await waitJobs(servers) 783 await waitJobs(servers)
@@ -790,10 +790,10 @@ describe('Test multiple servers', function () {
790 const childCommentId = res2.body.children[0].comment.id 790 const childCommentId = res2.body.children[0].comment.id
791 791
792 const text3 = 'my second answer to thread 1' 792 const text3 = 'my second answer to thread 1'
793 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, threadId, text3) 793 await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, threadId, text3)
794 794
795 const text2 = 'my super answer to answer of thread 1' 795 const text2 = 'my super answer to answer of thread 1'
796 await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, childCommentId, text2) 796 await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, childCommentId, text2)
797 } 797 }
798 798
799 await waitJobs(servers) 799 await waitJobs(servers)
@@ -900,9 +900,9 @@ describe('Test multiple servers', function () {
900 it('Should delete the thread comments', async function () { 900 it('Should delete the thread comments', async function () {
901 this.timeout(10000) 901 this.timeout(10000)
902 902
903 const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) 903 const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5)
904 const threadId = res.body.data.find(c => c.text === 'my super first comment').id 904 const threadId = res.body.data.find(c => c.text === 'my super first comment').id
905 await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) 905 await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
906 906
907 await waitJobs(servers) 907 await waitJobs(servers)
908 }) 908 })
@@ -945,9 +945,9 @@ describe('Test multiple servers', function () {
945 it('Should delete a remote thread by the origin server', async function () { 945 it('Should delete a remote thread by the origin server', async function () {
946 this.timeout(5000) 946 this.timeout(5000)
947 947
948 const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) 948 const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5)
949 const threadId = res.body.data.find(c => c.text === 'my super second comment').id 949 const threadId = res.body.data.find(c => c.text === 'my super second comment').id
950 await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) 950 await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId)
951 951
952 await waitJobs(servers) 952 await waitJobs(servers)
953 }) 953 })
@@ -1021,7 +1021,7 @@ describe('Test multiple servers', function () {
1021 const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') 1021 const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm')
1022 1022
1023 await req.attach('videofile', filePath) 1023 await req.attach('videofile', filePath)
1024 .expect(200) 1024 .expect(200)
1025 1025
1026 await waitJobs(servers) 1026 await waitJobs(servers)
1027 1027
@@ -1046,7 +1046,7 @@ describe('Test multiple servers', function () {
1046 duration: 5, 1046 duration: 5,
1047 commentsEnabled: true, 1047 commentsEnabled: true,
1048 downloadEnabled: true, 1048 downloadEnabled: true,
1049 tags: [ ], 1049 tags: [],
1050 privacy: VideoPrivacy.PUBLIC, 1050 privacy: VideoPrivacy.PUBLIC,
1051 channel: { 1051 channel: {
1052 displayName: 'Main root channel', 1052 displayName: 'Main root channel',
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts
index 17172331f..5505a845a 100644
--- a/server/tests/api/videos/services.ts
+++ b/server/tests/api/videos/services.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -31,8 +31,8 @@ describe('Test services', function () {
31 31
32 const res = await getOEmbed(server.url, oembedUrl) 32 const res = await getOEmbed(server.url, oembedUrl)
33 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + 33 const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
34 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + 34 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
35 'frameborder="0" allowfullscreen></iframe>' 35 'frameborder="0" allowfullscreen></iframe>'
36 const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg' 36 const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg'
37 37
38 expect(res.body.html).to.equal(expectedHtml) 38 expect(res.body.html).to.equal(expectedHtml)
@@ -53,8 +53,8 @@ describe('Test services', function () {
53 53
54 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) 54 const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
55 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + 55 const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
56 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + 56 `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
57 'frameborder="0" allowfullscreen></iframe>' 57 'frameborder="0" allowfullscreen></iframe>'
58 58
59 expect(res.body.html).to.equal(expectedHtml) 59 expect(res.body.html).to.equal(expectedHtml)
60 expect(res.body.title).to.equal(server.video.name) 60 expect(res.body.title).to.equal(server.video.name)
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 362d6b78f..596fff996 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { keyBy } from 'lodash' 4import { keyBy } from 'lodash'
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts
index 0cd6f22c7..cd6df7267 100644
--- a/server/tests/api/videos/video-abuse.ts
+++ b/server/tests/api/videos/video-abuse.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -183,9 +183,9 @@ describe('Test video abuses', function () {
183 const accountToBlock = 'root@localhost:' + servers[1].port 183 const accountToBlock = 'root@localhost:' + servers[1].port
184 184
185 { 185 {
186 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) 186 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
187 187
188 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 188 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
189 expect(res.body.total).to.equal(2) 189 expect(res.body.total).to.equal(2)
190 190
191 const abuse = res.body.data.find(a => a.reason === 'will mute this') 191 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -193,9 +193,9 @@ describe('Test video abuses', function () {
193 } 193 }
194 194
195 { 195 {
196 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) 196 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock)
197 197
198 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 198 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
199 expect(res.body.total).to.equal(3) 199 expect(res.body.total).to.equal(3)
200 } 200 }
201 }) 201 })
@@ -204,9 +204,9 @@ describe('Test video abuses', function () {
204 const serverToBlock = servers[1].host 204 const serverToBlock = servers[1].host
205 205
206 { 206 {
207 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host) 207 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host)
208 208
209 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 209 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
210 expect(res.body.total).to.equal(2) 210 expect(res.body.total).to.equal(2)
211 211
212 const abuse = res.body.data.find(a => a.reason === 'will mute this') 212 const abuse = res.body.data.find(a => a.reason === 'will mute this')
@@ -214,9 +214,9 @@ describe('Test video abuses', function () {
214 } 214 }
215 215
216 { 216 {
217 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock) 217 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock)
218 218
219 const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) 219 const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken)
220 expect(res.body.total).to.equal(3) 220 expect(res.body.total).to.equal(3)
221 } 221 }
222 }) 222 })
diff --git a/server/tests/api/videos/video-blacklist.ts b/server/tests/api/videos/video-blacklist.ts
index 854b2f0cb..67bc0114c 100644
--- a/server/tests/api/videos/video-blacklist.ts
+++ b/server/tests/api/videos/video-blacklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { orderBy } from 'lodash' 4import { orderBy } from 'lodash'
@@ -8,7 +8,8 @@ import {
8 cleanupTests, 8 cleanupTests,
9 createUser, 9 createUser,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 getBlacklistedVideosList, getMyUserInformation, 11 getBlacklistedVideosList,
12 getMyUserInformation,
12 getMyVideos, 13 getMyVideos,
13 getVideosList, 14 getVideosList,
14 killallServers, 15 killallServers,
@@ -17,7 +18,6 @@ import {
17 searchVideo, 18 searchVideo,
18 ServerInfo, 19 ServerInfo,
19 setAccessTokensToServers, 20 setAccessTokensToServers,
20 setDefaultVideoChannel,
21 updateVideo, 21 updateVideo,
22 updateVideoBlacklist, 22 updateVideoBlacklist,
23 uploadVideo, 23 uploadVideo,
@@ -27,7 +27,7 @@ import { doubleFollow } from '../../../../shared/extra-utils/server/follows'
27import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 27import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
28import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos' 28import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos'
29import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 29import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
30import { User, UserRole, UserUpdateMe } from '../../../../shared/models/users' 30import { User, UserRole } from '../../../../shared/models/users'
31import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' 31import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports'
32 32
33const expect = chai.expect 33const expect = chai.expect
@@ -40,7 +40,7 @@ describe('Test video blacklist', function () {
40 const res = await getVideosList(server.url) 40 const res = await getVideosList(server.url)
41 41
42 const videos = res.body.data 42 const videos = res.body.data
43 for (let video of videos) { 43 for (const video of videos) {
44 await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason') 44 await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
45 } 45 }
46 } 46 }
@@ -72,7 +72,7 @@ describe('Test video blacklist', function () {
72 72
73 it('Should not have the video blacklisted in videos list/search on server 1', async function () { 73 it('Should not have the video blacklisted in videos list/search on server 1', async function () {
74 { 74 {
75 const res = await getVideosList(servers[ 0 ].url) 75 const res = await getVideosList(servers[0].url)
76 76
77 expect(res.body.total).to.equal(0) 77 expect(res.body.total).to.equal(0)
78 expect(res.body.data).to.be.an('array') 78 expect(res.body.data).to.be.an('array')
@@ -80,7 +80,7 @@ describe('Test video blacklist', function () {
80 } 80 }
81 81
82 { 82 {
83 const res = await searchVideo(servers[ 0 ].url, 'name') 83 const res = await searchVideo(servers[0].url, 'name')
84 84
85 expect(res.body.total).to.equal(0) 85 expect(res.body.total).to.equal(0)
86 expect(res.body.data).to.be.an('array') 86 expect(res.body.data).to.be.an('array')
@@ -90,7 +90,7 @@ describe('Test video blacklist', function () {
90 90
91 it('Should have the blacklisted video in videos list/search on server 2', async function () { 91 it('Should have the blacklisted video in videos list/search on server 2', async function () {
92 { 92 {
93 const res = await getVideosList(servers[ 1 ].url) 93 const res = await getVideosList(servers[1].url)
94 94
95 expect(res.body.total).to.equal(2) 95 expect(res.body.total).to.equal(2)
96 expect(res.body.data).to.be.an('array') 96 expect(res.body.data).to.be.an('array')
@@ -98,7 +98,7 @@ describe('Test video blacklist', function () {
98 } 98 }
99 99
100 { 100 {
101 const res = await searchVideo(servers[ 1 ].url, 'video') 101 const res = await searchVideo(servers[1].url, 'video')
102 102
103 expect(res.body.total).to.equal(2) 103 expect(res.body.total).to.equal(2)
104 expect(res.body.data).to.be.an('array') 104 expect(res.body.data).to.be.an('array')
@@ -125,8 +125,8 @@ describe('Test video blacklist', function () {
125 125
126 it('Should display all the blacklisted videos when applying manual type filter', async function () { 126 it('Should display all the blacklisted videos when applying manual type filter', async function () {
127 const res = await getBlacklistedVideosList({ 127 const res = await getBlacklistedVideosList({
128 url: servers[ 0 ].url, 128 url: servers[0].url,
129 token: servers[ 0 ].accessToken, 129 token: servers[0].accessToken,
130 type: VideoBlacklistType.MANUAL 130 type: VideoBlacklistType.MANUAL
131 }) 131 })
132 132
@@ -139,8 +139,8 @@ describe('Test video blacklist', function () {
139 139
140 it('Should display nothing when applying automatic type filter', async function () { 140 it('Should display nothing when applying automatic type filter', async function () {
141 const res = await getBlacklistedVideosList({ 141 const res = await getBlacklistedVideosList({
142 url: servers[ 0 ].url, 142 url: servers[0].url,
143 token: servers[ 0 ].accessToken, 143 token: servers[0].accessToken,
144 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 144 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
145 }) 145 })
146 146
@@ -152,7 +152,7 @@ describe('Test video blacklist', function () {
152 }) 152 })
153 153
154 it('Should get the correct sort when sorting by descending id', async function () { 154 it('Should get the correct sort when sorting by descending id', async function () {
155 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-id' }) 155 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-id' })
156 expect(res.body.total).to.equal(2) 156 expect(res.body.total).to.equal(2)
157 157
158 const blacklistedVideos = res.body.data 158 const blacklistedVideos = res.body.data
@@ -165,7 +165,7 @@ describe('Test video blacklist', function () {
165 }) 165 })
166 166
167 it('Should get the correct sort when sorting by descending video name', async function () { 167 it('Should get the correct sort when sorting by descending video name', async function () {
168 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 168 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
169 expect(res.body.total).to.equal(2) 169 expect(res.body.total).to.equal(2)
170 170
171 const blacklistedVideos = res.body.data 171 const blacklistedVideos = res.body.data
@@ -178,7 +178,7 @@ describe('Test video blacklist', function () {
178 }) 178 })
179 179
180 it('Should get the correct sort when sorting by ascending creation date', async function () { 180 it('Should get the correct sort when sorting by ascending creation date', async function () {
181 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) 181 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' })
182 expect(res.body.total).to.equal(2) 182 expect(res.body.total).to.equal(2)
183 183
184 const blacklistedVideos = res.body.data 184 const blacklistedVideos = res.body.data
@@ -195,7 +195,7 @@ describe('Test video blacklist', function () {
195 it('Should change the reason', async function () { 195 it('Should change the reason', async function () {
196 await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') 196 await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
197 197
198 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 198 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
199 const video = res.body.data.find(b => b.video.id === videoId) 199 const video = res.body.data.find(b => b.video.id === videoId)
200 200
201 expect(video.reason).to.equal('my super reason updated') 201 expect(video.reason).to.equal('my super reason updated')
@@ -231,7 +231,7 @@ describe('Test video blacklist', function () {
231 231
232 it('Should remove a video from the blacklist on server 1', async function () { 232 it('Should remove a video from the blacklist on server 1', async function () {
233 // Get one video in the blacklist 233 // Get one video in the blacklist
234 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 234 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
235 videoToRemove = res.body.data[0] 235 videoToRemove = res.body.data[0]
236 blacklist = res.body.data.slice(1) 236 blacklist = res.body.data.slice(1)
237 237
@@ -252,7 +252,7 @@ describe('Test video blacklist', function () {
252 }) 252 })
253 253
254 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { 254 it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
255 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) 255 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' })
256 expect(res.body.total).to.equal(1) 256 expect(res.body.total).to.equal(1)
257 257
258 const videos = res.body.data 258 const videos = res.body.data
@@ -274,7 +274,7 @@ describe('Test video blacklist', function () {
274 video3UUID = res.body.video.uuid 274 video3UUID = res.body.video.uuid
275 } 275 }
276 { 276 {
277 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'Video 4' }) 277 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'Video 4' })
278 video4UUID = res.body.video.uuid 278 video4UUID = res.body.video.uuid
279 } 279 }
280 280
@@ -284,17 +284,17 @@ describe('Test video blacklist', function () {
284 it('Should blacklist video 3 and keep it federated', async function () { 284 it('Should blacklist video 3 and keep it federated', async function () {
285 this.timeout(10000) 285 this.timeout(10000)
286 286
287 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video3UUID, 'super reason', false) 287 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video3UUID, 'super reason', false)
288 288
289 await waitJobs(servers) 289 await waitJobs(servers)
290 290
291 { 291 {
292 const res = await getVideosList(servers[ 0 ].url) 292 const res = await getVideosList(servers[0].url)
293 expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined 293 expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined
294 } 294 }
295 295
296 { 296 {
297 const res = await getVideosList(servers[ 1 ].url) 297 const res = await getVideosList(servers[1].url)
298 expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined 298 expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined
299 } 299 }
300 }) 300 })
@@ -302,7 +302,7 @@ describe('Test video blacklist', function () {
302 it('Should unfederate the video', async function () { 302 it('Should unfederate the video', async function () {
303 this.timeout(10000) 303 this.timeout(10000)
304 304
305 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, 'super reason', true) 305 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video4UUID, 'super reason', true)
306 306
307 await waitJobs(servers) 307 await waitJobs(servers)
308 308
@@ -315,7 +315,7 @@ describe('Test video blacklist', function () {
315 it('Should have the video unfederated even after an Update AP message', async function () { 315 it('Should have the video unfederated even after an Update AP message', async function () {
316 this.timeout(10000) 316 this.timeout(10000)
317 317
318 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, { description: 'super description' }) 318 await updateVideo(servers[0].url, servers[0].accessToken, video4UUID, { description: 'super description' })
319 319
320 await waitJobs(servers) 320 await waitJobs(servers)
321 321
@@ -326,7 +326,7 @@ describe('Test video blacklist', function () {
326 }) 326 })
327 327
328 it('Should have the correct video blacklist unfederate attribute', async function () { 328 it('Should have the correct video blacklist unfederate attribute', async function () {
329 const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) 329 const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' })
330 330
331 const blacklistedVideos: VideoBlacklist[] = res.body.data 331 const blacklistedVideos: VideoBlacklist[] = res.body.data
332 const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) 332 const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID)
@@ -339,7 +339,7 @@ describe('Test video blacklist', function () {
339 it('Should remove the video from blacklist and refederate the video', async function () { 339 it('Should remove the video from blacklist and refederate the video', async function () {
340 this.timeout(10000) 340 this.timeout(10000)
341 341
342 await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID) 342 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video4UUID)
343 343
344 await waitJobs(servers) 344 await waitJobs(servers)
345 345
@@ -362,9 +362,9 @@ describe('Test video blacklist', function () {
362 killallServers([ servers[0] ]) 362 killallServers([ servers[0] ])
363 363
364 const config = { 364 const config = {
365 'auto_blacklist': { 365 auto_blacklist: {
366 videos: { 366 videos: {
367 'of_users': { 367 of_users: {
368 enabled: true 368 enabled: true
369 } 369 }
370 } 370 }
@@ -375,8 +375,8 @@ describe('Test video blacklist', function () {
375 { 375 {
376 const user = { username: 'user_without_flag', password: 'password' } 376 const user = { username: 'user_without_flag', password: 'password' }
377 await createUser({ 377 await createUser({
378 url: servers[ 0 ].url, 378 url: servers[0].url,
379 accessToken: servers[ 0 ].accessToken, 379 accessToken: servers[0].accessToken,
380 username: user.username, 380 username: user.username,
381 adminFlags: UserAdminFlag.NONE, 381 adminFlags: UserAdminFlag.NONE,
382 password: user.password, 382 password: user.password,
@@ -393,8 +393,8 @@ describe('Test video blacklist', function () {
393 { 393 {
394 const user = { username: 'user_with_flag', password: 'password' } 394 const user = { username: 'user_with_flag', password: 'password' }
395 await createUser({ 395 await createUser({
396 url: servers[ 0 ].url, 396 url: servers[0].url,
397 accessToken: servers[ 0 ].accessToken, 397 accessToken: servers[0].accessToken,
398 username: user.username, 398 username: user.username,
399 adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST, 399 adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST,
400 password: user.password, 400 password: user.password,
@@ -411,8 +411,8 @@ describe('Test video blacklist', function () {
411 await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' }) 411 await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' })
412 412
413 const res = await getBlacklistedVideosList({ 413 const res = await getBlacklistedVideosList({
414 url: servers[ 0 ].url, 414 url: servers[0].url,
415 token: servers[ 0 ].accessToken, 415 token: servers[0].accessToken,
416 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 416 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
417 }) 417 })
418 418
@@ -428,11 +428,11 @@ describe('Test video blacklist', function () {
428 name: 'URL import', 428 name: 'URL import',
429 channelId: channelOfUserWithoutFlag 429 channelId: channelOfUserWithoutFlag
430 } 430 }
431 await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) 431 await importVideo(servers[0].url, userWithoutFlag, attributes)
432 432
433 const res = await getBlacklistedVideosList({ 433 const res = await getBlacklistedVideosList({
434 url: servers[ 0 ].url, 434 url: servers[0].url,
435 token: servers[ 0 ].accessToken, 435 token: servers[0].accessToken,
436 sort: 'createdAt', 436 sort: 'createdAt',
437 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 437 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
438 }) 438 })
@@ -447,11 +447,11 @@ describe('Test video blacklist', function () {
447 name: 'Torrent import', 447 name: 'Torrent import',
448 channelId: channelOfUserWithoutFlag 448 channelId: channelOfUserWithoutFlag
449 } 449 }
450 await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) 450 await importVideo(servers[0].url, userWithoutFlag, attributes)
451 451
452 const res = await getBlacklistedVideosList({ 452 const res = await getBlacklistedVideosList({
453 url: servers[ 0 ].url, 453 url: servers[0].url,
454 token: servers[ 0 ].accessToken, 454 token: servers[0].accessToken,
455 sort: 'createdAt', 455 sort: 'createdAt',
456 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 456 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
457 }) 457 })
@@ -464,8 +464,8 @@ describe('Test video blacklist', function () {
464 await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' }) 464 await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' })
465 465
466 const res = await getBlacklistedVideosList({ 466 const res = await getBlacklistedVideosList({
467 url: servers[ 0 ].url, 467 url: servers[0].url,
468 token: servers[ 0 ].accessToken, 468 token: servers[0].accessToken,
469 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED 469 type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED
470 }) 470 })
471 471
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts
index 5e13f5949..b4ecb39f4 100644
--- a/server/tests/api/videos/video-captions.ts
+++ b/server/tests/api/videos/video-captions.ts
@@ -1,16 +1,17 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 checkVideoFilesWereRemoved, cleanupTests, 6 checkVideoFilesWereRemoved,
7 cleanupTests,
7 doubleFollow, 8 doubleFollow,
8 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
9 removeVideo, 10 removeVideo,
10 uploadVideo, 11 uploadVideo,
11 wait 12 wait
12} from '../../../../shared/extra-utils' 13} from '../../../../shared/extra-utils'
13import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' 14import { ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index'
14import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 15import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
15import { 16import {
16 createVideoCaption, 17 createVideoCaption,
@@ -36,7 +37,7 @@ describe('Test video captions', function () {
36 37
37 await waitJobs(servers) 38 await waitJobs(servers)
38 39
39 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name' }) 40 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my video name' })
40 videoUUID = res.body.video.uuid 41 videoUUID = res.body.video.uuid
41 42
42 await waitJobs(servers) 43 await waitJobs(servers)
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
index 64ee2355a..dee6575b9 100644
--- a/server/tests/api/videos/video-change-ownership.ts
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -38,7 +38,7 @@ describe('Test video change ownership - nominal', function () {
38 } 38 }
39 let firstUserAccessToken = '' 39 let firstUserAccessToken = ''
40 let secondUserAccessToken = '' 40 let secondUserAccessToken = ''
41 let lastRequestChangeOwnershipId = undefined 41 let lastRequestChangeOwnershipId = ''
42 42
43 before(async function () { 43 before(async function () {
44 this.timeout(50000) 44 this.timeout(50000)
@@ -48,15 +48,15 @@ describe('Test video change ownership - nominal', function () {
48 48
49 const videoQuota = 42000000 49 const videoQuota = 42000000
50 await createUser({ 50 await createUser({
51 url: servers[ 0 ].url, 51 url: servers[0].url,
52 accessToken: servers[ 0 ].accessToken, 52 accessToken: servers[0].accessToken,
53 username: firstUser.username, 53 username: firstUser.username,
54 password: firstUser.password, 54 password: firstUser.password,
55 videoQuota: videoQuota 55 videoQuota: videoQuota
56 }) 56 })
57 await createUser({ 57 await createUser({
58 url: servers[ 0 ].url, 58 url: servers[0].url,
59 accessToken: servers[ 0 ].accessToken, 59 accessToken: servers[0].accessToken,
60 username: secondUser.username, 60 username: secondUser.username,
61 password: secondUser.password, 61 password: secondUser.password,
62 videoQuota: videoQuota 62 videoQuota: videoQuota
@@ -209,7 +209,7 @@ describe('Test video change ownership - nominal', function () {
209}) 209})
210 210
211describe('Test video change ownership - quota too small', function () { 211describe('Test video change ownership - quota too small', function () {
212 let server: ServerInfo = undefined 212 let server: ServerInfo
213 const firstUser = { 213 const firstUser = {
214 username: 'first', 214 username: 'first',
215 password: 'My great password' 215 password: 'My great password'
@@ -220,14 +220,14 @@ describe('Test video change ownership - quota too small', function () {
220 } 220 }
221 let firstUserAccessToken = '' 221 let firstUserAccessToken = ''
222 let secondUserAccessToken = '' 222 let secondUserAccessToken = ''
223 let lastRequestChangeOwnershipId = undefined 223 let lastRequestChangeOwnershipId = ''
224 224
225 before(async function () { 225 before(async function () {
226 this.timeout(50000) 226 this.timeout(50000)
227 227
228 // Run one server 228 // Run one server
229 server = await flushAndRunServer(1) 229 server = await flushAndRunServer(1)
230 await setAccessTokensToServers([server]) 230 await setAccessTokensToServers([ server ])
231 231
232 const videoQuota = 42000000 232 const videoQuota = 42000000
233 const limitedVideoQuota = 10 233 const limitedVideoQuota = 10
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index 4f600cae8..f3a23bf17 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -7,7 +7,8 @@ import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, getVideo, 10 flushAndRunMultipleServers,
11 getVideo,
11 getVideoChannelVideos, 12 getVideoChannelVideos,
12 testImage, 13 testImage,
13 updateVideo, 14 updateVideo,
@@ -73,14 +74,14 @@ describe('Test video channels', function () {
73 description: 'super video channel description', 74 description: 'super video channel description',
74 support: 'super video channel support text' 75 support: 'super video channel support text'
75 } 76 }
76 const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 77 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
77 secondVideoChannelId = res.body.videoChannel.id 78 secondVideoChannelId = res.body.videoChannel.id
78 } 79 }
79 80
80 // The channel is 1 is propagated to servers 2 81 // The channel is 1 is propagated to servers 2
81 { 82 {
82 const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } 83 const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' }
83 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributesArg) 84 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributesArg)
84 videoUUID = res.body.video.uuid 85 videoUUID = res.body.video.uuid
85 } 86 }
86 87
@@ -106,7 +107,7 @@ describe('Test video channels', function () {
106 107
107 it('Should have two video channels when getting account channels on server 1', async function () { 108 it('Should have two video channels when getting account channels on server 1', async function () {
108 const res = await getAccountVideoChannelsList({ 109 const res = await getAccountVideoChannelsList({
109 url: servers[ 0 ].url, 110 url: servers[0].url,
110 accountName: userInfo.account.name + '@' + userInfo.account.host 111 accountName: userInfo.account.name + '@' + userInfo.account.host
111 }) 112 })
112 113
@@ -127,7 +128,7 @@ describe('Test video channels', function () {
127 it('Should paginate and sort account channels', async function () { 128 it('Should paginate and sort account channels', async function () {
128 { 129 {
129 const res = await getAccountVideoChannelsList({ 130 const res = await getAccountVideoChannelsList({
130 url: servers[ 0 ].url, 131 url: servers[0].url,
131 accountName: userInfo.account.name + '@' + userInfo.account.host, 132 accountName: userInfo.account.name + '@' + userInfo.account.host,
132 start: 0, 133 start: 0,
133 count: 1, 134 count: 1,
@@ -137,13 +138,13 @@ describe('Test video channels', function () {
137 expect(res.body.total).to.equal(2) 138 expect(res.body.total).to.equal(2)
138 expect(res.body.data).to.have.lengthOf(1) 139 expect(res.body.data).to.have.lengthOf(1)
139 140
140 const videoChannel: VideoChannel = res.body.data[ 0 ] 141 const videoChannel: VideoChannel = res.body.data[0]
141 expect(videoChannel.name).to.equal('root_channel') 142 expect(videoChannel.name).to.equal('root_channel')
142 } 143 }
143 144
144 { 145 {
145 const res = await getAccountVideoChannelsList({ 146 const res = await getAccountVideoChannelsList({
146 url: servers[ 0 ].url, 147 url: servers[0].url,
147 accountName: userInfo.account.name + '@' + userInfo.account.host, 148 accountName: userInfo.account.name + '@' + userInfo.account.host,
148 start: 0, 149 start: 0,
149 count: 1, 150 count: 1,
@@ -153,13 +154,13 @@ describe('Test video channels', function () {
153 expect(res.body.total).to.equal(2) 154 expect(res.body.total).to.equal(2)
154 expect(res.body.data).to.have.lengthOf(1) 155 expect(res.body.data).to.have.lengthOf(1)
155 156
156 const videoChannel: VideoChannel = res.body.data[ 0 ] 157 const videoChannel: VideoChannel = res.body.data[0]
157 expect(videoChannel.name).to.equal('second_video_channel') 158 expect(videoChannel.name).to.equal('second_video_channel')
158 } 159 }
159 160
160 { 161 {
161 const res = await getAccountVideoChannelsList({ 162 const res = await getAccountVideoChannelsList({
162 url: servers[ 0 ].url, 163 url: servers[0].url,
163 accountName: userInfo.account.name + '@' + userInfo.account.host, 164 accountName: userInfo.account.name + '@' + userInfo.account.host,
164 start: 1, 165 start: 1,
165 count: 1, 166 count: 1,
@@ -169,14 +170,14 @@ describe('Test video channels', function () {
169 expect(res.body.total).to.equal(2) 170 expect(res.body.total).to.equal(2)
170 expect(res.body.data).to.have.lengthOf(1) 171 expect(res.body.data).to.have.lengthOf(1)
171 172
172 const videoChannel: VideoChannel = res.body.data[ 0 ] 173 const videoChannel: VideoChannel = res.body.data[0]
173 expect(videoChannel.name).to.equal('root_channel') 174 expect(videoChannel.name).to.equal('root_channel')
174 } 175 }
175 }) 176 })
176 177
177 it('Should have one video channel when getting account channels on server 2', async function () { 178 it('Should have one video channel when getting account channels on server 2', async function () {
178 const res = await getAccountVideoChannelsList({ 179 const res = await getAccountVideoChannelsList({
179 url: servers[ 1 ].url, 180 url: servers[1].url,
180 accountName: userInfo.account.name + '@' + userInfo.account.host 181 accountName: userInfo.account.name + '@' + userInfo.account.host
181 }) 182 })
182 183
@@ -349,15 +350,15 @@ describe('Test video channels', function () {
349 it('Should create the main channel with an uuid if there is a conflict', async function () { 350 it('Should create the main channel with an uuid if there is a conflict', async function () {
350 { 351 {
351 const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' } 352 const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' }
352 await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) 353 await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel)
353 } 354 }
354 355
355 { 356 {
356 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'toto', password: 'password' }) 357 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'toto', password: 'password' })
357 const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' }) 358 const accessToken = await userLogin(servers[0], { username: 'toto', password: 'password' })
358 359
359 const res = await getMyUserInformation(servers[ 0 ].url, accessToken) 360 const res = await getMyUserInformation(servers[0].url, accessToken)
360 const videoChannel = res.body.videoChannels[ 0 ] 361 const videoChannel = res.body.videoChannels[0]
361 expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) 362 expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/)
362 } 363 }
363 }) 364 })
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
index 06e4ff9c8..afb58e95a 100644
--- a/server/tests/api/videos/video-comments.ts
+++ b/server/tests/api/videos/video-comments.ts
@@ -1,17 +1,17 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' 5import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
6import { cleanupTests, testImage } from '../../../../shared/extra-utils' 6import { cleanupTests, testImage } from '../../../../shared/extra-utils'
7import { 7import {
8 createUser,
8 dateIsValid, 9 dateIsValid,
9 flushAndRunServer, 10 flushAndRunServer,
11 getAccessToken,
10 ServerInfo, 12 ServerInfo,
11 setAccessTokensToServers, 13 setAccessTokensToServers,
12 updateMyAvatar, 14 updateMyAvatar,
13 getAccessToken,
14 createUser,
15 uploadVideo 15 uploadVideo
16} from '../../../../shared/extra-utils/index' 16} from '../../../../shared/extra-utils/index'
17import { 17import {
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts
index db4d278bf..b8e98e45f 100644
--- a/server/tests/api/videos/video-description.ts
+++ b/server/tests/api/videos/video-description.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,7 +8,6 @@ import {
8 getVideo, 8 getVideo,
9 getVideoDescription, 9 getVideoDescription,
10 getVideosList, 10 getVideosList,
11 killallServers,
12 ServerInfo, 11 ServerInfo,
13 setAccessTokensToServers, 12 setAccessTokensToServers,
14 updateVideo, 13 updateVideo,
@@ -23,7 +22,7 @@ describe('Test video description', function () {
23 let servers: ServerInfo[] = [] 22 let servers: ServerInfo[] = []
24 let videoUUID = '' 23 let videoUUID = ''
25 let videoId: number 24 let videoId: number
26 let longDescription = 'my super description for server 1'.repeat(50) 25 const longDescription = 'my super description for server 1'.repeat(50)
27 26
28 before(async function () { 27 before(async function () {
29 this.timeout(40000) 28 this.timeout(40000)
@@ -61,7 +60,7 @@ describe('Test video description', function () {
61 60
62 // 30 characters * 6 -> 240 characters 61 // 30 characters * 6 -> 240 characters
63 const truncatedDescription = 'my super description for server 1'.repeat(7) + 62 const truncatedDescription = 'my super description for server 1'.repeat(7) +
64 'my super descrip...' 63 'my super descrip...'
65 64
66 expect(video.description).to.equal(truncatedDescription) 65 expect(video.description).to.equal(truncatedDescription)
67 } 66 }
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts
index bde3b5656..6555bc8b6 100644
--- a/server/tests/api/videos/video-hls.ts
+++ b/server/tests/api/videos/video-hls.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -10,13 +10,16 @@ import {
10 doubleFollow, 10 doubleFollow,
11 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
12 getPlaylist, 12 getPlaylist,
13 getVideo, makeGetRequest, makeRawRequest, 13 getVideo,
14 makeRawRequest,
14 removeVideo, 15 removeVideo,
15 ServerInfo, 16 ServerInfo,
16 setAccessTokensToServers, updateCustomSubConfig, 17 setAccessTokensToServers,
18 updateCustomSubConfig,
17 updateVideo, 19 updateVideo,
18 uploadVideo, 20 uploadVideo,
19 waitJobs, webtorrentAdd 21 waitJobs,
22 webtorrentAdd
20} from '../../../../shared/extra-utils' 23} from '../../../../shared/extra-utils'
21import { VideoDetails } from '../../../../shared/models/videos' 24import { VideoDetails } from '../../../../shared/models/videos'
22import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 25import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
@@ -48,7 +51,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
48 51
49 expect(file.magnetUri).to.have.lengthOf.above(2) 52 expect(file.magnetUri).to.have.lengthOf.above(2)
50 expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) 53 expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`)
51 expect(file.fileUrl).to.equal(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4`) 54 expect(file.fileUrl).to.equal(
55 `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4`
56 )
52 expect(file.resolution.label).to.equal(resolution + 'p') 57 expect(file.resolution.label).to.equal(resolution + 'p')
53 58
54 await makeRawRequest(file.torrentUrl, 200) 59 await makeRawRequest(file.torrentUrl, 200)
@@ -66,7 +71,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn
66 const masterPlaylist = res.text 71 const masterPlaylist = res.text
67 72
68 for (const resolution of resolutions) { 73 for (const resolution of resolutions) {
69 const reg = new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"') 74 const reg = new RegExp(
75 '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"'
76 )
70 77
71 expect(masterPlaylist).to.match(reg) 78 expect(masterPlaylist).to.match(reg)
72 expect(masterPlaylist).to.contain(`${resolution}.m3u8`) 79 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
@@ -102,7 +109,7 @@ describe('Test HLS videos', function () {
102 it('Should upload a video and transcode it to HLS', async function () { 109 it('Should upload a video and transcode it to HLS', async function () {
103 this.timeout(120000) 110 this.timeout(120000)
104 111
105 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) 112 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1', fixture: 'video_short.webm' })
106 videoUUID = res.body.video.uuid 113 videoUUID = res.body.video.uuid
107 114
108 await waitJobs(servers) 115 await waitJobs(servers)
@@ -113,7 +120,7 @@ describe('Test HLS videos', function () {
113 it('Should upload an audio file and transcode it to HLS', async function () { 120 it('Should upload an audio file and transcode it to HLS', async function () {
114 this.timeout(120000) 121 this.timeout(120000)
115 122
116 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video audio', fixture: 'sample.ogg' }) 123 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video audio', fixture: 'sample.ogg' })
117 videoAudioUUID = res.body.video.uuid 124 videoAudioUUID = res.body.video.uuid
118 125
119 await waitJobs(servers) 126 await waitJobs(servers)
@@ -124,7 +131,7 @@ describe('Test HLS videos', function () {
124 it('Should update the video', async function () { 131 it('Should update the video', async function () {
125 this.timeout(10000) 132 this.timeout(10000)
126 133
127 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, { name: 'video 1 updated' }) 134 await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' })
128 135
129 await waitJobs(servers) 136 await waitJobs(servers)
130 137
@@ -134,8 +141,8 @@ describe('Test HLS videos', function () {
134 it('Should delete videos', async function () { 141 it('Should delete videos', async function () {
135 this.timeout(10000) 142 this.timeout(10000)
136 143
137 await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID) 144 await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
138 await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAudioUUID) 145 await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID)
139 146
140 await waitJobs(servers) 147 await waitJobs(servers)
141 148
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts
index 1233ed6eb..a67e528c6 100644
--- a/server/tests/api/videos/video-imports.ts
+++ b/server/tests/api/videos/video-imports.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -12,12 +12,11 @@ import {
12 getVideo, 12 getVideo,
13 getVideosList, 13 getVideosList,
14 immutableAssign, 14 immutableAssign,
15 killallServers,
16 ServerInfo, 15 ServerInfo,
17 setAccessTokensToServers 16 setAccessTokensToServers
18} from '../../../../shared/extra-utils' 17} from '../../../../shared/extra-utils'
19import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 18import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
20import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/extra-utils/videos/video-imports' 19import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports'
21 20
22const expect = chai.expect 21const expect = chai.expect
23 22
@@ -88,12 +87,12 @@ describe('Test video imports', function () {
88 87
89 { 88 {
90 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 89 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
91 channelIdServer1 = res.body.videoChannels[ 0 ].id 90 channelIdServer1 = res.body.videoChannels[0].id
92 } 91 }
93 92
94 { 93 {
95 const res = await getMyUserInformation(servers[1].url, servers[1].accessToken) 94 const res = await getMyUserInformation(servers[1].url, servers[1].accessToken)
96 channelIdServer2 = res.body.videoChannels[ 0 ].id 95 channelIdServer2 = res.body.videoChannels[0].id
97 } 96 }
98 97
99 await doubleFollow(servers[0], servers[1]) 98 await doubleFollow(servers[0], servers[1])
@@ -214,7 +213,7 @@ describe('Test video imports', function () {
214 213
215 await checkVideoServer2(server.url, res.body.data[0].uuid) 214 await checkVideoServer2(server.url, res.body.data[0].uuid)
216 215
217 const [ ,videoHttp, videoMagnet, videoTorrent ] = res.body.data 216 const [ , videoHttp, videoMagnet, videoTorrent ] = res.body.data
218 await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) 217 await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid)
219 } 218 }
220 }) 219 })
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts
index ad6a4b43f..7eba8d7d9 100644
--- a/server/tests/api/videos/video-nsfw.ts
+++ b/server/tests/api/videos/video-nsfw.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -89,8 +89,8 @@ describe('Test video NSFW policy', function () {
89 89
90 const videos = res.body.data 90 const videos = res.body.data
91 expect(videos).to.have.lengthOf(2) 91 expect(videos).to.have.lengthOf(2)
92 expect(videos[ 0 ].name).to.equal('normal') 92 expect(videos[0].name).to.equal('normal')
93 expect(videos[ 1 ].name).to.equal('nsfw') 93 expect(videos[1].name).to.equal('nsfw')
94 } 94 }
95 }) 95 })
96 96
@@ -107,7 +107,7 @@ describe('Test video NSFW policy', function () {
107 107
108 const videos = res.body.data 108 const videos = res.body.data
109 expect(videos).to.have.lengthOf(1) 109 expect(videos).to.have.lengthOf(1)
110 expect(videos[ 0 ].name).to.equal('normal') 110 expect(videos[0].name).to.equal('normal')
111 } 111 }
112 }) 112 })
113 113
@@ -124,8 +124,8 @@ describe('Test video NSFW policy', function () {
124 124
125 const videos = res.body.data 125 const videos = res.body.data
126 expect(videos).to.have.lengthOf(2) 126 expect(videos).to.have.lengthOf(2)
127 expect(videos[ 0 ].name).to.equal('normal') 127 expect(videos[0].name).to.equal('normal')
128 expect(videos[ 1 ].name).to.equal('nsfw') 128 expect(videos[1].name).to.equal('nsfw')
129 } 129 }
130 }) 130 })
131 }) 131 })
@@ -154,8 +154,8 @@ describe('Test video NSFW policy', function () {
154 154
155 const videos = res.body.data 155 const videos = res.body.data
156 expect(videos).to.have.lengthOf(2) 156 expect(videos).to.have.lengthOf(2)
157 expect(videos[ 0 ].name).to.equal('normal') 157 expect(videos[0].name).to.equal('normal')
158 expect(videos[ 1 ].name).to.equal('nsfw') 158 expect(videos[1].name).to.equal('nsfw')
159 } 159 }
160 }) 160 })
161 161
@@ -171,8 +171,8 @@ describe('Test video NSFW policy', function () {
171 171
172 const videos = res.body.data 172 const videos = res.body.data
173 expect(videos).to.have.lengthOf(2) 173 expect(videos).to.have.lengthOf(2)
174 expect(videos[ 0 ].name).to.equal('normal') 174 expect(videos[0].name).to.equal('normal')
175 expect(videos[ 1 ].name).to.equal('nsfw') 175 expect(videos[1].name).to.equal('nsfw')
176 } 176 }
177 }) 177 })
178 178
@@ -188,7 +188,7 @@ describe('Test video NSFW policy', function () {
188 188
189 const videos = res.body.data 189 const videos = res.body.data
190 expect(videos).to.have.lengthOf(1) 190 expect(videos).to.have.lengthOf(1)
191 expect(videos[ 0 ].name).to.equal('normal') 191 expect(videos[0].name).to.equal('normal')
192 } 192 }
193 }) 193 })
194 194
@@ -198,8 +198,8 @@ describe('Test video NSFW policy', function () {
198 198
199 const videos = res.body.data 199 const videos = res.body.data
200 expect(videos).to.have.lengthOf(2) 200 expect(videos).to.have.lengthOf(2)
201 expect(videos[ 0 ].name).to.equal('normal') 201 expect(videos[0].name).to.equal('normal')
202 expect(videos[ 1 ].name).to.equal('nsfw') 202 expect(videos[1].name).to.equal('nsfw')
203 }) 203 })
204 204
205 it('Should display NSFW videos when the nsfw param === true', async function () { 205 it('Should display NSFW videos when the nsfw param === true', async function () {
@@ -208,7 +208,7 @@ describe('Test video NSFW policy', function () {
208 208
209 const videos = res.body.data 209 const videos = res.body.data
210 expect(videos).to.have.lengthOf(1) 210 expect(videos).to.have.lengthOf(1)
211 expect(videos[ 0 ].name).to.equal('nsfw') 211 expect(videos[0].name).to.equal('nsfw')
212 } 212 }
213 }) 213 })
214 214
@@ -218,7 +218,7 @@ describe('Test video NSFW policy', function () {
218 218
219 const videos = res.body.data 219 const videos = res.body.data
220 expect(videos).to.have.lengthOf(1) 220 expect(videos).to.have.lengthOf(1)
221 expect(videos[ 0 ].name).to.equal('normal') 221 expect(videos[0].name).to.equal('normal')
222 } 222 }
223 }) 223 })
224 224
@@ -228,8 +228,8 @@ describe('Test video NSFW policy', function () {
228 228
229 const videos = res.body.data 229 const videos = res.body.data
230 expect(videos).to.have.lengthOf(2) 230 expect(videos).to.have.lengthOf(2)
231 expect(videos[ 0 ].name).to.equal('normal') 231 expect(videos[0].name).to.equal('normal')
232 expect(videos[ 1 ].name).to.equal('nsfw') 232 expect(videos[1].name).to.equal('nsfw')
233 } 233 }
234 }) 234 })
235 }) 235 })
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
index 73ab02c17..a93a0b7de 100644
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ b/server/tests/api/videos/video-playlist-thumbnails.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -8,14 +8,15 @@ import {
8 createVideoPlaylist, 8 createVideoPlaylist,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 getVideoPlaylistsList, removeVideoFromPlaylist, 11 getVideoPlaylistsList,
12 removeVideoFromPlaylist,
13 reorderVideosPlaylist,
12 ServerInfo, 14 ServerInfo,
13 setAccessTokensToServers, 15 setAccessTokensToServers,
14 setDefaultVideoChannel, 16 setDefaultVideoChannel,
15 testImage, 17 testImage,
16 uploadVideoAndGetId, 18 uploadVideoAndGetId,
17 waitJobs, 19 waitJobs
18 reorderVideosPlaylist
19} from '../../../../shared/extra-utils' 20} from '../../../../shared/extra-utils'
20import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' 21import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
21 22
@@ -69,19 +70,19 @@ describe('Playlist thumbnail', function () {
69 this.timeout(30000) 70 this.timeout(30000)
70 71
71 const res = await createVideoPlaylist({ 72 const res = await createVideoPlaylist({
72 url: servers[ 1 ].url, 73 url: servers[1].url,
73 token: servers[ 1 ].accessToken, 74 token: servers[1].accessToken,
74 playlistAttrs: { 75 playlistAttrs: {
75 displayName: 'playlist without thumbnail', 76 displayName: 'playlist without thumbnail',
76 privacy: VideoPlaylistPrivacy.PUBLIC, 77 privacy: VideoPlaylistPrivacy.PUBLIC,
77 videoChannelId: servers[ 1 ].videoChannel.id 78 videoChannelId: servers[1].videoChannel.id
78 } 79 }
79 }) 80 })
80 playlistWithoutThumbnail = res.body.videoPlaylist.id 81 playlistWithoutThumbnail = res.body.videoPlaylist.id
81 82
82 const res2 = await addVideoInPlaylist({ 83 const res2 = await addVideoInPlaylist({
83 url: servers[ 1 ].url, 84 url: servers[1].url,
84 token: servers[ 1 ].accessToken, 85 token: servers[1].accessToken,
85 playlistId: playlistWithoutThumbnail, 86 playlistId: playlistWithoutThumbnail,
86 elementAttrs: { videoId: video1 } 87 elementAttrs: { videoId: video1 }
87 }) 88 })
@@ -99,20 +100,20 @@ describe('Playlist thumbnail', function () {
99 this.timeout(30000) 100 this.timeout(30000)
100 101
101 const res = await createVideoPlaylist({ 102 const res = await createVideoPlaylist({
102 url: servers[ 1 ].url, 103 url: servers[1].url,
103 token: servers[ 1 ].accessToken, 104 token: servers[1].accessToken,
104 playlistAttrs: { 105 playlistAttrs: {
105 displayName: 'playlist with thumbnail', 106 displayName: 'playlist with thumbnail',
106 privacy: VideoPlaylistPrivacy.PUBLIC, 107 privacy: VideoPlaylistPrivacy.PUBLIC,
107 videoChannelId: servers[ 1 ].videoChannel.id, 108 videoChannelId: servers[1].videoChannel.id,
108 thumbnailfile: 'thumbnail.jpg' 109 thumbnailfile: 'thumbnail.jpg'
109 } 110 }
110 }) 111 })
111 playlistWithThumbnail = res.body.videoPlaylist.id 112 playlistWithThumbnail = res.body.videoPlaylist.id
112 113
113 const res2 = await addVideoInPlaylist({ 114 const res2 = await addVideoInPlaylist({
114 url: servers[ 1 ].url, 115 url: servers[1].url,
115 token: servers[ 1 ].accessToken, 116 token: servers[1].accessToken,
116 playlistId: playlistWithThumbnail, 117 playlistId: playlistWithThumbnail,
117 elementAttrs: { videoId: video1 } 118 elementAttrs: { videoId: video1 }
118 }) 119 })
@@ -130,8 +131,8 @@ describe('Playlist thumbnail', function () {
130 this.timeout(30000) 131 this.timeout(30000)
131 132
132 const res = await addVideoInPlaylist({ 133 const res = await addVideoInPlaylist({
133 url: servers[ 1 ].url, 134 url: servers[1].url,
134 token: servers[ 1 ].accessToken, 135 token: servers[1].accessToken,
135 playlistId: playlistWithoutThumbnail, 136 playlistId: playlistWithoutThumbnail,
136 elementAttrs: { videoId: video2 } 137 elementAttrs: { videoId: video2 }
137 }) 138 })
@@ -159,8 +160,8 @@ describe('Playlist thumbnail', function () {
159 this.timeout(30000) 160 this.timeout(30000)
160 161
161 const res = await addVideoInPlaylist({ 162 const res = await addVideoInPlaylist({
162 url: servers[ 1 ].url, 163 url: servers[1].url,
163 token: servers[ 1 ].accessToken, 164 token: servers[1].accessToken,
164 playlistId: playlistWithThumbnail, 165 playlistId: playlistWithThumbnail,
165 elementAttrs: { videoId: video2 } 166 elementAttrs: { videoId: video2 }
166 }) 167 })
@@ -188,8 +189,8 @@ describe('Playlist thumbnail', function () {
188 this.timeout(30000) 189 this.timeout(30000)
189 190
190 await removeVideoFromPlaylist({ 191 await removeVideoFromPlaylist({
191 url: servers[ 1 ].url, 192 url: servers[1].url,
192 token: servers[ 1 ].accessToken, 193 token: servers[1].accessToken,
193 playlistId: playlistWithoutThumbnail, 194 playlistId: playlistWithoutThumbnail,
194 playlistElementId: withoutThumbnailE1 195 playlistElementId: withoutThumbnailE1
195 }) 196 })
@@ -206,8 +207,8 @@ describe('Playlist thumbnail', function () {
206 this.timeout(30000) 207 this.timeout(30000)
207 208
208 await removeVideoFromPlaylist({ 209 await removeVideoFromPlaylist({
209 url: servers[ 1 ].url, 210 url: servers[1].url,
210 token: servers[ 1 ].accessToken, 211 token: servers[1].accessToken,
211 playlistId: playlistWithThumbnail, 212 playlistId: playlistWithThumbnail,
212 playlistElementId: withThumbnailE1 213 playlistElementId: withThumbnailE1
213 }) 214 })
@@ -224,8 +225,8 @@ describe('Playlist thumbnail', function () {
224 this.timeout(30000) 225 this.timeout(30000)
225 226
226 await removeVideoFromPlaylist({ 227 await removeVideoFromPlaylist({
227 url: servers[ 1 ].url, 228 url: servers[1].url,
228 token: servers[ 1 ].accessToken, 229 token: servers[1].accessToken,
229 playlistId: playlistWithoutThumbnail, 230 playlistId: playlistWithoutThumbnail,
230 playlistElementId: withoutThumbnailE2 231 playlistElementId: withoutThumbnailE2
231 }) 232 })
@@ -242,8 +243,8 @@ describe('Playlist thumbnail', function () {
242 this.timeout(30000) 243 this.timeout(30000)
243 244
244 await removeVideoFromPlaylist({ 245 await removeVideoFromPlaylist({
245 url: servers[ 1 ].url, 246 url: servers[1].url,
246 token: servers[ 1 ].accessToken, 247 token: servers[1].accessToken,
247 playlistId: playlistWithThumbnail, 248 playlistId: playlistWithThumbnail,
248 playlistElementId: withThumbnailE2 249 playlistElementId: withThumbnailE2
249 }) 250 })
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 9fd48ac7c..2bb97d7a8 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -141,12 +141,12 @@ describe('Test video playlists', function () {
141 servers[2].videos = await Promise.all(serverPromises[2]) 141 servers[2].videos = await Promise.all(serverPromises[2])
142 } 142 }
143 143
144 nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id 144 nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'NSFW video', nsfw: true })).id
145 145
146 { 146 {
147 await createUser({ 147 await createUser({
148 url: servers[ 0 ].url, 148 url: servers[0].url,
149 accessToken: servers[ 0 ].accessToken, 149 accessToken: servers[0].accessToken,
150 username: 'user1', 150 username: 'user1',
151 password: 'password' 151 password: 'password'
152 }) 152 })
@@ -158,17 +158,17 @@ describe('Test video playlists', function () {
158 158
159 describe('Get default playlists', function () { 159 describe('Get default playlists', function () {
160 it('Should list video playlist privacies', async function () { 160 it('Should list video playlist privacies', async function () {
161 const res = await getVideoPlaylistPrivacies(servers[ 0 ].url) 161 const res = await getVideoPlaylistPrivacies(servers[0].url)
162 162
163 const privacies = res.body 163 const privacies = res.body
164 expect(Object.keys(privacies)).to.have.length.at.least(3) 164 expect(Object.keys(privacies)).to.have.length.at.least(3)
165 165
166 expect(privacies[ 3 ]).to.equal('Private') 166 expect(privacies[3]).to.equal('Private')
167 }) 167 })
168 168
169 it('Should list watch later playlist', async function () { 169 it('Should list watch later playlist', async function () {
170 const url = servers[ 0 ].url 170 const url = servers[0].url
171 const accessToken = servers[ 0 ].accessToken 171 const accessToken = servers[0].accessToken
172 172
173 { 173 {
174 const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) 174 const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER)
@@ -176,7 +176,7 @@ describe('Test video playlists', function () {
176 expect(res.body.total).to.equal(1) 176 expect(res.body.total).to.equal(1)
177 expect(res.body.data).to.have.lengthOf(1) 177 expect(res.body.data).to.have.lengthOf(1)
178 178
179 const playlist: VideoPlaylist = res.body.data[ 0 ] 179 const playlist: VideoPlaylist = res.body.data[0]
180 expect(playlist.displayName).to.equal('Watch later') 180 expect(playlist.displayName).to.equal('Watch later')
181 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) 181 expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER)
182 expect(playlist.type.label).to.equal('Watch later') 182 expect(playlist.type.label).to.equal('Watch later')
@@ -197,15 +197,15 @@ describe('Test video playlists', function () {
197 }) 197 })
198 198
199 it('Should get private playlist for a classic user', async function () { 199 it('Should get private playlist for a classic user', async function () {
200 const token = await generateUserAccessToken(servers[ 0 ], 'toto') 200 const token = await generateUserAccessToken(servers[0], 'toto')
201 201
202 const res = await getAccountPlaylistsListWithToken(servers[ 0 ].url, token, 'toto', 0, 5) 202 const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5)
203 203
204 expect(res.body.total).to.equal(1) 204 expect(res.body.total).to.equal(1)
205 expect(res.body.data).to.have.lengthOf(1) 205 expect(res.body.data).to.have.lengthOf(1)
206 206
207 const playlistId = res.body.data[ 0 ].id 207 const playlistId = res.body.data[0].id
208 await getPlaylistVideos(servers[ 0 ].url, token, playlistId, 0, 5) 208 await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5)
209 }) 209 })
210 }) 210 })
211 211
@@ -215,14 +215,14 @@ describe('Test video playlists', function () {
215 this.timeout(30000) 215 this.timeout(30000)
216 216
217 await createVideoPlaylist({ 217 await createVideoPlaylist({
218 url: servers[ 0 ].url, 218 url: servers[0].url,
219 token: servers[ 0 ].accessToken, 219 token: servers[0].accessToken,
220 playlistAttrs: { 220 playlistAttrs: {
221 displayName: 'my super playlist', 221 displayName: 'my super playlist',
222 privacy: VideoPlaylistPrivacy.PUBLIC, 222 privacy: VideoPlaylistPrivacy.PUBLIC,
223 description: 'my super description', 223 description: 'my super description',
224 thumbnailfile: 'thumbnail.jpg', 224 thumbnailfile: 'thumbnail.jpg',
225 videoChannelId: servers[ 0 ].videoChannel.id 225 videoChannelId: servers[0].videoChannel.id
226 } 226 }
227 }) 227 })
228 228
@@ -233,7 +233,7 @@ describe('Test video playlists', function () {
233 expect(res.body.total).to.equal(1) 233 expect(res.body.total).to.equal(1)
234 expect(res.body.data).to.have.lengthOf(1) 234 expect(res.body.data).to.have.lengthOf(1)
235 235
236 const playlistFromList = res.body.data[ 0 ] as VideoPlaylist 236 const playlistFromList = res.body.data[0] as VideoPlaylist
237 237
238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) 238 const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid)
239 const playlistFromGet = res2.body 239 const playlistFromGet = res2.body
@@ -266,12 +266,12 @@ describe('Test video playlists', function () {
266 266
267 { 267 {
268 const res = await createVideoPlaylist({ 268 const res = await createVideoPlaylist({
269 url: servers[ 1 ].url, 269 url: servers[1].url,
270 token: servers[ 1 ].accessToken, 270 token: servers[1].accessToken,
271 playlistAttrs: { 271 playlistAttrs: {
272 displayName: 'playlist 2', 272 displayName: 'playlist 2',
273 privacy: VideoPlaylistPrivacy.PUBLIC, 273 privacy: VideoPlaylistPrivacy.PUBLIC,
274 videoChannelId: servers[ 1 ].videoChannel.id 274 videoChannelId: servers[1].videoChannel.id
275 } 275 }
276 }) 276 })
277 playlistServer2Id1 = res.body.videoPlaylist.id 277 playlistServer2Id1 = res.body.videoPlaylist.id
@@ -279,13 +279,13 @@ describe('Test video playlists', function () {
279 279
280 { 280 {
281 const res = await createVideoPlaylist({ 281 const res = await createVideoPlaylist({
282 url: servers[ 1 ].url, 282 url: servers[1].url,
283 token: servers[ 1 ].accessToken, 283 token: servers[1].accessToken,
284 playlistAttrs: { 284 playlistAttrs: {
285 displayName: 'playlist 3', 285 displayName: 'playlist 3',
286 privacy: VideoPlaylistPrivacy.PUBLIC, 286 privacy: VideoPlaylistPrivacy.PUBLIC,
287 thumbnailfile: 'thumbnail.jpg', 287 thumbnailfile: 'thumbnail.jpg',
288 videoChannelId: servers[ 1 ].videoChannel.id 288 videoChannelId: servers[1].videoChannel.id
289 } 289 }
290 }) 290 })
291 291
@@ -293,24 +293,24 @@ describe('Test video playlists', function () {
293 playlistServer2UUID2 = res.body.videoPlaylist.uuid 293 playlistServer2UUID2 = res.body.videoPlaylist.uuid
294 } 294 }
295 295
296 for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { 296 for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) {
297 await addVideoInPlaylist({ 297 await addVideoInPlaylist({
298 url: servers[ 1 ].url, 298 url: servers[1].url,
299 token: servers[ 1 ].accessToken, 299 token: servers[1].accessToken,
300 playlistId: id, 300 playlistId: id,
301 elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } 301 elementAttrs: { videoId: servers[1].videos[0].id, startTimestamp: 1, stopTimestamp: 2 }
302 }) 302 })
303 await addVideoInPlaylist({ 303 await addVideoInPlaylist({
304 url: servers[ 1 ].url, 304 url: servers[1].url,
305 token: servers[ 1 ].accessToken, 305 token: servers[1].accessToken,
306 playlistId: id, 306 playlistId: id,
307 elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } 307 elementAttrs: { videoId: servers[1].videos[1].id }
308 }) 308 })
309 } 309 }
310 310
311 await waitJobs(servers) 311 await waitJobs(servers)
312 312
313 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 313 for (const server of [ servers[0], servers[1] ]) {
314 const res = await getVideoPlaylistsList(server.url, 0, 5) 314 const res = await getVideoPlaylistsList(server.url, 0, 5)
315 315
316 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') 316 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
@@ -322,7 +322,7 @@ describe('Test video playlists', function () {
322 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) 322 await testImage(server.url, 'thumbnail', playlist3.thumbnailPath)
323 } 323 }
324 324
325 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 325 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
326 expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined 326 expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined
327 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined 327 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined
328 }) 328 })
@@ -331,13 +331,13 @@ describe('Test video playlists', function () {
331 this.timeout(30000) 331 this.timeout(30000)
332 332
333 // Server 2 and server 3 follow each other 333 // Server 2 and server 3 follow each other
334 await doubleFollow(servers[ 1 ], servers[ 2 ]) 334 await doubleFollow(servers[1], servers[2])
335 335
336 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 336 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
337 337
338 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') 338 const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2')
339 expect(playlist2).to.not.be.undefined 339 expect(playlist2).to.not.be.undefined
340 await testImage(servers[ 2 ].url, 'thumbnail-playlist', playlist2.thumbnailPath) 340 await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath)
341 341
342 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined 342 expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined
343 }) 343 })
@@ -349,25 +349,25 @@ describe('Test video playlists', function () {
349 this.timeout(30000) 349 this.timeout(30000)
350 350
351 { 351 {
352 const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') 352 const res = await getVideoPlaylistsList(servers[2].url, 1, 2, 'createdAt')
353 353
354 expect(res.body.total).to.equal(3) 354 expect(res.body.total).to.equal(3)
355 355
356 const data: VideoPlaylist[] = res.body.data 356 const data: VideoPlaylist[] = res.body.data
357 expect(data).to.have.lengthOf(2) 357 expect(data).to.have.lengthOf(2)
358 expect(data[ 0 ].displayName).to.equal('playlist 2') 358 expect(data[0].displayName).to.equal('playlist 2')
359 expect(data[ 1 ].displayName).to.equal('playlist 3') 359 expect(data[1].displayName).to.equal('playlist 3')
360 } 360 }
361 361
362 { 362 {
363 const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') 363 const res = await getVideoPlaylistsList(servers[2].url, 1, 2, '-createdAt')
364 364
365 expect(res.body.total).to.equal(3) 365 expect(res.body.total).to.equal(3)
366 366
367 const data: VideoPlaylist[] = res.body.data 367 const data: VideoPlaylist[] = res.body.data
368 expect(data).to.have.lengthOf(2) 368 expect(data).to.have.lengthOf(2)
369 expect(data[ 0 ].displayName).to.equal('playlist 2') 369 expect(data[0].displayName).to.equal('playlist 2')
370 expect(data[ 1 ].displayName).to.equal('my super playlist') 370 expect(data[1].displayName).to.equal('my super playlist')
371 } 371 }
372 }) 372 })
373 373
@@ -375,13 +375,13 @@ describe('Test video playlists', function () {
375 this.timeout(30000) 375 this.timeout(30000)
376 376
377 { 377 {
378 const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') 378 const res = await getVideoChannelPlaylistsList(servers[0].url, 'root_channel', 0, 2, '-createdAt')
379 379
380 expect(res.body.total).to.equal(1) 380 expect(res.body.total).to.equal(1)
381 381
382 const data: VideoPlaylist[] = res.body.data 382 const data: VideoPlaylist[] = res.body.data
383 expect(data).to.have.lengthOf(1) 383 expect(data).to.have.lengthOf(1)
384 expect(data[ 0 ].displayName).to.equal('my super playlist') 384 expect(data[0].displayName).to.equal('my super playlist')
385 } 385 }
386 }) 386 })
387 387
@@ -389,37 +389,37 @@ describe('Test video playlists', function () {
389 this.timeout(30000) 389 this.timeout(30000)
390 390
391 { 391 {
392 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') 392 const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, '-createdAt')
393 393
394 expect(res.body.total).to.equal(2) 394 expect(res.body.total).to.equal(2)
395 395
396 const data: VideoPlaylist[] = res.body.data 396 const data: VideoPlaylist[] = res.body.data
397 expect(data).to.have.lengthOf(1) 397 expect(data).to.have.lengthOf(1)
398 expect(data[ 0 ].displayName).to.equal('playlist 2') 398 expect(data[0].displayName).to.equal('playlist 2')
399 } 399 }
400 400
401 { 401 {
402 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') 402 const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, 'createdAt')
403 403
404 expect(res.body.total).to.equal(2) 404 expect(res.body.total).to.equal(2)
405 405
406 const data: VideoPlaylist[] = res.body.data 406 const data: VideoPlaylist[] = res.body.data
407 expect(data).to.have.lengthOf(1) 407 expect(data).to.have.lengthOf(1)
408 expect(data[ 0 ].displayName).to.equal('playlist 3') 408 expect(data[0].displayName).to.equal('playlist 3')
409 } 409 }
410 410
411 { 411 {
412 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '3') 412 const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '3')
413 413
414 expect(res.body.total).to.equal(1) 414 expect(res.body.total).to.equal(1)
415 415
416 const data: VideoPlaylist[] = res.body.data 416 const data: VideoPlaylist[] = res.body.data
417 expect(data).to.have.lengthOf(1) 417 expect(data).to.have.lengthOf(1)
418 expect(data[ 0 ].displayName).to.equal('playlist 3') 418 expect(data[0].displayName).to.equal('playlist 3')
419 } 419 }
420 420
421 { 421 {
422 const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '4') 422 const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '4')
423 423
424 expect(res.body.total).to.equal(0) 424 expect(res.body.total).to.equal(0)
425 425
@@ -432,8 +432,8 @@ describe('Test video playlists', function () {
432 this.timeout(30000) 432 this.timeout(30000)
433 433
434 await createVideoPlaylist({ 434 await createVideoPlaylist({
435 url: servers[ 1 ].url, 435 url: servers[1].url,
436 token: servers[ 1 ].accessToken, 436 token: servers[1].accessToken,
437 playlistAttrs: { 437 playlistAttrs: {
438 displayName: 'playlist unlisted', 438 displayName: 'playlist unlisted',
439 privacy: VideoPlaylistPrivacy.UNLISTED 439 privacy: VideoPlaylistPrivacy.UNLISTED
@@ -441,8 +441,8 @@ describe('Test video playlists', function () {
441 }) 441 })
442 442
443 await createVideoPlaylist({ 443 await createVideoPlaylist({
444 url: servers[ 1 ].url, 444 url: servers[1].url,
445 token: servers[ 1 ].accessToken, 445 token: servers[1].accessToken,
446 playlistAttrs: { 446 playlistAttrs: {
447 displayName: 'playlist private', 447 displayName: 'playlist private',
448 privacy: VideoPlaylistPrivacy.PRIVATE 448 privacy: VideoPlaylistPrivacy.PRIVATE
@@ -453,18 +453,18 @@ describe('Test video playlists', function () {
453 453
454 for (const server of servers) { 454 for (const server of servers) {
455 const results = [ 455 const results = [
456 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[ 1 ].port, 0, 5, '-createdAt'), 456 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
457 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') 457 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
458 ] 458 ]
459 459
460 expect(results[ 0 ].body.total).to.equal(2) 460 expect(results[0].body.total).to.equal(2)
461 expect(results[ 1 ].body.total).to.equal(3) 461 expect(results[1].body.total).to.equal(3)
462 462
463 for (const res of results) { 463 for (const res of results) {
464 const data: VideoPlaylist[] = res.body.data 464 const data: VideoPlaylist[] = res.body.data
465 expect(data).to.have.lengthOf(2) 465 expect(data).to.have.lengthOf(2)
466 expect(data[ 0 ].displayName).to.equal('playlist 3') 466 expect(data[0].displayName).to.equal('playlist 3')
467 expect(data[ 1 ].displayName).to.equal('playlist 2') 467 expect(data[1].displayName).to.equal('playlist 2')
468 } 468 }
469 } 469 }
470 }) 470 })
@@ -519,32 +519,32 @@ describe('Test video playlists', function () {
519 this.timeout(30000) 519 this.timeout(30000)
520 520
521 const addVideo = (elementAttrs: any) => { 521 const addVideo = (elementAttrs: any) => {
522 return addVideoInPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: playlistServer1Id, elementAttrs }) 522 return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs })
523 } 523 }
524 524
525 const res = await createVideoPlaylist({ 525 const res = await createVideoPlaylist({
526 url: servers[ 0 ].url, 526 url: servers[0].url,
527 token: servers[ 0 ].accessToken, 527 token: servers[0].accessToken,
528 playlistAttrs: { 528 playlistAttrs: {
529 displayName: 'playlist 4', 529 displayName: 'playlist 4',
530 privacy: VideoPlaylistPrivacy.PUBLIC, 530 privacy: VideoPlaylistPrivacy.PUBLIC,
531 videoChannelId: servers[ 0 ].videoChannel.id 531 videoChannelId: servers[0].videoChannel.id
532 } 532 }
533 }) 533 })
534 534
535 playlistServer1Id = res.body.videoPlaylist.id 535 playlistServer1Id = res.body.videoPlaylist.id
536 playlistServer1UUID = res.body.videoPlaylist.uuid 536 playlistServer1UUID = res.body.videoPlaylist.uuid
537 537
538 await addVideo({ videoId: servers[ 0 ].videos[ 0 ].uuid, startTimestamp: 15, stopTimestamp: 28 }) 538 await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 })
539 await addVideo({ videoId: servers[ 2 ].videos[ 1 ].uuid, startTimestamp: 35 }) 539 await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 })
540 await addVideo({ videoId: servers[ 2 ].videos[ 2 ].uuid }) 540 await addVideo({ videoId: servers[2].videos[2].uuid })
541 { 541 {
542 const res = await addVideo({ videoId: servers[ 0 ].videos[ 3 ].uuid, stopTimestamp: 35 }) 542 const res = await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 })
543 playlistElementServer1Video4 = res.body.videoPlaylistElement.id 543 playlistElementServer1Video4 = res.body.videoPlaylistElement.id
544 } 544 }
545 545
546 { 546 {
547 const res = await addVideo({ videoId: servers[ 0 ].videos[ 4 ].uuid, startTimestamp: 45, stopTimestamp: 60 }) 547 const res = await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 })
548 playlistElementServer1Video5 = res.body.videoPlaylistElement.id 548 playlistElementServer1Video5 = res.body.videoPlaylistElement.id
549 } 549 }
550 550
@@ -567,35 +567,35 @@ describe('Test video playlists', function () {
567 const videoElements: VideoPlaylistElement[] = res.body.data 567 const videoElements: VideoPlaylistElement[] = res.body.data
568 expect(videoElements).to.have.lengthOf(6) 568 expect(videoElements).to.have.lengthOf(6)
569 569
570 expect(videoElements[ 0 ].video.name).to.equal('video 0 server 1') 570 expect(videoElements[0].video.name).to.equal('video 0 server 1')
571 expect(videoElements[ 0 ].position).to.equal(1) 571 expect(videoElements[0].position).to.equal(1)
572 expect(videoElements[ 0 ].startTimestamp).to.equal(15) 572 expect(videoElements[0].startTimestamp).to.equal(15)
573 expect(videoElements[ 0 ].stopTimestamp).to.equal(28) 573 expect(videoElements[0].stopTimestamp).to.equal(28)
574 574
575 expect(videoElements[ 1 ].video.name).to.equal('video 1 server 3') 575 expect(videoElements[1].video.name).to.equal('video 1 server 3')
576 expect(videoElements[ 1 ].position).to.equal(2) 576 expect(videoElements[1].position).to.equal(2)
577 expect(videoElements[ 1 ].startTimestamp).to.equal(35) 577 expect(videoElements[1].startTimestamp).to.equal(35)
578 expect(videoElements[ 1 ].stopTimestamp).to.be.null 578 expect(videoElements[1].stopTimestamp).to.be.null
579 579
580 expect(videoElements[ 2 ].video.name).to.equal('video 2 server 3') 580 expect(videoElements[2].video.name).to.equal('video 2 server 3')
581 expect(videoElements[ 2 ].position).to.equal(3) 581 expect(videoElements[2].position).to.equal(3)
582 expect(videoElements[ 2 ].startTimestamp).to.be.null 582 expect(videoElements[2].startTimestamp).to.be.null
583 expect(videoElements[ 2 ].stopTimestamp).to.be.null 583 expect(videoElements[2].stopTimestamp).to.be.null
584 584
585 expect(videoElements[ 3 ].video.name).to.equal('video 3 server 1') 585 expect(videoElements[3].video.name).to.equal('video 3 server 1')
586 expect(videoElements[ 3 ].position).to.equal(4) 586 expect(videoElements[3].position).to.equal(4)
587 expect(videoElements[ 3 ].startTimestamp).to.be.null 587 expect(videoElements[3].startTimestamp).to.be.null
588 expect(videoElements[ 3 ].stopTimestamp).to.equal(35) 588 expect(videoElements[3].stopTimestamp).to.equal(35)
589 589
590 expect(videoElements[ 4 ].video.name).to.equal('video 4 server 1') 590 expect(videoElements[4].video.name).to.equal('video 4 server 1')
591 expect(videoElements[ 4 ].position).to.equal(5) 591 expect(videoElements[4].position).to.equal(5)
592 expect(videoElements[ 4 ].startTimestamp).to.equal(45) 592 expect(videoElements[4].startTimestamp).to.equal(45)
593 expect(videoElements[ 4 ].stopTimestamp).to.equal(60) 593 expect(videoElements[4].stopTimestamp).to.equal(60)
594 594
595 expect(videoElements[ 5 ].video.name).to.equal('NSFW video') 595 expect(videoElements[5].video.name).to.equal('NSFW video')
596 expect(videoElements[ 5 ].position).to.equal(6) 596 expect(videoElements[5].position).to.equal(6)
597 expect(videoElements[ 5 ].startTimestamp).to.equal(5) 597 expect(videoElements[5].startTimestamp).to.equal(5)
598 expect(videoElements[ 5 ].stopTimestamp).to.be.null 598 expect(videoElements[5].stopTimestamp).to.be.null
599 599
600 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) 600 const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2)
601 expect(res3.body.data).to.have.lengthOf(2) 601 expect(res3.body.data).to.have.lengthOf(2)
@@ -616,18 +616,18 @@ describe('Test video playlists', function () {
616 before(async function () { 616 before(async function () {
617 this.timeout(30000) 617 this.timeout(30000)
618 618
619 groupUser1 = [ Object.assign({}, servers[ 0 ], { accessToken: userAccessTokenServer1 }) ] 619 groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ]
620 groupWithoutToken1 = [ Object.assign({}, servers[ 0 ], { accessToken: undefined }) ] 620 groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ]
621 group1 = [ servers[ 0 ] ] 621 group1 = [ servers[0] ]
622 group2 = [ servers[ 1 ], servers[ 2 ] ] 622 group2 = [ servers[1], servers[2] ]
623 623
624 const res = await createVideoPlaylist({ 624 const res = await createVideoPlaylist({
625 url: servers[ 0 ].url, 625 url: servers[0].url,
626 token: userAccessTokenServer1, 626 token: userAccessTokenServer1,
627 playlistAttrs: { 627 playlistAttrs: {
628 displayName: 'playlist 56', 628 displayName: 'playlist 56',
629 privacy: VideoPlaylistPrivacy.PUBLIC, 629 privacy: VideoPlaylistPrivacy.PUBLIC,
630 videoChannelId: servers[ 0 ].videoChannel.id 630 videoChannelId: servers[0].videoChannel.id
631 } 631 }
632 }) 632 })
633 633
@@ -635,7 +635,7 @@ describe('Test video playlists', function () {
635 playlistServer1UUID2 = res.body.videoPlaylist.uuid 635 playlistServer1UUID2 = res.body.videoPlaylist.uuid
636 636
637 const addVideo = (elementAttrs: any) => { 637 const addVideo = (elementAttrs: any) => {
638 return addVideoInPlaylist({ url: servers[ 0 ].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) 638 return addVideoInPlaylist({ url: servers[0].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs })
639 } 639 }
640 640
641 video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid 641 video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid
@@ -656,7 +656,7 @@ describe('Test video playlists', function () {
656 const position = 1 656 const position = 1
657 657
658 { 658 {
659 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) 659 await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PRIVATE })
660 await waitJobs(servers) 660 await waitJobs(servers)
661 661
662 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 662 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -666,7 +666,7 @@ describe('Test video playlists', function () {
666 } 666 }
667 667
668 { 668 {
669 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) 669 await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PUBLIC })
670 await waitJobs(servers) 670 await waitJobs(servers)
671 671
672 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 672 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -684,7 +684,7 @@ describe('Test video playlists', function () {
684 const position = 1 684 const position = 1
685 685
686 { 686 {
687 await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1, 'reason', true) 687 await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video1, 'reason', true)
688 await waitJobs(servers) 688 await waitJobs(servers)
689 689
690 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 690 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -694,7 +694,7 @@ describe('Test video playlists', function () {
694 } 694 }
695 695
696 { 696 {
697 await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1) 697 await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video1)
698 await waitJobs(servers) 698 await waitJobs(servers)
699 699
700 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 700 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -712,52 +712,52 @@ describe('Test video playlists', function () {
712 const position = 2 712 const position = 2
713 713
714 { 714 {
715 await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) 715 await addAccountToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
716 await waitJobs(servers) 716 await waitJobs(servers)
717 717
718 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 718 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 719 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
720 720
721 await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) 721 await removeAccountFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port)
722 await waitJobs(servers) 722 await waitJobs(servers)
723 723
724 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 724 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
725 } 725 }
726 726
727 { 727 {
728 await addServerToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) 728 await addServerToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
729 await waitJobs(servers) 729 await waitJobs(servers)
730 730
731 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 731 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 732 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
733 733
734 await removeServerFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) 734 await removeServerFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port)
735 await waitJobs(servers) 735 await waitJobs(servers)
736 736
737 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 737 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
738 } 738 }
739 739
740 { 740 {
741 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) 741 await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
742 await waitJobs(servers) 742 await waitJobs(servers)
743 743
744 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 744 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 745 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
746 746
747 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) 747 await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port)
748 await waitJobs(servers) 748 await waitJobs(servers)
749 749
750 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 750 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
751 } 751 }
752 752
753 { 753 {
754 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 754 await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
755 await waitJobs(servers) 755 await waitJobs(servers)
756 756
757 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) 757 await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3)
758 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 758 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
759 759
760 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) 760 await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port)
761 await waitJobs(servers) 761 await waitJobs(servers)
762 762
763 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) 763 await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3)
@@ -785,8 +785,8 @@ describe('Test video playlists', function () {
785 785
786 { 786 {
787 await reorderVideosPlaylist({ 787 await reorderVideosPlaylist({
788 url: servers[ 0 ].url, 788 url: servers[0].url,
789 token: servers[ 0 ].accessToken, 789 token: servers[0].accessToken,
790 playlistId: playlistServer1Id, 790 playlistId: playlistServer1Id,
791 elementAttrs: { 791 elementAttrs: {
792 startPosition: 2, 792 startPosition: 2,
@@ -813,8 +813,8 @@ describe('Test video playlists', function () {
813 813
814 { 814 {
815 await reorderVideosPlaylist({ 815 await reorderVideosPlaylist({
816 url: servers[ 0 ].url, 816 url: servers[0].url,
817 token: servers[ 0 ].accessToken, 817 token: servers[0].accessToken,
818 playlistId: playlistServer1Id, 818 playlistId: playlistServer1Id,
819 elementAttrs: { 819 elementAttrs: {
820 startPosition: 1, 820 startPosition: 1,
@@ -842,8 +842,8 @@ describe('Test video playlists', function () {
842 842
843 { 843 {
844 await reorderVideosPlaylist({ 844 await reorderVideosPlaylist({
845 url: servers[ 0 ].url, 845 url: servers[0].url,
846 token: servers[ 0 ].accessToken, 846 token: servers[0].accessToken,
847 playlistId: playlistServer1Id, 847 playlistId: playlistServer1Id,
848 elementAttrs: { 848 elementAttrs: {
849 startPosition: 6, 849 startPosition: 6,
@@ -868,7 +868,7 @@ describe('Test video playlists', function () {
868 ]) 868 ])
869 869
870 for (let i = 1; i <= elements.length; i++) { 870 for (let i = 1; i <= elements.length; i++) {
871 expect(elements[ i - 1 ].position).to.equal(i) 871 expect(elements[i - 1].position).to.equal(i)
872 } 872 }
873 } 873 }
874 } 874 }
@@ -878,8 +878,8 @@ describe('Test video playlists', function () {
878 this.timeout(30000) 878 this.timeout(30000)
879 879
880 await updateVideoPlaylistElement({ 880 await updateVideoPlaylistElement({
881 url: servers[ 0 ].url, 881 url: servers[0].url,
882 token: servers[ 0 ].accessToken, 882 token: servers[0].accessToken,
883 playlistId: playlistServer1Id, 883 playlistId: playlistServer1Id,
884 playlistElementId: playlistElementServer1Video4, 884 playlistElementId: playlistElementServer1Video4,
885 elementAttrs: { 885 elementAttrs: {
@@ -888,8 +888,8 @@ describe('Test video playlists', function () {
888 }) 888 })
889 889
890 await updateVideoPlaylistElement({ 890 await updateVideoPlaylistElement({
891 url: servers[ 0 ].url, 891 url: servers[0].url,
892 token: servers[ 0 ].accessToken, 892 token: servers[0].accessToken,
893 playlistId: playlistServer1Id, 893 playlistId: playlistServer1Id,
894 playlistElementId: playlistElementServer1Video5, 894 playlistElementId: playlistElementServer1Video5,
895 elementAttrs: { 895 elementAttrs: {
@@ -903,62 +903,62 @@ describe('Test video playlists', function () {
903 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) 903 const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10)
904 const elements: VideoPlaylistElement[] = res.body.data 904 const elements: VideoPlaylistElement[] = res.body.data
905 905
906 expect(elements[ 0 ].video.name).to.equal('video 3 server 1') 906 expect(elements[0].video.name).to.equal('video 3 server 1')
907 expect(elements[ 0 ].position).to.equal(1) 907 expect(elements[0].position).to.equal(1)
908 expect(elements[ 0 ].startTimestamp).to.equal(1) 908 expect(elements[0].startTimestamp).to.equal(1)
909 expect(elements[ 0 ].stopTimestamp).to.equal(35) 909 expect(elements[0].stopTimestamp).to.equal(35)
910 910
911 expect(elements[ 5 ].video.name).to.equal('video 4 server 1') 911 expect(elements[5].video.name).to.equal('video 4 server 1')
912 expect(elements[ 5 ].position).to.equal(6) 912 expect(elements[5].position).to.equal(6)
913 expect(elements[ 5 ].startTimestamp).to.equal(45) 913 expect(elements[5].startTimestamp).to.equal(45)
914 expect(elements[ 5 ].stopTimestamp).to.be.null 914 expect(elements[5].stopTimestamp).to.be.null
915 } 915 }
916 }) 916 })
917 917
918 it('Should check videos existence in my playlist', async function () { 918 it('Should check videos existence in my playlist', async function () {
919 const videoIds = [ 919 const videoIds = [
920 servers[ 0 ].videos[ 0 ].id, 920 servers[0].videos[0].id,
921 42000, 921 42000,
922 servers[ 0 ].videos[ 3 ].id, 922 servers[0].videos[3].id,
923 43000, 923 43000,
924 servers[ 0 ].videos[ 4 ].id 924 servers[0].videos[4].id
925 ] 925 ]
926 const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) 926 const res = await doVideosExistInMyPlaylist(servers[0].url, servers[0].accessToken, videoIds)
927 const obj = res.body as VideoExistInPlaylist 927 const obj = res.body as VideoExistInPlaylist
928 928
929 { 929 {
930 const elem = obj[ servers[ 0 ].videos[ 0 ].id ] 930 const elem = obj[servers[0].videos[0].id]
931 expect(elem).to.have.lengthOf(1) 931 expect(elem).to.have.lengthOf(1)
932 expect(elem[ 0 ].playlistElementId).to.exist 932 expect(elem[0].playlistElementId).to.exist
933 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 933 expect(elem[0].playlistId).to.equal(playlistServer1Id)
934 expect(elem[ 0 ].startTimestamp).to.equal(15) 934 expect(elem[0].startTimestamp).to.equal(15)
935 expect(elem[ 0 ].stopTimestamp).to.equal(28) 935 expect(elem[0].stopTimestamp).to.equal(28)
936 } 936 }
937 937
938 { 938 {
939 const elem = obj[ servers[ 0 ].videos[ 3 ].id ] 939 const elem = obj[servers[0].videos[3].id]
940 expect(elem).to.have.lengthOf(1) 940 expect(elem).to.have.lengthOf(1)
941 expect(elem[ 0 ].playlistElementId).to.equal(playlistElementServer1Video4) 941 expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4)
942 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 942 expect(elem[0].playlistId).to.equal(playlistServer1Id)
943 expect(elem[ 0 ].startTimestamp).to.equal(1) 943 expect(elem[0].startTimestamp).to.equal(1)
944 expect(elem[ 0 ].stopTimestamp).to.equal(35) 944 expect(elem[0].stopTimestamp).to.equal(35)
945 } 945 }
946 946
947 { 947 {
948 const elem = obj[ servers[ 0 ].videos[ 4 ].id ] 948 const elem = obj[servers[0].videos[4].id]
949 expect(elem).to.have.lengthOf(1) 949 expect(elem).to.have.lengthOf(1)
950 expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) 950 expect(elem[0].playlistId).to.equal(playlistServer1Id)
951 expect(elem[ 0 ].startTimestamp).to.equal(45) 951 expect(elem[0].startTimestamp).to.equal(45)
952 expect(elem[ 0 ].stopTimestamp).to.equal(null) 952 expect(elem[0].stopTimestamp).to.equal(null)
953 } 953 }
954 954
955 expect(obj[ 42000 ]).to.have.lengthOf(0) 955 expect(obj[42000]).to.have.lengthOf(0)
956 expect(obj[ 43000 ]).to.have.lengthOf(0) 956 expect(obj[43000]).to.have.lengthOf(0)
957 }) 957 })
958 958
959 it('Should automatically update updatedAt field of playlists', async function () { 959 it('Should automatically update updatedAt field of playlists', async function () {
960 const server = servers[ 1 ] 960 const server = servers[1]
961 const videoId = servers[ 1 ].videos[ 5 ].id 961 const videoId = servers[1].videos[5].id
962 962
963 async function getPlaylistNames () { 963 async function getPlaylistNames () {
964 const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') 964 const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt')
@@ -974,8 +974,8 @@ describe('Test video playlists', function () {
974 const element2 = res2.body.videoPlaylistElement.id 974 const element2 = res2.body.videoPlaylistElement.id
975 975
976 const names1 = await getPlaylistNames() 976 const names1 = await getPlaylistNames()
977 expect(names1[ 0 ]).to.equal('playlist 3 updated') 977 expect(names1[0]).to.equal('playlist 3 updated')
978 expect(names1[ 1 ]).to.equal('playlist 2') 978 expect(names1[1]).to.equal('playlist 2')
979 979
980 await removeVideoFromPlaylist({ 980 await removeVideoFromPlaylist({
981 url: server.url, 981 url: server.url,
@@ -985,8 +985,8 @@ describe('Test video playlists', function () {
985 }) 985 })
986 986
987 const names2 = await getPlaylistNames() 987 const names2 = await getPlaylistNames()
988 expect(names2[ 0 ]).to.equal('playlist 2') 988 expect(names2[0]).to.equal('playlist 2')
989 expect(names2[ 1 ]).to.equal('playlist 3 updated') 989 expect(names2[1]).to.equal('playlist 3 updated')
990 990
991 await removeVideoFromPlaylist({ 991 await removeVideoFromPlaylist({
992 url: server.url, 992 url: server.url,
@@ -996,23 +996,23 @@ describe('Test video playlists', function () {
996 }) 996 })
997 997
998 const names3 = await getPlaylistNames() 998 const names3 = await getPlaylistNames()
999 expect(names3[ 0 ]).to.equal('playlist 3 updated') 999 expect(names3[0]).to.equal('playlist 3 updated')
1000 expect(names3[ 1 ]).to.equal('playlist 2') 1000 expect(names3[1]).to.equal('playlist 2')
1001 }) 1001 })
1002 1002
1003 it('Should delete some elements', async function () { 1003 it('Should delete some elements', async function () {
1004 this.timeout(30000) 1004 this.timeout(30000)
1005 1005
1006 await removeVideoFromPlaylist({ 1006 await removeVideoFromPlaylist({
1007 url: servers[ 0 ].url, 1007 url: servers[0].url,
1008 token: servers[ 0 ].accessToken, 1008 token: servers[0].accessToken,
1009 playlistId: playlistServer1Id, 1009 playlistId: playlistServer1Id,
1010 playlistElementId: playlistElementServer1Video4 1010 playlistElementId: playlistElementServer1Video4
1011 }) 1011 })
1012 1012
1013 await removeVideoFromPlaylist({ 1013 await removeVideoFromPlaylist({
1014 url: servers[ 0 ].url, 1014 url: servers[0].url,
1015 token: servers[ 0 ].accessToken, 1015 token: servers[0].accessToken,
1016 playlistId: playlistServer1Id, 1016 playlistId: playlistServer1Id,
1017 playlistElementId: playlistElementNSFW 1017 playlistElementId: playlistElementNSFW
1018 }) 1018 })
@@ -1027,17 +1027,17 @@ describe('Test video playlists', function () {
1027 const elements: VideoPlaylistElement[] = res.body.data 1027 const elements: VideoPlaylistElement[] = res.body.data
1028 expect(elements).to.have.lengthOf(4) 1028 expect(elements).to.have.lengthOf(4)
1029 1029
1030 expect(elements[ 0 ].video.name).to.equal('video 0 server 1') 1030 expect(elements[0].video.name).to.equal('video 0 server 1')
1031 expect(elements[ 0 ].position).to.equal(1) 1031 expect(elements[0].position).to.equal(1)
1032 1032
1033 expect(elements[ 1 ].video.name).to.equal('video 2 server 3') 1033 expect(elements[1].video.name).to.equal('video 2 server 3')
1034 expect(elements[ 1 ].position).to.equal(2) 1034 expect(elements[1].position).to.equal(2)
1035 1035
1036 expect(elements[ 2 ].video.name).to.equal('video 1 server 3') 1036 expect(elements[2].video.name).to.equal('video 1 server 3')
1037 expect(elements[ 2 ].position).to.equal(3) 1037 expect(elements[2].position).to.equal(3)
1038 1038
1039 expect(elements[ 3 ].video.name).to.equal('video 4 server 1') 1039 expect(elements[3].video.name).to.equal('video 4 server 1')
1040 expect(elements[ 3 ].position).to.equal(4) 1040 expect(elements[3].position).to.equal(4)
1041 } 1041 }
1042 }) 1042 })
1043 1043
@@ -1045,12 +1045,12 @@ describe('Test video playlists', function () {
1045 this.timeout(30000) 1045 this.timeout(30000)
1046 1046
1047 const res = await createVideoPlaylist({ 1047 const res = await createVideoPlaylist({
1048 url: servers[ 0 ].url, 1048 url: servers[0].url,
1049 token: servers[ 0 ].accessToken, 1049 token: servers[0].accessToken,
1050 playlistAttrs: { 1050 playlistAttrs: {
1051 displayName: 'my super public playlist', 1051 displayName: 'my super public playlist',
1052 privacy: VideoPlaylistPrivacy.PUBLIC, 1052 privacy: VideoPlaylistPrivacy.PUBLIC,
1053 videoChannelId: servers[ 0 ].videoChannel.id 1053 videoChannelId: servers[0].videoChannel.id
1054 } 1054 }
1055 }) 1055 })
1056 const videoPlaylistIds = res.body.videoPlaylist 1056 const videoPlaylistIds = res.body.videoPlaylist
@@ -1062,16 +1062,16 @@ describe('Test video playlists', function () {
1062 } 1062 }
1063 1063
1064 const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } 1064 const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE }
1065 await updateVideoPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) 1065 await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs })
1066 1066
1067 await waitJobs(servers) 1067 await waitJobs(servers)
1068 1068
1069 for (const server of [ servers[ 1 ], servers[ 2 ] ]) { 1069 for (const server of [ servers[1], servers[2] ]) {
1070 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) 1070 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404)
1071 } 1071 }
1072 await getVideoPlaylist(servers[ 0 ].url, videoPlaylistIds.uuid, 401) 1072 await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401)
1073 1073
1074 await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistIds.uuid, 200) 1074 await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200)
1075 }) 1075 })
1076 }) 1076 })
1077 1077
@@ -1080,7 +1080,7 @@ describe('Test video playlists', function () {
1080 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { 1080 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
1081 this.timeout(30000) 1081 this.timeout(30000)
1082 1082
1083 await deleteVideoPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, playlistServer1Id) 1083 await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id)
1084 1084
1085 await waitJobs(servers) 1085 await waitJobs(servers)
1086 1086
@@ -1103,15 +1103,15 @@ describe('Test video playlists', function () {
1103 const finder = data => data.find(p => p.displayName === 'my super playlist') 1103 const finder = data => data.find(p => p.displayName === 'my super playlist')
1104 1104
1105 { 1105 {
1106 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 1106 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1107 expect(res.body.total).to.equal(3) 1107 expect(res.body.total).to.equal(3)
1108 expect(finder(res.body.data)).to.not.be.undefined 1108 expect(finder(res.body.data)).to.not.be.undefined
1109 } 1109 }
1110 1110
1111 await unfollow(servers[ 2 ].url, servers[ 2 ].accessToken, servers[ 0 ]) 1111 await unfollow(servers[2].url, servers[2].accessToken, servers[0])
1112 1112
1113 { 1113 {
1114 const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) 1114 const res = await getVideoPlaylistsList(servers[2].url, 0, 5)
1115 expect(res.body.total).to.equal(1) 1115 expect(res.body.total).to.equal(1)
1116 1116
1117 expect(finder(res.body.data)).to.be.undefined 1117 expect(finder(res.body.data)).to.be.undefined
@@ -1121,12 +1121,12 @@ describe('Test video playlists', function () {
1121 it('Should delete a channel and put the associated playlist in private mode', async function () { 1121 it('Should delete a channel and put the associated playlist in private mode', async function () {
1122 this.timeout(30000) 1122 this.timeout(30000)
1123 1123
1124 const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'super_channel', displayName: 'super channel' }) 1124 const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' })
1125 const videoChannelId = res.body.videoChannel.id 1125 const videoChannelId = res.body.videoChannel.id
1126 1126
1127 const res2 = await createVideoPlaylist({ 1127 const res2 = await createVideoPlaylist({
1128 url: servers[ 0 ].url, 1128 url: servers[0].url,
1129 token: servers[ 0 ].accessToken, 1129 token: servers[0].accessToken,
1130 playlistAttrs: { 1130 playlistAttrs: {
1131 displayName: 'channel playlist', 1131 displayName: 'channel playlist',
1132 privacy: VideoPlaylistPrivacy.PUBLIC, 1132 privacy: VideoPlaylistPrivacy.PUBLIC,
@@ -1137,15 +1137,15 @@ describe('Test video playlists', function () {
1137 1137
1138 await waitJobs(servers) 1138 await waitJobs(servers)
1139 1139
1140 await deleteVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, 'super_channel') 1140 await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel')
1141 1141
1142 await waitJobs(servers) 1142 await waitJobs(servers)
1143 1143
1144 const res3 = await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistUUID) 1144 const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID)
1145 expect(res3.body.displayName).to.equal('channel playlist') 1145 expect(res3.body.displayName).to.equal('channel playlist')
1146 expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) 1146 expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE)
1147 1147
1148 await getVideoPlaylist(servers[ 1 ].url, videoPlaylistUUID, 404) 1148 await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404)
1149 }) 1149 })
1150 1150
1151 it('Should delete an account and delete its playlists', async function () { 1151 it('Should delete an account and delete its playlists', async function () {
@@ -1153,20 +1153,20 @@ describe('Test video playlists', function () {
1153 1153
1154 const user = { username: 'user_1', password: 'password' } 1154 const user = { username: 'user_1', password: 'password' }
1155 const res = await createUser({ 1155 const res = await createUser({
1156 url: servers[ 0 ].url, 1156 url: servers[0].url,
1157 accessToken: servers[ 0 ].accessToken, 1157 accessToken: servers[0].accessToken,
1158 username: user.username, 1158 username: user.username,
1159 password: user.password 1159 password: user.password
1160 }) 1160 })
1161 1161
1162 const userId = res.body.user.id 1162 const userId = res.body.user.id
1163 const userAccessToken = await userLogin(servers[ 0 ], user) 1163 const userAccessToken = await userLogin(servers[0], user)
1164 1164
1165 const resChannel = await getMyUserInformation(servers[ 0 ].url, userAccessToken) 1165 const resChannel = await getMyUserInformation(servers[0].url, userAccessToken)
1166 const userChannel = (resChannel.body as User).videoChannels[ 0 ] 1166 const userChannel = (resChannel.body as User).videoChannels[0]
1167 1167
1168 await createVideoPlaylist({ 1168 await createVideoPlaylist({
1169 url: servers[ 0 ].url, 1169 url: servers[0].url,
1170 token: userAccessToken, 1170 token: userAccessToken,
1171 playlistAttrs: { 1171 playlistAttrs: {
1172 displayName: 'playlist to be deleted', 1172 displayName: 'playlist to be deleted',
@@ -1180,17 +1180,17 @@ describe('Test video playlists', function () {
1180 const finder = data => data.find(p => p.displayName === 'playlist to be deleted') 1180 const finder = data => data.find(p => p.displayName === 'playlist to be deleted')
1181 1181
1182 { 1182 {
1183 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 1183 for (const server of [ servers[0], servers[1] ]) {
1184 const res = await getVideoPlaylistsList(server.url, 0, 15) 1184 const res = await getVideoPlaylistsList(server.url, 0, 15)
1185 expect(finder(res.body.data)).to.not.be.undefined 1185 expect(finder(res.body.data)).to.not.be.undefined
1186 } 1186 }
1187 } 1187 }
1188 1188
1189 await removeUser(servers[ 0 ].url, userId, servers[ 0 ].accessToken) 1189 await removeUser(servers[0].url, userId, servers[0].accessToken)
1190 await waitJobs(servers) 1190 await waitJobs(servers)
1191 1191
1192 { 1192 {
1193 for (const server of [ servers[ 0 ], servers[ 1 ] ]) { 1193 for (const server of [ servers[0], servers[1] ]) {
1194 const res = await getVideoPlaylistsList(server.url, 0, 15) 1194 const res = await getVideoPlaylistsList(server.url, 0, 15)
1195 expect(finder(res.body.data)).to.be.undefined 1195 expect(finder(res.body.data)).to.be.undefined
1196 } 1196 }
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index e630ca84a..4bbbb90f3 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -6,7 +6,8 @@ import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enu
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 getVideosList, getVideosListWithToken, 9 getVideosList,
10 getVideosListWithToken,
10 ServerInfo, 11 ServerInfo,
11 setAccessTokensToServers, 12 setAccessTokensToServers,
12 uploadVideo 13 uploadVideo
@@ -110,7 +111,7 @@ describe('Test video privacy', function () {
110 username: 'hello', 111 username: 'hello',
111 password: 'super password' 112 password: 'super password'
112 } 113 }
113 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) 114 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password })
114 115
115 anotherUserToken = await userLogin(servers[0], user) 116 anotherUserToken = await userLogin(servers[0], user)
116 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403) 117 await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403)
@@ -174,7 +175,7 @@ describe('Test video privacy', function () {
174 privacy: VideoPrivacy.PUBLIC 175 privacy: VideoPrivacy.PUBLIC
175 } 176 }
176 177
177 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, privateVideoId, attribute) 178 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute)
178 } 179 }
179 180
180 { 181 {
@@ -182,7 +183,7 @@ describe('Test video privacy', function () {
182 name: 'internal video becomes public', 183 name: 'internal video becomes public',
183 privacy: VideoPrivacy.PUBLIC 184 privacy: VideoPrivacy.PUBLIC
184 } 185 }
185 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, internalVideoId, attribute) 186 await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute)
186 } 187 }
187 188
188 await waitJobs(servers) 189 await waitJobs(servers)
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts
index 65a8eafb8..204f43611 100644
--- a/server/tests/api/videos/video-schedule-update.ts
+++ b/server/tests/api/videos/video-schedule-update.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -10,7 +10,6 @@ import {
10 getMyVideos, 10 getMyVideos,
11 getVideosList, 11 getVideosList,
12 getVideoWithToken, 12 getVideoWithToken,
13 killallServers,
14 ServerInfo, 13 ServerInfo,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
16 updateVideo, 15 updateVideo,
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 4be74901a..3e73ccbfa 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -11,6 +11,7 @@ import {
11 doubleFollow, 11 doubleFollow,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 generateHighBitrateVideo, 13 generateHighBitrateVideo,
14 generateVideoWithFramerate,
14 getMyVideos, 15 getMyVideos,
15 getVideo, 16 getVideo,
16 getVideosList, 17 getVideosList,
@@ -55,19 +56,19 @@ describe('Test video transcoding', function () {
55 56
56 for (const server of servers) { 57 for (const server of servers) {
57 const res = await getVideosList(server.url) 58 const res = await getVideosList(server.url)
58 const video = res.body.data[ 0 ] 59 const video = res.body.data[0]
59 60
60 const res2 = await getVideo(server.url, video.id) 61 const res2 = await getVideo(server.url, video.id)
61 const videoDetails = res2.body 62 const videoDetails = res2.body
62 expect(videoDetails.files).to.have.lengthOf(1) 63 expect(videoDetails.files).to.have.lengthOf(1)
63 64
64 const magnetUri = videoDetails.files[ 0 ].magnetUri 65 const magnetUri = videoDetails.files[0].magnetUri
65 expect(magnetUri).to.match(/\.webm/) 66 expect(magnetUri).to.match(/\.webm/)
66 67
67 const torrent = await webtorrentAdd(magnetUri, true) 68 const torrent = await webtorrentAdd(magnetUri, true)
68 expect(torrent.files).to.be.an('array') 69 expect(torrent.files).to.be.an('array')
69 expect(torrent.files.length).to.equal(1) 70 expect(torrent.files.length).to.equal(1)
70 expect(torrent.files[ 0 ].path).match(/\.webm$/) 71 expect(torrent.files[0].path).match(/\.webm$/)
71 } 72 }
72 }) 73 })
73 74
@@ -92,13 +93,13 @@ describe('Test video transcoding', function () {
92 93
93 expect(videoDetails.files).to.have.lengthOf(4) 94 expect(videoDetails.files).to.have.lengthOf(4)
94 95
95 const magnetUri = videoDetails.files[ 0 ].magnetUri 96 const magnetUri = videoDetails.files[0].magnetUri
96 expect(magnetUri).to.match(/\.mp4/) 97 expect(magnetUri).to.match(/\.mp4/)
97 98
98 const torrent = await webtorrentAdd(magnetUri, true) 99 const torrent = await webtorrentAdd(magnetUri, true)
99 expect(torrent.files).to.be.an('array') 100 expect(torrent.files).to.be.an('array')
100 expect(torrent.files.length).to.equal(1) 101 expect(torrent.files.length).to.equal(1)
101 expect(torrent.files[ 0 ].path).match(/\.mp4$/) 102 expect(torrent.files[0].path).match(/\.mp4$/)
102 } 103 }
103 }) 104 })
104 105
@@ -126,8 +127,8 @@ describe('Test video transcoding', function () {
126 const probe = await audio.get(path) 127 const probe = await audio.get(path)
127 128
128 if (probe.audioStream) { 129 if (probe.audioStream) {
129 expect(probe.audioStream[ 'codec_name' ]).to.be.equal('aac') 130 expect(probe.audioStream['codec_name']).to.be.equal('aac')
130 expect(probe.audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) 131 expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000)
131 } else { 132 } else {
132 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath) 133 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
133 } 134 }
@@ -211,10 +212,10 @@ describe('Test video transcoding', function () {
211 const videoDetails: VideoDetails = res2.body 212 const videoDetails: VideoDetails = res2.body
212 213
213 expect(videoDetails.files).to.have.lengthOf(4) 214 expect(videoDetails.files).to.have.lengthOf(4)
214 expect(videoDetails.files[ 0 ].fps).to.be.above(58).and.below(62) 215 expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
215 expect(videoDetails.files[ 1 ].fps).to.be.below(31) 216 expect(videoDetails.files[1].fps).to.be.below(31)
216 expect(videoDetails.files[ 2 ].fps).to.be.below(31) 217 expect(videoDetails.files[2].fps).to.be.below(31)
217 expect(videoDetails.files[ 3 ].fps).to.be.below(31) 218 expect(videoDetails.files[3].fps).to.be.below(31)
218 219
219 for (const resolution of [ '240', '360', '480' ]) { 220 for (const resolution of [ '240', '360', '480' ]) {
220 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') 221 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
@@ -240,11 +241,11 @@ describe('Test video transcoding', function () {
240 fixture: 'video_short1.webm', 241 fixture: 'video_short1.webm',
241 waitTranscoding: true 242 waitTranscoding: true
242 } 243 }
243 const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) 244 const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
244 const videoId = resVideo.body.video.uuid 245 const videoId = resVideo.body.video.uuid
245 246
246 // Should be in transcode state 247 // Should be in transcode state
247 const { body } = await getVideo(servers[ 1 ].url, videoId) 248 const { body } = await getVideo(servers[1].url, videoId)
248 expect(body.name).to.equal('waiting video') 249 expect(body.name).to.equal('waiting video')
249 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) 250 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE)
250 expect(body.state.label).to.equal('To transcode') 251 expect(body.state.label).to.equal('To transcode')
@@ -310,7 +311,7 @@ describe('Test video transcoding', function () {
310 311
311 const video = res.body.data.find(v => v.name === videoAttributes.name) 312 const video = res.body.data.find(v => v.name === videoAttributes.name)
312 313
313 for (const resolution of ['240', '360', '480', '720', '1080']) { 314 for (const resolution of [ '240', '360', '480', '720', '1080' ]) {
314 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') 315 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
315 const bitrate = await getVideoFileBitrate(path) 316 const bitrate = await getVideoFileBitrate(path)
316 const fps = await getVideoFileFPS(path) 317 const fps = await getVideoFileFPS(path)
@@ -340,7 +341,7 @@ describe('Test video transcoding', function () {
340 fixture 341 fixture
341 } 342 }
342 343
343 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) 344 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
344 345
345 await waitJobs(servers) 346 await waitJobs(servers)
346 347
@@ -353,7 +354,7 @@ describe('Test video transcoding', function () {
353 354
354 expect(videoDetails.files).to.have.lengthOf(4) 355 expect(videoDetails.files).to.have.lengthOf(4)
355 356
356 const magnetUri = videoDetails.files[ 0 ].magnetUri 357 const magnetUri = videoDetails.files[0].magnetUri
357 expect(magnetUri).to.contain('.mp4') 358 expect(magnetUri).to.contain('.mp4')
358 } 359 }
359 } 360 }
@@ -370,7 +371,7 @@ describe('Test video transcoding', function () {
370 this.timeout(60000) 371 this.timeout(60000)
371 372
372 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } 373 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
373 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) 374 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg)
374 375
375 await waitJobs(servers) 376 await waitJobs(servers)
376 377
@@ -386,7 +387,7 @@ describe('Test video transcoding', function () {
386 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) 387 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
387 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) 388 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
388 389
389 const magnetUri = videoDetails.files[ 0 ].magnetUri 390 const magnetUri = videoDetails.files[0].magnetUri
390 expect(magnetUri).to.contain('.mp4') 391 expect(magnetUri).to.contain('.mp4')
391 } 392 }
392 }) 393 })
@@ -395,7 +396,7 @@ describe('Test video transcoding', function () {
395 this.timeout(60000) 396 this.timeout(60000)
396 397
397 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' } 398 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
398 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) 399 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg)
399 400
400 await waitJobs(servers) 401 await waitJobs(servers)
401 402
@@ -411,11 +412,52 @@ describe('Test video transcoding', function () {
411 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) 412 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
412 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) 413 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
413 414
414 const magnetUri = videoDetails.files[ 0 ].magnetUri 415 const magnetUri = videoDetails.files[0].magnetUri
415 expect(magnetUri).to.contain('.mp4') 416 expect(magnetUri).to.contain('.mp4')
416 } 417 }
417 }) 418 })
418 419
420 it('Should downscale to the closest divisor standard framerate', async function () {
421 this.timeout(160000)
422
423 let tempFixturePath: string
424
425 {
426 tempFixturePath = await generateVideoWithFramerate(59)
427
428 const fps = await getVideoFileFPS(tempFixturePath)
429 expect(fps).to.be.equal(59)
430 }
431
432 const videoAttributes = {
433 name: '59fps video',
434 description: '59fps video',
435 fixture: tempFixturePath
436 }
437
438 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
439
440 await waitJobs(servers)
441
442 for (const server of servers) {
443 const res = await getVideosList(server.url)
444
445 const video = res.body.data.find(v => v.name === videoAttributes.name)
446
447 {
448 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
449 const fps = await getVideoFileFPS(path)
450 expect(fps).to.be.equal(25)
451 }
452
453 {
454 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4')
455 const fps = await getVideoFileFPS(path)
456 expect(fps).to.be.equal(59)
457 }
458 }
459 })
460
419 after(async function () { 461 after(async function () {
420 await cleanupTests(servers) 462 await cleanupTests(servers)
421 }) 463 })
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts
index e1e65260f..95e12e43c 100644
--- a/server/tests/api/videos/videos-filter.ts
+++ b/server/tests/api/videos/videos-filter.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -7,8 +7,6 @@ import {
7 createUser, 7 createUser,
8 doubleFollow, 8 doubleFollow,
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 flushTests,
11 killallServers,
12 makeGetRequest, 10 makeGetRequest,
13 ServerInfo, 11 ServerInfo,
14 setAccessTokensToServers, 12 setAccessTokensToServers,
@@ -98,7 +96,7 @@ describe('Test videos filter validator', function () {
98 const namesResults = await getVideosNames(server, server.accessToken, 'local') 96 const namesResults = await getVideosNames(server, server.accessToken, 'local')
99 for (const names of namesResults) { 97 for (const names of namesResults) {
100 expect(names).to.have.lengthOf(1) 98 expect(names).to.have.lengthOf(1)
101 expect(names[ 0 ]).to.equal('public ' + server.serverNumber) 99 expect(names[0]).to.equal('public ' + server.serverNumber)
102 } 100 }
103 } 101 }
104 }) 102 })
@@ -111,9 +109,9 @@ describe('Test videos filter validator', function () {
111 for (const names of namesResults) { 109 for (const names of namesResults) {
112 expect(names).to.have.lengthOf(3) 110 expect(names).to.have.lengthOf(3)
113 111
114 expect(names[ 0 ]).to.equal('public ' + server.serverNumber) 112 expect(names[0]).to.equal('public ' + server.serverNumber)
115 expect(names[ 1 ]).to.equal('unlisted ' + server.serverNumber) 113 expect(names[1]).to.equal('unlisted ' + server.serverNumber)
116 expect(names[ 2 ]).to.equal('private ' + server.serverNumber) 114 expect(names[2]).to.equal('private ' + server.serverNumber)
117 } 115 }
118 } 116 }
119 } 117 }
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts
index c7e55c1ab..6f90e9a57 100644
--- a/server/tests/api/videos/videos-history.ts
+++ b/server/tests/api/videos/videos-history.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts
index 975a5c87a..ca08ab5b1 100644
--- a/server/tests/api/videos/videos-overview.ts
+++ b/server/tests/api/videos/videos-overview.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts
index fbddd40f4..d063d7973 100644
--- a/server/tests/api/videos/videos-views-cleaner.ts
+++ b/server/tests/api/videos/videos-views-cleaner.ts
@@ -1,20 +1,22 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 cleanupTests,
7 closeAllSequelize,
8 countVideoViewsOf,
9 doubleFollow,
6 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
7 flushTests,
8 killallServers, 11 killallServers,
9 reRunServer, 12 reRunServer,
10 flushAndRunServer,
11 ServerInfo, 13 ServerInfo,
12 setAccessTokensToServers, 14 setAccessTokensToServers,
13 uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests, closeAllSequelize 15 uploadVideoAndGetId,
16 viewVideo,
17 wait,
18 waitJobs
14} from '../../../../shared/extra-utils' 19} from '../../../../shared/extra-utils'
15import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews'
16import { VideosOverview } from '../../../../shared/models/overviews'
17import { listMyVideosHistory } from '../../../../shared/extra-utils/videos/video-history'
18 20
19const expect = chai.expect 21const expect = chai.expect
20 22
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts
index aca3216bb..dac049fe4 100644
--- a/server/tests/cli/create-import-video-file-job.ts
+++ b/server/tests/cli/create-import-video-file-job.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -71,7 +71,7 @@ describe('Test create import video jobs', function () {
71 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body 71 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body
72 72
73 expect(videoDetail.files).to.have.lengthOf(2) 73 expect(videoDetail.files).to.have.lengthOf(2)
74 const [originalVideo, transcodedVideo] = videoDetail.files 74 const [ originalVideo, transcodedVideo ] = videoDetail.files
75 assertVideoProperties(originalVideo, 720, 'webm', 218910) 75 assertVideoProperties(originalVideo, 720, 'webm', 218910)
76 assertVideoProperties(transcodedVideo, 480, 'webm', 69217) 76 assertVideoProperties(transcodedVideo, 480, 'webm', 69217)
77 77
@@ -95,7 +95,7 @@ describe('Test create import video jobs', function () {
95 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body 95 const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body
96 96
97 expect(videoDetail.files).to.have.lengthOf(4) 97 expect(videoDetail.files).to.have.lengthOf(4)
98 const [originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240] = videoDetail.files 98 const [ originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240 ] = videoDetail.files
99 assertVideoProperties(originalVideo, 720, 'ogv', 140849) 99 assertVideoProperties(originalVideo, 720, 'ogv', 140849)
100 assertVideoProperties(transcodedVideo420, 480, 'mp4') 100 assertVideoProperties(transcodedVideo420, 480, 'mp4')
101 assertVideoProperties(transcodedVideo320, 360, 'mp4') 101 assertVideoProperties(transcodedVideo320, 360, 'mp4')
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts
index 7897ff1b3..997a9a1fd 100644
--- a/server/tests/cli/create-transcoding-job.ts
+++ b/server/tests/cli/create-transcoding-job.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -8,14 +8,13 @@ import {
8 doubleFollow, 8 doubleFollow,
9 execCLI, 9 execCLI,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests,
12 getEnvCli, 11 getEnvCli,
13 getVideo, 12 getVideo,
14 getVideosList, 13 getVideosList,
15 killallServers,
16 ServerInfo, 14 ServerInfo,
17 setAccessTokensToServers, updateCustomSubConfig, 15 setAccessTokensToServers,
18 uploadVideo, wait 16 updateCustomSubConfig,
17 uploadVideo
19} from '../../../shared/extra-utils' 18} from '../../../shared/extra-utils'
20import { waitJobs } from '../../../shared/extra-utils/server/jobs' 19import { waitJobs } from '../../../shared/extra-utils/server/jobs'
21 20
@@ -23,7 +22,7 @@ const expect = chai.expect
23 22
24describe('Test create transcoding jobs', function () { 23describe('Test create transcoding jobs', function () {
25 let servers: ServerInfo[] = [] 24 let servers: ServerInfo[] = []
26 let videosUUID: string[] = [] 25 const videosUUID: string[] = []
27 26
28 const config = { 27 const config = {
29 transcoding: { 28 transcoding: {
@@ -54,7 +53,7 @@ describe('Test create transcoding jobs', function () {
54 await doubleFollow(servers[0], servers[1]) 53 await doubleFollow(servers[0], servers[1])
55 54
56 for (let i = 1; i <= 5; i++) { 55 for (let i = 1; i <= 5; i++) {
57 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' + i }) 56 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' + i })
58 videosUUID.push(res.body.video.uuid) 57 videosUUID.push(res.body.video.uuid)
59 } 58 }
60 59
@@ -90,7 +89,7 @@ describe('Test create transcoding jobs', function () {
90 const res = await getVideosList(server.url) 89 const res = await getVideosList(server.url)
91 const videos = res.body.data 90 const videos = res.body.data
92 91
93 let infoHashes: { [ id: number ]: string } 92 let infoHashes: { [id: number]: string }
94 93
95 for (const video of videos) { 94 for (const video of videos) {
96 const res2 = await getVideo(server.url, video.uuid) 95 const res2 = await getVideo(server.url, video.uuid)
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts
index de5d672f5..e2e13598f 100644
--- a/server/tests/cli/optimize-old-videos.ts
+++ b/server/tests/cli/optimize-old-videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -28,7 +28,9 @@ const expect = chai.expect
28 28
29describe('Test optimize old videos', function () { 29describe('Test optimize old videos', function () {
30 let servers: ServerInfo[] = [] 30 let servers: ServerInfo[] = []
31 // eslint-disable-next-line @typescript-eslint/no-unused-vars
31 let video1UUID: string 32 let video1UUID: string
33 // eslint-disable-next-line @typescript-eslint/no-unused-vars
32 let video2UUID: string 34 let video2UUID: string
33 35
34 before(async function () { 36 before(async function () {
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
index b8c0b1f79..27fbde02d 100644
--- a/server/tests/cli/peertube.ts
+++ b/server/tests/cli/peertube.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { expect } from 'chai' 4import { expect } from 'chai'
@@ -7,14 +7,17 @@ import {
7 buildAbsoluteFixturePath, 7 buildAbsoluteFixturePath,
8 cleanupTests, 8 cleanupTests,
9 createUser, 9 createUser,
10 doubleFollow,
10 execCLI, 11 execCLI,
11 flushAndRunServer, 12 flushAndRunServer,
12 getEnvCli, 13 getEnvCli,
14 getLocalIdByUUID,
13 getVideo, 15 getVideo,
14 getVideosList, 16 getVideosList,
15 getVideosListWithToken, removeVideo, 17 removeVideo,
16 ServerInfo, 18 ServerInfo,
17 setAccessTokensToServers, 19 setAccessTokensToServers,
20 uploadVideoAndGetId,
18 userLogin, 21 userLogin,
19 waitJobs 22 waitJobs
20} from '../../../shared/extra-utils' 23} from '../../../shared/extra-utils'
@@ -101,7 +104,7 @@ describe('Test CLI wrapper', function () {
101 104
102 const videos: Video[] = res.body.data 105 const videos: Video[] = res.body.data
103 106
104 const video: VideoDetails = (await getVideo(server.url, videos[ 0 ].uuid)).body 107 const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body
105 108
106 expect(video.name).to.equal('test upload') 109 expect(video.name).to.equal('test upload')
107 expect(video.support).to.equal('support_text') 110 expect(video.support).to.equal('support_text')
@@ -210,6 +213,81 @@ describe('Test CLI wrapper', function () {
210 }) 213 })
211 }) 214 })
212 215
216 describe('Manage video redundancies', function () {
217 let anotherServer: ServerInfo
218 let video1Server2: number
219 let servers: ServerInfo[]
220
221 before(async function () {
222 this.timeout(120000)
223
224 anotherServer = await flushAndRunServer(2)
225 await setAccessTokensToServers([ anotherServer ])
226
227 await doubleFollow(server, anotherServer)
228
229 servers = [ server, anotherServer ]
230 await waitJobs(servers)
231
232 const uuid = (await uploadVideoAndGetId({ server: anotherServer, videoName: 'super video' })).uuid
233 await waitJobs(servers)
234
235 video1Server2 = await getLocalIdByUUID(server.url, uuid)
236 })
237
238 it('Should add a redundancy', async function () {
239 this.timeout(60000)
240
241 const env = getEnvCli(server)
242
243 const params = `add --video ${video1Server2}`
244
245 await execCLI(`${env} ${cmd} redundancy ${params}`)
246
247 await waitJobs(servers)
248 })
249
250 it('Should list redundancies', async function () {
251 this.timeout(60000)
252
253 {
254 const env = getEnvCli(server)
255
256 const params = 'list-my-redundancies'
257 const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`)
258
259 expect(stdout).to.contain('super video')
260 expect(stdout).to.contain(`localhost:${server.port}`)
261 }
262 })
263
264 it('Should remove a redundancy', async function () {
265 this.timeout(60000)
266
267 const env = getEnvCli(server)
268
269 const params = `remove --video ${video1Server2}`
270
271 await execCLI(`${env} ${cmd} redundancy ${params}`)
272
273 await waitJobs(servers)
274
275 {
276 const env = getEnvCli(server)
277 const params = 'list-my-redundancies'
278 const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`)
279
280 expect(stdout).to.not.contain('super video')
281 }
282 })
283
284 after(async function () {
285 this.timeout(10000)
286
287 await cleanupTests([ anotherServer ])
288 })
289 })
290
213 after(async function () { 291 after(async function () {
214 this.timeout(10000) 292 this.timeout(10000)
215 293
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts
index a5257d671..7f19f14b7 100644
--- a/server/tests/cli/plugins.ts
+++ b/server/tests/cli/plugins.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts
index 144e67c44..304c8ca56 100644
--- a/server/tests/cli/prune-storage.ts
+++ b/server/tests/cli/prune-storage.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
@@ -11,9 +11,11 @@ import {
11 execCLI, 11 execCLI,
12 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
13 getAccount, 13 getAccount,
14 getEnvCli, makeGetRequest, makeRawRequest, 14 getEnvCli,
15 makeGetRequest,
15 ServerInfo, 16 ServerInfo,
16 setAccessTokensToServers, setDefaultVideoChannel, 17 setAccessTokensToServers,
18 setDefaultVideoChannel,
17 updateMyAvatar, 19 updateMyAvatar,
18 uploadVideo, 20 uploadVideo,
19 wait 21 wait
@@ -22,7 +24,6 @@ import { Account, VideoPlaylistPrivacy } from '../../../shared/models'
22import { createFile, readdir } from 'fs-extra' 24import { createFile, readdir } from 'fs-extra'
23import * as uuidv4 from 'uuid/v4' 25import * as uuidv4 from 'uuid/v4'
24import { join } from 'path' 26import { join } from 'path'
25import * as request from 'supertest'
26 27
27const expect = chai.expect 28const expect = chai.expect
28 29
@@ -61,7 +62,7 @@ async function assertCountAreOkay (servers: ServerInfo[]) {
61 62
62describe('Test prune storage scripts', function () { 63describe('Test prune storage scripts', function () {
63 let servers: ServerInfo[] 64 let servers: ServerInfo[]
64 const badNames: { [ directory: string ]: string[] } = {} 65 const badNames: { [directory: string]: string[] } = {}
65 66
66 before(async function () { 67 before(async function () {
67 this.timeout(120000) 68 this.timeout(120000)
@@ -92,20 +93,20 @@ describe('Test prune storage scripts', function () {
92 93
93 // Lazy load the remote avatar 94 // Lazy load the remote avatar
94 { 95 {
95 const res = await getAccount(servers[ 0 ].url, 'root@localhost:' + servers[ 1 ].port) 96 const res = await getAccount(servers[0].url, 'root@localhost:' + servers[1].port)
96 const account: Account = res.body 97 const account: Account = res.body
97 await makeGetRequest({ 98 await makeGetRequest({
98 url: servers[ 0 ].url, 99 url: servers[0].url,
99 path: account.avatar.path, 100 path: account.avatar.path,
100 statusCodeExpected: 200 101 statusCodeExpected: 200
101 }) 102 })
102 } 103 }
103 104
104 { 105 {
105 const res = await getAccount(servers[ 1 ].url, 'root@localhost:' + servers[ 0 ].port) 106 const res = await getAccount(servers[1].url, 'root@localhost:' + servers[0].port)
106 const account: Account = res.body 107 const account: Account = res.body
107 await makeGetRequest({ 108 await makeGetRequest({
108 url: servers[ 1 ].url, 109 url: servers[1].url,
109 path: account.avatar.path, 110 path: account.avatar.path,
110 statusCodeExpected: 200 111 statusCodeExpected: 200
111 }) 112 })
diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts
index 55c43b32f..2070f16f5 100644
--- a/server/tests/cli/update-host.ts
+++ b/server/tests/cli/update-host.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/client.ts b/server/tests/client.ts
index 778dcd08e..d61724e51 100644
--- a/server/tests/client.ts
+++ b/server/tests/client.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 437470327..4510177cc 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -51,7 +51,7 @@ describe('Test syndication feeds', () => {
51 51
52 { 52 {
53 const attr = { username: 'john', password: 'password' } 53 const attr = { username: 'john', password: 'password' }
54 await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: attr.username, password: attr.password }) 54 await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password })
55 userAccessToken = await userLogin(servers[0], attr) 55 userAccessToken = await userLogin(servers[0], attr)
56 56
57 const res = await getMyUserInformation(servers[0].url, userAccessToken) 57 const res = await getMyUserInformation(servers[0].url, userAccessToken)
@@ -61,7 +61,7 @@ describe('Test syndication feeds', () => {
61 } 61 }
62 62
63 { 63 {
64 await uploadVideo(servers[ 0 ].url, userAccessToken, { name: 'user video' }) 64 await uploadVideo(servers[0].url, userAccessToken, { name: 'user video' })
65 } 65 }
66 66
67 { 67 {
@@ -70,11 +70,11 @@ describe('Test syndication feeds', () => {
70 description: 'my super description for server 1', 70 description: 'my super description for server 1',
71 fixture: 'video_short.webm' 71 fixture: 'video_short.webm'
72 } 72 }
73 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) 73 const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
74 const videoId = res.body.video.id 74 const videoId = res.body.video.id
75 75
76 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 1') 76 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 1')
77 await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 2') 77 await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 2')
78 } 78 }
79 79
80 await waitJobs(servers) 80 await waitJobs(servers)
@@ -84,18 +84,18 @@ describe('Test syndication feeds', () => {
84 84
85 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { 85 it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () {
86 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { 86 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
87 const rss = await getXMLfeed(servers[ 0 ].url, feed) 87 const rss = await getXMLfeed(servers[0].url, feed)
88 expect(rss.text).xml.to.be.valid() 88 expect(rss.text).xml.to.be.valid()
89 89
90 const atom = await getXMLfeed(servers[ 0 ].url, feed, 'atom') 90 const atom = await getXMLfeed(servers[0].url, feed, 'atom')
91 expect(atom.text).xml.to.be.valid() 91 expect(atom.text).xml.to.be.valid()
92 } 92 }
93 }) 93 })
94 94
95 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () { 95 it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () {
96 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { 96 for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) {
97 const json = await getJSONfeed(servers[ 0 ].url, feed) 97 const json = await getJSONfeed(servers[0].url, feed)
98 expect(JSON.parse(json.text)).to.be.jsonSchema({ 'type': 'object' }) 98 expect(JSON.parse(json.text)).to.be.jsonSchema({ type: 'object' })
99 } 99 }
100 }) 100 })
101 }) 101 })
@@ -118,11 +118,11 @@ describe('Test syndication feeds', () => {
118 const json = await getJSONfeed(server.url, 'videos') 118 const json = await getJSONfeed(server.url, 'videos')
119 const jsonObj = JSON.parse(json.text) 119 const jsonObj = JSON.parse(json.text)
120 expect(jsonObj.items.length).to.be.equal(2) 120 expect(jsonObj.items.length).to.be.equal(2)
121 expect(jsonObj.items[ 0 ].attachments).to.exist 121 expect(jsonObj.items[0].attachments).to.exist
122 expect(jsonObj.items[ 0 ].attachments.length).to.be.eq(1) 122 expect(jsonObj.items[0].attachments.length).to.be.eq(1)
123 expect(jsonObj.items[ 0 ].attachments[ 0 ].mime_type).to.be.eq('application/x-bittorrent') 123 expect(jsonObj.items[0].attachments[0].mime_type).to.be.eq('application/x-bittorrent')
124 expect(jsonObj.items[ 0 ].attachments[ 0 ].size_in_bytes).to.be.eq(218910) 124 expect(jsonObj.items[0].attachments[0].size_in_bytes).to.be.eq(218910)
125 expect(jsonObj.items[ 0 ].attachments[ 0 ].url).to.contain('720.torrent') 125 expect(jsonObj.items[0].attachments[0].url).to.contain('720.torrent')
126 } 126 }
127 }) 127 })
128 128
@@ -131,16 +131,16 @@ describe('Test syndication feeds', () => {
131 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId }) 131 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId })
132 const jsonObj = JSON.parse(json.text) 132 const jsonObj = JSON.parse(json.text)
133 expect(jsonObj.items.length).to.be.equal(1) 133 expect(jsonObj.items.length).to.be.equal(1)
134 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 134 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
135 expect(jsonObj.items[ 0 ].author.name).to.equal('root') 135 expect(jsonObj.items[0].author.name).to.equal('root')
136 } 136 }
137 137
138 { 138 {
139 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId }) 139 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId })
140 const jsonObj = JSON.parse(json.text) 140 const jsonObj = JSON.parse(json.text)
141 expect(jsonObj.items.length).to.be.equal(1) 141 expect(jsonObj.items.length).to.be.equal(1)
142 expect(jsonObj.items[ 0 ].title).to.equal('user video') 142 expect(jsonObj.items[0].title).to.equal('user video')
143 expect(jsonObj.items[ 0 ].author.name).to.equal('john') 143 expect(jsonObj.items[0].author.name).to.equal('john')
144 } 144 }
145 145
146 for (const server of servers) { 146 for (const server of servers) {
@@ -148,14 +148,14 @@ describe('Test syndication feeds', () => {
148 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port }) 148 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port })
149 const jsonObj = JSON.parse(json.text) 149 const jsonObj = JSON.parse(json.text)
150 expect(jsonObj.items.length).to.be.equal(1) 150 expect(jsonObj.items.length).to.be.equal(1)
151 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 151 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
152 } 152 }
153 153
154 { 154 {
155 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port }) 155 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port })
156 const jsonObj = JSON.parse(json.text) 156 const jsonObj = JSON.parse(json.text)
157 expect(jsonObj.items.length).to.be.equal(1) 157 expect(jsonObj.items.length).to.be.equal(1)
158 expect(jsonObj.items[ 0 ].title).to.equal('user video') 158 expect(jsonObj.items[0].title).to.equal('user video')
159 } 159 }
160 } 160 }
161 }) 161 })
@@ -165,16 +165,16 @@ describe('Test syndication feeds', () => {
165 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId }) 165 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId })
166 const jsonObj = JSON.parse(json.text) 166 const jsonObj = JSON.parse(json.text)
167 expect(jsonObj.items.length).to.be.equal(1) 167 expect(jsonObj.items.length).to.be.equal(1)
168 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 168 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
169 expect(jsonObj.items[ 0 ].author.name).to.equal('root') 169 expect(jsonObj.items[0].author.name).to.equal('root')
170 } 170 }
171 171
172 { 172 {
173 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId }) 173 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId })
174 const jsonObj = JSON.parse(json.text) 174 const jsonObj = JSON.parse(json.text)
175 expect(jsonObj.items.length).to.be.equal(1) 175 expect(jsonObj.items.length).to.be.equal(1)
176 expect(jsonObj.items[ 0 ].title).to.equal('user video') 176 expect(jsonObj.items[0].title).to.equal('user video')
177 expect(jsonObj.items[ 0 ].author.name).to.equal('john') 177 expect(jsonObj.items[0].author.name).to.equal('john')
178 } 178 }
179 179
180 for (const server of servers) { 180 for (const server of servers) {
@@ -182,14 +182,14 @@ describe('Test syndication feeds', () => {
182 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port }) 182 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port })
183 const jsonObj = JSON.parse(json.text) 183 const jsonObj = JSON.parse(json.text)
184 expect(jsonObj.items.length).to.be.equal(1) 184 expect(jsonObj.items.length).to.be.equal(1)
185 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') 185 expect(jsonObj.items[0].title).to.equal('my super name for server 1')
186 } 186 }
187 187
188 { 188 {
189 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port }) 189 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port })
190 const jsonObj = JSON.parse(json.text) 190 const jsonObj = JSON.parse(json.text)
191 expect(jsonObj.items.length).to.be.equal(1) 191 expect(jsonObj.items.length).to.be.equal(1)
192 expect(jsonObj.items[ 0 ].title).to.equal('user video') 192 expect(jsonObj.items[0].title).to.equal('user video')
193 } 193 }
194 } 194 }
195 }) 195 })
@@ -202,8 +202,8 @@ describe('Test syndication feeds', () => {
202 202
203 const jsonObj = JSON.parse(json.text) 203 const jsonObj = JSON.parse(json.text)
204 expect(jsonObj.items.length).to.be.equal(2) 204 expect(jsonObj.items.length).to.be.equal(2)
205 expect(jsonObj.items[ 0 ].html_content).to.equal('super comment 2') 205 expect(jsonObj.items[0].html_content).to.equal('super comment 2')
206 expect(jsonObj.items[ 1 ].html_content).to.equal('super comment 1') 206 expect(jsonObj.items[1].html_content).to.equal('super comment 1')
207 } 207 }
208 }) 208 })
209 }) 209 })
diff --git a/server/tests/helpers/comment-model.ts b/server/tests/helpers/comment-model.ts
index ebfd779e1..4c51b7000 100644
--- a/server/tests/helpers/comment-model.ts
+++ b/server/tests/helpers/comment-model.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
@@ -20,7 +20,7 @@ describe('Comment model', function () {
20 20
21 comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' + 21 comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' +
22 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end' 22 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end'
23 const result = comment.extractMentions().sort() 23 const result = comment.extractMentions().sort((a, b) => a.localeCompare(b))
24 24
25 expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ]) 25 expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ])
26 }) 26 })
diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts
index 31fc6dd7c..c028b316d 100644
--- a/server/tests/helpers/core-utils.ts
+++ b/server/tests/helpers/core-utils.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts
index a754bc6e2..f8b2d599b 100644
--- a/server/tests/helpers/request.ts
+++ b/server/tests/helpers/request.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' 4import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests'
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts
index ab2dd3a0f..32b035c9e 100644
--- a/server/tests/misc-endpoints.ts
+++ b/server/tests/misc-endpoints.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts
index 510ec3151..ca57a4b51 100644
--- a/server/tests/plugins/action-hooks.ts
+++ b/server/tests/plugins/action-hooks.ts
@@ -1,6 +1,5 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai'
4import 'mocha' 3import 'mocha'
5import { 4import {
6 cleanupTests, 5 cleanupTests,
@@ -17,18 +16,18 @@ import {
17 createUser, 16 createUser,
18 deleteVideoComment, 17 deleteVideoComment,
19 getPluginTestPath, 18 getPluginTestPath,
20 installPlugin, login, 19 installPlugin,
21 registerUser, removeUser, 20 registerUser,
21 removeUser,
22 setAccessTokensToServers, 22 setAccessTokensToServers,
23 unblockUser, updateUser, 23 unblockUser,
24 updateUser,
24 updateVideo, 25 updateVideo,
25 uploadVideo, 26 uploadVideo,
26 viewVideo, 27 userLogin,
27 userLogin 28 viewVideo
28} from '../../../shared/extra-utils' 29} from '../../../shared/extra-utils'
29 30
30const expect = chai.expect
31
32describe('Test plugin action hooks', function () { 31describe('Test plugin action hooks', function () {
33 let servers: ServerInfo[] 32 let servers: ServerInfo[]
34 let videoUUID: string 33 let videoUUID: string
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts
index 6a5ea4641..6c1fd40ba 100644
--- a/server/tests/plugins/filter-hooks.ts
+++ b/server/tests/plugins/filter-hooks.ts
@@ -1,34 +1,27 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers'
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import { 6import {
13 addVideoCommentReply, 7 addVideoCommentReply,
14 addVideoCommentThread, 8 addVideoCommentThread,
15 deleteVideoComment, 9 doubleFollow,
10 getConfig,
16 getPluginTestPath, 11 getPluginTestPath,
17 getVideosList,
18 installPlugin,
19 removeVideo,
20 setAccessTokensToServers,
21 updateVideo,
22 uploadVideo,
23 viewVideo,
24 getVideosListPagination,
25 getVideo, 12 getVideo,
26 getVideoCommentThreads, 13 getVideoCommentThreads,
14 getVideosList,
15 getVideosListPagination,
27 getVideoThreadComments, 16 getVideoThreadComments,
28 getVideoWithToken, 17 getVideoWithToken,
18 installPlugin,
19 registerUser,
20 setAccessTokensToServers,
29 setDefaultVideoChannel, 21 setDefaultVideoChannel,
30 waitJobs, 22 updateVideo,
31 doubleFollow, getConfig, registerUser 23 uploadVideo,
24 waitJobs
32} from '../../../shared/extra-utils' 25} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' 26import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos' 27import { VideoDetails } from '../../../shared/models/videos'
@@ -140,7 +133,7 @@ describe('Test plugin filter hooks', function () {
140 } 133 }
141 134
142 it('Should blacklist on upload', async function () { 135 it('Should blacklist on upload', async function () {
143 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video please blacklist me' }) 136 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video please blacklist me' })
144 await checkIsBlacklisted(res, true) 137 await checkIsBlacklisted(res, true)
145 }) 138 })
146 139
@@ -157,18 +150,18 @@ describe('Test plugin filter hooks', function () {
157 }) 150 })
158 151
159 it('Should blacklist on update', async function () { 152 it('Should blacklist on update', async function () {
160 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' }) 153 const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' })
161 const videoId = res.body.video.uuid 154 const videoId = res.body.video.uuid
162 await checkIsBlacklisted(res, false) 155 await checkIsBlacklisted(res, false)
163 156
164 await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, { name: 'please blacklist me' }) 157 await updateVideo(servers[0].url, servers[0].accessToken, videoId, { name: 'please blacklist me' })
165 await checkIsBlacklisted(res, true) 158 await checkIsBlacklisted(res, true)
166 }) 159 })
167 160
168 it('Should blacklist on remote upload', async function () { 161 it('Should blacklist on remote upload', async function () {
169 this.timeout(45000) 162 this.timeout(45000)
170 163
171 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'remote please blacklist me' }) 164 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'remote please blacklist me' })
172 await waitJobs(servers) 165 await waitJobs(servers)
173 166
174 await checkIsBlacklisted(res, true) 167 await checkIsBlacklisted(res, true)
@@ -177,7 +170,7 @@ describe('Test plugin filter hooks', function () {
177 it('Should blacklist on remote update', async function () { 170 it('Should blacklist on remote update', async function () {
178 this.timeout(45000) 171 this.timeout(45000)
179 172
180 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video' }) 173 const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video' })
181 await waitJobs(servers) 174 await waitJobs(servers)
182 175
183 const videoId = res.body.video.uuid 176 const videoId = res.body.video.uuid
diff --git a/server/tests/plugins/translations.ts b/server/tests/plugins/translations.ts
index 88d91a033..8dc2043b8 100644
--- a/server/tests/plugins/translations.ts
+++ b/server/tests/plugins/translations.ts
@@ -1,38 +1,15 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
5import { 6import {
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import {
13 addVideoCommentReply,
14 addVideoCommentThread,
15 deleteVideoComment,
16 getPluginTestPath, 7 getPluginTestPath,
17 getVideosList, 8 getPluginTranslations,
18 installPlugin, 9 installPlugin,
19 removeVideo,
20 setAccessTokensToServers, 10 setAccessTokensToServers,
21 updateVideo, 11 uninstallPlugin
22 uploadVideo,
23 viewVideo,
24 getVideosListPagination,
25 getVideo,
26 getVideoCommentThreads,
27 getVideoThreadComments,
28 getVideoWithToken,
29 setDefaultVideoChannel,
30 waitJobs,
31 doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin, getPluginTranslations
32} from '../../../shared/extra-utils' 12} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos'
35import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
36 13
37const expect = chai.expect 14const expect = chai.expect
38 15
@@ -69,7 +46,7 @@ describe('Test plugin translations', function () {
69 46
70 expect(res.body).to.deep.equal({ 47 expect(res.body).to.deep.equal({
71 'peertube-plugin-test': { 48 'peertube-plugin-test': {
72 'Hi': 'Coucou' 49 Hi: 'Coucou'
73 }, 50 },
74 'peertube-plugin-test-two': { 51 'peertube-plugin-test-two': {
75 'Hello world': 'Bonjour le monde' 52 'Hello world': 'Bonjour le monde'
@@ -95,7 +72,7 @@ describe('Test plugin translations', function () {
95 72
96 expect(res.body).to.deep.equal({ 73 expect(res.body).to.deep.equal({
97 'peertube-plugin-test': { 74 'peertube-plugin-test': {
98 'Hi': 'Coucou' 75 Hi: 'Coucou'
99 } 76 }
100 }) 77 })
101 } 78 }
diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts
index 6562e2b45..5374b5ecc 100644
--- a/server/tests/plugins/video-constants.ts
+++ b/server/tests/plugins/video-constants.ts
@@ -1,38 +1,20 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers'
5import { 6import {
6 cleanupTests,
7 flushAndRunMultipleServers,
8 flushAndRunServer, killallServers, reRunServer,
9 ServerInfo,
10 waitUntilLog
11} from '../../../shared/extra-utils/server/servers'
12import {
13 addVideoCommentReply,
14 addVideoCommentThread,
15 deleteVideoComment,
16 getPluginTestPath, 7 getPluginTestPath,
17 getVideosList, 8 getVideo,
9 getVideoCategories,
10 getVideoLanguages,
11 getVideoLicences,
18 installPlugin, 12 installPlugin,
19 removeVideo,
20 setAccessTokensToServers, 13 setAccessTokensToServers,
21 updateVideo, 14 uninstallPlugin,
22 uploadVideo, 15 uploadVideo
23 viewVideo,
24 getVideosListPagination,
25 getVideo,
26 getVideoCommentThreads,
27 getVideoThreadComments,
28 getVideoWithToken,
29 setDefaultVideoChannel,
30 waitJobs,
31 doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin
32} from '../../../shared/extra-utils' 16} from '../../../shared/extra-utils'
33import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
34import { VideoDetails } from '../../../shared/models/videos' 17import { VideoDetails } from '../../../shared/models/videos'
35import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
36 18
37const expect = chai.expect 19const expect = chai.expect
38 20
@@ -104,33 +86,33 @@ describe('Test plugin altering video constants', function () {
104 const res = await getVideoLanguages(server.url) 86 const res = await getVideoLanguages(server.url)
105 const languages = res.body 87 const languages = res.body
106 88
107 expect(languages[ 'en' ]).to.equal('English') 89 expect(languages['en']).to.equal('English')
108 expect(languages[ 'fr' ]).to.equal('French') 90 expect(languages['fr']).to.equal('French')
109 91
110 expect(languages[ 'al_bhed' ]).to.not.exist 92 expect(languages['al_bhed']).to.not.exist
111 expect(languages[ 'al_bhed2' ]).to.not.exist 93 expect(languages['al_bhed2']).to.not.exist
112 } 94 }
113 95
114 { 96 {
115 const res = await getVideoCategories(server.url) 97 const res = await getVideoCategories(server.url)
116 const categories = res.body 98 const categories = res.body
117 99
118 expect(categories[ 1 ]).to.equal('Music') 100 expect(categories[1]).to.equal('Music')
119 expect(categories[ 2 ]).to.equal('Films') 101 expect(categories[2]).to.equal('Films')
120 102
121 expect(categories[ 42 ]).to.not.exist 103 expect(categories[42]).to.not.exist
122 expect(categories[ 43 ]).to.not.exist 104 expect(categories[43]).to.not.exist
123 } 105 }
124 106
125 { 107 {
126 const res = await getVideoLicences(server.url) 108 const res = await getVideoLicences(server.url)
127 const licences = res.body 109 const licences = res.body
128 110
129 expect(licences[ 1 ]).to.equal('Attribution') 111 expect(licences[1]).to.equal('Attribution')
130 expect(licences[ 7 ]).to.equal('Public Domain Dedication') 112 expect(licences[7]).to.equal('Public Domain Dedication')
131 113
132 expect(licences[ 42 ]).to.not.exist 114 expect(licences[42]).to.not.exist
133 expect(licences[ 43 ]).to.not.exist 115 expect(licences[43]).to.not.exist
134 } 116 }
135 }) 117 })
136 118
diff --git a/server/tests/real-world/populate-database.ts b/server/tests/real-world/populate-database.ts
deleted file mode 100644
index b1c1688e7..000000000
--- a/server/tests/real-world/populate-database.ts
+++ /dev/null
@@ -1,122 +0,0 @@
1import { VideoRateType } from '../../../shared'
2import {
3 addVideoChannel,
4 createUser,
5 flushTests,
6 getVideosList,
7 killallServers,
8 rateVideo,
9 flushAndRunServer,
10 ServerInfo,
11 setAccessTokensToServers,
12 uploadVideo
13} from '../../../shared/extra-utils'
14import * as Bluebird from 'bluebird'
15
16start()
17 .catch(err => console.error(err))
18
19// ----------------------------------------------------------------------------
20
21async function start () {
22
23 console.log('Flushed tests.')
24
25 const server = await flushAndRunServer(6)
26
27 process.on('exit', async () => {
28 killallServers([ server ])
29 return
30 })
31 process.on('SIGINT', goodbye)
32 process.on('SIGTERM', goodbye)
33
34 await setAccessTokensToServers([ server ])
35
36 console.log('Servers ran.')
37
38 // Forever
39 const fakeTab = Array.from(Array(1000000).keys())
40 const funs = [
41 uploadCustom
42 // uploadCustom,
43 // uploadCustom,
44 // uploadCustom,
45 // likeCustom,
46 // createUserCustom,
47 // createCustomChannel
48 ]
49 const promises = []
50
51 for (const fun of funs) {
52 promises.push(
53 Bluebird.map(fakeTab, () => {
54 return fun(server).catch(err => console.error(err))
55 }, { concurrency: 3 })
56 )
57 }
58
59 await Promise.all(promises)
60}
61
62function getRandomInt (min, max) {
63 return Math.floor(Math.random() * (max - min)) + min
64}
65
66function createCustomChannel (server: ServerInfo) {
67 const videoChannel = {
68 name: Date.now().toString(),
69 displayName: Date.now().toString(),
70 description: Date.now().toString()
71 }
72
73 return addVideoChannel(server.url, server.accessToken, videoChannel)
74}
75
76function createUserCustom (server: ServerInfo) {
77 const username = Date.now().toString() + getRandomInt(0, 100000)
78 console.log('Creating user %s.', username)
79
80 return createUser({ url: server.url, accessToken: server.accessToken, username: username, password: 'coucou' })
81}
82
83function uploadCustom (server: ServerInfo) {
84 console.log('Uploading video.')
85
86 const videoAttributes = {
87 name: Date.now() + ' name',
88 category: 4,
89 nsfw: false,
90 licence: 2,
91 language: 'en',
92 description: Date.now() + ' description',
93 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ],
94 fixture: 'video_short.mp4'
95 }
96
97 return uploadVideo(server.url, server.accessToken, videoAttributes)
98}
99
100function likeCustom (server: ServerInfo) {
101 return rateCustom(server, 'like')
102}
103
104function dislikeCustom (server: ServerInfo) {
105 return rateCustom(server, 'dislike')
106}
107
108async function rateCustom (server: ServerInfo, rating: VideoRateType) {
109 const res = await getVideosList(server.url)
110
111 const videos = res.body.data
112 if (videos.length === 0) return undefined
113
114 const videoToRate = videos[getRandomInt(0, videos.length)]
115
116 console.log('Rating (%s) video.', rating)
117 return rateVideo(server.url, server.accessToken, videoToRate.id, rating)
118}
119
120function goodbye () {
121 return process.exit(-1)
122}
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts
deleted file mode 100644
index cba5ac311..000000000
--- a/server/tests/real-world/real-world.ts
+++ /dev/null
@@ -1,375 +0,0 @@
1// /!\ Before imports /!\
2process.env.NODE_ENV = 'test'
3
4import * as program from 'commander'
5import { Video, VideoFile, VideoRateType } from '../../../shared'
6import { JobState } from '../../../shared/models'
7import {
8 flushAndRunMultipleServers,
9 flushTests, follow,
10 getVideo,
11 getVideosList, getVideosListPagination,
12 killallServers,
13 removeVideo,
14 ServerInfo as DefaultServerInfo,
15 setAccessTokensToServers,
16 updateVideo,
17 uploadVideo, viewVideo,
18 wait
19} from '../../../shared/extra-utils'
20import { getJobsListPaginationAndSort } from '../../../shared/extra-utils/server/jobs'
21
22interface ServerInfo extends DefaultServerInfo {
23 requestsNumber: number
24}
25
26program
27 .option('-c, --create [weight]', 'Weight for creating videos')
28 .option('-r, --remove [weight]', 'Weight for removing videos')
29 .option('-u, --update [weight]', 'Weight for updating videos')
30 .option('-v, --view [weight]', 'Weight for viewing videos')
31 .option('-l, --like [weight]', 'Weight for liking videos')
32 .option('-s, --dislike [weight]', 'Weight for disliking videos')
33 .option('-p, --servers [n]', 'Number of servers to run (3 or 6)', /^3|6$/, 3)
34 .option('-i, --interval-action [interval]', 'Interval in ms for an action')
35 .option('-I, --interval-integrity [interval]', 'Interval in ms for an integrity check')
36 .option('-f, --flush', 'Flush data on exit')
37 .option('-d, --difference', 'Display difference if integrity is not okay')
38 .parse(process.argv)
39
40const createWeight = program['create'] !== undefined ? parseInt(program['create'], 10) : 5
41const removeWeight = program['remove'] !== undefined ? parseInt(program['remove'], 10) : 4
42const updateWeight = program['update'] !== undefined ? parseInt(program['update'], 10) : 4
43const viewWeight = program['view'] !== undefined ? parseInt(program['view'], 10) : 4
44const likeWeight = program['like'] !== undefined ? parseInt(program['like'], 10) : 4
45const dislikeWeight = program['dislike'] !== undefined ? parseInt(program['dislike'], 10) : 4
46const flushAtExit = program['flush'] || false
47const actionInterval = program['intervalAction'] !== undefined ? parseInt(program['intervalAction'], 10) : 500
48const integrityInterval = program['intervalIntegrity'] !== undefined ? parseInt(program['intervalIntegrity'], 10) : 60000
49const displayDiffOnFail = program['difference'] || false
50
51const numberOfServers = 6
52
53console.log(
54 'Create weight: %d, update weight: %d, remove weight: %d, view weight: %d, like weight: %d, dislike weight: %d.',
55 createWeight, updateWeight, removeWeight, viewWeight, likeWeight, dislikeWeight
56)
57
58if (flushAtExit) {
59 console.log('Program will flush data on exit.')
60} else {
61 console.log('Program will not flush data on exit.')
62}
63if (displayDiffOnFail) {
64 console.log('Program will display diff on failure.')
65} else {
66 console.log('Program will not display diff on failure')
67}
68console.log('Interval in ms for each action: %d.', actionInterval)
69console.log('Interval in ms for each integrity check: %d.', integrityInterval)
70
71console.log('Run servers...')
72
73start()
74
75// ----------------------------------------------------------------------------
76
77async function start () {
78 const servers = await runServers(numberOfServers)
79
80 process.on('exit', async () => {
81 await exitServers(servers, flushAtExit)
82
83 return
84 })
85 process.on('SIGINT', goodbye)
86 process.on('SIGTERM', goodbye)
87
88 console.log('Servers ran')
89 initializeRequestsPerServer(servers)
90
91 let checking = false
92
93 setInterval(async () => {
94 if (checking === true) return
95
96 const rand = getRandomInt(0, createWeight + updateWeight + removeWeight + viewWeight + likeWeight + dislikeWeight)
97
98 const numServer = getRandomNumServer(servers)
99 servers[numServer].requestsNumber++
100
101 if (rand < createWeight) {
102 await upload(servers, numServer)
103 } else if (rand < createWeight + updateWeight) {
104 await update(servers, numServer)
105 } else if (rand < createWeight + updateWeight + removeWeight) {
106 await remove(servers, numServer)
107 } else if (rand < createWeight + updateWeight + removeWeight + viewWeight) {
108 await view(servers, numServer)
109 } else if (rand < createWeight + updateWeight + removeWeight + viewWeight + likeWeight) {
110 await like(servers, numServer)
111 } else {
112 await dislike(servers, numServer)
113 }
114 }, actionInterval)
115
116 // The function will check the consistency between servers (should have the same videos with same attributes...)
117 setInterval(function () {
118 if (checking === true) return
119
120 console.log('Checking integrity...')
121 checking = true
122
123 const waitingInterval = setInterval(async () => {
124 const pendingRequests = await isTherePendingRequests(servers)
125 if (pendingRequests === true) {
126 console.log('A server has pending requests, waiting...')
127 return
128 }
129
130 // Even if there are no pending request, wait some potential processes
131 await wait(2000)
132 await checkIntegrity(servers)
133
134 initializeRequestsPerServer(servers)
135 checking = false
136 clearInterval(waitingInterval)
137 }, 10000)
138 }, integrityInterval)
139}
140
141function initializeRequestsPerServer (servers: ServerInfo[]) {
142 servers.forEach(server => server.requestsNumber = 0)
143}
144
145function getRandomInt (min, max) {
146 return Math.floor(Math.random() * (max - min)) + min
147}
148
149function getRandomNumServer (servers) {
150 return getRandomInt(0, servers.length)
151}
152
153async function runServers (numberOfServers: number) {
154 const servers: ServerInfo[] = (await flushAndRunMultipleServers(numberOfServers))
155 .map(s => Object.assign({ requestsNumber: 0 }, s))
156
157 // Get the access tokens
158 await setAccessTokensToServers(servers)
159
160 for (let i = 0; i < numberOfServers; i++) {
161 for (let j = 0; j < numberOfServers; j++) {
162 if (i === j) continue
163
164 await follow(servers[i].url, [ servers[j].url ], servers[i].accessToken)
165 }
166 }
167
168 return servers
169}
170
171async function exitServers (servers: ServerInfo[], flushAtExit: boolean) {
172 killallServers(servers)
173
174 if (flushAtExit) await flushTests()
175}
176
177function upload (servers: ServerInfo[], numServer: number) {
178 console.log('Uploading video to server ' + numServer)
179
180 const videoAttributes = {
181 name: Date.now() + ' name',
182 category: 4,
183 nsfw: false,
184 licence: 2,
185 language: 'en',
186 description: Date.now() + ' description',
187 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ],
188 fixture: 'video_short1.webm'
189 }
190 return uploadVideo(servers[numServer].url, servers[numServer].accessToken, videoAttributes)
191}
192
193async function update (servers: ServerInfo[], numServer: number) {
194 const res = await getVideosList(servers[numServer].url)
195
196 const videos = res.body.data.filter(video => video.isLocal === true)
197 if (videos.length === 0) return undefined
198
199 const toUpdate = videos[getRandomInt(0, videos.length)].id
200 const attributes = {
201 name: Date.now() + ' name',
202 description: Date.now() + ' description',
203 tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ]
204 }
205
206 console.log('Updating video of server ' + numServer)
207
208 return updateVideo(servers[numServer].url, servers[numServer].accessToken, toUpdate, attributes)
209}
210
211async function remove (servers: ServerInfo[], numServer: number) {
212 const res = await getVideosList(servers[numServer].url)
213 const videos = res.body.data.filter(video => video.isLocal === true)
214 if (videos.length === 0) return undefined
215
216 const toRemove = videos[getRandomInt(0, videos.length)].id
217
218 console.log('Removing video from server ' + numServer)
219 return removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove)
220}
221
222async function view (servers: ServerInfo[], numServer: number) {
223 const res = await getVideosList(servers[numServer].url)
224
225 const videos = res.body.data
226 if (videos.length === 0) return undefined
227
228 const toView = videos[getRandomInt(0, videos.length)].id
229
230 console.log('Viewing video from server ' + numServer)
231 return viewVideo(servers[numServer].url, toView)
232}
233
234function like (servers: ServerInfo[], numServer: number) {
235 return rate(servers, numServer, 'like')
236}
237
238function dislike (servers: ServerInfo[], numServer: number) {
239 return rate(servers, numServer, 'dislike')
240}
241
242async function rate (servers: ServerInfo[], numServer: number, rating: VideoRateType) {
243 const res = await getVideosList(servers[numServer].url)
244
245 const videos = res.body.data
246 if (videos.length === 0) return undefined
247
248 const toRate = videos[getRandomInt(0, videos.length)].id
249
250 console.log('Rating (%s) video from server %d', rating, numServer)
251 return getVideo(servers[numServer].url, toRate)
252}
253
254async function checkIntegrity (servers: ServerInfo[]) {
255 const videos: Video[][] = []
256 const tasks: Promise<any>[] = []
257
258 // Fetch all videos and remove some fields that can differ between servers
259 for (const server of servers) {
260 const p = getVideosListPagination(server.url, 0, 1000000, '-createdAt')
261 .then(res => videos.push(res.body.data))
262 tasks.push(p)
263 }
264
265 await Promise.all(tasks)
266
267 let i = 0
268 for (const video of videos) {
269 const differences = areDifferences(video, videos[0])
270 if (differences !== undefined) {
271 console.error('Integrity not ok with server %d!', i + 1)
272
273 if (displayDiffOnFail) {
274 console.log(differences)
275 }
276
277 process.exit(-1)
278 }
279
280 i++
281 }
282
283 console.log('Integrity ok.')
284}
285
286function areDifferences (videos1: Video[], videos2: Video[]) {
287 // Remove some keys we don't want to compare
288 videos1.concat(videos2).forEach(video => {
289 delete video.id
290 delete video.isLocal
291 delete video.thumbnailPath
292 delete video.updatedAt
293 delete video.views
294 })
295
296 if (videos1.length !== videos2.length) {
297 return `Videos length are different (${videos1.length}/${videos2.length}).`
298 }
299
300 for (const video1 of videos1) {
301 const video2 = videos2.find(video => video.uuid === video1.uuid)
302
303 if (!video2) return 'Video ' + video1.uuid + ' is missing.'
304
305 for (const videoKey of Object.keys(video1)) {
306 const attribute1 = video1[videoKey]
307 const attribute2 = video2[videoKey]
308
309 if (videoKey === 'tags') {
310 if (attribute1.length !== attribute2.length) {
311 return 'Tags are different.'
312 }
313
314 attribute1.forEach(tag1 => {
315 if (attribute2.indexOf(tag1) === -1) {
316 return 'Tag ' + tag1 + ' is missing.'
317 }
318 })
319 } else if (videoKey === 'files') {
320 if (attribute1.length !== attribute2.length) {
321 return 'Video files are different.'
322 }
323
324 attribute1.forEach((videoFile1: VideoFile) => {
325 const videoFile2: VideoFile = attribute2.find(videoFile => videoFile.magnetUri === videoFile1.magnetUri)
326 if (!videoFile2) {
327 return `Video ${video1.uuid} has missing video file ${videoFile1.magnetUri}.`
328 }
329
330 if (videoFile1.size !== videoFile2.size || videoFile1.resolution.label !== videoFile2.resolution.label) {
331 return `Video ${video1.uuid} has different video file ${videoFile1.magnetUri}.`
332 }
333 })
334 } else {
335 if (attribute1 !== attribute2) {
336 return `Video ${video1.uuid} has different value for attribute ${videoKey}.`
337 }
338 }
339 }
340 }
341
342 return undefined
343}
344
345function goodbye () {
346 return process.exit(-1)
347}
348
349async function isTherePendingRequests (servers: ServerInfo[]) {
350 const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
351 const tasks: Promise<any>[] = []
352 let pendingRequests = false
353
354 // Check if each server has pending request
355 for (const server of servers) {
356 for (const state of states) {
357 const p = getJobsListPaginationAndSort({
358 url: server.url,
359 accessToken: server.accessToken,
360 state: state,
361 start: 0,
362 count: 10,
363 sort: '-createdAt'
364 })
365 .then(res => {
366 if (res.body.total > 0) pendingRequests = true
367 })
368 tasks.push(p)
369 }
370 }
371
372 await Promise.all(tasks)
373
374 return pendingRequests
375}
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index 58e2445ac..d1a631b69 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -6,6 +6,9 @@ import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels'
6import { Command } from 'commander' 6import { Command } from 'commander'
7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' 7import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
8import { createLogger, format, transports } from 'winston' 8import { createLogger, format, transports } from 'winston'
9import { getMyUserInformation } from '@shared/extra-utils/users/users'
10import { User, UserRole } from '@shared/models'
11import { getAccessToken } from '@shared/extra-utils/users/login'
9 12
10let configName = 'PeerTube/CLI' 13let configName = 'PeerTube/CLI'
11if (isTestInstance()) configName += `-${getAppNumber()}` 14if (isTestInstance()) configName += `-${getAppNumber()}`
@@ -14,8 +17,21 @@ const config = require('application-config')(configName)
14 17
15const version = require('../../../package.json').version 18const version = require('../../../package.json').version
16 19
20async function getAdminTokenOrDie (url: string, username: string, password: string) {
21 const accessToken = await getAccessToken(url, username, password)
22 const resMe = await getMyUserInformation(url, accessToken)
23 const me: User = resMe.body
24
25 if (me.role !== UserRole.ADMINISTRATOR) {
26 console.error('You must be an administrator.')
27 process.exit(-1)
28 }
29
30 return accessToken
31}
32
17interface Settings { 33interface Settings {
18 remotes: any[], 34 remotes: any[]
19 default: number 35 default: number
20} 36}
21 37
@@ -74,9 +90,9 @@ function getRemoteObjectOrDie (
74 if (!program['url'] || !program['username'] || !program['password']) { 90 if (!program['url'] || !program['username'] || !program['password']) {
75 // No remote and we don't have program parameters: quit 91 // No remote and we don't have program parameters: quit
76 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { 92 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
77 if (!program[ 'url' ]) console.error('--url field is required.') 93 if (!program['url']) console.error('--url field is required.')
78 if (!program[ 'username' ]) console.error('--username field is required.') 94 if (!program['username']) console.error('--username field is required.')
79 if (!program[ 'password' ]) console.error('--password field is required.') 95 if (!program['password']) console.error('--password field is required.')
80 96
81 return process.exit(-1) 97 return process.exit(-1)
82 } 98 }
@@ -96,9 +112,9 @@ function getRemoteObjectOrDie (
96 } 112 }
97 113
98 return { 114 return {
99 url: program[ 'url' ], 115 url: program['url'],
100 username: program[ 'username' ], 116 username: program['username'],
101 password: program[ 'password' ] 117 password: program['password']
102 } 118 }
103} 119}
104 120
@@ -134,8 +150,8 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
134 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {} 150 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
135 151
136 for (const key of Object.keys(defaultBooleanAttributes)) { 152 for (const key of Object.keys(defaultBooleanAttributes)) {
137 if (command[ key ] !== undefined) { 153 if (command[key] !== undefined) {
138 booleanAttributes[key] = command[ key ] 154 booleanAttributes[key] = command[key]
139 } else if (defaultAttributes[key] !== undefined) { 155 } else if (defaultAttributes[key] !== undefined) {
140 booleanAttributes[key] = defaultAttributes[key] 156 booleanAttributes[key] = defaultAttributes[key]
141 } else { 157 } else {
@@ -144,19 +160,19 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
144 } 160 }
145 161
146 const videoAttributes = { 162 const videoAttributes = {
147 name: command[ 'videoName' ] || defaultAttributes.name, 163 name: command['videoName'] || defaultAttributes.name,
148 category: command[ 'category' ] || defaultAttributes.category || undefined, 164 category: command['category'] || defaultAttributes.category || undefined,
149 licence: command[ 'licence' ] || defaultAttributes.licence || undefined, 165 licence: command['licence'] || defaultAttributes.licence || undefined,
150 language: command[ 'language' ] || defaultAttributes.language || undefined, 166 language: command['language'] || defaultAttributes.language || undefined,
151 privacy: command[ 'privacy' ] || defaultAttributes.privacy || VideoPrivacy.PUBLIC, 167 privacy: command['privacy'] || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
152 support: command[ 'support' ] || defaultAttributes.support || undefined, 168 support: command['support'] || defaultAttributes.support || undefined,
153 description: command[ 'videoDescription' ] || defaultAttributes.description || undefined, 169 description: command['videoDescription'] || defaultAttributes.description || undefined,
154 tags: command[ 'tags' ] || defaultAttributes.tags || undefined 170 tags: command['tags'] || defaultAttributes.tags || undefined
155 } 171 }
156 172
157 Object.assign(videoAttributes, booleanAttributes) 173 Object.assign(videoAttributes, booleanAttributes)
158 174
159 if (command[ 'channelName' ]) { 175 if (command['channelName']) {
160 const res = await getVideoChannel(url, command['channelName']) 176 const res = await getVideoChannel(url, command['channelName'])
161 const videoChannel: VideoChannel = res.body 177 const videoChannel: VideoChannel = res.body
162 178
@@ -172,9 +188,9 @@ async function buildVideoAttributesFromCommander (url: string, command: Command,
172 188
173function getServerCredentials (program: any) { 189function getServerCredentials (program: any) {
174 return Promise.all([ getSettings(), getNetrc() ]) 190 return Promise.all([ getSettings(), getNetrc() ])
175 .then(([ settings, netrc ]) => { 191 .then(([ settings, netrc ]) => {
176 return getRemoteObjectOrDie(program, settings, netrc) 192 return getRemoteObjectOrDie(program, settings, netrc)
177 }) 193 })
178} 194}
179 195
180function getLogger (logLevel = 'info') { 196function getLogger (logLevel = 'info') {
@@ -222,5 +238,7 @@ export {
222 getServerCredentials, 238 getServerCredentials,
223 239
224 buildCommonVideoOptions, 240 buildCommonVideoOptions,
225 buildVideoAttributesFromCommander 241 buildVideoAttributesFromCommander,
242
243 getAdminTokenOrDie
226} 244}
diff --git a/server/tools/package.json b/server/tools/package.json
index 40959d76e..06ad31cab 100644
--- a/server/tools/package.json
+++ b/server/tools/package.json
@@ -4,11 +4,12 @@
4 "private": true, 4 "private": true,
5 "dependencies": { 5 "dependencies": {
6 "application-config": "^1.0.1", 6 "application-config": "^1.0.1",
7 "cli-table": "^0.3.1", 7 "cli-table3": "^0.5.1",
8 "netrc-parser": "^3.1.6", 8 "netrc-parser": "^3.1.6",
9 "webtorrent-hybrid": "^4.0.1" 9 "webtorrent-hybrid": "^4.0.1"
10 }, 10 },
11 "summon": { 11 "summon": {
12 "silent": true 12 "silent": true
13 } 13 },
14 "devDependencies": {}
14} 15}
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index 6597a5c36..6b486e575 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -1,3 +1,5 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
1import { registerTSPaths } from '../helpers/register-ts-paths' 3import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths() 4registerTSPaths()
3 5
@@ -5,9 +7,8 @@ import * as program from 'commander'
5import * as prompt from 'prompt' 7import * as prompt from 'prompt'
6import { getNetrc, getSettings, writeSettings } from './cli' 8import { getNetrc, getSettings, writeSettings } from './cli'
7import { isUserUsernameValid } from '../helpers/custom-validators/users' 9import { isUserUsernameValid } from '../helpers/custom-validators/users'
8import { getAccessToken, login } from '../../shared/extra-utils' 10import { getAccessToken } from '../../shared/extra-utils'
9 11import * as CliTable3 from 'cli-table3'
10const Table = require('cli-table')
11 12
12async function delInstance (url: string) { 13async function delInstance (url: string) {
13 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 14 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
@@ -108,10 +109,10 @@ program
108 .action(async () => { 109 .action(async () => {
109 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 110 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
110 111
111 const table = new Table({ 112 const table = new CliTable3({
112 head: ['instance', 'login'], 113 head: [ 'instance', 'login' ],
113 colWidths: [30, 30] 114 colWidths: [ 30, 30 ]
114 }) 115 }) as any
115 116
116 settings.remotes.forEach(element => { 117 settings.remotes.forEach(element => {
117 if (!netrc.machines[element]) return 118 if (!netrc.machines[element]) return
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index eaa792763..0efe87810 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -1,10 +1,6 @@
1import { registerTSPaths } from '../helpers/register-ts-paths' 1import { registerTSPaths } from '../helpers/register-ts-paths'
2
3registerTSPaths() 2registerTSPaths()
4 3
5// FIXME: https://github.com/nodejs/node/pull/16853
6require('tls').DEFAULT_ECDH_CURVE = 'auto'
7
8import * as program from 'commander' 4import * as program from 'commander'
9import { join } from 'path' 5import { join } from 'path'
10import { doRequestAndSaveToFile } from '../helpers/requests' 6import { doRequestAndSaveToFile } from '../helpers/requests'
@@ -16,7 +12,7 @@ import { accessSync, constants } from 'fs'
16import { remove } from 'fs-extra' 12import { remove } from 'fs-extra'
17import { sha256 } from '../helpers/core-utils' 13import { sha256 } from '../helpers/core-utils'
18import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' 14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
19import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli' 15import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli'
20 16
21type UserInfo = { 17type UserInfo = {
22 username: string 18 username: string
@@ -44,30 +40,29 @@ command
44 .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) 40 .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname)
45 .parse(process.argv) 41 .parse(process.argv)
46 42
47let log = getLogger(program[ 'verbose' ]) 43const log = getLogger(program['verbose'])
48 44
49getServerCredentials(command) 45getServerCredentials(command)
50 .then(({ url, username, password }) => { 46 .then(({ url, username, password }) => {
51 if (!program[ 'targetUrl' ]) { 47 if (!program['targetUrl']) {
52 exitError('--target-url field is required.') 48 exitError('--target-url field is required.')
53 } 49 }
54 50
55 try { 51 try {
56 accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK) 52 accessSync(program['tmpdir'], constants.R_OK | constants.W_OK)
57 } catch (e) { 53 } catch (e) {
58 exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ]) 54 exitError('--tmpdir %s: directory does not exist or is not accessible', program['tmpdir'])
59 } 55 }
60 56
61 url = normalizeTargetUrl(url) 57 url = normalizeTargetUrl(url)
62 program[ 'targetUrl' ] = normalizeTargetUrl(program[ 'targetUrl' ]) 58 program['targetUrl'] = normalizeTargetUrl(program['targetUrl'])
63 59
64 const user = { username, password } 60 const user = { username, password }
65 61
66 run(url, user) 62 run(url, user)
67 .catch(err => { 63 .catch(err => exitError(err))
68 exitError(err)
69 })
70 }) 64 })
65 .catch(err => console.error(err))
71 66
72async function run (url: string, user: UserInfo) { 67async function run (url: string, user: UserInfo) {
73 if (!user.password) { 68 if (!user.password) {
@@ -77,7 +72,7 @@ async function run (url: string, user: UserInfo) {
77 const youtubeDL = await safeGetYoutubeDL() 72 const youtubeDL = await safeGetYoutubeDL()
78 73
79 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 74 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
80 youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { 75 youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => {
81 if (err) { 76 if (err) {
82 exitError(err.message) 77 exitError(err.message)
83 } 78 }
@@ -86,10 +81,10 @@ async function run (url: string, user: UserInfo) {
86 81
87 // Normalize utf8 fields 82 // Normalize utf8 fields
88 infoArray = [].concat(info) 83 infoArray = [].concat(info)
89 if (program[ 'first' ]) { 84 if (program['first']) {
90 infoArray = infoArray.slice(0, program[ 'first' ]) 85 infoArray = infoArray.slice(0, program['first'])
91 } else if (program[ 'last' ]) { 86 } else if (program['last']) {
92 infoArray = infoArray.slice(-program[ 'last' ]) 87 infoArray = infoArray.slice(-program['last'])
93 } 88 }
94 infoArray = infoArray.map(i => normalizeObject(i)) 89 infoArray = infoArray.map(i => normalizeObject(i))
95 90
@@ -97,22 +92,22 @@ async function run (url: string, user: UserInfo) {
97 92
98 for (const info of infoArray) { 93 for (const info of infoArray) {
99 await processVideo({ 94 await processVideo({
100 cwd: program[ 'tmpdir' ], 95 cwd: program['tmpdir'],
101 url, 96 url,
102 user, 97 user,
103 youtubeInfo: info 98 youtubeInfo: info
104 }) 99 })
105 } 100 }
106 101
107 log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ]) 102 log.info('Video/s for user %s imported: %s', user.username, program['targetUrl'])
108 process.exit(0) 103 process.exit(0)
109 }) 104 })
110} 105}
111 106
112function processVideo (parameters: { 107function processVideo (parameters: {
113 cwd: string, 108 cwd: string
114 url: string, 109 url: string
115 user: { username: string, password: string }, 110 user: { username: string, password: string }
116 youtubeInfo: any 111 youtubeInfo: any
117}) { 112}) {
118 const { youtubeInfo, cwd, url, user } = parameters 113 const { youtubeInfo, cwd, url, user } = parameters
@@ -123,17 +118,17 @@ function processVideo (parameters: {
123 const videoInfo = await fetchObject(youtubeInfo) 118 const videoInfo = await fetchObject(youtubeInfo)
124 log.debug('Fetched object.', videoInfo) 119 log.debug('Fetched object.', videoInfo)
125 120
126 if (program[ 'since' ]) { 121 if (program['since']) {
127 if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { 122 if (buildOriginallyPublishedAt(videoInfo).getTime() < program['since'].getTime()) {
128 log.info('Video "%s" has been published before "%s", don\'t upload it.\n', 123 log.info('Video "%s" has been published before "%s", don\'t upload it.\n',
129 videoInfo.title, formatDate(program[ 'since' ])) 124 videoInfo.title, formatDate(program['since']))
130 return res() 125 return res()
131 } 126 }
132 } 127 }
133 if (program[ 'until' ]) { 128 if (program['until']) {
134 if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { 129 if (buildOriginallyPublishedAt(videoInfo).getTime() > program['until'].getTime()) {
135 log.info('Video "%s" has been published after "%s", don\'t upload it.\n', 130 log.info('Video "%s" has been published after "%s", don\'t upload it.\n',
136 videoInfo.title, formatDate(program[ 'until' ])) 131 videoInfo.title, formatDate(program['until']))
137 return res() 132 return res()
138 } 133 }
139 } 134 }
@@ -178,11 +173,11 @@ function processVideo (parameters: {
178} 173}
179 174
180async function uploadVideoOnPeerTube (parameters: { 175async function uploadVideoOnPeerTube (parameters: {
181 videoInfo: any, 176 videoInfo: any
182 videoPath: string, 177 videoPath: string
183 cwd: string, 178 cwd: string
184 url: string, 179 url: string
185 user: { username: string; password: string } 180 user: { username: string, password: string }
186}) { 181}) {
187 const { videoInfo, videoPath, cwd, url, user } = parameters 182 const { videoInfo, videoPath, cwd, url, user } = parameters
188 183
@@ -210,9 +205,9 @@ async function uploadVideoOnPeerTube (parameters: {
210 205
211 const defaultAttributes = { 206 const defaultAttributes = {
212 name: truncate(videoInfo.title, { 207 name: truncate(videoInfo.title, {
213 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, 208 length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max,
214 'separator': /,? +/, 209 separator: /,? +/,
215 'omission': ' […]' 210 omission: ' […]'
216 }), 211 }),
217 category, 212 category,
218 licence, 213 licence,
@@ -259,7 +254,7 @@ async function uploadVideoOnPeerTube (parameters: {
259async function getCategory (categories: string[], url: string) { 254async function getCategory (categories: string[], url: string) {
260 if (!categories) return undefined 255 if (!categories) return undefined
261 256
262 const categoryString = categories[ 0 ] 257 const categoryString = categories[0]
263 258
264 if (categoryString === 'News & Politics') return 11 259 if (categoryString === 'News & Politics') return 11
265 260
@@ -267,7 +262,7 @@ async function getCategory (categories: string[], url: string) {
267 const categoriesServer = res.body 262 const categoriesServer = res.body
268 263
269 for (const key of Object.keys(categoriesServer)) { 264 for (const key of Object.keys(categoriesServer)) {
270 const categoryServer = categoriesServer[ key ] 265 const categoryServer = categoriesServer[key]
271 if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) 266 if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10)
272 } 267 }
273 268
@@ -289,12 +284,12 @@ function normalizeObject (obj: any) {
289 // Deprecated key 284 // Deprecated key
290 if (key === 'resolution') continue 285 if (key === 'resolution') continue
291 286
292 const value = obj[ key ] 287 const value = obj[key]
293 288
294 if (typeof value === 'string') { 289 if (typeof value === 'string') {
295 newObj[ key ] = value.normalize() 290 newObj[key] = value.normalize()
296 } else { 291 } else {
297 newObj[ key ] = value 292 newObj[key] = value
298 } 293 }
299 } 294 }
300 295
@@ -306,7 +301,7 @@ function fetchObject (info: any) {
306 301
307 return new Promise<any>(async (res, rej) => { 302 return new Promise<any>(async (res, rej) => {
308 const youtubeDL = await safeGetYoutubeDL() 303 const youtubeDL = await safeGetYoutubeDL()
309 youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { 304 youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => {
310 if (err) return rej(err) 305 if (err) return rej(err)
311 306
312 const videoInfoWithUrl = Object.assign(videoInfo, { url }) 307 const videoInfoWithUrl = Object.assign(videoInfo, { url })
@@ -317,10 +312,10 @@ function fetchObject (info: any) {
317 312
318function buildUrl (info: any) { 313function buildUrl (info: any) {
319 const webpageUrl = info.webpage_url as string 314 const webpageUrl = info.webpage_url as string
320 if (webpageUrl && webpageUrl.match(/^https?:\/\//)) return webpageUrl 315 if (webpageUrl?.match(/^https?:\/\//)) return webpageUrl
321 316
322 const url = info.url as string 317 const url = info.url as string
323 if (url && url.match(/^https?:\/\//)) return url 318 if (url?.match(/^https?:\/\//)) return url
324 319
325 // It seems youtube-dl does not return the video url 320 // It seems youtube-dl does not return the video url
326 return 'https://www.youtube.com/watch?v=' + info.id 321 return 'https://www.youtube.com/watch?v=' + info.id
@@ -388,7 +383,7 @@ function parseDate (dateAsStr: string): Date {
388} 383}
389 384
390function formatDate (date: Date): string { 385function formatDate (date: Date): string {
391 return date.toISOString().split('T')[ 0 ] 386 return date.toISOString().split('T')[0]
392} 387}
393 388
394function exitError (message: string, ...meta: any[]) { 389function exitError (message: string, ...meta: any[]) {
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts
index e40606107..05b75fab2 100644
--- a/server/tools/peertube-plugins.ts
+++ b/server/tools/peertube-plugins.ts
@@ -1,17 +1,15 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
1import { registerTSPaths } from '../helpers/register-ts-paths' 3import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths() 4registerTSPaths()
3 5
4import * as program from 'commander' 6import * as program from 'commander'
5import { PluginType } from '../../shared/models/plugins/plugin.type' 7import { PluginType } from '../../shared/models/plugins/plugin.type'
6import { getAccessToken } from '../../shared/extra-utils/users/login'
7import { getMyUserInformation } from '../../shared/extra-utils/users/users'
8import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' 8import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins'
9import { getServerCredentials } from './cli' 9import { getAdminTokenOrDie, getServerCredentials } from './cli'
10import { User, UserRole } from '../../shared/models/users'
11import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' 10import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model'
12import { isAbsolute } from 'path' 11import { isAbsolute } from 'path'
13 12import * as CliTable3 from 'cli-table3'
14const Table = require('cli-table')
15 13
16program 14program
17 .name('plugins') 15 .name('plugins')
@@ -82,10 +80,10 @@ async function pluginsListCLI () {
82 }) 80 })
83 const plugins: PeerTubePlugin[] = res.body.data 81 const plugins: PeerTubePlugin[] = res.body.data
84 82
85 const table = new Table({ 83 const table = new CliTable3({
86 head: ['name', 'version', 'homepage'], 84 head: [ 'name', 'version', 'homepage' ],
87 colWidths: [ 50, 10, 50 ] 85 colWidths: [ 50, 10, 50 ]
88 }) 86 }) as any
89 87
90 for (const plugin of plugins) { 88 for (const plugin of plugins) {
91 const npmName = plugin.type === PluginType.PLUGIN 89 const npmName = plugin.type === PluginType.PLUGIN
@@ -128,7 +126,6 @@ async function installPluginCLI (options: any) {
128 } catch (err) { 126 } catch (err) {
129 console.error('Cannot install plugin.', err) 127 console.error('Cannot install plugin.', err)
130 process.exit(-1) 128 process.exit(-1)
131 return
132 } 129 }
133 130
134 console.log('Plugin installed.') 131 console.log('Plugin installed.')
@@ -160,7 +157,6 @@ async function updatePluginCLI (options: any) {
160 } catch (err) { 157 } catch (err) {
161 console.error('Cannot update plugin.', err) 158 console.error('Cannot update plugin.', err)
162 process.exit(-1) 159 process.exit(-1)
163 return
164 } 160 }
165 161
166 console.log('Plugin updated.') 162 console.log('Plugin updated.')
@@ -181,27 +177,13 @@ async function uninstallPluginCLI (options: any) {
181 await uninstallPlugin({ 177 await uninstallPlugin({
182 url, 178 url,
183 accessToken, 179 accessToken,
184 npmName: options[ 'npmName' ] 180 npmName: options['npmName']
185 }) 181 })
186 } catch (err) { 182 } catch (err) {
187 console.error('Cannot uninstall plugin.', err) 183 console.error('Cannot uninstall plugin.', err)
188 process.exit(-1) 184 process.exit(-1)
189 return
190 } 185 }
191 186
192 console.log('Plugin uninstalled.') 187 console.log('Plugin uninstalled.')
193 process.exit(0) 188 process.exit(0)
194} 189}
195
196async function getAdminTokenOrDie (url: string, username: string, password: string) {
197 const accessToken = await getAccessToken(url, username, password)
198 const resMe = await getMyUserInformation(url, accessToken)
199 const me: User = resMe.body
200
201 if (me.role !== UserRole.ADMINISTRATOR) {
202 console.error('Cannot list plugins if you are not administrator.')
203 process.exit(-1)
204 }
205
206 return accessToken
207}
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts
new file mode 100644
index 000000000..1ab58a438
--- /dev/null
+++ b/server/tools/peertube-redundancy.ts
@@ -0,0 +1,197 @@
1// eslint-disable @typescript-eslint/no-unnecessary-type-assertion
2
3import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths()
5
6import * as program from 'commander'
7import { getAdminTokenOrDie, getServerCredentials } from './cli'
8import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models'
9import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy'
10import validator from 'validator'
11import * as CliTable3 from 'cli-table3'
12import { URL } from 'url'
13import { uniq } from 'lodash'
14
15import bytes = require('bytes')
16
17program
18 .name('plugins')
19 .usage('[command] [options]')
20
21program
22 .command('list-remote-redundancies')
23 .description('List remote redundancies on your videos')
24 .option('-u, --url <url>', 'Server url')
25 .option('-U, --username <username>', 'Username')
26 .option('-p, --password <token>', 'Password')
27 .action(() => listRedundanciesCLI('my-videos'))
28
29program
30 .command('list-my-redundancies')
31 .description('List your redundancies of remote videos')
32 .option('-u, --url <url>', 'Server url')
33 .option('-U, --username <username>', 'Username')
34 .option('-p, --password <token>', 'Password')
35 .action(() => listRedundanciesCLI('remote-videos'))
36
37program
38 .command('add')
39 .description('Duplicate a video in your redundancy system')
40 .option('-u, --url <url>', 'Server url')
41 .option('-U, --username <username>', 'Username')
42 .option('-p, --password <token>', 'Password')
43 .option('-v, --video <videoId>', 'Video id to duplicate')
44 .action((options) => addRedundancyCLI(options))
45
46program
47 .command('remove')
48 .description('Remove a video from your redundancies')
49 .option('-u, --url <url>', 'Server url')
50 .option('-U, --username <username>', 'Username')
51 .option('-p, --password <token>', 'Password')
52 .option('-v, --video <videoId>', 'Video id to remove from redundancies')
53 .action((options) => removeRedundancyCLI(options))
54
55if (!process.argv.slice(2).length) {
56 program.outputHelp()
57}
58
59program.parse(process.argv)
60
61// ----------------------------------------------------------------------------
62
63async function listRedundanciesCLI (target: VideoRedundanciesTarget) {
64 const { url, username, password } = await getServerCredentials(program)
65 const accessToken = await getAdminTokenOrDie(url, username, password)
66
67 const redundancies = await listVideoRedundanciesData(url, accessToken, target)
68
69 const table = new CliTable3({
70 head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ]
71 }) as any
72
73 for (const redundancy of redundancies) {
74 const webtorrentFiles = redundancy.redundancies.files
75 const streamingPlaylists = redundancy.redundancies.streamingPlaylists
76
77 let totalSize = ''
78 if (target === 'remote-videos') {
79 const tmp = webtorrentFiles.concat(streamingPlaylists)
80 .reduce((a, b) => a + b.size, 0)
81
82 totalSize = bytes(tmp)
83 }
84
85 const instances = uniq(
86 webtorrentFiles.concat(streamingPlaylists)
87 .map(r => r.fileUrl)
88 .map(u => new URL(u).host)
89 )
90
91 table.push([
92 redundancy.id.toString(),
93 redundancy.name,
94 redundancy.url,
95 webtorrentFiles.length,
96 streamingPlaylists.length,
97 instances.join('\n'),
98 totalSize
99 ])
100 }
101
102 console.log(table.toString())
103 process.exit(0)
104}
105
106async function addRedundancyCLI (options: { videoId: number }) {
107 const { url, username, password } = await getServerCredentials(program)
108 const accessToken = await getAdminTokenOrDie(url, username, password)
109
110 if (!options['video'] || validator.isInt('' + options['video']) === false) {
111 console.error('You need to specify the video id to duplicate and it should be a number.\n')
112 program.outputHelp()
113 process.exit(-1)
114 }
115
116 try {
117 await addVideoRedundancy({
118 url,
119 accessToken,
120 videoId: options['video']
121 })
122
123 console.log('Video will be duplicated by your instance!')
124
125 process.exit(0)
126 } catch (err) {
127 if (err.message.includes(409)) {
128 console.error('This video is already duplicated by your instance.')
129 } else if (err.message.includes(404)) {
130 console.error('This video id does not exist.')
131 } else {
132 console.error(err)
133 }
134
135 process.exit(-1)
136 }
137}
138
139async function removeRedundancyCLI (options: { videoId: number }) {
140 const { url, username, password } = await getServerCredentials(program)
141 const accessToken = await getAdminTokenOrDie(url, username, password)
142
143 if (!options['video'] || validator.isInt('' + options['video']) === false) {
144 console.error('You need to specify the video id to remove from your redundancies.\n')
145 program.outputHelp()
146 process.exit(-1)
147 }
148
149 const videoId = parseInt(options['video'] + '', 10)
150
151 let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos')
152 let videoRedundancy = redundancies.find(r => videoId === r.id)
153
154 if (!videoRedundancy) {
155 redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos')
156 videoRedundancy = redundancies.find(r => videoId === r.id)
157 }
158
159 if (!videoRedundancy) {
160 console.error('Video redundancy not found.')
161 process.exit(-1)
162 }
163
164 try {
165 const ids = videoRedundancy.redundancies.files
166 .concat(videoRedundancy.redundancies.streamingPlaylists)
167 .map(r => r.id)
168
169 for (const id of ids) {
170 await removeVideoRedundancy({
171 url,
172 accessToken,
173 redundancyId: id
174 })
175 }
176
177 console.log('Video redundancy removed!')
178
179 process.exit(0)
180 } catch (err) {
181 console.error(err)
182 process.exit(-1)
183 }
184}
185
186async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) {
187 const res = await listVideoRedundancies({
188 url,
189 accessToken,
190 start: 0,
191 count: 100,
192 sort: 'name',
193 target
194 })
195
196 return res.body.data as VideoRedundancy[]
197}
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index ab6e215d9..7c936ae0d 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -1,6 +1,4 @@
1import { registerTSPaths } from '../helpers/register-ts-paths' 1import { registerTSPaths } from '../helpers/register-ts-paths'
2registerTSPaths()
3
4import * as repl from 'repl' 2import * as repl from 'repl'
5import * as path from 'path' 3import * as path from 'path'
6import * as _ from 'lodash' 4import * as _ from 'lodash'
@@ -23,6 +21,8 @@ import * as signupUtils from '../helpers/signup'
23import * as utils from '../helpers/utils' 21import * as utils from '../helpers/utils'
24import * as YoutubeDLUtils from '../helpers/youtube-dl' 22import * as YoutubeDLUtils from '../helpers/youtube-dl'
25 23
24registerTSPaths()
25
26const start = async () => { 26const start = async () => {
27 await initDatabaseModels(true) 27 await initDatabaseModels(true)
28 28
@@ -31,22 +31,39 @@ const start = async () => {
31 const initContext = (replServer) => { 31 const initContext = (replServer) => {
32 return (context) => { 32 return (context) => {
33 const properties = { 33 const properties = {
34 context, repl: replServer, env: process.env, 34 context,
35 lodash: _, path, 35 repl: replServer,
36 uuidv1, uuidv3, uuidv4, uuidv5, 36 env: process.env,
37 cli, logger, constants, 37 lodash: _,
38 Sequelize, sequelizeTypescript, modelsUtils, 38 path,
39 models: sequelizeTypescript.models, transaction: sequelizeTypescript.transaction, 39 uuidv1,
40 query: sequelizeTypescript.query, queryInterface: sequelizeTypescript.getQueryInterface(), 40 uuidv3,
41 uuidv4,
42 uuidv5,
43 cli,
44 logger,
45 constants,
46 Sequelize,
47 sequelizeTypescript,
48 modelsUtils,
49 models: sequelizeTypescript.models,
50 transaction: sequelizeTypescript.transaction,
51 query: sequelizeTypescript.query,
52 queryInterface: sequelizeTypescript.getQueryInterface(),
41 YoutubeDL, 53 YoutubeDL,
42 coreUtils, ffmpegUtils, peertubeCryptoUtils, signupUtils, utils, YoutubeDLUtils 54 coreUtils,
55 ffmpegUtils,
56 peertubeCryptoUtils,
57 signupUtils,
58 utils,
59 YoutubeDLUtils
43 } 60 }
44 61
45 for (let prop in properties) { 62 for (const prop in properties) {
46 Object.defineProperty(context, prop, { 63 Object.defineProperty(context, prop, {
47 configurable: false, 64 configurable: false,
48 enumerable: true, 65 enumerable: true,
49 value: properties[ prop ] 66 value: properties[prop]
50 }) 67 })
51 } 68 }
52 } 69 }
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index f604c9bee..8de952e7b 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -24,14 +24,14 @@ command
24 24
25getServerCredentials(command) 25getServerCredentials(command)
26 .then(({ url, username, password }) => { 26 .then(({ url, username, password }) => {
27 if (!program[ 'videoName' ] || !program[ 'file' ]) { 27 if (!program['videoName'] || !program['file']) {
28 if (!program[ 'videoName' ]) console.error('--video-name is required.') 28 if (!program['videoName']) console.error('--video-name is required.')
29 if (!program[ 'file' ]) console.error('--file is required.') 29 if (!program['file']) console.error('--file is required.')
30 30
31 process.exit(-1) 31 process.exit(-1)
32 } 32 }
33 33
34 if (isAbsolute(program[ 'file' ]) === false) { 34 if (isAbsolute(program['file']) === false) {
35 console.error('File path should be absolute.') 35 console.error('File path should be absolute.')
36 process.exit(-1) 36 process.exit(-1)
37 } 37 }
@@ -41,25 +41,26 @@ getServerCredentials(command)
41 process.exit(-1) 41 process.exit(-1)
42 }) 42 })
43 }) 43 })
44 .catch(err => console.error(err))
44 45
45async function run (url: string, username: string, password: string) { 46async function run (url: string, username: string, password: string) {
46 const accessToken = await getAccessToken(url, username, password) 47 const accessToken = await getAccessToken(url, username, password)
47 48
48 await access(program[ 'file' ], constants.F_OK) 49 await access(program['file'], constants.F_OK)
49 50
50 console.log('Uploading %s video...', program[ 'videoName' ]) 51 console.log('Uploading %s video...', program['videoName'])
51 52
52 const videoAttributes = await buildVideoAttributesFromCommander(url, program) 53 const videoAttributes = await buildVideoAttributesFromCommander(url, program)
53 54
54 Object.assign(videoAttributes, { 55 Object.assign(videoAttributes, {
55 fixture: program[ 'file' ], 56 fixture: program['file'],
56 thumbnailfile: program[ 'thumbnail' ], 57 thumbnailfile: program['thumbnail'],
57 previewfile: program[ 'preview' ] 58 previewfile: program['preview']
58 }) 59 })
59 60
60 try { 61 try {
61 await uploadVideo(url, accessToken, videoAttributes) 62 await uploadVideo(url, accessToken, videoAttributes)
62 console.log(`Video ${program[ 'videoName' ]} uploaded.`) 63 console.log(`Video ${program['videoName']} uploaded.`)
63 process.exit(0) 64 process.exit(0)
64 } catch (err) { 65 } catch (err) {
65 console.error(require('util').inspect(err)) 66 console.error(require('util').inspect(err))
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
index 9ac1d05f9..5f7d1bb07 100644
--- a/server/tools/peertube-watch.ts
+++ b/server/tools/peertube-watch.ts
@@ -29,16 +29,10 @@ program
29 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') 29 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
30 console.log() 30 console.log()
31 }) 31 })
32 .action((url, cmd) => { 32 .action((url, cmd) => run(url, cmd))
33 run(url, cmd)
34 .catch(err => {
35 console.error(err)
36 process.exit(-1)
37 })
38 })
39 .parse(process.argv) 33 .parse(process.argv)
40 34
41async function run (url: string, program: any) { 35function run (url: string, program: any) {
42 if (!url) { 36 if (!url) {
43 console.error('<url> positional argument is required.') 37 console.error('<url> positional argument is required.')
44 process.exit(-1) 38 process.exit(-1)
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index fc85c4210..88dd5f7f6 100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -1,13 +1,12 @@
1#!/usr/bin/env node 1#!/usr/bin/env node
2 2
3/* eslint-disable no-useless-escape */
4
3import { registerTSPaths } from '../helpers/register-ts-paths' 5import { registerTSPaths } from '../helpers/register-ts-paths'
4registerTSPaths() 6registerTSPaths()
5 7
6import * as program from 'commander' 8import * as program from 'commander'
7import { 9import { getSettings, version } from './cli'
8 version,
9 getSettings
10} from './cli'
11 10
12program 11program
13 .version(version, '-v, --version') 12 .version(version, '-v, --version')
@@ -22,17 +21,19 @@ program
22 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') 21 .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w')
23 .command('repl', 'initiate a REPL to access internals') 22 .command('repl', 'initiate a REPL to access internals')
24 .command('plugins [action]', 'manage instance plugins/themes').alias('p') 23 .command('plugins [action]', 'manage instance plugins/themes').alias('p')
24 .command('redundancy [action]', 'manage instance redundancies').alias('r')
25 25
26/* Not Yet Implemented */ 26/* Not Yet Implemented */
27program 27program
28 .command('diagnostic [action]', 28 .command(
29 'like couple therapy, but for your instance', 29 'diagnostic [action]',
30 { noHelp: true } as program.CommandOptions 30 'like couple therapy, but for your instance',
31 ).alias('d') 31 { noHelp: true } as program.CommandOptions
32 ).alias('d')
32 .command('admin', 33 .command('admin',
33 'manage an instance where you have elevated rights', 34 'manage an instance where you have elevated rights',
34 { noHelp: true } as program.CommandOptions 35 { noHelp: true } as program.CommandOptions
35 ).alias('a') 36 ).alias('a')
36 37
37// help on no command 38// help on no command
38if (!process.argv.slice(2).length) { 39if (!process.argv.slice(2).length) {
@@ -81,3 +82,4 @@ getSettings()
81 }) 82 })
82 .parse(process.argv) 83 .parse(process.argv)
83 }) 84 })
85 .catch(err => console.error(err))
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock
index 28756cbc2..ccd716a51 100644
--- a/server/tools/yarn.lock
+++ b/server/tools/yarn.lock
@@ -347,12 +347,15 @@ chunk-store-stream@^4.0.0:
347 block-stream2 "^2.0.0" 347 block-stream2 "^2.0.0"
348 readable-stream "^3.4.0" 348 readable-stream "^3.4.0"
349 349
350cli-table@^0.3.1: 350cli-table3@^0.5.1:
351 version "0.3.1" 351 version "0.5.1"
352 resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" 352 resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
353 integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= 353 integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
354 dependencies: 354 dependencies:
355 colors "1.0.3" 355 object-assign "^4.1.0"
356 string-width "^2.1.1"
357 optionalDependencies:
358 colors "^1.1.2"
356 359
357clivas@^0.2.0: 360clivas@^0.2.0:
358 version "0.2.0" 361 version "0.2.0"
@@ -364,10 +367,10 @@ code-point-at@^1.0.0:
364 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 367 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
365 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 368 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
366 369
367colors@1.0.3: 370colors@^1.1.2:
368 version "1.0.3" 371 version "1.4.0"
369 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" 372 resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
370 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= 373 integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
371 374
372common-tags@^1.8.0: 375common-tags@^1.8.0:
373 version "1.8.0" 376 version "1.8.0"
@@ -1609,7 +1612,7 @@ string-width@^1.0.1:
1609 is-fullwidth-code-point "^1.0.0" 1612 is-fullwidth-code-point "^1.0.0"
1610 strip-ansi "^3.0.0" 1613 strip-ansi "^3.0.0"
1611 1614
1612"string-width@^1.0.2 || 2": 1615"string-width@^1.0.2 || 2", string-width@^2.1.1:
1613 version "2.1.1" 1616 version "2.1.1"
1614 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 1617 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
1615 integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 1618 integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
diff --git a/server/typings/express.ts b/server/typings/express.ts
index 3cc7c7632..43a9b2c99 100644
--- a/server/typings/express.ts
+++ b/server/typings/express.ts
@@ -21,7 +21,7 @@ import {
21} from './models' 21} from './models'
22import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' 22import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist'
23import { MVideoImportDefault } from '@server/typings/models/video/video-import' 23import { MVideoImportDefault } from '@server/typings/models/video/video-import'
24import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models' 24import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile } from '@server/typings/models'
25import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' 25import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element'
26import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' 26import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate'
27import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' 27import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership'
@@ -74,6 +74,7 @@ declare module 'express' {
74 74
75 account?: MAccountDefault 75 account?: MAccountDefault
76 76
77 actorUrl?: MActorUrl
77 actorFull?: MActorFull 78 actorFull?: MActorFull
78 79
79 user?: MUserDefault 80 user?: MUserDefault
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts
index c9cb55332..0d8bf11bd 100644
--- a/server/typings/models/account/account-blocklist.ts
+++ b/server/typings/models/account/account-blocklist.ts
@@ -12,7 +12,8 @@ export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'Block
12 12
13export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> 13export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'>
14 14
15export type MAccountBlocklistAccounts = MAccountBlocklist & 15export type MAccountBlocklistAccounts =
16 MAccountBlocklist &
16 Use<'ByAccount', MAccountDefault> & 17 Use<'ByAccount', MAccountDefault> &
17 Use<'BlockedAccount', MAccountDefault> 18 Use<'BlockedAccount', MAccountDefault>
18 19
@@ -20,6 +21,7 @@ export type MAccountBlocklistAccounts = MAccountBlocklist &
20 21
21// Format for API or AP object 22// Format for API or AP object
22 23
23export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> & 24export type MAccountBlocklistFormattable =
25 Pick<MAccountBlocklist, 'createdAt'> &
24 Use<'ByAccount', MAccountFormattable> & 26 Use<'ByAccount', MAccountFormattable> &
25 Use<'BlockedAccount', MAccountFormattable> 27 Use<'BlockedAccount', MAccountFormattable>
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts
index adb1f3689..7b826ee04 100644
--- a/server/typings/models/account/account.ts
+++ b/server/typings/models/account/account.ts
@@ -21,7 +21,8 @@ type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M>
21 21
22// ############################################################################ 22// ############################################################################
23 23
24export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | 24export type MAccount =
25 Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' |
25 'VideoComments' | 'BlockedAccounts'> 26 'VideoComments' | 'BlockedAccounts'>
26 27
27// ############################################################################ 28// ############################################################################
@@ -34,62 +35,75 @@ export type MAccountUserId = Pick<MAccount, 'userId'>
34export type MAccountUrl = Use<'Actor', MActorUrl> 35export type MAccountUrl = Use<'Actor', MActorUrl>
35export type MAccountAudience = Use<'Actor', MActorAudience> 36export type MAccountAudience = Use<'Actor', MActorAudience>
36 37
37export type MAccountIdActor = MAccountId & 38export type MAccountIdActor =
39 MAccountId &
38 Use<'Actor', MActor> 40 Use<'Actor', MActor>
39 41
40export type MAccountIdActorId = MAccountId & 42export type MAccountIdActorId =
43 MAccountId &
41 Use<'Actor', MActorId> 44 Use<'Actor', MActorId>
42 45
43// ############################################################################ 46// ############################################################################
44 47
45// Default scope 48// Default scope
46export type MAccountDefault = MAccount & 49export type MAccountDefault =
50 MAccount &
47 Use<'Actor', MActorDefault> 51 Use<'Actor', MActorDefault>
48 52
49// Default with default association scopes 53// Default with default association scopes
50export type MAccountDefaultChannelDefault = MAccount & 54export type MAccountDefaultChannelDefault =
55 MAccount &
51 Use<'Actor', MActorDefault> & 56 Use<'Actor', MActorDefault> &
52 Use<'VideoChannels', MChannelDefault[]> 57 Use<'VideoChannels', MChannelDefault[]>
53 58
54// We don't need some actors attributes 59// We don't need some actors attributes
55export type MAccountLight = MAccount & 60export type MAccountLight =
61 MAccount &
56 Use<'Actor', MActorDefaultLight> 62 Use<'Actor', MActorDefaultLight>
57 63
58// ############################################################################ 64// ############################################################################
59 65
60// Full actor 66// Full actor
61export type MAccountActor = MAccount & 67export type MAccountActor =
68 MAccount &
62 Use<'Actor', MActor> 69 Use<'Actor', MActor>
63 70
64// Full actor with server 71// Full actor with server
65export type MAccountServer = MAccount & 72export type MAccountServer =
73 MAccount &
66 Use<'Actor', MActorServer> 74 Use<'Actor', MActorServer>
67 75
68// ############################################################################ 76// ############################################################################
69 77
70// For API 78// For API
71 79
72export type MAccountSummary = FunctionProperties<MAccount> & 80export type MAccountSummary =
81 FunctionProperties<MAccount> &
73 Pick<MAccount, 'id' | 'name'> & 82 Pick<MAccount, 'id' | 'name'> &
74 Use<'Actor', MActorSummary> 83 Use<'Actor', MActorSummary>
75 84
76export type MAccountSummaryBlocks = MAccountSummary & 85export type MAccountSummaryBlocks =
86 MAccountSummary &
77 Use<'BlockedAccounts', MAccountBlocklistId[]> 87 Use<'BlockedAccounts', MAccountBlocklistId[]>
78 88
79export type MAccountAPI = MAccount & 89export type MAccountAPI =
90 MAccount &
80 Use<'Actor', MActorAPI> 91 Use<'Actor', MActorAPI>
81 92
82// ############################################################################ 93// ############################################################################
83 94
84// Format for API or AP object 95// Format for API or AP object
85 96
86export type MAccountSummaryFormattable = FunctionProperties<MAccount> & 97export type MAccountSummaryFormattable =
98 FunctionProperties<MAccount> &
87 Pick<MAccount, 'id' | 'name'> & 99 Pick<MAccount, 'id' | 'name'> &
88 Use<'Actor', MActorSummaryFormattable> 100 Use<'Actor', MActorSummaryFormattable>
89 101
90export type MAccountFormattable = FunctionProperties<MAccount> & 102export type MAccountFormattable =
103 FunctionProperties<MAccount> &
91 Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & 104 Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> &
92 Use<'Actor', MActorFormattable> 105 Use<'Actor', MActorFormattable>
93 106
94export type MAccountAP = Pick<MAccount, 'name' | 'description'> & 107export type MAccountAP =
108 Pick<MAccount, 'name' | 'description'> &
95 Use<'Actor', MActorAP> 109 Use<'Actor', MActorAP>
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts
index f44157eba..5d0c03c8d 100644
--- a/server/typings/models/account/actor-follow.ts
+++ b/server/typings/models/account/actor-follow.ts
@@ -20,22 +20,26 @@ export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollow
20 20
21// ############################################################################ 21// ############################################################################
22 22
23export type MActorFollowFollowingHost = MActorFollow & 23export type MActorFollowFollowingHost =
24 MActorFollow &
24 Use<'ActorFollowing', MActorUsername & MActorHost> 25 Use<'ActorFollowing', MActorUsername & MActorHost>
25 26
26// ############################################################################ 27// ############################################################################
27 28
28// With actors or actors default 29// With actors or actors default
29 30
30export type MActorFollowActors = MActorFollow & 31export type MActorFollowActors =
32 MActorFollow &
31 Use<'ActorFollower', MActor> & 33 Use<'ActorFollower', MActor> &
32 Use<'ActorFollowing', MActor> 34 Use<'ActorFollowing', MActor>
33 35
34export type MActorFollowActorsDefault = MActorFollow & 36export type MActorFollowActorsDefault =
37 MActorFollow &
35 Use<'ActorFollower', MActorDefault> & 38 Use<'ActorFollower', MActorDefault> &
36 Use<'ActorFollowing', MActorDefault> 39 Use<'ActorFollowing', MActorDefault>
37 40
38export type MActorFollowFull = MActorFollow & 41export type MActorFollowFull =
42 MActorFollow &
39 Use<'ActorFollower', MActorDefaultAccountChannel> & 43 Use<'ActorFollower', MActorDefaultAccountChannel> &
40 Use<'ActorFollowing', MActorDefaultAccountChannel> 44 Use<'ActorFollowing', MActorDefaultAccountChannel>
41 45
@@ -43,20 +47,24 @@ export type MActorFollowFull = MActorFollow &
43 47
44// For subscriptions 48// For subscriptions
45 49
46type SubscriptionFollowing = MActorDefault & 50type SubscriptionFollowing =
51 MActorDefault &
47 PickWith<ActorModel, 'VideoChannel', MChannelDefault> 52 PickWith<ActorModel, 'VideoChannel', MChannelDefault>
48 53
49export type MActorFollowActorsDefaultSubscription = MActorFollow & 54export type MActorFollowActorsDefaultSubscription =
55 MActorFollow &
50 Use<'ActorFollower', MActorDefault> & 56 Use<'ActorFollower', MActorDefault> &
51 Use<'ActorFollowing', SubscriptionFollowing> 57 Use<'ActorFollowing', SubscriptionFollowing>
52 58
53export type MActorFollowSubscriptions = MActorFollow & 59export type MActorFollowSubscriptions =
60 MActorFollow &
54 Use<'ActorFollowing', MActorChannelAccountActor> 61 Use<'ActorFollowing', MActorChannelAccountActor>
55 62
56// ############################################################################ 63// ############################################################################
57 64
58// Format for API or AP object 65// Format for API or AP object
59 66
60export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & 67export type MActorFollowFormattable =
68 Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> &
61 Use<'ActorFollower', MActorFormattable> & 69 Use<'ActorFollower', MActorFormattable> &
62 Use<'ActorFollowing', MActorFormattable> 70 Use<'ActorFollowing', MActorFormattable>
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts
index ee4ece755..1160e84cb 100644
--- a/server/typings/models/account/actor.ts
+++ b/server/typings/models/account/actor.ts
@@ -31,18 +31,23 @@ export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'>
31export type MActorHost = Use<'Server', MServerHost> 31export type MActorHost = Use<'Server', MServerHost>
32export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> 32export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed>
33 33
34export type MActorDefaultLight = MActorLight & 34export type MActorDefaultLight =
35 MActorLight &
35 Use<'Server', MServerHost> & 36 Use<'Server', MServerHost> &
36 Use<'Avatar', MAvatar> 37 Use<'Avatar', MAvatar>
37 38
38export type MActorAccountId = MActor & 39export type MActorAccountId =
40 MActor &
39 Use<'Account', MAccountId> 41 Use<'Account', MAccountId>
40export type MActorAccountIdActor = MActor & 42export type MActorAccountIdActor =
43 MActor &
41 Use<'Account', MAccountIdActor> 44 Use<'Account', MAccountIdActor>
42 45
43export type MActorChannelId = MActor & 46export type MActorChannelId =
47 MActor &
44 Use<'VideoChannel', MChannelId> 48 Use<'VideoChannel', MChannelId>
45export type MActorChannelIdActor = MActor & 49export type MActorChannelIdActor =
50 MActor &
46 Use<'VideoChannel', MChannelIdActor> 51 Use<'VideoChannel', MChannelIdActor>
47 52
48export type MActorAccountChannelId = MActorAccountId & MActorChannelId 53export type MActorAccountChannelId = MActorAccountId & MActorChannelId
@@ -52,38 +57,45 @@ export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelId
52 57
53// Include raw account/channel/server 58// Include raw account/channel/server
54 59
55export type MActorAccount = MActor & 60export type MActorAccount =
61 MActor &
56 Use<'Account', MAccount> 62 Use<'Account', MAccount>
57 63
58export type MActorChannel = MActor & 64export type MActorChannel =
65 MActor &
59 Use<'VideoChannel', MChannel> 66 Use<'VideoChannel', MChannel>
60 67
61export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel 68export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel
62 69
63export type MActorServer = MActor & 70export type MActorServer =
71 MActor &
64 Use<'Server', MServer> 72 Use<'Server', MServer>
65 73
66// ############################################################################ 74// ############################################################################
67 75
68// Complex actor associations 76// Complex actor associations
69 77
70export type MActorDefault = MActor & 78export type MActorDefault =
79 MActor &
71 Use<'Server', MServer> & 80 Use<'Server', MServer> &
72 Use<'Avatar', MAvatar> 81 Use<'Avatar', MAvatar>
73 82
74// Actor with channel that is associated to an account and its actor 83// Actor with channel that is associated to an account and its actor
75// Actor -> VideoChannel -> Account -> Actor 84// Actor -> VideoChannel -> Account -> Actor
76export type MActorChannelAccountActor = MActor & 85export type MActorChannelAccountActor =
86 MActor &
77 Use<'VideoChannel', MChannelAccountActor> 87 Use<'VideoChannel', MChannelAccountActor>
78 88
79export type MActorFull = MActor & 89export type MActorFull =
90 MActor &
80 Use<'Server', MServer> & 91 Use<'Server', MServer> &
81 Use<'Avatar', MAvatar> & 92 Use<'Avatar', MAvatar> &
82 Use<'Account', MAccount> & 93 Use<'Account', MAccount> &
83 Use<'VideoChannel', MChannelAccountActor> 94 Use<'VideoChannel', MChannelAccountActor>
84 95
85// Same than ActorFull, but the account and the channel have their actor 96// Same than ActorFull, but the account and the channel have their actor
86export type MActorFullActor = MActor & 97export type MActorFullActor =
98 MActor &
87 Use<'Server', MServer> & 99 Use<'Server', MServer> &
88 Use<'Avatar', MAvatar> & 100 Use<'Avatar', MAvatar> &
89 Use<'Account', MAccountDefault> & 101 Use<'Account', MAccountDefault> &
@@ -93,29 +105,35 @@ export type MActorFullActor = MActor &
93 105
94// API 106// API
95 107
96export type MActorSummary = FunctionProperties<MActor> & 108export type MActorSummary =
109 FunctionProperties<MActor> &
97 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & 110 Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> &
98 Use<'Server', MServerHost> & 111 Use<'Server', MServerHost> &
99 Use<'Avatar', MAvatar> 112 Use<'Avatar', MAvatar>
100 113
101export type MActorSummaryBlocks = MActorSummary & 114export type MActorSummaryBlocks =
115 MActorSummary &
102 Use<'Server', MServerHostBlocks> 116 Use<'Server', MServerHostBlocks>
103 117
104export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | 118export type MActorAPI =
119 Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' |
105 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> 120 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'>
106 121
107// ############################################################################ 122// ############################################################################
108 123
109// Format for API or AP object 124// Format for API or AP object
110 125
111export type MActorSummaryFormattable = FunctionProperties<MActor> & 126export type MActorSummaryFormattable =
127 FunctionProperties<MActor> &
112 Pick<MActor, 'url' | 'preferredUsername'> & 128 Pick<MActor, 'url' | 'preferredUsername'> &
113 Use<'Server', MServerHost> & 129 Use<'Server', MServerHost> &
114 Use<'Avatar', MAvatarFormattable> 130 Use<'Avatar', MAvatarFormattable>
115 131
116export type MActorFormattable = MActorSummaryFormattable & 132export type MActorFormattable =
133 MActorSummaryFormattable &
117 Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & 134 Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> &
118 Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> 135 Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>>
119 136
120export type MActorAP = MActor & 137export type MActorAP =
138 MActor &
121 Use<'Avatar', MAvatar> 139 Use<'Avatar', MAvatar>
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts
index 8af6cc787..21b47180f 100644
--- a/server/typings/models/account/avatar.ts
+++ b/server/typings/models/account/avatar.ts
@@ -7,5 +7,6 @@ export type MAvatar = AvatarModel
7 7
8// Format for API or AP object 8// Format for API or AP object
9 9
10export type MAvatarFormattable = FunctionProperties<MAvatar> & 10export type MAvatarFormattable =
11 FunctionProperties<MAvatar> &
11 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> 12 Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts
index 8ef042d4e..b24a95fd8 100644
--- a/server/typings/models/oauth/oauth-token.ts
+++ b/server/typings/models/oauth/oauth-token.ts
@@ -8,6 +8,7 @@ type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M>
8 8
9export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> 9export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'>
10 10
11export type MOAuthTokenUser = MOAuthToken & 11export type MOAuthTokenUser =
12 MOAuthToken &
12 Use<'User', MUserAccountUrl> & 13 Use<'User', MUserAccountUrl> &
13 { user?: MUserAccountUrl } 14 { user?: MUserAccountUrl }
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts
index 94674c318..83eb83794 100644
--- a/server/typings/models/server/plugin.ts
+++ b/server/typings/models/server/plugin.ts
@@ -6,5 +6,6 @@ export type MPlugin = PluginModel
6 6
7// Format for API or AP object 7// Format for API or AP object
8 8
9export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' 9export type MPluginFormattable =
10 Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled'
10 | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> 11 | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'>
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts
index c3e6230f2..ff6f49176 100644
--- a/server/typings/models/server/server-blocklist.ts
+++ b/server/typings/models/server/server-blocklist.ts
@@ -11,7 +11,8 @@ export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'Blocked
11 11
12// ############################################################################ 12// ############################################################################
13 13
14export type MServerBlocklistAccountServer = MServerBlocklist & 14export type MServerBlocklistAccountServer =
15 MServerBlocklist &
15 Use<'ByAccount', MAccountDefault> & 16 Use<'ByAccount', MAccountDefault> &
16 Use<'BlockedServer', MServer> 17 Use<'BlockedServer', MServer>
17 18
@@ -19,6 +20,7 @@ export type MServerBlocklistAccountServer = MServerBlocklist &
19 20
20// Format for API or AP object 21// Format for API or AP object
21 22
22export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> & 23export type MServerBlocklistFormattable =
24 Pick<MServerBlocklist, 'createdAt'> &
23 Use<'ByAccount', MAccountFormattable> & 25 Use<'ByAccount', MAccountFormattable> &
24 Use<'BlockedServer', MServerFormattable> 26 Use<'BlockedServer', MServerFormattable>
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts
index 190cc0c28..b35e55aeb 100644
--- a/server/typings/models/server/server.ts
+++ b/server/typings/models/server/server.ts
@@ -13,12 +13,14 @@ export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'>
13export type MServerHost = Pick<MServer, 'host'> 13export type MServerHost = Pick<MServer, 'host'>
14export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> 14export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'>
15 15
16export type MServerHostBlocks = MServerHost & 16export type MServerHostBlocks =
17 MServerHost &
17 Use<'BlockedByAccounts', MAccountBlocklistId[]> 18 Use<'BlockedByAccounts', MAccountBlocklistId[]>
18 19
19// ############################################################################ 20// ############################################################################
20 21
21// Format for API or AP object 22// Format for API or AP object
22 23
23export type MServerFormattable = FunctionProperties<MServer> & 24export type MServerFormattable =
25 FunctionProperties<MServer> &
24 Pick<MServer, 'host'> 26 Pick<MServer, 'host'>
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts
index 1cdc691b0..2080360e1 100644
--- a/server/typings/models/user/user-notification.ts
+++ b/server/typings/models/user/user-notification.ts
@@ -16,59 +16,73 @@ type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationMo
16 16
17// ############################################################################ 17// ############################################################################
18 18
19export namespace UserNotificationIncludes { 19export module UserNotificationIncludes {
20
20 export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> 21 export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'>
21 export type VideoIncludeChannel = VideoInclude & 22 export type VideoIncludeChannel =
23 VideoInclude &
22 PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> 24 PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor>
23 25
24 export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> & 26 export type ActorInclude =
27 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
25 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & 28 PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> &
26 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> 29 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
27 30
28 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> 31 export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'>
29 export type VideoChannelIncludeActor = VideoChannelInclude & 32 export type VideoChannelIncludeActor =
33 VideoChannelInclude &
30 PickWith<VideoChannelModel, 'Actor', ActorInclude> 34 PickWith<VideoChannelModel, 'Actor', ActorInclude>
31 35
32 export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> 36 export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'>
33 export type AccountIncludeActor = AccountInclude & 37 export type AccountIncludeActor =
38 AccountInclude &
34 PickWith<AccountModel, 'Actor', ActorInclude> 39 PickWith<AccountModel, 'Actor', ActorInclude>
35 40
36 export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & 41 export type VideoCommentInclude =
42 Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> &
37 PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & 43 PickWith<VideoCommentModel, 'Account', AccountIncludeActor> &
38 PickWith<VideoCommentModel, 'Video', VideoInclude> 44 PickWith<VideoCommentModel, 'Video', VideoInclude>
39 45
40 export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> & 46 export type VideoAbuseInclude =
47 Pick<VideoAbuseModel, 'id'> &
41 PickWith<VideoAbuseModel, 'Video', VideoInclude> 48 PickWith<VideoAbuseModel, 'Video', VideoInclude>
42 49
43 export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> & 50 export type VideoBlacklistInclude =
51 Pick<VideoBlacklistModel, 'id'> &
44 PickWith<VideoAbuseModel, 'Video', VideoInclude> 52 PickWith<VideoAbuseModel, 'Video', VideoInclude>
45 53
46 export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & 54 export type VideoImportInclude =
55 Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> &
47 PickWith<VideoImportModel, 'Video', VideoInclude> 56 PickWith<VideoImportModel, 'Video', VideoInclude>
48 57
49 export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> & 58 export type ActorFollower =
59 Pick<ActorModel, 'preferredUsername' | 'getHost'> &
50 PickWith<ActorModel, 'Account', AccountInclude> & 60 PickWith<ActorModel, 'Account', AccountInclude> &
51 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & 61 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> &
52 PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> 62 PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>>
53 63
54 export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & 64 export type ActorFollowing =
65 Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> &
55 PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & 66 PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> &
56 PickWith<ActorModel, 'Account', AccountInclude> & 67 PickWith<ActorModel, 'Account', AccountInclude> &
57 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> 68 PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>>
58 69
59 export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> & 70 export type ActorFollowInclude =
71 Pick<ActorFollowModel, 'id' | 'state'> &
60 PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & 72 PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> &
61 PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> 73 PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing>
62} 74}
63 75
64// ############################################################################ 76// ############################################################################
65 77
66export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | 78export type MUserNotification =
79 Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' |
67 'VideoImport' | 'Account' | 'ActorFollow'> 80 'VideoImport' | 'Account' | 'ActorFollow'>
68 81
69// ############################################################################ 82// ############################################################################
70 83
71export type UserNotificationModelForApi = MUserNotification & 84export type UserNotificationModelForApi =
85 MUserNotification &
72 Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & 86 Use<'Video', UserNotificationIncludes.VideoIncludeChannel> &
73 Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & 87 Use<'Comment', UserNotificationIncludes.VideoCommentInclude> &
74 Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & 88 Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> &
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts
index 6ac19c20b..31cf075ef 100644
--- a/server/typings/models/user/user.ts
+++ b/server/typings/models/user/user.ts
@@ -29,36 +29,44 @@ export type MUserId = Pick<UserModel, 'id'>
29 29
30// With account 30// With account
31 31
32export type MUserAccountId = MUser & 32export type MUserAccountId =
33 MUser &
33 Use<'Account', MAccountId> 34 Use<'Account', MAccountId>
34 35
35export type MUserAccountUrl = MUser & 36export type MUserAccountUrl =
37 MUser &
36 Use<'Account', MAccountUrl & MAccountIdActorId> 38 Use<'Account', MAccountUrl & MAccountIdActorId>
37 39
38export type MUserAccount = MUser & 40export type MUserAccount =
41 MUser &
39 Use<'Account', MAccount> 42 Use<'Account', MAccount>
40 43
41export type MUserAccountDefault = MUser & 44export type MUserAccountDefault =
45 MUser &
42 Use<'Account', MAccountDefault> 46 Use<'Account', MAccountDefault>
43 47
44// With channel 48// With channel
45 49
46export type MUserNotifSettingChannelDefault = MUser & 50export type MUserNotifSettingChannelDefault =
51 MUser &
47 Use<'NotificationSetting', MNotificationSetting> & 52 Use<'NotificationSetting', MNotificationSetting> &
48 Use<'Account', MAccountDefaultChannelDefault> 53 Use<'Account', MAccountDefaultChannelDefault>
49 54
50// With notification settings 55// With notification settings
51 56
52export type MUserWithNotificationSetting = MUser & 57export type MUserWithNotificationSetting =
58 MUser &
53 Use<'NotificationSetting', MNotificationSetting> 59 Use<'NotificationSetting', MNotificationSetting>
54 60
55export type MUserNotifSettingAccount = MUser & 61export type MUserNotifSettingAccount =
62 MUser &
56 Use<'NotificationSetting', MNotificationSetting> & 63 Use<'NotificationSetting', MNotificationSetting> &
57 Use<'Account', MAccount> 64 Use<'Account', MAccount>
58 65
59// Default scope 66// Default scope
60 67
61export type MUserDefault = MUser & 68export type MUserDefault =
69 MUser &
62 Use<'NotificationSetting', MNotificationSetting> & 70 Use<'NotificationSetting', MNotificationSetting> &
63 Use<'Account', MAccountDefault> 71 Use<'Account', MAccountDefault>
64 72
@@ -67,12 +75,15 @@ export type MUserDefault = MUser &
67// Format for API or AP object 75// Format for API or AP object
68 76
69type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]> 77type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>
70type MAccountWithChannelsAndSpecialPlaylists = MAccountWithChannels & 78type MAccountWithChannelsAndSpecialPlaylists =
79 MAccountWithChannels &
71 PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]> 80 PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]>
72 81
73export type MUserFormattable = MUserQuotaUsed & 82export type MUserFormattable =
83 MUserQuotaUsed &
74 Use<'Account', MAccountWithChannels> & 84 Use<'Account', MAccountWithChannels> &
75 PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> 85 PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable>
76 86
77export type MMyUserFormattable = MUserFormattable & 87export type MMyUserFormattable =
88 MUserFormattable &
78 Use<'Account', MAccountWithChannelsAndSpecialPlaylists> 89 Use<'Account', MAccountWithChannelsAndSpecialPlaylists>
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts
index e6f478cdf..95a53d139 100644
--- a/server/typings/models/video/schedule-video-update.ts
+++ b/server/typings/models/video/schedule-video-update.ts
@@ -10,7 +10,8 @@ export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'>
10 10
11// ############################################################################ 11// ############################################################################
12 12
13export type MScheduleVideoUpdateVideoAll = MScheduleVideoUpdate & 13export type MScheduleVideoUpdateVideoAll =
14 MScheduleVideoUpdate &
14 Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight> 15 Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight>
15 16
16// Format for API or AP object 17// Format for API or AP object
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts
index e38c3f586..955ec4780 100644
--- a/server/typings/models/video/video-abuse.ts
+++ b/server/typings/models/video/video-abuse.ts
@@ -13,11 +13,13 @@ export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivit
13 13
14export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> 14export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'>
15 15
16export type MVideoAbuseVideo = MVideoAbuse & 16export type MVideoAbuseVideo =
17 MVideoAbuse &
17 Pick<VideoAbuseModel, 'toActivityPubObject'> & 18 Pick<VideoAbuseModel, 'toActivityPubObject'> &
18 Use<'Video', MVideo> 19 Use<'Video', MVideo>
19 20
20export type MVideoAbuseAccountVideo = MVideoAbuse & 21export type MVideoAbuseAccountVideo =
22 MVideoAbuse &
21 Pick<VideoAbuseModel, 'toActivityPubObject'> & 23 Pick<VideoAbuseModel, 'toActivityPubObject'> &
22 Use<'Video', MVideo> & 24 Use<'Video', MVideo> &
23 Use<'Account', MAccountDefault> 25 Use<'Account', MAccountDefault>
@@ -26,6 +28,7 @@ export type MVideoAbuseAccountVideo = MVideoAbuse &
26 28
27// Format for API or AP object 29// Format for API or AP object
28 30
29export type MVideoAbuseFormattable = MVideoAbuse & 31export type MVideoAbuseFormattable =
32 MVideoAbuse &
30 Use<'Account', MAccountFormattable> & 33 Use<'Account', MAccountFormattable> &
31 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> 34 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>>
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts
index 7122a9dc0..ddb4db832 100644
--- a/server/typings/models/video/video-blacklist.ts
+++ b/server/typings/models/video/video-blacklist.ts
@@ -13,15 +13,18 @@ export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'>
13 13
14// ############################################################################ 14// ############################################################################
15 15
16export type MVideoBlacklistLightVideo = MVideoBlacklistLight & 16export type MVideoBlacklistLightVideo =
17 MVideoBlacklistLight &
17 Use<'Video', MVideo> 18 Use<'Video', MVideo>
18 19
19export type MVideoBlacklistVideo = MVideoBlacklist & 20export type MVideoBlacklistVideo =
21 MVideoBlacklist &
20 Use<'Video', MVideo> 22 Use<'Video', MVideo>
21 23
22// ############################################################################ 24// ############################################################################
23 25
24// Format for API or AP object 26// Format for API or AP object
25 27
26export type MVideoBlacklistFormattable = MVideoBlacklist & 28export type MVideoBlacklistFormattable =
29 MVideoBlacklist &
27 Use<'Video', MVideoFormattable> 30 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts
index ffa56f544..e7aff6956 100644
--- a/server/typings/models/video/video-caption.ts
+++ b/server/typings/models/video/video-caption.ts
@@ -11,14 +11,17 @@ export type MVideoCaption = Omit<VideoCaptionModel, 'Video'>
11// ############################################################################ 11// ############################################################################
12 12
13export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> 13export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'>
14export type MVideoCaptionLanguageUrl = Pick<MVideoCaption, 'language' | 'fileUrl' | 'getFileUrl'>
14 15
15export type MVideoCaptionVideo = MVideoCaption & 16export type MVideoCaptionVideo =
17 MVideoCaption &
16 Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> 18 Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>>
17 19
18// ############################################################################ 20// ############################################################################
19 21
20// Format for API or AP object 22// Format for API or AP object
21 23
22export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> & 24export type MVideoCaptionFormattable =
25 FunctionProperties<MVideoCaption> &
23 Pick<MVideoCaption, 'language'> & 26 Pick<MVideoCaption, 'language'> &
24 Use<'Video', MVideoUUID> 27 Use<'Video', MVideoUUID>
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts
index e5b5bbc1d..971dc3db5 100644
--- a/server/typings/models/video/video-change-ownership.ts
+++ b/server/typings/models/video/video-change-ownership.ts
@@ -9,7 +9,8 @@ type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwn
9 9
10export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> 10export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'>
11 11
12export type MVideoChangeOwnershipFull = MVideoChangeOwnership & 12export type MVideoChangeOwnershipFull =
13 MVideoChangeOwnership &
13 Use<'Initiator', MAccountDefault> & 14 Use<'Initiator', MAccountDefault> &
14 Use<'NextOwner', MAccountDefault> & 15 Use<'NextOwner', MAccountDefault> &
15 Use<'Video', MVideoWithAllFiles> 16 Use<'Video', MVideoWithAllFiles>
@@ -18,7 +19,8 @@ export type MVideoChangeOwnershipFull = MVideoChangeOwnership &
18 19
19// Format for API or AP object 20// Format for API or AP object
20 21
21export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & 22export type MVideoChangeOwnershipFormattable =
23 Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> &
22 Use<'Initiator', MAccountFormattable> & 24 Use<'Initiator', MAccountFormattable> &
23 Use<'NextOwner', MAccountFormattable> & 25 Use<'NextOwner', MAccountFormattable> &
24 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> 26 Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>>
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts
index 292d0ac95..50f7c2d8a 100644
--- a/server/typings/models/video/video-channels.ts
+++ b/server/typings/models/video/video-channels.ts
@@ -35,32 +35,39 @@ export type MChannelId = Pick<MChannel, 'id'>
35 35
36// ############################################################################ 36// ############################################################################
37 37
38export type MChannelIdActor = MChannelId & 38export type MChannelIdActor =
39 MChannelId &
39 Use<'Actor', MActorAccountChannelId> 40 Use<'Actor', MActorAccountChannelId>
40 41
41export type MChannelUserId = Pick<MChannel, 'accountId'> & 42export type MChannelUserId =
43 Pick<MChannel, 'accountId'> &
42 Use<'Account', MAccountUserId> 44 Use<'Account', MAccountUserId>
43 45
44export type MChannelActor = MChannel & 46export type MChannelActor =
47 MChannel &
45 Use<'Actor', MActor> 48 Use<'Actor', MActor>
46 49
47export type MChannelUrl = Use<'Actor', MActorUrl> 50export type MChannelUrl = Use<'Actor', MActorUrl>
48 51
49// Default scope 52// Default scope
50export type MChannelDefault = MChannel & 53export type MChannelDefault =
54 MChannel &
51 Use<'Actor', MActorDefault> 55 Use<'Actor', MActorDefault>
52 56
53// ############################################################################ 57// ############################################################################
54 58
55// Not all association attributes 59// Not all association attributes
56 60
57export type MChannelLight = MChannel & 61export type MChannelLight =
62 MChannel &
58 Use<'Actor', MActorDefaultLight> 63 Use<'Actor', MActorDefaultLight>
59 64
60export type MChannelActorLight = MChannel & 65export type MChannelActorLight =
66 MChannel &
61 Use<'Actor', MActorLight> 67 Use<'Actor', MActorLight>
62 68
63export type MChannelAccountLight = MChannel & 69export type MChannelAccountLight =
70 MChannel &
64 Use<'Actor', MActorDefaultLight> & 71 Use<'Actor', MActorDefaultLight> &
65 Use<'Account', MAccountLight> 72 Use<'Account', MAccountLight>
66 73
@@ -68,24 +75,29 @@ export type MChannelAccountLight = MChannel &
68 75
69// Account associations 76// Account associations
70 77
71export type MChannelAccountActor = MChannel & 78export type MChannelAccountActor =
79 MChannel &
72 Use<'Account', MAccountActor> 80 Use<'Account', MAccountActor>
73 81
74export type MChannelAccountDefault = MChannel & 82export type MChannelAccountDefault =
83 MChannel &
75 Use<'Actor', MActorDefault> & 84 Use<'Actor', MActorDefault> &
76 Use<'Account', MAccountDefault> 85 Use<'Account', MAccountDefault>
77 86
78export type MChannelActorAccountActor = MChannel & 87export type MChannelActorAccountActor =
88 MChannel &
79 Use<'Account', MAccountActor> & 89 Use<'Account', MAccountActor> &
80 Use<'Actor', MActor> 90 Use<'Actor', MActor>
81 91
82// ############################################################################ 92// ############################################################################
83 93
84// Videos associations 94// Videos associations
85export type MChannelVideos = MChannel & 95export type MChannelVideos =
96 MChannel &
86 Use<'Videos', MVideo[]> 97 Use<'Videos', MVideo[]>
87 98
88export type MChannelActorAccountDefaultVideos = MChannel & 99export type MChannelActorAccountDefaultVideos =
100 MChannel &
89 Use<'Actor', MActorDefault> & 101 Use<'Actor', MActorDefault> &
90 Use<'Account', MAccountDefault> & 102 Use<'Account', MAccountDefault> &
91 Use<'Videos', MVideo[]> 103 Use<'Videos', MVideo[]>
@@ -94,14 +106,17 @@ export type MChannelActorAccountDefaultVideos = MChannel &
94 106
95// For API 107// For API
96 108
97export type MChannelSummary = FunctionProperties<MChannel> & 109export type MChannelSummary =
110 FunctionProperties<MChannel> &
98 Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & 111 Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> &
99 Use<'Actor', MActorSummary> 112 Use<'Actor', MActorSummary>
100 113
101export type MChannelSummaryAccount = MChannelSummary & 114export type MChannelSummaryAccount =
115 MChannelSummary &
102 Use<'Account', MAccountSummaryBlocks> 116 Use<'Account', MAccountSummaryBlocks>
103 117
104export type MChannelAPI = MChannel & 118export type MChannelAPI =
119 MChannel &
105 Use<'Actor', MActorAPI> & 120 Use<'Actor', MActorAPI> &
106 Use<'Account', MAccountAPI> 121 Use<'Account', MAccountAPI>
107 122
@@ -109,18 +124,22 @@ export type MChannelAPI = MChannel &
109 124
110// Format for API or AP object 125// Format for API or AP object
111 126
112export type MChannelSummaryFormattable = FunctionProperties<MChannel> & 127export type MChannelSummaryFormattable =
128 FunctionProperties<MChannel> &
113 Pick<MChannel, 'id' | 'name'> & 129 Pick<MChannel, 'id' | 'name'> &
114 Use<'Actor', MActorSummaryFormattable> 130 Use<'Actor', MActorSummaryFormattable>
115 131
116export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & 132export type MChannelAccountSummaryFormattable =
133 MChannelSummaryFormattable &
117 Use<'Account', MAccountSummaryFormattable> 134 Use<'Account', MAccountSummaryFormattable>
118 135
119export type MChannelFormattable = FunctionProperties<MChannel> & 136export type MChannelFormattable =
137 FunctionProperties<MChannel> &
120 Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & 138 Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> &
121 Use<'Actor', MActorFormattable> & 139 Use<'Actor', MActorFormattable> &
122 PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> 140 PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable>
123 141
124export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> & 142export type MChannelAP =
143 Pick<MChannel, 'name' | 'description' | 'support'> &
125 Use<'Actor', MActorAP> & 144 Use<'Actor', MActorAP> &
126 Use<'Account', MAccountUrl> 145 Use<'Account', MAccountUrl>
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts
index d693f9186..d6e0b66f5 100644
--- a/server/typings/models/video/video-comment.ts
+++ b/server/typings/models/video/video-comment.ts
@@ -14,30 +14,37 @@ export type MCommentUrl = Pick<MComment, 'url'>
14 14
15// ############################################################################ 15// ############################################################################
16 16
17export type MCommentOwner = MComment & 17export type MCommentOwner =
18 MComment &
18 Use<'Account', MAccountDefault> 19 Use<'Account', MAccountDefault>
19 20
20export type MCommentVideo = MComment & 21export type MCommentVideo =
22 MComment &
21 Use<'Video', MVideoAccountLight> 23 Use<'Video', MVideoAccountLight>
22 24
23export type MCommentReply = MComment & 25export type MCommentReply =
26 MComment &
24 Use<'InReplyToVideoComment', MComment> 27 Use<'InReplyToVideoComment', MComment>
25 28
26export type MCommentOwnerVideo = MComment & 29export type MCommentOwnerVideo =
30 MComment &
27 Use<'Account', MAccountDefault> & 31 Use<'Account', MAccountDefault> &
28 Use<'Video', MVideoAccountLight> 32 Use<'Video', MVideoAccountLight>
29 33
30export type MCommentOwnerVideoReply = MComment & 34export type MCommentOwnerVideoReply =
35 MComment &
31 Use<'Account', MAccountDefault> & 36 Use<'Account', MAccountDefault> &
32 Use<'Video', MVideoAccountLight> & 37 Use<'Video', MVideoAccountLight> &
33 Use<'InReplyToVideoComment', MComment> 38 Use<'InReplyToVideoComment', MComment>
34 39
35export type MCommentOwnerReplyVideoLight = MComment & 40export type MCommentOwnerReplyVideoLight =
41 MComment &
36 Use<'Account', MAccountDefault> & 42 Use<'Account', MAccountDefault> &
37 Use<'InReplyToVideoComment', MComment> & 43 Use<'InReplyToVideoComment', MComment> &
38 Use<'Video', MVideoIdUrl> 44 Use<'Video', MVideoIdUrl>
39 45
40export type MCommentOwnerVideoFeed = MCommentOwner & 46export type MCommentOwnerVideoFeed =
47 MCommentOwner &
41 Use<'Video', MVideoFeed> 48 Use<'Video', MVideoFeed>
42 49
43// ############################################################################ 50// ############################################################################
@@ -48,10 +55,12 @@ export type MCommentAPI = MComment & { totalReplies: number }
48 55
49// Format for API or AP object 56// Format for API or AP object
50 57
51export type MCommentFormattable = MCommentTotalReplies & 58export type MCommentFormattable =
59 MCommentTotalReplies &
52 Use<'Account', MAccountFormattable> 60 Use<'Account', MAccountFormattable>
53 61
54export type MCommentAP = MComment & 62export type MCommentAP =
63 MComment &
55 Use<'Account', MAccountUrl> & 64 Use<'Account', MAccountUrl> &
56 PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & 65 PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> &
57 PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> 66 PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl>
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts
index 352fe3d32..3fcaca78f 100644
--- a/server/typings/models/video/video-file.ts
+++ b/server/typings/models/video/video-file.ts
@@ -1,7 +1,7 @@
1import { VideoFileModel } from '../../../models/video/video-file' 1import { VideoFileModel } from '../../../models/video/video-file'
2import { PickWith, PickWithOpt } from '../../utils' 2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideo, MVideoUUID } from './video' 3import { MVideo, MVideoUUID } from './video'
4import { MVideoRedundancyFileUrl } from './video-redundancy' 4import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy'
5import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist' 5import { MStreamingPlaylistVideo, MStreamingPlaylist } 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>
@@ -10,19 +10,28 @@ type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M>
10 10
11export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'> 11export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'>
12 12
13export type MVideoFileVideo = MVideoFile & 13export type MVideoFileVideo =
14 MVideoFile &
14 Use<'Video', MVideo> 15 Use<'Video', MVideo>
15 16
16export type MVideoFileStreamingPlaylist = MVideoFile & 17export type MVideoFileStreamingPlaylist =
18 MVideoFile &
17 Use<'VideoStreamingPlaylist', MStreamingPlaylist> 19 Use<'VideoStreamingPlaylist', MStreamingPlaylist>
18 20
19export type MVideoFileStreamingPlaylistVideo = MVideoFile & 21export type MVideoFileStreamingPlaylistVideo =
22 MVideoFile &
20 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 23 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
21 24
22export type MVideoFileVideoUUID = MVideoFile & 25export type MVideoFileVideoUUID =
26 MVideoFile &
23 Use<'Video', MVideoUUID> 27 Use<'Video', MVideoUUID>
24 28
25export type MVideoFileRedundanciesOpt = MVideoFile & 29export type MVideoFileRedundanciesAll =
30 MVideoFile &
31 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancy[]>
32
33export type MVideoFileRedundanciesOpt =
34 MVideoFile &
26 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> 35 PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
27 36
28export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist { 37export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist {
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts
index e119f17f9..4e5c2e4f0 100644
--- a/server/typings/models/video/video-import.ts
+++ b/server/typings/models/video/video-import.ts
@@ -9,18 +9,21 @@ type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M>
9 9
10export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> 10export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'>
11 11
12export type MVideoImportVideo = MVideoImport & 12export type MVideoImportVideo =
13 MVideoImport &
13 Use<'Video', MVideo> 14 Use<'Video', MVideo>
14 15
15// ############################################################################ 16// ############################################################################
16 17
17type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail 18type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail
18 19
19export type MVideoImportDefault = MVideoImport & 20export type MVideoImportDefault =
21 MVideoImport &
20 Use<'User', MUser> & 22 Use<'User', MUser> &
21 Use<'Video', VideoAssociation> 23 Use<'Video', VideoAssociation>
22 24
23export type MVideoImportDefaultFiles = MVideoImport & 25export type MVideoImportDefaultFiles =
26 MVideoImport &
24 Use<'User', MUser> & 27 Use<'User', MUser> &
25 Use<'Video', VideoAssociation & MVideoWithFile> 28 Use<'Video', VideoAssociation & MVideoWithFile>
26 29
@@ -28,5 +31,6 @@ export type MVideoImportDefaultFiles = MVideoImport &
28 31
29// Format for API or AP object 32// Format for API or AP object
30 33
31export type MVideoImportFormattable = MVideoImport & 34export type MVideoImportFormattable =
35 MVideoImport &
32 PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> 36 PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag>
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts
index 1aeff78d8..f33c76594 100644
--- a/server/typings/models/video/video-playlist-element.ts
+++ b/server/typings/models/video/video-playlist-element.ts
@@ -17,10 +17,12 @@ export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'vid
17 17
18// ############################################################################ 18// ############################################################################
19 19
20export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement & 20export type MVideoPlaylistVideoThumbnail =
21 MVideoPlaylistElement &
21 Use<'Video', MVideoThumbnail> 22 Use<'Video', MVideoThumbnail>
22 23
23export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement & 24export type MVideoPlaylistElementVideoUrlPlaylistPrivacy =
25 MVideoPlaylistElement &
24 Use<'Video', MVideoUrl> & 26 Use<'Video', MVideoUrl> &
25 Use<'VideoPlaylist', MVideoPlaylistPrivacy> 27 Use<'VideoPlaylist', MVideoPlaylistPrivacy>
26 28
@@ -28,8 +30,10 @@ export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement
28 30
29// Format for API or AP object 31// Format for API or AP object
30 32
31export type MVideoPlaylistElementFormattable = MVideoPlaylistElement & 33export type MVideoPlaylistElementFormattable =
34 MVideoPlaylistElement &
32 Use<'Video', MVideoFormattable> 35 Use<'Video', MVideoFormattable>
33 36
34export type MVideoPlaylistElementAP = MVideoPlaylistElement & 37export type MVideoPlaylistElementAP =
38 MVideoPlaylistElement &
35 Use<'Video', MVideoUrl> 39 Use<'Video', MVideoUrl>
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts
index a40c7aca9..49c27f4a7 100644
--- a/server/typings/models/video/video-playlist.ts
+++ b/server/typings/models/video/video-playlist.ts
@@ -22,30 +22,36 @@ export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: numbe
22 22
23// With elements 23// With elements
24 24
25export type MVideoPlaylistWithElements = MVideoPlaylist & 25export type MVideoPlaylistWithElements =
26 MVideoPlaylist &
26 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> 27 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
27 28
28export type MVideoPlaylistIdWithElements = MVideoPlaylistId & 29export type MVideoPlaylistIdWithElements =
30 MVideoPlaylistId &
29 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> 31 Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]>
30 32
31// ############################################################################ 33// ############################################################################
32 34
33// With account 35// With account
34 36
35export type MVideoPlaylistOwner = MVideoPlaylist & 37export type MVideoPlaylistOwner =
38 MVideoPlaylist &
36 Use<'OwnerAccount', MAccount> 39 Use<'OwnerAccount', MAccount>
37 40
38export type MVideoPlaylistOwnerDefault = MVideoPlaylist & 41export type MVideoPlaylistOwnerDefault =
42 MVideoPlaylist &
39 Use<'OwnerAccount', MAccountDefault> 43 Use<'OwnerAccount', MAccountDefault>
40 44
41// ############################################################################ 45// ############################################################################
42 46
43// With thumbnail 47// With thumbnail
44 48
45export type MVideoPlaylistThumbnail = MVideoPlaylist & 49export type MVideoPlaylistThumbnail =
50 MVideoPlaylist &
46 Use<'Thumbnail', MThumbnail> 51 Use<'Thumbnail', MThumbnail>
47 52
48export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & 53export type MVideoPlaylistAccountThumbnail =
54 MVideoPlaylist &
49 Use<'OwnerAccount', MAccountDefault> & 55 Use<'OwnerAccount', MAccountDefault> &
50 Use<'Thumbnail', MThumbnail> 56 Use<'Thumbnail', MThumbnail>
51 57
@@ -53,7 +59,8 @@ export type MVideoPlaylistAccountThumbnail = MVideoPlaylist &
53 59
54// With channel 60// With channel
55 61
56export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & 62export type MVideoPlaylistAccountChannelDefault =
63 MVideoPlaylist &
57 Use<'OwnerAccount', MAccountDefault> & 64 Use<'OwnerAccount', MAccountDefault> &
58 Use<'VideoChannel', MChannelDefault> 65 Use<'VideoChannel', MChannelDefault>
59 66
@@ -61,7 +68,8 @@ export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist &
61 68
62// With all associations 69// With all associations
63 70
64export type MVideoPlaylistFull = MVideoPlaylist & 71export type MVideoPlaylistFull =
72 MVideoPlaylist &
65 Use<'OwnerAccount', MAccountDefault> & 73 Use<'OwnerAccount', MAccountDefault> &
66 Use<'VideoChannel', MChannelDefault> & 74 Use<'VideoChannel', MChannelDefault> &
67 Use<'Thumbnail', MThumbnail> 75 Use<'Thumbnail', MThumbnail>
@@ -70,11 +78,13 @@ export type MVideoPlaylistFull = MVideoPlaylist &
70 78
71// For API 79// For API
72 80
73export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist & 81export type MVideoPlaylistAccountChannelSummary =
82 MVideoPlaylist &
74 Use<'OwnerAccount', MAccountSummary> & 83 Use<'OwnerAccount', MAccountSummary> &
75 Use<'VideoChannel', MChannelSummary> 84 Use<'VideoChannel', MChannelSummary>
76 85
77export type MVideoPlaylistFullSummary = MVideoPlaylist & 86export type MVideoPlaylistFullSummary =
87 MVideoPlaylist &
78 Use<'Thumbnail', MThumbnail> & 88 Use<'Thumbnail', MThumbnail> &
79 Use<'OwnerAccount', MAccountSummary> & 89 Use<'OwnerAccount', MAccountSummary> &
80 Use<'VideoChannel', MChannelSummary> 90 Use<'VideoChannel', MChannelSummary>
@@ -83,10 +93,12 @@ export type MVideoPlaylistFullSummary = MVideoPlaylist &
83 93
84// Format for API or AP object 94// Format for API or AP object
85 95
86export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength & 96export type MVideoPlaylistFormattable =
97 MVideoPlaylistVideosLength &
87 Use<'OwnerAccount', MAccountSummaryFormattable> & 98 Use<'OwnerAccount', MAccountSummaryFormattable> &
88 Use<'VideoChannel', MChannelSummaryFormattable> 99 Use<'VideoChannel', MChannelSummaryFormattable>
89 100
90export type MVideoPlaylistAP = MVideoPlaylist & 101export type MVideoPlaylistAP =
102 MVideoPlaylist &
91 Use<'Thumbnail', MThumbnail> & 103 Use<'Thumbnail', MThumbnail> &
92 Use<'VideoChannel', MChannelUrl> 104 Use<'VideoChannel', MChannelUrl>
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts
index f6bb527fc..64ce4965b 100644
--- a/server/typings/models/video/video-rate.ts
+++ b/server/typings/models/video/video-rate.ts
@@ -9,10 +9,12 @@ type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateMo
9 9
10export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> 10export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'>
11 11
12export type MAccountVideoRateAccountUrl = MAccountVideoRate & 12export type MAccountVideoRateAccountUrl =
13 MAccountVideoRate &
13 Use<'Account', MAccountUrl> 14 Use<'Account', MAccountUrl>
14 15
15export type MAccountVideoRateAccountVideo = MAccountVideoRate & 16export type MAccountVideoRateAccountVideo =
17 MAccountVideoRate &
16 Use<'Account', MAccountAudience> & 18 Use<'Account', MAccountAudience> &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
18 20
@@ -20,5 +22,6 @@ export type MAccountVideoRateAccountVideo = MAccountVideoRate &
20 22
21// Format for API or AP object 23// Format for API or AP object
22 24
23export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> & 25export type MAccountVideoRateFormattable =
26 Pick<MAccountVideoRate, 'type'> &
24 Use<'Video', MVideoFormattable> 27 Use<'Video', MVideoFormattable>
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts
index 25bdac057..5107aa7f4 100644
--- a/server/typings/models/video/video-redundancy.ts
+++ b/server/typings/models/video/video-redundancy.ts
@@ -16,16 +16,20 @@ export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'>
16 16
17// ############################################################################ 17// ############################################################################
18 18
19export type MVideoRedundancyFile = MVideoRedundancy & 19export type MVideoRedundancyFile =
20 MVideoRedundancy &
20 Use<'VideoFile', MVideoFile> 21 Use<'VideoFile', MVideoFile>
21 22
22export type MVideoRedundancyFileVideo = MVideoRedundancy & 23export type MVideoRedundancyFileVideo =
24 MVideoRedundancy &
23 Use<'VideoFile', MVideoFileVideo> 25 Use<'VideoFile', MVideoFileVideo>
24 26
25export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy & 27export type MVideoRedundancyStreamingPlaylistVideo =
28 MVideoRedundancy &
26 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 29 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
27 30
28export type MVideoRedundancyVideo = MVideoRedundancy & 31export type MVideoRedundancyVideo =
32 MVideoRedundancy &
29 Use<'VideoFile', MVideoFileVideo> & 33 Use<'VideoFile', MVideoFileVideo> &
30 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> 34 Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo>
31 35
@@ -33,6 +37,7 @@ export type MVideoRedundancyVideo = MVideoRedundancy &
33 37
34// Format for API or AP object 38// Format for API or AP object
35 39
36export type MVideoRedundancyAP = MVideoRedundancy & 40export type MVideoRedundancyAP =
41 MVideoRedundancy &
37 PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & 42 PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> &
38 PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> 43 PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>>
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts
index a7a90beeb..50ca75d26 100644
--- a/server/typings/models/video/video-share.ts
+++ b/server/typings/models/video/video-share.ts
@@ -9,9 +9,11 @@ type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M>
9 9
10export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> 10export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'>
11 11
12export type MVideoShareActor = MVideoShare & 12export type MVideoShareActor =
13 MVideoShare &
13 Use<'Actor', MActorDefault> 14 Use<'Actor', MActorDefault>
14 15
15export type MVideoShareFull = MVideoShare & 16export type MVideoShareFull =
17 MVideoShare &
16 Use<'Actor', MActorDefault> & 18 Use<'Actor', MActorDefault> &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts
index 436c0c072..3f54aa560 100644
--- a/server/typings/models/video/video-streaming-playlist.ts
+++ b/server/typings/models/video/video-streaming-playlist.ts
@@ -1,6 +1,6 @@
1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' 1import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist'
2import { PickWith, PickWithOpt } from '../../utils' 2import { PickWith, PickWithOpt } from '../../utils'
3import { MVideoRedundancyFileUrl } from './video-redundancy' 3import { MVideoRedundancyFileUrl, MVideoRedundancy } from './video-redundancy'
4import { MVideo } from './video' 4import { MVideo } from './video'
5import { MVideoFile } from './video-file' 5import { MVideoFile } from './video-file'
6 6
@@ -10,21 +10,31 @@ type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreami
10 10
11export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'> 11export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'>
12 12
13export type MStreamingPlaylistFiles = MStreamingPlaylist & 13export type MStreamingPlaylistFiles =
14 MStreamingPlaylist &
14 Use<'VideoFiles', MVideoFile[]> 15 Use<'VideoFiles', MVideoFile[]>
15 16
16export type MStreamingPlaylistVideo = MStreamingPlaylist & 17export type MStreamingPlaylistVideo =
18 MStreamingPlaylist &
17 Use<'Video', MVideo> 19 Use<'Video', MVideo>
18 20
19export type MStreamingPlaylistFilesVideo = MStreamingPlaylist & 21export type MStreamingPlaylistFilesVideo =
22 MStreamingPlaylist &
20 Use<'VideoFiles', MVideoFile[]> & 23 Use<'VideoFiles', MVideoFile[]> &
21 Use<'Video', MVideo> 24 Use<'Video', MVideo>
22 25
23export type MStreamingPlaylistRedundancies = MStreamingPlaylist & 26export type MStreamingPlaylistRedundanciesAll =
27 MStreamingPlaylist &
28 Use<'VideoFiles', MVideoFile[]> &
29 Use<'RedundancyVideos', MVideoRedundancy[]>
30
31export type MStreamingPlaylistRedundancies =
32 MStreamingPlaylist &
24 Use<'VideoFiles', MVideoFile[]> & 33 Use<'VideoFiles', MVideoFile[]> &
25 Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> 34 Use<'RedundancyVideos', MVideoRedundancyFileUrl[]>
26 35
27export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & 36export type MStreamingPlaylistRedundanciesOpt =
37 MStreamingPlaylist &
28 Use<'VideoFiles', MVideoFile[]> & 38 Use<'VideoFiles', MVideoFile[]> &
29 PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> 39 PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]>
30 40
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts
index 7f69a91de..7eff0a913 100644
--- a/server/typings/models/video/video.ts
+++ b/server/typings/models/video/video.ts
@@ -9,9 +9,14 @@ import {
9 MChannelUserId 9 MChannelUserId
10} from './video-channels' 10} from './video-channels'
11import { MTag } from './tag' 11import { MTag } from './tag'
12import { MVideoCaptionLanguage } from './video-caption' 12import { MVideoCaptionLanguage, MVideoCaptionLanguageUrl } from './video-caption'
13import { MStreamingPlaylistFiles, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist' 13import {
14import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file' 14 MStreamingPlaylistFiles,
15 MStreamingPlaylistRedundancies,
16 MStreamingPlaylistRedundanciesAll,
17 MStreamingPlaylistRedundanciesOpt
18} from './video-streaming-playlist'
19import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file'
15import { MThumbnail } from './thumbnail' 20import { MThumbnail } from './thumbnail'
16import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' 21import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist'
17import { MScheduleVideoUpdate } from './schedule-video-update' 22import { MScheduleVideoUpdate } from './schedule-video-update'
@@ -21,7 +26,8 @@ type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M>
21 26
22// ############################################################################ 27// ############################################################################
23 28
24export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | 29export type MVideo =
30 Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' |
25 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | 31 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' |
26 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> 32 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'>
27 33
@@ -39,50 +45,63 @@ export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'>
39// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists 45// Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists
40 46
41// "With" to not confuse with the VideoFile model 47// "With" to not confuse with the VideoFile model
42export type MVideoWithFile = MVideo & 48export type MVideoWithFile =
49 MVideo &
43 Use<'VideoFiles', MVideoFile[]> & 50 Use<'VideoFiles', MVideoFile[]> &
44 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 51 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
45 52
46export type MVideoThumbnail = MVideo & 53export type MVideoThumbnail =
54 MVideo &
47 Use<'Thumbnails', MThumbnail[]> 55 Use<'Thumbnails', MThumbnail[]>
48 56
49export type MVideoIdThumbnail = MVideoId & 57export type MVideoIdThumbnail =
58 MVideoId &
50 Use<'Thumbnails', MThumbnail[]> 59 Use<'Thumbnails', MThumbnail[]>
51 60
52export type MVideoWithFileThumbnail = MVideo & 61export type MVideoWithFileThumbnail =
62 MVideo &
53 Use<'VideoFiles', MVideoFile[]> & 63 Use<'VideoFiles', MVideoFile[]> &
54 Use<'Thumbnails', MThumbnail[]> 64 Use<'Thumbnails', MThumbnail[]>
55 65
56export type MVideoThumbnailBlacklist = MVideo & 66export type MVideoThumbnailBlacklist =
67 MVideo &
57 Use<'Thumbnails', MThumbnail[]> & 68 Use<'Thumbnails', MThumbnail[]> &
58 Use<'VideoBlacklist', MVideoBlacklistLight> 69 Use<'VideoBlacklist', MVideoBlacklistLight>
59 70
60export type MVideoTag = MVideo & 71export type MVideoTag =
72 MVideo &
61 Use<'Tags', MTag[]> 73 Use<'Tags', MTag[]>
62 74
63export type MVideoWithSchedule = MVideo & 75export type MVideoWithSchedule =
76 MVideo &
64 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> 77 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate>
65 78
66export type MVideoWithCaptions = MVideo & 79export type MVideoWithCaptions =
80 MVideo &
67 Use<'VideoCaptions', MVideoCaptionLanguage[]> 81 Use<'VideoCaptions', MVideoCaptionLanguage[]>
68 82
69export type MVideoWithStreamingPlaylist = MVideo & 83export type MVideoWithStreamingPlaylist =
84 MVideo &
70 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 85 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
71 86
72// ############################################################################ 87// ############################################################################
73 88
74// Associations with not all their attributes 89// Associations with not all their attributes
75 90
76export type MVideoUserHistory = MVideo & 91export type MVideoUserHistory =
92 MVideo &
77 Use<'UserVideoHistories', MUserVideoHistoryTime[]> 93 Use<'UserVideoHistories', MUserVideoHistoryTime[]>
78 94
79export type MVideoWithBlacklistLight = MVideo & 95export type MVideoWithBlacklistLight =
96 MVideo &
80 Use<'VideoBlacklist', MVideoBlacklistLight> 97 Use<'VideoBlacklist', MVideoBlacklistLight>
81 98
82export type MVideoAccountLight = MVideo & 99export type MVideoAccountLight =
100 MVideo &
83 Use<'VideoChannel', MChannelAccountLight> 101 Use<'VideoChannel', MChannelAccountLight>
84 102
85export type MVideoWithRights = MVideo & 103export type MVideoWithRights =
104 MVideo &
86 Use<'VideoBlacklist', MVideoBlacklistLight> & 105 Use<'VideoBlacklist', MVideoBlacklistLight> &
87 Use<'Thumbnails', MThumbnail[]> & 106 Use<'Thumbnails', MThumbnail[]> &
88 Use<'VideoChannel', MChannelUserId> 107 Use<'VideoChannel', MChannelUserId>
@@ -91,12 +110,14 @@ export type MVideoWithRights = MVideo &
91 110
92// All files with some additional associations 111// All files with some additional associations
93 112
94export type MVideoWithAllFiles = MVideo & 113export type MVideoWithAllFiles =
114 MVideo &
95 Use<'VideoFiles', MVideoFile[]> & 115 Use<'VideoFiles', MVideoFile[]> &
96 Use<'Thumbnails', MThumbnail[]> & 116 Use<'Thumbnails', MThumbnail[]> &
97 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> 117 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]>
98 118
99export type MVideoAccountLightBlacklistAllFiles = MVideo & 119export type MVideoAccountLightBlacklistAllFiles =
120 MVideo &
100 Use<'VideoFiles', MVideoFile[]> & 121 Use<'VideoFiles', MVideoFile[]> &
101 Use<'Thumbnails', MThumbnail[]> & 122 Use<'Thumbnails', MThumbnail[]> &
102 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & 123 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
@@ -107,17 +128,21 @@ export type MVideoAccountLightBlacklistAllFiles = MVideo &
107 128
108// With account 129// With account
109 130
110export type MVideoAccountDefault = MVideo & 131export type MVideoAccountDefault =
132 MVideo &
111 Use<'VideoChannel', MChannelAccountDefault> 133 Use<'VideoChannel', MChannelAccountDefault>
112 134
113export type MVideoThumbnailAccountDefault = MVideo & 135export type MVideoThumbnailAccountDefault =
136 MVideo &
114 Use<'Thumbnails', MThumbnail[]> & 137 Use<'Thumbnails', MThumbnail[]> &
115 Use<'VideoChannel', MChannelAccountDefault> 138 Use<'VideoChannel', MChannelAccountDefault>
116 139
117export type MVideoWithChannelActor = MVideo & 140export type MVideoWithChannelActor =
141 MVideo &
118 Use<'VideoChannel', MChannelActor> 142 Use<'VideoChannel', MChannelActor>
119 143
120export type MVideoFullLight = MVideo & 144export type MVideoFullLight =
145 MVideo &
121 Use<'Thumbnails', MThumbnail[]> & 146 Use<'Thumbnails', MThumbnail[]> &
122 Use<'VideoBlacklist', MVideoBlacklistLight> & 147 Use<'VideoBlacklist', MVideoBlacklistLight> &
123 Use<'Tags', MTag[]> & 148 Use<'Tags', MTag[]> &
@@ -131,18 +156,20 @@ export type MVideoFullLight = MVideo &
131 156
132// API 157// API
133 158
134export type MVideoAP = MVideo & 159export type MVideoAP =
160 MVideo &
135 Use<'Tags', MTag[]> & 161 Use<'Tags', MTag[]> &
136 Use<'VideoChannel', MChannelAccountLight> & 162 Use<'VideoChannel', MChannelAccountLight> &
137 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & 163 Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> &
138 Use<'VideoCaptions', MVideoCaptionLanguage[]> & 164 Use<'VideoCaptions', MVideoCaptionLanguageUrl[]> &
139 Use<'VideoBlacklist', MVideoBlacklistUnfederated> & 165 Use<'VideoBlacklist', MVideoBlacklistUnfederated> &
140 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> & 166 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> &
141 Use<'Thumbnails', MThumbnail[]> 167 Use<'Thumbnails', MThumbnail[]>
142 168
143export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> 169export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'>
144 170
145export type MVideoDetails = MVideo & 171export type MVideoDetails =
172 MVideo &
146 Use<'VideoBlacklist', MVideoBlacklistLight> & 173 Use<'VideoBlacklist', MVideoBlacklistLight> &
147 Use<'Tags', MTag[]> & 174 Use<'Tags', MTag[]> &
148 Use<'VideoChannel', MChannelAccountLight> & 175 Use<'VideoChannel', MChannelAccountLight> &
@@ -152,23 +179,31 @@ export type MVideoDetails = MVideo &
152 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & 179 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> &
153 Use<'VideoFiles', MVideoFileRedundanciesOpt[]> 180 Use<'VideoFiles', MVideoFileRedundanciesOpt[]>
154 181
155export type MVideoForUser = MVideo & 182export type MVideoForUser =
183 MVideo &
156 Use<'VideoChannel', MChannelAccountDefault> & 184 Use<'VideoChannel', MChannelAccountDefault> &
157 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & 185 Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> &
158 Use<'VideoBlacklist', MVideoBlacklistLight> & 186 Use<'VideoBlacklist', MVideoBlacklistLight> &
159 Use<'Thumbnails', MThumbnail[]> 187 Use<'Thumbnails', MThumbnail[]>
160 188
189export type MVideoForRedundancyAPI =
190 MVideo &
191 Use<'VideoFiles', MVideoFileRedundanciesAll[]> &
192 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesAll[]>
193
161// ############################################################################ 194// ############################################################################
162 195
163// Format for API or AP object 196// Format for API or AP object
164 197
165export type MVideoFormattable = MVideo & 198export type MVideoFormattable =
199 MVideo &
166 PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & 200 PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> &
167 Use<'VideoChannel', MChannelAccountSummaryFormattable> & 201 Use<'VideoChannel', MChannelAccountSummaryFormattable> &
168 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & 202 PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> &
169 PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> 203 PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>>
170 204
171export type MVideoFormattableDetails = MVideoFormattable & 205export type MVideoFormattableDetails =
206 MVideoFormattable &
172 Use<'VideoChannel', MChannelFormattable> & 207 Use<'VideoChannel', MChannelFormattable> &
173 Use<'Tags', MTag[]> & 208 Use<'Tags', MTag[]> &
174 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & 209 Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> &
diff --git a/server/typings/utils.ts b/server/typings/utils.ts
index 24d43b258..55500d8c4 100644
--- a/server/typings/utils.ts
+++ b/server/typings/utils.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/array-type */
2
1export type FunctionPropertyNames<T> = { 3export type FunctionPropertyNames<T> = {
2 [K in keyof T]: T[K] extends Function ? K : never 4 [K in keyof T]: T[K] extends Function ? K : never
3}[keyof T] 5}[keyof T]
diff --git a/shared/core-utils/miscs/miscs.ts b/shared/core-utils/miscs/miscs.ts
index 5de024c08..1eee22d82 100644
--- a/shared/core-utils/miscs/miscs.ts
+++ b/shared/core-utils/miscs/miscs.ts
@@ -11,7 +11,7 @@ function compareSemVer (a: string, b: string) {
11 const l = Math.min(segmentsA.length, segmentsB.length) 11 const l = Math.min(segmentsA.length, segmentsB.length)
12 12
13 for (let i = 0; i < l; i++) { 13 for (let i = 0; i < l; i++) {
14 const diff = parseInt(segmentsA[ i ], 10) - parseInt(segmentsB[ i ], 10) 14 const diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10)
15 15
16 if (diff) return diff 16 if (diff) return diff
17 } 17 }
diff --git a/shared/extra-utils/instances-index/mock-instances-index.ts b/shared/extra-utils/instances-index/mock-instances-index.ts
index cfa4523c1..c58e8bcf8 100644
--- a/shared/extra-utils/instances-index/mock-instances-index.ts
+++ b/shared/extra-utils/instances-index/mock-instances-index.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2 2
3export class MockInstancesIndex { 3export class MockInstancesIndex {
4 private indexInstances: { host: string, createdAt: string }[] = [] 4 private readonly indexInstances: { host: string, createdAt: string }[] = []
5 5
6 initialize () { 6 initialize () {
7 return new Promise(res => { 7 return new Promise(res => {
diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts
index 6b0f6d990..f4e86b85a 100644
--- a/shared/extra-utils/miscs/miscs.ts
+++ b/shared/extra-utils/miscs/miscs.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { basename, dirname, isAbsolute, join, resolve } from 'path' 4import { basename, dirname, isAbsolute, join, resolve } from 'path'
@@ -10,11 +10,11 @@ import * as ffmpeg from 'fluent-ffmpeg'
10const expect = chai.expect 10const expect = chai.expect
11let webtorrent: WebTorrent.Instance 11let webtorrent: WebTorrent.Instance
12 12
13function immutableAssign <T, U> (target: T, source: U) { 13function immutableAssign<T, U> (target: T, source: U) {
14 return Object.assign<{}, T, U>({}, target, source) 14 return Object.assign<{}, T, U>({}, target, source)
15} 15}
16 16
17 // Default interval -> 5 minutes 17// Default interval -> 5 minutes
18function dateIsValid (dateString: string, interval = 300000) { 18function dateIsValid (dateString: string, interval = 300000) {
19 const dateToCheck = new Date(dateString) 19 const dateToCheck = new Date(dateString)
20 const now = new Date() 20 const now = new Date()
@@ -89,7 +89,7 @@ async function generateHighBitrateVideo () {
89 // a large file in the repo. The video needs to have a certain minimum length so 89 // a large file in the repo. The video needs to have a certain minimum length so
90 // that FFmpeg properly applies bitrate limits. 90 // that FFmpeg properly applies bitrate limits.
91 // https://stackoverflow.com/a/15795112 91 // https://stackoverflow.com/a/15795112
92 return new Promise<string>(async (res, rej) => { 92 return new Promise<string>((res, rej) => {
93 ffmpeg() 93 ffmpeg()
94 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ]) 94 .outputOptions([ '-f rawvideo', '-video_size 1920x1080', '-i /dev/urandom' ])
95 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ]) 95 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
@@ -104,6 +104,28 @@ async function generateHighBitrateVideo () {
104 return tempFixturePath 104 return tempFixturePath
105} 105}
106 106
107async function generateVideoWithFramerate (fps = 60) {
108 const tempFixturePath = buildAbsoluteFixturePath(`video_${fps}fps.mp4`, true)
109
110 await ensureDir(dirname(tempFixturePath))
111
112 const exists = await pathExists(tempFixturePath)
113 if (!exists) {
114 return new Promise<string>((res, rej) => {
115 ffmpeg()
116 .outputOptions([ '-f rawvideo', '-video_size 1280x720', '-i /dev/urandom' ])
117 .outputOptions([ '-ac 2', '-f s16le', '-i /dev/urandom', '-t 10' ])
118 .outputOptions([ `-r ${fps}` ])
119 .output(tempFixturePath)
120 .on('error', rej)
121 .on('end', () => res(tempFixturePath))
122 .run()
123 })
124 }
125
126 return tempFixturePath
127}
128
107// --------------------------------------------------------------------------- 129// ---------------------------------------------------------------------------
108 130
109export { 131export {
@@ -115,5 +137,6 @@ export {
115 testImage, 137 testImage,
116 buildAbsoluteFixturePath, 138 buildAbsoluteFixturePath,
117 root, 139 root,
118 generateHighBitrateVideo 140 generateHighBitrateVideo,
141 generateVideoWithFramerate
119} 142}
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
index 167649c6d..5bd5d5d8a 100644
--- a/shared/extra-utils/miscs/sql.ts
+++ b/shared/extra-utils/miscs/sql.ts
@@ -1,7 +1,7 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { ServerInfo } from '../server/servers' 2import { ServerInfo } from '../server/servers'
3 3
4let sequelizes: { [ id: number ]: Sequelize } = {} 4const sequelizes: { [ id: number ]: Sequelize } = {}
5 5
6function getSequelize (internalServerNumber: number) { 6function getSequelize (internalServerNumber: number) {
7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber] 7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber]
@@ -52,22 +52,23 @@ async function countVideoViewsOf (internalServerNumber: number, uuid: string) {
52 const seq = getSequelize(internalServerNumber) 52 const seq = getSequelize(internalServerNumber)
53 53
54 // tslint:disable 54 // tslint:disable
55 const query = `SELECT SUM("videoView"."views") AS "total" FROM "videoView" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'` 55 const query = 'SELECT SUM("videoView"."views") AS "total" FROM "videoView" ' +
56 `INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
56 57
57 const options = { type: QueryTypes.SELECT as QueryTypes.SELECT } 58 const options = { type: QueryTypes.SELECT as QueryTypes.SELECT }
58 const [ { total } ] = await seq.query<{ total: number }>(query, options) 59 const [ { total } ] = await seq.query<{ total: number }>(query, options)
59 60
60 if (!total) return 0 61 if (!total) return 0
61 62
62 // FIXME: check if we really need parseInt
63 return parseInt(total + '', 10) 63 return parseInt(total + '', 10)
64} 64}
65 65
66async function closeAllSequelize (servers: ServerInfo[]) { 66async function closeAllSequelize (servers: ServerInfo[]) {
67 for (const server of servers) { 67 for (const server of servers) {
68 if (sequelizes[ server.internalServerNumber ]) { 68 if (sequelizes[server.internalServerNumber]) {
69 await sequelizes[ server.internalServerNumber ].close() 69 await sequelizes[server.internalServerNumber].close()
70 delete sequelizes[ server.internalServerNumber ] 70 // eslint-disable-next-line
71 delete sequelizes[server.internalServerNumber]
71 } 72 }
72 } 73 }
73} 74}
diff --git a/shared/extra-utils/requests/requests.ts b/shared/extra-utils/requests/requests.ts
index 3532fb429..61167f212 100644
--- a/shared/extra-utils/requests/requests.ts
+++ b/shared/extra-utils/requests/requests.ts
@@ -1,25 +1,27 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { buildAbsoluteFixturePath, root } from '../miscs/miscs' 4import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
3import { isAbsolute, join } from 'path' 5import { isAbsolute, join } from 'path'
4import { parse } from 'url' 6import { URL } from 'url'
5 7
6function get4KFileUrl () { 8function get4KFileUrl () {
7 return 'https://download.cpy.re/peertube/4k_file.txt' 9 return 'https://download.cpy.re/peertube/4k_file.txt'
8} 10}
9 11
10function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) { 12function makeRawRequest (url: string, statusCodeExpected?: number, range?: string) {
11 const { host, protocol, pathname } = parse(url) 13 const { host, protocol, pathname } = new URL(url)
12 14
13 return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range }) 15 return makeGetRequest({ url: `${protocol}//${host}`, path: pathname, statusCodeExpected, range })
14} 16}
15 17
16function makeGetRequest (options: { 18function makeGetRequest (options: {
17 url: string, 19 url: string
18 path?: string, 20 path?: string
19 query?: any, 21 query?: any
20 token?: string, 22 token?: string
21 statusCodeExpected?: number, 23 statusCodeExpected?: number
22 contentType?: string, 24 contentType?: string
23 range?: string 25 range?: string
24}) { 26}) {
25 if (!options.statusCodeExpected) options.statusCodeExpected = 400 27 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -36,9 +38,9 @@ function makeGetRequest (options: {
36} 38}
37 39
38function makeDeleteRequest (options: { 40function makeDeleteRequest (options: {
39 url: string, 41 url: string
40 path: string, 42 path: string
41 token?: string, 43 token?: string
42 statusCodeExpected?: number 44 statusCodeExpected?: number
43}) { 45}) {
44 if (!options.statusCodeExpected) options.statusCodeExpected = 400 46 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -53,12 +55,12 @@ function makeDeleteRequest (options: {
53} 55}
54 56
55function makeUploadRequest (options: { 57function makeUploadRequest (options: {
56 url: string, 58 url: string
57 method?: 'POST' | 'PUT', 59 method?: 'POST' | 'PUT'
58 path: string, 60 path: string
59 token?: string, 61 token?: string
60 fields: { [ fieldName: string ]: any }, 62 fields: { [ fieldName: string ]: any }
61 attaches: { [ attachName: string ]: any | any[] }, 63 attaches: { [ attachName: string ]: any | any[] }
62 statusCodeExpected?: number 64 statusCodeExpected?: number
63}) { 65}) {
64 if (!options.statusCodeExpected) options.statusCodeExpected = 400 66 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -101,10 +103,10 @@ function makeUploadRequest (options: {
101} 103}
102 104
103function makePostBodyRequest (options: { 105function makePostBodyRequest (options: {
104 url: string, 106 url: string
105 path: string, 107 path: string
106 token?: string, 108 token?: string
107 fields?: { [ fieldName: string ]: any }, 109 fields?: { [ fieldName: string ]: any }
108 statusCodeExpected?: number 110 statusCodeExpected?: number
109}) { 111}) {
110 if (!options.fields) options.fields = {} 112 if (!options.fields) options.fields = {}
@@ -121,10 +123,10 @@ function makePostBodyRequest (options: {
121} 123}
122 124
123function makePutBodyRequest (options: { 125function makePutBodyRequest (options: {
124 url: string, 126 url: string
125 path: string, 127 path: string
126 token?: string, 128 token?: string
127 fields: { [ fieldName: string ]: any }, 129 fields: { [ fieldName: string ]: any }
128 statusCodeExpected?: number 130 statusCodeExpected?: number
129}) { 131}) {
130 if (!options.statusCodeExpected) options.statusCodeExpected = 400 132 if (!options.statusCodeExpected) options.statusCodeExpected = 400
@@ -147,9 +149,9 @@ function makeHTMLRequest (url: string, path: string) {
147} 149}
148 150
149function updateAvatarRequest (options: { 151function updateAvatarRequest (options: {
150 url: string, 152 url: string
151 path: string, 153 path: string
152 accessToken: string, 154 accessToken: string
153 fixture: string 155 fixture: string
154}) { 156}) {
155 let filePath = '' 157 let filePath = ''
diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts
index da806e692..4c52ea11c 100644
--- a/shared/extra-utils/search/videos.ts
+++ b/shared/extra-utils/search/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as request from 'supertest' 3import * as request from 'supertest'
4import { VideosSearchQuery } from '../../models/search' 4import { VideosSearchQuery } from '../../models/search'
diff --git a/shared/extra-utils/server/clients.ts b/shared/extra-utils/server/clients.ts
index 273aac747..dc631e823 100644
--- a/shared/extra-utils/server/clients.ts
+++ b/shared/extra-utils/server/clients.ts
@@ -1,12 +1,12 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import * as urlUtil from 'url' 2import { URL } from 'url'
3 3
4function getClient (url: string) { 4function getClient (url: string) {
5 const path = '/api/v1/oauth-clients/local' 5 const path = '/api/v1/oauth-clients/local'
6 6
7 return request(url) 7 return request(url)
8 .get(path) 8 .get(path)
9 .set('Host', urlUtil.parse(url).host) 9 .set('Host', new URL(url).host)
10 .set('Accept', 'application/json') 10 .set('Accept', 'application/json')
11 .expect(200) 11 .expect(200)
12 .expect('Content-Type', /json/) 12 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/contact-form.ts b/shared/extra-utils/server/contact-form.ts
index e002e03dd..d50f83241 100644
--- a/shared/extra-utils/server/contact-form.ts
+++ b/shared/extra-utils/server/contact-form.ts
@@ -2,11 +2,11 @@ import * as request from 'supertest'
2import { ContactForm } from '../../models/server' 2import { ContactForm } from '../../models/server'
3 3
4function sendContactForm (options: { 4function sendContactForm (options: {
5 url: string, 5 url: string
6 fromEmail: string, 6 fromEmail: string
7 fromName: string, 7 fromName: string
8 subject: string, 8 subject: string
9 body: string, 9 body: string
10 expectedStatus?: number 10 expectedStatus?: number
11}) { 11}) {
12 const path = '/api/v1/server/contact' 12 const path = '/api/v1/server/contact'
diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts
index 3f7729c20..006d59199 100644
--- a/shared/extra-utils/server/follows.ts
+++ b/shared/extra-utils/server/follows.ts
@@ -5,12 +5,12 @@ import { makePostBodyRequest } from '../requests/requests'
5import { ActivityPubActorType, FollowState } from '@shared/models' 5import { ActivityPubActorType, FollowState } from '@shared/models'
6 6
7function getFollowersListPaginationAndSort (options: { 7function getFollowersListPaginationAndSort (options: {
8 url: string, 8 url: string
9 start: number, 9 start: number
10 count: number, 10 count: number
11 sort: string, 11 sort: string
12 search?: string, 12 search?: string
13 actorType?: ActivityPubActorType, 13 actorType?: ActivityPubActorType
14 state?: FollowState 14 state?: FollowState
15}) { 15}) {
16 const { url, start, count, sort, search, state, actorType } = options 16 const { url, start, count, sort, search, state, actorType } = options
@@ -56,12 +56,12 @@ function rejectFollower (url: string, token: string, follower: string, statusCod
56} 56}
57 57
58function getFollowingListPaginationAndSort (options: { 58function getFollowingListPaginationAndSort (options: {
59 url: string, 59 url: string
60 start: number, 60 start: number
61 count: number, 61 count: number
62 sort: string, 62 sort: string
63 search?: string, 63 search?: string
64 actorType?: ActivityPubActorType, 64 actorType?: ActivityPubActorType
65 state?: FollowState 65 state?: FollowState
66}) { 66}) {
67 const { url, start, count, sort, search, state, actorType } = options 67 const { url, start, count, sort, search, state, actorType } = options
@@ -92,7 +92,7 @@ function follow (follower: string, following: string[], accessToken: string, exp
92 .post(path) 92 .post(path)
93 .set('Accept', 'application/json') 93 .set('Accept', 'application/json')
94 .set('Authorization', 'Bearer ' + accessToken) 94 .set('Authorization', 'Bearer ' + accessToken)
95 .send({ 'hosts': followingHosts }) 95 .send({ hosts: followingHosts })
96 .expect(expectedStatus) 96 .expect(expectedStatus)
97} 97}
98 98
diff --git a/shared/extra-utils/server/jobs.ts b/shared/extra-utils/server/jobs.ts
index 56fe5fa2a..d984b3d1e 100644
--- a/shared/extra-utils/server/jobs.ts
+++ b/shared/extra-utils/server/jobs.ts
@@ -8,20 +8,20 @@ function getJobsList (url: string, accessToken: string, state: JobState) {
8 const path = '/api/v1/jobs/' + state 8 const path = '/api/v1/jobs/' + state
9 9
10 return request(url) 10 return request(url)
11 .get(path) 11 .get(path)
12 .set('Accept', 'application/json') 12 .set('Accept', 'application/json')
13 .set('Authorization', 'Bearer ' + accessToken) 13 .set('Authorization', 'Bearer ' + accessToken)
14 .expect(200) 14 .expect(200)
15 .expect('Content-Type', /json/) 15 .expect('Content-Type', /json/)
16} 16}
17 17
18function getJobsListPaginationAndSort (options: { 18function getJobsListPaginationAndSort (options: {
19 url: string, 19 url: string
20 accessToken: string, 20 accessToken: string
21 state: JobState, 21 state: JobState
22 start: number, 22 start: number
23 count: number, 23 count: number
24 sort: string, 24 sort: string
25 jobType?: JobType 25 jobType?: JobType
26}) { 26}) {
27 const { url, accessToken, state, start, count, sort, jobType } = options 27 const { url, accessToken, state, start, count, sort, jobType } = options
diff --git a/shared/extra-utils/server/plugins.ts b/shared/extra-utils/server/plugins.ts
index 5c0d1e511..2d02d823d 100644
--- a/shared/extra-utils/server/plugins.ts
+++ b/shared/extra-utils/server/plugins.ts
@@ -7,13 +7,13 @@ import { root } from '../miscs/miscs'
7import { join } from 'path' 7import { join } from 'path'
8 8
9function listPlugins (parameters: { 9function listPlugins (parameters: {
10 url: string, 10 url: string
11 accessToken: string, 11 accessToken: string
12 start?: number, 12 start?: number
13 count?: number, 13 count?: number
14 sort?: string, 14 sort?: string
15 pluginType?: PluginType, 15 pluginType?: PluginType
16 uninstalled?: boolean, 16 uninstalled?: boolean
17 expectedStatus?: number 17 expectedStatus?: number
18}) { 18}) {
19 const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = 200 } = parameters 19 const { url, accessToken, start, count, sort, pluginType, uninstalled, expectedStatus = 200 } = parameters
@@ -35,13 +35,13 @@ function listPlugins (parameters: {
35} 35}
36 36
37function listAvailablePlugins (parameters: { 37function listAvailablePlugins (parameters: {
38 url: string, 38 url: string
39 accessToken: string, 39 accessToken: string
40 start?: number, 40 start?: number
41 count?: number, 41 count?: number
42 sort?: string, 42 sort?: string
43 pluginType?: PluginType, 43 pluginType?: PluginType
44 currentPeerTubeEngine?: string, 44 currentPeerTubeEngine?: string
45 search?: string 45 search?: string
46 expectedStatus?: number 46 expectedStatus?: number
47}) { 47}) {
@@ -67,9 +67,9 @@ function listAvailablePlugins (parameters: {
67} 67}
68 68
69function getPlugin (parameters: { 69function getPlugin (parameters: {
70 url: string, 70 url: string
71 accessToken: string, 71 accessToken: string
72 npmName: string, 72 npmName: string
73 expectedStatus?: number 73 expectedStatus?: number
74}) { 74}) {
75 const { url, accessToken, npmName, expectedStatus = 200 } = parameters 75 const { url, accessToken, npmName, expectedStatus = 200 } = parameters
@@ -84,10 +84,10 @@ function getPlugin (parameters: {
84} 84}
85 85
86function updatePluginSettings (parameters: { 86function updatePluginSettings (parameters: {
87 url: string, 87 url: string
88 accessToken: string, 88 accessToken: string
89 npmName: string, 89 npmName: string
90 settings: any, 90 settings: any
91 expectedStatus?: number 91 expectedStatus?: number
92}) { 92}) {
93 const { url, accessToken, npmName, settings, expectedStatus = 204 } = parameters 93 const { url, accessToken, npmName, settings, expectedStatus = 204 } = parameters
@@ -103,9 +103,9 @@ function updatePluginSettings (parameters: {
103} 103}
104 104
105function getPluginRegisteredSettings (parameters: { 105function getPluginRegisteredSettings (parameters: {
106 url: string, 106 url: string
107 accessToken: string, 107 accessToken: string
108 npmName: string, 108 npmName: string
109 expectedStatus?: number 109 expectedStatus?: number
110}) { 110}) {
111 const { url, accessToken, npmName, expectedStatus = 200 } = parameters 111 const { url, accessToken, npmName, expectedStatus = 200 } = parameters
@@ -120,8 +120,8 @@ function getPluginRegisteredSettings (parameters: {
120} 120}
121 121
122function getPublicSettings (parameters: { 122function getPublicSettings (parameters: {
123 url: string, 123 url: string
124 npmName: string, 124 npmName: string
125 expectedStatus?: number 125 expectedStatus?: number
126}) { 126}) {
127 const { url, npmName, expectedStatus = 200 } = parameters 127 const { url, npmName, expectedStatus = 200 } = parameters
@@ -135,8 +135,8 @@ function getPublicSettings (parameters: {
135} 135}
136 136
137function getPluginTranslations (parameters: { 137function getPluginTranslations (parameters: {
138 url: string, 138 url: string
139 locale: string, 139 locale: string
140 expectedStatus?: number 140 expectedStatus?: number
141}) { 141}) {
142 const { url, locale, expectedStatus = 200 } = parameters 142 const { url, locale, expectedStatus = 200 } = parameters
@@ -150,9 +150,9 @@ function getPluginTranslations (parameters: {
150} 150}
151 151
152function installPlugin (parameters: { 152function installPlugin (parameters: {
153 url: string, 153 url: string
154 accessToken: string, 154 accessToken: string
155 path?: string, 155 path?: string
156 npmName?: string 156 npmName?: string
157 expectedStatus?: number 157 expectedStatus?: number
158}) { 158}) {
@@ -169,9 +169,9 @@ function installPlugin (parameters: {
169} 169}
170 170
171function updatePlugin (parameters: { 171function updatePlugin (parameters: {
172 url: string, 172 url: string
173 accessToken: string, 173 accessToken: string
174 path?: string, 174 path?: string
175 npmName?: string 175 npmName?: string
176 expectedStatus?: number 176 expectedStatus?: number
177}) { 177}) {
@@ -188,8 +188,8 @@ function updatePlugin (parameters: {
188} 188}
189 189
190function uninstallPlugin (parameters: { 190function uninstallPlugin (parameters: {
191 url: string, 191 url: string
192 accessToken: string, 192 accessToken: string
193 npmName: string 193 npmName: string
194 expectedStatus?: number 194 expectedStatus?: number
195}) { 195}) {
diff --git a/shared/extra-utils/server/redundancy.ts b/shared/extra-utils/server/redundancy.ts
index c39ff2c8b..08467e4c0 100644
--- a/shared/extra-utils/server/redundancy.ts
+++ b/shared/extra-utils/server/redundancy.ts
@@ -1,6 +1,7 @@
1import { makePutBodyRequest } from '../requests/requests' 1import { makeDeleteRequest, makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
2import { VideoRedundanciesTarget } from '@shared/models'
2 3
3async function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) { 4function updateRedundancy (url: string, accessToken: string, host: string, redundancyAllowed: boolean, expectedStatus = 204) {
4 const path = '/api/v1/server/redundancy/' + host 5 const path = '/api/v1/server/redundancy/' + host
5 6
6 return makePutBodyRequest({ 7 return makePutBodyRequest({
@@ -12,6 +13,69 @@ async function updateRedundancy (url: string, accessToken: string, host: string,
12 }) 13 })
13} 14}
14 15
16function listVideoRedundancies (options: {
17 url: string
18 accessToken: string
19 target: VideoRedundanciesTarget
20 start?: number
21 count?: number
22 sort?: string
23 statusCodeExpected?: number
24}) {
25 const path = '/api/v1/server/redundancy/videos'
26
27 const { url, accessToken, target, statusCodeExpected, start, count, sort } = options
28
29 return makeGetRequest({
30 url,
31 token: accessToken,
32 path,
33 query: {
34 start: start ?? 0,
35 count: count ?? 5,
36 sort: sort ?? 'name',
37 target
38 },
39 statusCodeExpected: statusCodeExpected || 200
40 })
41}
42
43function addVideoRedundancy (options: {
44 url: string
45 accessToken: string
46 videoId: number
47}) {
48 const path = '/api/v1/server/redundancy/videos'
49 const { url, accessToken, videoId } = options
50
51 return makePostBodyRequest({
52 url,
53 token: accessToken,
54 path,
55 fields: { videoId },
56 statusCodeExpected: 204
57 })
58}
59
60function removeVideoRedundancy (options: {
61 url: string
62 accessToken: string
63 redundancyId: number
64}) {
65 const { url, accessToken, redundancyId } = options
66 const path = '/api/v1/server/redundancy/videos/' + redundancyId
67
68 return makeDeleteRequest({
69 url,
70 token: accessToken,
71 path,
72 statusCodeExpected: 204
73 })
74}
75
15export { 76export {
16 updateRedundancy 77 updateRedundancy,
78 listVideoRedundancies,
79 addVideoRedundancy,
80 removeVideoRedundancy
17} 81}
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index a0720d778..a0f0ce9c9 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -1,16 +1,15 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2 2
3import { ChildProcess, exec, fork } from 'child_process' 3import { ChildProcess, exec, fork } from 'child_process'
4import { join } from 'path' 4import { join } from 'path'
5import { root, wait } from '../miscs/miscs' 5import { root, wait } from '../miscs/miscs'
6import { copy, pathExists, readdir, readFile, remove } from 'fs-extra' 6import { copy, pathExists, readdir, readFile, remove } from 'fs-extra'
7import { existsSync } from 'fs'
8import { expect } from 'chai' 7import { expect } from 'chai'
9import { VideoChannel } from '../../models/videos' 8import { VideoChannel } from '../../models/videos'
10import { randomInt } from '../../core-utils/miscs/miscs' 9import { randomInt } from '../../core-utils/miscs/miscs'
11 10
12interface ServerInfo { 11interface ServerInfo {
13 app: ChildProcess, 12 app: ChildProcess
14 url: string 13 url: string
15 host: string 14 host: string
16 15
@@ -20,13 +19,13 @@ interface ServerInfo {
20 serverNumber: number 19 serverNumber: number
21 20
22 client: { 21 client: {
23 id: string, 22 id: string
24 secret: string 23 secret: string
25 } 24 }
26 25
27 user: { 26 user: {
28 username: string, 27 username: string
29 password: string, 28 password: string
30 email?: string 29 email?: string
31 } 30 }
32 31
@@ -57,7 +56,7 @@ function parallelTests () {
57} 56}
58 57
59function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) { 58function flushAndRunMultipleServers (totalServers: number, configOverride?: Object) {
60 let apps = [] 59 const apps = []
61 let i = 0 60 let i = 0
62 61
63 return new Promise<ServerInfo[]>(res => { 62 return new Promise<ServerInfo[]>(res => {
@@ -203,20 +202,20 @@ async function runServer (server: ServerInfo, configOverrideArg?: any, args = []
203 202
204 // Capture things if we want to 203 // Capture things if we want to
205 for (const key of Object.keys(regexps)) { 204 for (const key of Object.keys(regexps)) {
206 const regexp = regexps[ key ] 205 const regexp = regexps[key]
207 const matches = data.toString().match(regexp) 206 const matches = data.toString().match(regexp)
208 if (matches !== null) { 207 if (matches !== null) {
209 if (key === 'client_id') server.client.id = matches[ 1 ] 208 if (key === 'client_id') server.client.id = matches[1]
210 else if (key === 'client_secret') server.client.secret = matches[ 1 ] 209 else if (key === 'client_secret') server.client.secret = matches[1]
211 else if (key === 'user_username') server.user.username = matches[ 1 ] 210 else if (key === 'user_username') server.user.username = matches[1]
212 else if (key === 'user_password') server.user.password = matches[ 1 ] 211 else if (key === 'user_password') server.user.password = matches[1]
213 } 212 }
214 } 213 }
215 214
216 // Check if all required sentences are here 215 // Check if all required sentences are here
217 for (const key of Object.keys(serverRunString)) { 216 for (const key of Object.keys(serverRunString)) {
218 if (data.toString().indexOf(key) !== -1) serverRunString[ key ] = true 217 if (data.toString().indexOf(key) !== -1) serverRunString[key] = true
219 if (serverRunString[ key ] === false) dontContinue = true 218 if (serverRunString[key] === false) dontContinue = true
220 } 219 }
221 220
222 // If no, there is maybe one thing not already initialized (client/user credentials generation...) 221 // If no, there is maybe one thing not already initialized (client/user credentials generation...)
diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
index 627e17cc3..f87706f6a 100644
--- a/shared/extra-utils/users/accounts.ts
+++ b/shared/extra-utils/users/accounts.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import * as request from 'supertest' 3import * as request from 'supertest'
4import { expect } from 'chai' 4import { expect } from 'chai'
diff --git a/shared/extra-utils/users/blocklist.ts b/shared/extra-utils/users/blocklist.ts
index 5feb84179..39e720b42 100644
--- a/shared/extra-utils/users/blocklist.ts
+++ b/shared/extra-utils/users/blocklist.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests' 3import { makeGetRequest, makeDeleteRequest, makePostBodyRequest } from '../requests/requests'
4 4
diff --git a/shared/extra-utils/users/login.ts b/shared/extra-utils/users/login.ts
index f9bfb3cb3..4fe54a74a 100644
--- a/shared/extra-utils/users/login.ts
+++ b/shared/extra-utils/users/login.ts
@@ -60,7 +60,7 @@ function setAccessTokensToServers (servers: ServerInfo[]) {
60 const tasks: Promise<any>[] = [] 60 const tasks: Promise<any>[] = []
61 61
62 for (const server of servers) { 62 for (const server of servers) {
63 const p = serverLogin(server).then(t => server.accessToken = t) 63 const p = serverLogin(server).then(t => { server.accessToken = t })
64 tasks.push(p) 64 tasks.push(p)
65 } 65 }
66 66
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts
index 9a5fd7e86..f949878e4 100644
--- a/shared/extra-utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/user-notifications.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2 2
3import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests' 3import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users' 4import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
@@ -54,6 +54,7 @@ function markAsReadNotifications (url: string, token: string, ids: number[], sta
54 statusCodeExpected 54 statusCodeExpected
55 }) 55 })
56} 56}
57
57function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) { 58function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
58 const path = '/api/v1/users/me/notifications/read-all' 59 const path = '/api/v1/users/me/notifications/read-all'
59 60
@@ -77,7 +78,7 @@ type CheckerBaseParams = {
77 server: ServerInfo 78 server: ServerInfo
78 emails: object[] 79 emails: object[]
79 socketNotifications: UserNotification[] 80 socketNotifications: UserNotification[]
80 token: string, 81 token: string
81 check?: { web: boolean, mail: boolean } 82 check?: { web: boolean, mail: boolean }
82} 83}
83 84
@@ -172,7 +173,7 @@ async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName
172 } 173 }
173 174
174 function emailFinder (email: object) { 175 function emailFinder (email: object) {
175 const text = email[ 'text' ] 176 const text = email['text']
176 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1 177 return text.indexOf(videoUUID) !== -1 && text.indexOf('Your subscription') !== -1
177 } 178 }
178 179
@@ -195,7 +196,7 @@ async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string
195 } 196 }
196 197
197 function emailFinder (email: object) { 198 function emailFinder (email: object) {
198 const text: string = email[ 'text' ] 199 const text: string = email['text']
199 return text.includes(videoUUID) && text.includes('Your video') 200 return text.includes(videoUUID) && text.includes('Your video')
200 } 201 }
201 202
@@ -226,7 +227,7 @@ async function checkMyVideoImportIsFinished (
226 } 227 }
227 228
228 function emailFinder (email: object) { 229 function emailFinder (email: object) {
229 const text: string = email[ 'text' ] 230 const text: string = email['text']
230 const toFind = success ? ' finished' : ' error' 231 const toFind = success ? ' finished' : ' error'
231 232
232 return text.includes(url) && text.includes(toFind) 233 return text.includes(url) && text.includes(toFind)
@@ -251,7 +252,7 @@ async function checkUserRegistered (base: CheckerBaseParams, username: string, t
251 } 252 }
252 253
253 function emailFinder (email: object) { 254 function emailFinder (email: object) {
254 const text: string = email[ 'text' ] 255 const text: string = email['text']
255 256
256 return text.includes(' registered ') && text.includes(username) 257 return text.includes(' registered ') && text.includes(username)
257 } 258 }
@@ -291,7 +292,7 @@ async function checkNewActorFollow (
291 } 292 }
292 293
293 function emailFinder (email: object) { 294 function emailFinder (email: object) {
294 const text: string = email[ 'text' ] 295 const text: string = email['text']
295 296
296 return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName) 297 return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
297 } 298 }
@@ -320,7 +321,7 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost:
320 } 321 }
321 322
322 function emailFinder (email: object) { 323 function emailFinder (email: object) {
323 const text: string = email[ 'text' ] 324 const text: string = email['text']
324 325
325 return text.includes('instance has a new follower') && text.includes(followerHost) 326 return text.includes('instance has a new follower') && text.includes(followerHost)
326 } 327 }
@@ -351,7 +352,7 @@ async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost
351 } 352 }
352 353
353 function emailFinder (email: object) { 354 function emailFinder (email: object) {
354 const text: string = email[ 'text' ] 355 const text: string = email['text']
355 356
356 return text.includes(' automatically followed a new instance') && text.includes(followingHost) 357 return text.includes(' automatically followed a new instance') && text.includes(followingHost)
357 } 358 }
@@ -385,7 +386,7 @@ async function checkCommentMention (
385 } 386 }
386 387
387 function emailFinder (email: object) { 388 function emailFinder (email: object) {
388 const text: string = email[ 'text' ] 389 const text: string = email['text']
389 390
390 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName) 391 return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
391 } 392 }
@@ -394,6 +395,7 @@ async function checkCommentMention (
394} 395}
395 396
396let lastEmailCount = 0 397let lastEmailCount = 0
398
397async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) { 399async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
398 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO 400 const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
399 401
@@ -413,8 +415,9 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
413 } 415 }
414 416
415 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}` 417 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
418
416 function emailFinder (email: object) { 419 function emailFinder (email: object) {
417 return email[ 'text' ].indexOf(commentUrl) !== -1 420 return email['text'].indexOf(commentUrl) !== -1
418 } 421 }
419 422
420 await checkNotification(base, notificationChecker, emailFinder, type) 423 await checkNotification(base, notificationChecker, emailFinder, type)
@@ -444,7 +447,7 @@ async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUU
444 } 447 }
445 448
446 function emailFinder (email: object) { 449 function emailFinder (email: object) {
447 const text = email[ 'text' ] 450 const text = email['text']
448 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1 451 return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
449 } 452 }
450 453
@@ -469,8 +472,8 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi
469 } 472 }
470 473
471 function emailFinder (email: object) { 474 function emailFinder (email: object) {
472 const text = email[ 'text' ] 475 const text = email['text']
473 return text.indexOf(videoUUID) !== -1 && email[ 'text' ].indexOf('video-auto-blacklist/list') !== -1 476 return text.indexOf(videoUUID) !== -1 && email['text'].indexOf('video-auto-blacklist/list') !== -1
474 } 477 }
475 478
476 await checkNotification(base, notificationChecker, emailFinder, type) 479 await checkNotification(base, notificationChecker, emailFinder, type)
@@ -496,7 +499,7 @@ async function checkNewBlacklistOnMyVideo (
496 } 499 }
497 500
498 function emailFinder (email: object) { 501 function emailFinder (email: object) {
499 const text = email[ 'text' ] 502 const text = email['text']
500 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1 503 return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
501 } 504 }
502 505
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 2fe0e55c2..248af2d6e 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -9,14 +9,14 @@ import { UserUpdateMe } from '../../models/users'
9import { omit } from 'lodash' 9import { omit } from 'lodash'
10 10
11type CreateUserArgs = { 11type CreateUserArgs = {
12 url: string, 12 url: string
13 accessToken: string, 13 accessToken: string
14 username: string, 14 username: string
15 password: string, 15 password: string
16 videoQuota?: number, 16 videoQuota?: number
17 videoQuotaDaily?: number, 17 videoQuotaDaily?: number
18 role?: UserRole, 18 role?: UserRole
19 adminFlags?: UserAdminFlag, 19 adminFlags?: UserAdminFlag
20 specialStatus?: number 20 specialStatus?: number
21} 21}
22function createUser (parameters: CreateUserArgs) { 22function createUser (parameters: CreateUserArgs) {
@@ -74,8 +74,8 @@ function registerUser (url: string, username: string, password: string, specialS
74} 74}
75 75
76function registerUserWithChannel (options: { 76function registerUserWithChannel (options: {
77 url: string, 77 url: string
78 user: { username: string, password: string, displayName?: string }, 78 user: { username: string, password: string, displayName?: string }
79 channel: { name: string, displayName: string } 79 channel: { name: string, displayName: string }
80}) { 80}) {
81 const path = '/api/v1/users/register' 81 const path = '/api/v1/users/register'
@@ -230,8 +230,8 @@ function updateMyUser (options: { url: string, accessToken: string } & UserUpdat
230} 230}
231 231
232function updateMyAvatar (options: { 232function updateMyAvatar (options: {
233 url: string, 233 url: string
234 accessToken: string, 234 accessToken: string
235 fixture: string 235 fixture: string
236}) { 236}) {
237 const path = '/api/v1/users/me/avatar/pick' 237 const path = '/api/v1/users/me/avatar/pick'
@@ -241,14 +241,14 @@ function updateMyAvatar (options: {
241 241
242function updateUser (options: { 242function updateUser (options: {
243 url: string 243 url: string
244 userId: number, 244 userId: number
245 accessToken: string, 245 accessToken: string
246 email?: string, 246 email?: string
247 emailVerified?: boolean, 247 emailVerified?: boolean
248 videoQuota?: number, 248 videoQuota?: number
249 videoQuotaDaily?: number, 249 videoQuotaDaily?: number
250 password?: string, 250 password?: string
251 adminFlags?: UserAdminFlag, 251 adminFlags?: UserAdminFlag
252 role?: UserRole 252 role?: UserRole
253}) { 253}) {
254 const path = '/api/v1/users/' + options.userId 254 const path = '/api/v1/users/' + options.userId
diff --git a/shared/extra-utils/videos/video-blacklist.ts b/shared/extra-utils/videos/video-blacklist.ts
index e25a292fc..ba139ef95 100644
--- a/shared/extra-utils/videos/video-blacklist.ts
+++ b/shared/extra-utils/videos/video-blacklist.ts
@@ -13,11 +13,11 @@ function addVideoToBlacklist (
13 const path = '/api/v1/videos/' + videoId + '/blacklist' 13 const path = '/api/v1/videos/' + videoId + '/blacklist'
14 14
15 return request(url) 15 return request(url)
16 .post(path) 16 .post(path)
17 .send({ reason, unfederate }) 17 .send({ reason, unfederate })
18 .set('Accept', 'application/json') 18 .set('Accept', 'application/json')
19 .set('Authorization', 'Bearer ' + token) 19 .set('Authorization', 'Bearer ' + token)
20 .expect(specialStatus) 20 .expect(specialStatus)
21} 21}
22 22
23function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) { 23function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
@@ -35,20 +35,20 @@ function removeVideoFromBlacklist (url: string, token: string, videoId: number |
35 const path = '/api/v1/videos/' + videoId + '/blacklist' 35 const path = '/api/v1/videos/' + videoId + '/blacklist'
36 36
37 return request(url) 37 return request(url)
38 .delete(path) 38 .delete(path)
39 .set('Accept', 'application/json') 39 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + token) 40 .set('Authorization', 'Bearer ' + token)
41 .expect(specialStatus) 41 .expect(specialStatus)
42} 42}
43 43
44function getBlacklistedVideosList (parameters: { 44function getBlacklistedVideosList (parameters: {
45 url: string, 45 url: string
46 token: string, 46 token: string
47 sort?: string, 47 sort?: string
48 type?: VideoBlacklistType, 48 type?: VideoBlacklistType
49 specialStatus?: number 49 specialStatus?: number
50}) { 50}) {
51 let { url, token, sort, type, specialStatus = 200 } = parameters 51 const { url, token, sort, type, specialStatus = 200 } = parameters
52 const path = '/api/v1/videos/blacklist/' 52 const path = '/api/v1/videos/blacklist/'
53 53
54 const query = { sort, type } 54 const query = { sort, type }
diff --git a/shared/extra-utils/videos/video-captions.ts b/shared/extra-utils/videos/video-captions.ts
index 8d67f617b..5bd533bba 100644
--- a/shared/extra-utils/videos/video-captions.ts
+++ b/shared/extra-utils/videos/video-captions.ts
@@ -6,12 +6,12 @@ import { buildAbsoluteFixturePath } from '../miscs/miscs'
6const expect = chai.expect 6const expect = chai.expect
7 7
8function createVideoCaption (args: { 8function createVideoCaption (args: {
9 url: string, 9 url: string
10 accessToken: string 10 accessToken: string
11 videoId: string | number 11 videoId: string | number
12 language: string 12 language: string
13 fixture: string, 13 fixture: string
14 mimeType?: string, 14 mimeType?: string
15 statusCodeExpected?: number 15 statusCodeExpected?: number
16}) { 16}) {
17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language 17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 053842331..51d433940 100644
--- a/shared/extra-utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model' 4import { VideoChannelUpdate } from '../../models/videos/channel/video-channel-update.model'
3import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model' 5import { VideoChannelCreate } from '../../models/videos/channel/video-channel-create.model'
@@ -22,11 +24,11 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
22} 24}
23 25
24function getAccountVideoChannelsList (parameters: { 26function getAccountVideoChannelsList (parameters: {
25 url: string, 27 url: string
26 accountName: string, 28 accountName: string
27 start?: number, 29 start?: number
28 count?: number, 30 count?: number
29 sort?: string, 31 sort?: string
30 specialStatus?: number 32 specialStatus?: number
31}) { 33}) {
32 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200 } = parameters 34 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200 } = parameters
@@ -113,9 +115,9 @@ function getVideoChannel (url: string, channelName: string) {
113} 115}
114 116
115function updateVideoChannelAvatar (options: { 117function updateVideoChannelAvatar (options: {
116 url: string, 118 url: string
117 accessToken: string, 119 accessToken: string
118 fixture: string, 120 fixture: string
119 videoChannelName: string | number 121 videoChannelName: string | number
120}) { 122}) {
121 123
@@ -129,7 +131,7 @@ function setDefaultVideoChannel (servers: ServerInfo[]) {
129 131
130 for (const server of servers) { 132 for (const server of servers) {
131 const p = getMyUserInformation(server.url, server.accessToken) 133 const p = getMyUserInformation(server.url, server.accessToken)
132 .then(res => server.videoChannel = (res.body as User).videoChannels[0]) 134 .then(res => { server.videoChannel = (res.body as User).videoChannels[0] })
133 135
134 tasks.push(p) 136 tasks.push(p)
135 } 137 }
diff --git a/shared/extra-utils/videos/video-comments.ts b/shared/extra-utils/videos/video-comments.ts
index 0ebf69ced..81c48412d 100644
--- a/shared/extra-utils/videos/video-comments.ts
+++ b/shared/extra-utils/videos/video-comments.ts
@@ -1,3 +1,5 @@
1/* eslint-disable @typescript-eslint/no-floating-promises */
2
1import * as request from 'supertest' 3import * as request from 'supertest'
2import { makeDeleteRequest } from '../requests/requests' 4import { makeDeleteRequest } from '../requests/requests'
3 5
diff --git a/shared/extra-utils/videos/video-imports.ts b/shared/extra-utils/videos/video-imports.ts
index 150cc94ed..8e5abd2f5 100644
--- a/shared/extra-utils/videos/video-imports.ts
+++ b/shared/extra-utils/videos/video-imports.ts
@@ -7,7 +7,7 @@ function getYoutubeVideoUrl () {
7} 7}
8 8
9function getMagnetURI () { 9function getMagnetURI () {
10 // tslint:disable:max-line-length 10 // eslint-disable-next-line max-len
11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4' 11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4'
12} 12}
13 13
diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts
index 6762c5973..5bcc02570 100644
--- a/shared/extra-utils/videos/video-playlists.ts
+++ b/shared/extra-utils/videos/video-playlists.ts
@@ -123,9 +123,9 @@ function deleteVideoPlaylist (url: string, token: string, playlistId: number | s
123} 123}
124 124
125function createVideoPlaylist (options: { 125function createVideoPlaylist (options: {
126 url: string, 126 url: string
127 token: string, 127 token: string
128 playlistAttrs: VideoPlaylistCreate, 128 playlistAttrs: VideoPlaylistCreate
129 expectedStatus?: number 129 expectedStatus?: number
130}) { 130}) {
131 const path = '/api/v1/video-playlists' 131 const path = '/api/v1/video-playlists'
@@ -148,10 +148,10 @@ function createVideoPlaylist (options: {
148} 148}
149 149
150function updateVideoPlaylist (options: { 150function updateVideoPlaylist (options: {
151 url: string, 151 url: string
152 token: string, 152 token: string
153 playlistAttrs: VideoPlaylistUpdate, 153 playlistAttrs: VideoPlaylistUpdate
154 playlistId: number | string, 154 playlistId: number | string
155 expectedStatus?: number 155 expectedStatus?: number
156}) { 156}) {
157 const path = '/api/v1/video-playlists/' + options.playlistId 157 const path = '/api/v1/video-playlists/' + options.playlistId
@@ -174,9 +174,9 @@ function updateVideoPlaylist (options: {
174} 174}
175 175
176async function addVideoInPlaylist (options: { 176async function addVideoInPlaylist (options: {
177 url: string, 177 url: string
178 token: string, 178 token: string
179 playlistId: number | string, 179 playlistId: number | string
180 elementAttrs: VideoPlaylistElementCreate | { videoId: string } 180 elementAttrs: VideoPlaylistElementCreate | { videoId: string }
181 expectedStatus?: number 181 expectedStatus?: number
182}) { 182}) {
@@ -194,11 +194,11 @@ async function addVideoInPlaylist (options: {
194} 194}
195 195
196function updateVideoPlaylistElement (options: { 196function updateVideoPlaylistElement (options: {
197 url: string, 197 url: string
198 token: string, 198 token: string
199 playlistId: number | string, 199 playlistId: number | string
200 playlistElementId: number | string, 200 playlistElementId: number | string
201 elementAttrs: VideoPlaylistElementUpdate, 201 elementAttrs: VideoPlaylistElementUpdate
202 expectedStatus?: number 202 expectedStatus?: number
203}) { 203}) {
204 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId 204 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId
@@ -213,10 +213,10 @@ function updateVideoPlaylistElement (options: {
213} 213}
214 214
215function removeVideoFromPlaylist (options: { 215function removeVideoFromPlaylist (options: {
216 url: string, 216 url: string
217 token: string, 217 token: string
218 playlistId: number | string, 218 playlistId: number | string
219 playlistElementId: number, 219 playlistElementId: number
220 expectedStatus?: number 220 expectedStatus?: number
221}) { 221}) {
222 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId 222 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/' + options.playlistElementId
@@ -230,14 +230,14 @@ function removeVideoFromPlaylist (options: {
230} 230}
231 231
232function reorderVideosPlaylist (options: { 232function reorderVideosPlaylist (options: {
233 url: string, 233 url: string
234 token: string, 234 token: string
235 playlistId: number | string, 235 playlistId: number | string
236 elementAttrs: { 236 elementAttrs: {
237 startPosition: number, 237 startPosition: number
238 insertAfterPosition: number, 238 insertAfterPosition: number
239 reorderLength?: number 239 reorderLength?: number
240 }, 240 }
241 expectedStatus?: number 241 expectedStatus?: number
242}) { 242}) {
243 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder' 243 const path = '/api/v1/video-playlists/' + options.playlistId + '/videos/reorder'
diff --git a/shared/extra-utils/videos/video-streaming-playlists.ts b/shared/extra-utils/videos/video-streaming-playlists.ts
index eb25011cb..e54da84aa 100644
--- a/shared/extra-utils/videos/video-streaming-playlists.ts
+++ b/shared/extra-utils/videos/video-streaming-playlists.ts
@@ -37,7 +37,7 @@ async function checkSegmentHash (
37 37
38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url) 38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
39 39
40 const sha256Server = resSha.body[ videoName ][range] 40 const sha256Server = resSha.body[videoName][range]
41 expect(sha256(res2.body)).to.equal(sha256Server) 41 expect(sha256(res2.body)).to.equal(sha256Server)
42} 42}
43 43
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index 7a77a03ad..39a06b0d7 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -1,4 +1,4 @@
1/* tslint:disable:no-unused-expression */ 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2 2
3import { expect } from 'chai' 3import { expect } from 'chai'
4import { pathExists, readdir, readFile } from 'fs-extra' 4import { pathExists, readdir, readFile } from 'fs-extra'
@@ -488,7 +488,7 @@ async function completeVideoCheck (
488 description: string 488 description: string
489 publishedAt?: string 489 publishedAt?: string
490 support: string 490 support: string
491 originallyPublishedAt?: string, 491 originallyPublishedAt?: string
492 account: { 492 account: {
493 name: string 493 name: string
494 host: string 494 host: string
@@ -509,7 +509,7 @@ async function completeVideoCheck (
509 files: { 509 files: {
510 resolution: number 510 resolution: number
511 size: number 511 size: number
512 }[], 512 }[]
513 thumbnailfile?: string 513 thumbnailfile?: string
514 previewfile?: string 514 previewfile?: string
515 } 515 }
@@ -583,9 +583,10 @@ async function completeVideoCheck (
583 583
584 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100) 584 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
585 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100) 585 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
586 expect(file.size, 586 expect(
587 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')') 587 file.size,
588 .to.be.above(minSize).and.below(maxSize) 588 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
589 ).to.be.above(minSize).and.below(maxSize)
589 590
590 const torrent = await webtorrentAdd(file.magnetUri, true) 591 const torrent = await webtorrentAdd(file.magnetUri, true)
591 expect(torrent.files).to.be.an('array') 592 expect(torrent.files).to.be.an('array')
@@ -607,15 +608,28 @@ async function videoUUIDToId (url: string, id: number | string) {
607 return res.body.id 608 return res.body.id
608} 609}
609 610
610async function uploadVideoAndGetId (options: { server: ServerInfo, videoName: string, nsfw?: boolean, token?: string }) { 611async function uploadVideoAndGetId (options: {
612 server: ServerInfo
613 videoName: string
614 nsfw?: boolean
615 privacy?: VideoPrivacy
616 token?: string
617}) {
611 const videoAttrs: any = { name: options.videoName } 618 const videoAttrs: any = { name: options.videoName }
612 if (options.nsfw) videoAttrs.nsfw = options.nsfw 619 if (options.nsfw) videoAttrs.nsfw = options.nsfw
620 if (options.privacy) videoAttrs.privacy = options.privacy
613 621
614 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs) 622 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
615 623
616 return { id: res.body.video.id, uuid: res.body.video.uuid } 624 return { id: res.body.video.id, uuid: res.body.video.uuid }
617} 625}
618 626
627async function getLocalIdByUUID (url: string, uuid: string) {
628 const res = await getVideo(url, uuid)
629
630 return res.body.id
631}
632
619// --------------------------------------------------------------------------- 633// ---------------------------------------------------------------------------
620 634
621export { 635export {
@@ -645,5 +659,6 @@ export {
645 completeVideoCheck, 659 completeVideoCheck,
646 checkVideoFilesWereRemoved, 660 checkVideoFilesWereRemoved,
647 getPlaylistVideos, 661 getPlaylistVideos,
648 uploadVideoAndGetId 662 uploadVideoAndGetId,
663 getLocalIdByUUID
649} 664}
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts
index 492b672c7..20ecf176c 100644
--- a/shared/models/activitypub/activity.ts
+++ b/shared/models/activitypub/activity.ts
@@ -8,12 +8,33 @@ import { ViewObject } from './objects/view-object'
8import { APObject } from './objects/object.model' 8import { APObject } from './objects/object.model'
9import { PlaylistObject } from './objects/playlist-object' 9import { PlaylistObject } from './objects/playlist-object'
10 10
11export type Activity = ActivityCreate | ActivityUpdate | 11export type Activity =
12 ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce | 12 ActivityCreate |
13 ActivityUndo | ActivityLike | ActivityReject | ActivityView | ActivityDislike | ActivityFlag 13 ActivityUpdate |
14 14 ActivityDelete |
15export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like' | 'Reject' | 15 ActivityFollow |
16 'View' | 'Dislike' | 'Flag' 16 ActivityAccept |
17 ActivityAnnounce |
18 ActivityUndo |
19 ActivityLike |
20 ActivityReject |
21 ActivityView |
22 ActivityDislike |
23 ActivityFlag
24
25export type ActivityType =
26 'Create' |
27 'Update' |
28 'Delete' |
29 'Follow' |
30 'Accept' |
31 'Announce' |
32 'Undo' |
33 'Like' |
34 'Reject' |
35 'View' |
36 'Dislike' |
37 'Flag'
17 38
18export interface ActivityAudience { 39export interface ActivityAudience {
19 to: string[] 40 to: string[]
@@ -66,17 +87,17 @@ export interface ActivityAnnounce extends BaseActivity {
66} 87}
67 88
68export interface ActivityUndo extends BaseActivity { 89export interface ActivityUndo extends BaseActivity {
69 type: 'Undo', 90 type: 'Undo'
70 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce 91 object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce
71} 92}
72 93
73export interface ActivityLike extends BaseActivity { 94export interface ActivityLike extends BaseActivity {
74 type: 'Like', 95 type: 'Like'
75 object: APObject 96 object: APObject
76} 97}
77 98
78export interface ActivityView extends BaseActivity { 99export interface ActivityView extends BaseActivity {
79 type: 'View', 100 type: 'View'
80 actor: string 101 actor: string
81 object: APObject 102 object: APObject
82} 103}
@@ -89,7 +110,7 @@ export interface ActivityDislike extends BaseActivity {
89} 110}
90 111
91export interface ActivityFlag extends BaseActivity { 112export interface ActivityFlag extends BaseActivity {
92 type: 'Flag', 113 type: 'Flag'
93 content: string, 114 content: string
94 object: APObject | APObject[] 115 object: APObject | APObject[]
95} 116}
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index b8a2dc925..f022f3d02 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -1,4 +1,4 @@
1import { ActivityPubAttributedTo } from './objects/common-objects' 1import { ActivityIconObject, ActivityPubAttributedTo } from './objects/common-objects'
2 2
3export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization' 3export type ActivityPubActorType = 'Person' | 'Application' | 'Group' | 'Service' | 'Organization'
4 4
@@ -27,9 +27,5 @@ export interface ActivityPubActor {
27 publicKeyPem: string 27 publicKeyPem: string
28 } 28 }
29 29
30 icon: { 30 icon: ActivityIconObject
31 type: 'Image'
32 mediaType: 'image/png'
33 url: string
34 }
35} 31}
diff --git a/shared/models/activitypub/activitypub-signature.ts b/shared/models/activitypub/activitypub-signature.ts
index 1d9f4b3b3..fafdc246d 100644
--- a/shared/models/activitypub/activitypub-signature.ts
+++ b/shared/models/activitypub/activitypub-signature.ts
@@ -1,6 +1,6 @@
1export interface ActivityPubSignature { 1export interface ActivityPubSignature {
2 type: 'GraphSignature2012' 2 type: string
3 created: Date, 3 created: Date
4 creator: string 4 creator: string
5 signatureValue: string 5 signatureValue: string
6} 6}
diff --git a/shared/models/activitypub/objects/cache-file-object.ts b/shared/models/activitypub/objects/cache-file-object.ts
index 4b0a3a724..19a817582 100644
--- a/shared/models/activitypub/objects/cache-file-object.ts
+++ b/shared/models/activitypub/objects/cache-file-object.ts
@@ -2,7 +2,7 @@ import { ActivityVideoUrlObject, ActivityPlaylistUrlObject } from './common-obje
2 2
3export interface CacheFileObject { 3export interface CacheFileObject {
4 id: string 4 id: string
5 type: 'CacheFile', 5 type: 'CacheFile'
6 object: string 6 object: string
7 expires: string 7 expires: string
8 url: ActivityVideoUrlObject | ActivityPlaylistUrlObject 8 url: ActivityVideoUrlObject | ActivityPlaylistUrlObject
diff --git a/shared/models/activitypub/objects/common-objects.ts b/shared/models/activitypub/objects/common-objects.ts
index de1116ab3..e94d05429 100644
--- a/shared/models/activitypub/objects/common-objects.ts
+++ b/shared/models/activitypub/objects/common-objects.ts
@@ -1,14 +1,15 @@
1export interface ActivityIdentifierObject { 1export interface ActivityIdentifierObject {
2 identifier: string 2 identifier: string
3 name: string 3 name: string
4 url?: string
4} 5}
5 6
6export interface ActivityIconObject { 7export interface ActivityIconObject {
7 type: 'Image' 8 type: 'Image'
8 url: string 9 url: string
9 mediaType: 'image/jpeg' 10 mediaType: 'image/jpeg' | 'image/png'
10 width: number 11 width?: number
11 height: number 12 height?: number
12} 13}
13 14
14export type ActivityVideoUrlObject = { 15export type ActivityVideoUrlObject = {
@@ -71,19 +72,21 @@ export interface ActivityMentionObject {
71 name: string 72 name: string
72} 73}
73 74
74export type ActivityTagObject = ActivityPlaylistSegmentHashesObject | 75export type ActivityTagObject =
75 ActivityPlaylistInfohashesObject | 76 ActivityPlaylistSegmentHashesObject
76 ActivityVideoUrlObject | 77 | ActivityPlaylistInfohashesObject
77 ActivityHashTagObject | 78 | ActivityVideoUrlObject
78 ActivityMentionObject | 79 | ActivityHashTagObject
79 ActivityBitTorrentUrlObject | 80 | ActivityMentionObject
80 ActivityMagnetUrlObject 81 | ActivityBitTorrentUrlObject
82 | ActivityMagnetUrlObject
81 83
82export type ActivityUrlObject = ActivityVideoUrlObject | 84export type ActivityUrlObject =
83 ActivityPlaylistUrlObject | 85 ActivityVideoUrlObject
84 ActivityBitTorrentUrlObject | 86 | ActivityPlaylistUrlObject
85 ActivityMagnetUrlObject | 87 | ActivityBitTorrentUrlObject
86 ActivityHtmlUrlObject 88 | ActivityMagnetUrlObject
89 | ActivityHtmlUrlObject
87 90
88export interface ActivityPubAttributedTo { 91export interface ActivityPubAttributedTo {
89 type: 'Group' | 'Person' 92 type: 'Group' | 'Person'
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts
index 5f1264a76..d9622b414 100644
--- a/shared/models/activitypub/objects/video-abuse-object.ts
+++ b/shared/models/activitypub/objects/video-abuse-object.ts
@@ -1,5 +1,5 @@
1export interface VideoAbuseObject { 1export interface VideoAbuseObject {
2 type: 'Flag', 2 type: 'Flag'
3 content: string 3 content: string
4 object: string | string[] 4 object: string | string[]
5} 5}
diff --git a/shared/models/activitypub/objects/video-torrent-object.ts b/shared/models/activitypub/objects/video-torrent-object.ts
index 239822bc4..11de8fc56 100644
--- a/shared/models/activitypub/objects/video-torrent-object.ts
+++ b/shared/models/activitypub/objects/video-torrent-object.ts
@@ -20,8 +20,8 @@ export interface VideoTorrentObject {
20 subtitleLanguage: ActivityIdentifierObject[] 20 subtitleLanguage: ActivityIdentifierObject[]
21 views: number 21 views: number
22 sensitive: boolean 22 sensitive: boolean
23 commentsEnabled: boolean, 23 commentsEnabled: boolean
24 downloadEnabled: boolean, 24 downloadEnabled: boolean
25 waitTranscoding: boolean 25 waitTranscoding: boolean
26 state: VideoState 26 state: VideoState
27 published: string 27 published: string
@@ -30,7 +30,9 @@ export interface VideoTorrentObject {
30 mediaType: 'text/markdown' 30 mediaType: 'text/markdown'
31 content: string 31 content: string
32 support: string 32 support: string
33 icon: ActivityIconObject 33
34 icon: ActivityIconObject[]
35
34 url: ActivityUrlObject[] 36 url: ActivityUrlObject[]
35 likes: string 37 likes: string
36 dislikes: string 38 dislikes: string
diff --git a/shared/models/activitypub/objects/view-object.ts b/shared/models/activitypub/objects/view-object.ts
index 00348116a..4dd21ce8e 100644
--- a/shared/models/activitypub/objects/view-object.ts
+++ b/shared/models/activitypub/objects/view-object.ts
@@ -1,5 +1,5 @@
1export interface ViewObject { 1export interface ViewObject {
2 type: 'View', 2 type: 'View'
3 actor: string 3 actor: string
4 object: string 4 object: string
5} 5}
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts
index 032944281..9ae175df9 100644
--- a/shared/models/i18n/i18n.ts
+++ b/shared/models/i18n/i18n.ts
@@ -56,6 +56,8 @@ export function isDefaultLocale (locale: string) {
56} 56}
57 57
58export function peertubeTranslate (str: string, translations?: { [ id: string ]: string }) { 58export function peertubeTranslate (str: string, translations?: { [ id: string ]: string }) {
59 // FIXME: remove disable rule when the client is upgraded to typescript 3.7
60 // eslint-disable-next-line
59 return translations && translations[str] ? translations[str] : str 61 return translations && translations[str] ? translations[str] : str
60} 62}
61 63
diff --git a/shared/models/nodeinfo/index.d.ts b/shared/models/nodeinfo/index.d.ts
index 0a2d0492e..336cb66d2 100644
--- a/shared/models/nodeinfo/index.d.ts
+++ b/shared/models/nodeinfo/index.d.ts
@@ -98,7 +98,7 @@ export interface HttpNodeinfoDiasporaSoftwareNsSchema20 {
98 * The amount of users that signed in at least once in the last 30 days. 98 * The amount of users that signed in at least once in the last 30 days.
99 */ 99 */
100 activeMonth?: number 100 activeMonth?: number
101 }; 101 }
102 /** 102 /**
103 * The amount of posts that were made by users that are registered on this server. 103 * The amount of posts that were made by users that are registered on this server.
104 */ 104 */
diff --git a/shared/models/plugins/peertube-plugin-latest-version.model.ts b/shared/models/plugins/peertube-plugin-latest-version.model.ts
index dec4618fa..811a64429 100644
--- a/shared/models/plugins/peertube-plugin-latest-version.model.ts
+++ b/shared/models/plugins/peertube-plugin-latest-version.model.ts
@@ -1,5 +1,5 @@
1export interface PeertubePluginLatestVersionRequest { 1export interface PeertubePluginLatestVersionRequest {
2 currentPeerTubeEngine?: string, 2 currentPeerTubeEngine?: string
3 3
4 npmNames: string[] 4 npmNames: string[]
5} 5}
diff --git a/shared/models/plugins/plugin-package-json.model.ts b/shared/models/plugins/plugin-package-json.model.ts
index 3f3077671..c26e9ae5b 100644
--- a/shared/models/plugins/plugin-package-json.model.ts
+++ b/shared/models/plugins/plugin-package-json.model.ts
@@ -5,7 +5,7 @@ export type PluginTranslationPaths = {
5} 5}
6 6
7export type ClientScript = { 7export type ClientScript = {
8 script: string, 8 script: string
9 scopes: PluginClientScope[] 9 scopes: PluginClientScope[]
10} 10}
11 11
@@ -13,12 +13,12 @@ export type PluginPackageJson = {
13 name: string 13 name: string
14 version: string 14 version: string
15 description: string 15 description: string
16 engine: { peertube: string }, 16 engine: { peertube: string }
17 17
18 homepage: string, 18 homepage: string
19 author: string, 19 author: string
20 bugs: string, 20 bugs: string
21 library: string, 21 library: string
22 22
23 staticDirs: { [ name: string ]: string } 23 staticDirs: { [ name: string ]: string }
24 css: string[] 24 css: string[]
diff --git a/shared/models/plugins/server-hook.model.ts b/shared/models/plugins/server-hook.model.ts
index 80ecd9e24..20f89b86d 100644
--- a/shared/models/plugins/server-hook.model.ts
+++ b/shared/models/plugins/server-hook.model.ts
@@ -70,7 +70,7 @@ export const serverActionHookObject = {
70 // Fired when a user is updated by an admin/moderator 70 // Fired when a user is updated by an admin/moderator
71 'action:api.user.updated': true, 71 'action:api.user.updated': true,
72 72
73 // Fired when a user got a new oauth2 token 73 // Fired when a user got a new oauth2 token
74 'action:api.user.oauth2-got-token': true 74 'action:api.user.oauth2-got-token': true
75} 75}
76 76
diff --git a/shared/models/redundancy/index.ts b/shared/models/redundancy/index.ts
index 61bf0fca7..649cc489f 100644
--- a/shared/models/redundancy/index.ts
+++ b/shared/models/redundancy/index.ts
@@ -1 +1,3 @@
1export * from './videos-redundancy.model' 1export * from './videos-redundancy-strategy.model'
2export * from './video-redundancies-filters.model'
3export * from './video-redundancy.model'
diff --git a/shared/models/redundancy/video-redundancies-filters.model.ts b/shared/models/redundancy/video-redundancies-filters.model.ts
new file mode 100644
index 000000000..05ba7dfd3
--- /dev/null
+++ b/shared/models/redundancy/video-redundancies-filters.model.ts
@@ -0,0 +1 @@
export type VideoRedundanciesTarget = 'my-videos' | 'remote-videos'
diff --git a/shared/models/redundancy/video-redundancy.model.ts b/shared/models/redundancy/video-redundancy.model.ts
new file mode 100644
index 000000000..fa6e05832
--- /dev/null
+++ b/shared/models/redundancy/video-redundancy.model.ts
@@ -0,0 +1,35 @@
1export interface VideoRedundancy {
2 id: number
3 name: string
4 url: string
5 uuid: string
6
7 redundancies: {
8 files: FileRedundancyInformation[]
9
10 streamingPlaylists: StreamingPlaylistRedundancyInformation[]
11 }
12}
13
14interface RedundancyInformation {
15 id: number
16 fileUrl: string
17 strategy: string
18
19 createdAt: Date | string
20 updatedAt: Date | string
21
22 expiresOn: Date | string
23
24 size: number
25}
26
27// eslint-disable-next-line @typescript-eslint/no-empty-interface
28export interface FileRedundancyInformation extends RedundancyInformation {
29
30}
31
32// eslint-disable-next-line @typescript-eslint/no-empty-interface
33export interface StreamingPlaylistRedundancyInformation extends RedundancyInformation {
34
35}
diff --git a/shared/models/redundancy/videos-redundancy.model.ts b/shared/models/redundancy/videos-redundancy-strategy.model.ts
index a8c2743c1..15409abf0 100644
--- a/shared/models/redundancy/videos-redundancy.model.ts
+++ b/shared/models/redundancy/videos-redundancy-strategy.model.ts
@@ -1,4 +1,5 @@
1export type VideoRedundancyStrategy = 'most-views' | 'trending' | 'recently-added' 1export type VideoRedundancyStrategy = 'most-views' | 'trending' | 'recently-added'
2export type VideoRedundancyStrategyWithManual = VideoRedundancyStrategy | 'manual'
2 3
3export type MostViewsRedundancyStrategy = { 4export type MostViewsRedundancyStrategy = {
4 strategy: 'most-views' 5 strategy: 'most-views'
@@ -19,4 +20,4 @@ export type RecentlyAddedStrategy = {
19 minLifetime: number 20 minLifetime: number
20} 21}
21 22
22export type VideosRedundancy = MostViewsRedundancyStrategy | TrendingRedundancyStrategy | RecentlyAddedStrategy 23export type VideosRedundancyStrategy = MostViewsRedundancyStrategy | TrendingRedundancyStrategy | RecentlyAddedStrategy
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index 032b91a29..07e17bda2 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -97,7 +97,7 @@ export interface CustomConfig {
97 videos: { 97 videos: {
98 http: { 98 http: {
99 enabled: boolean 99 enabled: boolean
100 }, 100 }
101 torrent: { 101 torrent: {
102 enabled: boolean 102 enabled: boolean
103 } 103 }
@@ -114,7 +114,7 @@ export interface CustomConfig {
114 114
115 followers: { 115 followers: {
116 instance: { 116 instance: {
117 enabled: boolean, 117 enabled: boolean
118 manualApproval: boolean 118 manualApproval: boolean
119 } 119 }
120 } 120 }
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts
index b82a633b2..cf29d20d4 100644
--- a/shared/models/server/job.model.ts
+++ b/shared/models/server/job.model.ts
@@ -1,22 +1,24 @@
1export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed' 1export type JobState = 'active' | 'completed' | 'failed' | 'waiting' | 'delayed'
2 2
3export type JobType = 'activitypub-http-unicast' | 3export type JobType =
4 'activitypub-http-broadcast' | 4 | 'activitypub-http-unicast'
5 'activitypub-http-fetcher' | 5 | 'activitypub-http-broadcast'
6 'activitypub-follow' | 6 | 'activitypub-http-fetcher'
7 'video-file-import' | 7 | 'activitypub-follow'
8 'video-transcoding' | 8 | 'video-file-import'
9 'email' | 9 | 'video-transcoding'
10 'video-import' | 10 | 'email'
11 'videos-views' | 11 | 'video-import'
12 'activitypub-refresher' 12 | 'videos-views'
13 | 'activitypub-refresher'
14 | 'video-redundancy'
13 15
14export interface Job { 16export interface Job {
15 id: number 17 id: number
16 state: JobState 18 state: JobState
17 type: JobType 19 type: JobType
18 data: any, 20 data: any
19 error: any, 21 error: any
20 createdAt: Date | string 22 createdAt: Date | string
21 finishedOn: Date | string 23 finishedOn: Date | string
22 processedOn: Date | string 24 processedOn: Date | string
diff --git a/shared/models/server/server-config.model.ts b/shared/models/server/server-config.model.ts
index f1bb2153c..76e0d6f2d 100644
--- a/shared/models/server/server-config.model.ts
+++ b/shared/models/server/server-config.model.ts
@@ -46,7 +46,7 @@ export interface ServerConfig {
46 } 46 }
47 47
48 signup: { 48 signup: {
49 allowed: boolean, 49 allowed: boolean
50 allowedForCurrentIP: boolean 50 allowedForCurrentIP: boolean
51 requiresEmailVerification: boolean 51 requiresEmailVerification: boolean
52 } 52 }
@@ -97,7 +97,7 @@ export interface ServerConfig {
97 max: number 97 max: number
98 } 98 }
99 extensions: string[] 99 extensions: string[]
100 }, 100 }
101 file: { 101 file: {
102 extensions: string[] 102 extensions: string[]
103 } 103 }
@@ -107,7 +107,7 @@ export interface ServerConfig {
107 file: { 107 file: {
108 size: { 108 size: {
109 max: number 109 max: number
110 }, 110 }
111 extensions: string[] 111 extensions: string[]
112 } 112 }
113 } 113 }
diff --git a/shared/models/server/server-stats.model.ts b/shared/models/server/server-stats.model.ts
index 74f3de5d3..11778e6ed 100644
--- a/shared/models/server/server-stats.model.ts
+++ b/shared/models/server/server-stats.model.ts
@@ -1,4 +1,4 @@
1import { VideoRedundancyStrategy } from '../redundancy' 1import { VideoRedundancyStrategyWithManual } from '../redundancy'
2 2
3export interface ServerStats { 3export interface ServerStats {
4 totalUsers: number 4 totalUsers: number
@@ -13,11 +13,13 @@ export interface ServerStats {
13 totalInstanceFollowers: number 13 totalInstanceFollowers: number
14 totalInstanceFollowing: number 14 totalInstanceFollowing: number
15 15
16 videosRedundancy: { 16 videosRedundancy: VideosRedundancyStats[]
17 strategy: VideoRedundancyStrategy 17}
18 totalSize: number 18
19 totalUsed: number 19export interface VideosRedundancyStats {
20 totalVideoFiles: number 20 strategy: VideoRedundancyStrategyWithManual
21 totalVideos: number 21 totalSize: number
22 }[] 22 totalUsed: number
23 totalVideoFiles: number
24 totalVideos: number
23} 25}
diff --git a/shared/models/users/user-right.enum.ts b/shared/models/users/user-right.enum.ts
index 4a28a229d..2f88a65de 100644
--- a/shared/models/users/user-right.enum.ts
+++ b/shared/models/users/user-right.enum.ts
@@ -33,5 +33,7 @@ export enum UserRight {
33 SEE_ALL_VIDEOS, 33 SEE_ALL_VIDEOS,
34 CHANGE_VIDEO_OWNERSHIP, 34 CHANGE_VIDEO_OWNERSHIP,
35 35
36 MANAGE_PLUGINS 36 MANAGE_PLUGINS,
37
38 MANAGE_VIDEOS_REDUNDANCIES
37} 39}
diff --git a/shared/models/users/user-role.ts b/shared/models/users/user-role.ts
index 0b6554e51..ae3a0d983 100644
--- a/shared/models/users/user-role.ts
+++ b/shared/models/users/user-role.ts
@@ -7,15 +7,13 @@ export enum UserRole {
7 USER = 2 7 USER = 2
8} 8}
9 9
10// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed 10export const USER_ROLE_LABELS: { [ id in UserRole ]: string } = {
11export const USER_ROLE_LABELS: { [ id: number ]: string } = {
12 [UserRole.USER]: 'User', 11 [UserRole.USER]: 'User',
13 [UserRole.MODERATOR]: 'Moderator', 12 [UserRole.MODERATOR]: 'Moderator',
14 [UserRole.ADMINISTRATOR]: 'Administrator' 13 [UserRole.ADMINISTRATOR]: 'Administrator'
15} 14}
16 15
17// TODO: use UserRole for key once https://github.com/Microsoft/TypeScript/issues/13042 is fixed 16const userRoleRights: { [ id in UserRole ]: UserRight[] } = {
18const userRoleRights: { [ id: number ]: UserRight[] } = {
19 [UserRole.ADMINISTRATOR]: [ 17 [UserRole.ADMINISTRATOR]: [
20 UserRight.ALL 18 UserRight.ALL
21 ], 19 ],
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 168851196..efb451014 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -1,6 +1,5 @@
1import { Account } from '../actors' 1import { Account } from '../actors'
2import { VideoChannel } from '../videos/channel/video-channel.model' 2import { VideoChannel } from '../videos/channel/video-channel.model'
3import { VideoPlaylist } from '../videos/playlist/video-playlist.model'
4import { UserRole } from './user-role' 3import { UserRole } from './user-role'
5import { NSFWPolicyType } from '../videos/nsfw-policy.type' 4import { NSFWPolicyType } from '../videos/nsfw-policy.type'
6import { UserNotificationSetting } from './user-notification-setting.model' 5import { UserNotificationSetting } from './user-notification-setting.model'
diff --git a/shared/models/videos/video-transcoding-fps.model.ts b/shared/models/videos/video-transcoding-fps.model.ts
index 82022d2f1..25fc1c2da 100644
--- a/shared/models/videos/video-transcoding-fps.model.ts
+++ b/shared/models/videos/video-transcoding-fps.model.ts
@@ -1,6 +1,8 @@
1export type VideoTranscodingFPS = { 1export type VideoTranscodingFPS = {
2 MIN: number, 2 MIN: number
3 AVERAGE: number, 3 STANDARD: number[]
4 MAX: number, 4 HD_STANDARD: number[]
5 AVERAGE: number
6 MAX: number
5 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number 7 KEEP_ORIGIN_FPS_RESOLUTION_MIN: number
6} 8}
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 7576439fe..a69152759 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -1,4 +1,4 @@
1import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index' 1import { AccountSummary, VideoChannelSummary, VideoState } from '../../index'
2import { Account } from '../actors' 2import { Account } from '../actors'
3import { VideoChannel } from './channel/video-channel.model' 3import { VideoChannel } from './channel/video-channel.model'
4import { VideoPrivacy } from './video-privacy.enum' 4import { VideoPrivacy } from './video-privacy.enum'
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 43718e2a1..907187e4c 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -977,6 +977,12 @@ paths:
977 application/json: 977 application/json:
978 schema: 978 schema:
979 $ref: '#/components/schemas/VideoUploadResponse' 979 $ref: '#/components/schemas/VideoUploadResponse'
980 '403':
981 description: 'The user video quota is exceeded with this video.'
982 '408':
983 description: 'Upload has timed out'
984 '422':
985 description: 'Invalid input file.'
980 requestBody: 986 requestBody:
981 content: 987 content:
982 multipart/form-data: 988 multipart/form-data:
diff --git a/support/doc/production.md b/support/doc/production.md
index 8f061f868..6febaba5d 100644
--- a/support/doc/production.md
+++ b/support/doc/production.md
@@ -39,7 +39,7 @@ Create the production database and a peertube user inside PostgreSQL:
39 39
40``` 40```
41$ sudo -u postgres createuser -P peertube 41$ sudo -u postgres createuser -P peertube
42$ sudo -u postgres createdb -O peertube peertube_prod 42$ sudo -u postgres createdb -O peertube -E UTF8 -T template0 peertube_prod
43``` 43```
44 44
45Then enable extensions PeerTube needs: 45Then enable extensions PeerTube needs:
diff --git a/support/doc/tools.md b/support/doc/tools.md
index d5427b5b7..1f1e52c36 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -12,6 +12,7 @@
12 - [peertube-upload.js](#peertube-uploadjs) 12 - [peertube-upload.js](#peertube-uploadjs)
13 - [peertube-watch.js](#peertube-watchjs) 13 - [peertube-watch.js](#peertube-watchjs)
14 - [peertube-plugins.js](#peertube-pluginsjs) 14 - [peertube-plugins.js](#peertube-pluginsjs)
15 - [peertube-redundancy.js](#peertube-redundancyjs)
15- [Server tools](#server-tools) 16- [Server tools](#server-tools)
16 - [parse-log](#parse-log) 17 - [parse-log](#parse-log)
17 - [create-transcoding-job.js](#create-transcoding-jobjs) 18 - [create-transcoding-job.js](#create-transcoding-jobjs)
@@ -77,7 +78,8 @@ You can access it as `peertube` via an alias in your `.bashrc` like `alias peert
77 import-videos|import import a video from a streaming platform 78 import-videos|import import a video from a streaming platform
78 watch|w watch a video in the terminal ✩°。⋆ 79 watch|w watch a video in the terminal ✩°。⋆
79 repl initiate a REPL to access internals 80 repl initiate a REPL to access internals
80 plugins|p [action] manag instance plugins 81 plugins|p [action] manage instance plugins
82 redundancy|r [action] manage video redundancies
81 help [cmd] display help for [cmd] 83 help [cmd] display help for [cmd]
82``` 84```
83 85
@@ -200,6 +202,34 @@ $ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path
200$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example 202$ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example
201``` 203```
202 204
205#### peertube-redundancy.js
206
207Manage (list/add/remove) video redundancies:
208
209To list your videos that are duplicated by remote instances:
210
211```
212$ node dist/server/tools/peertube.js redundancy list-remote-redundancies
213```
214
215To list remote videos that your instance duplicated:
216
217```
218$ node dist/server/tools/peertube.js redundancy list-my-redundancies
219```
220
221To duplicate a specific video in your redundancy system:
222
223```
224$ node dist/server/tools/peertube.js redundancy add --video 823
225```
226
227To remove a video redundancy:
228
229```
230$ node dist/server/tools/peertube.js redundancy remove --video 823
231```
232
203## Server tools 233## Server tools
204 234
205These scripts should be run on the server, in `peertube-latest` directory. 235These scripts should be run on the server, in `peertube-latest` directory.
diff --git a/tslint.json b/tslint.json
deleted file mode 100644
index cfe2ac712..000000000
--- a/tslint.json
+++ /dev/null
@@ -1,19 +0,0 @@
1{
2 "extends": "tslint-config-standard",
3 "rules": {
4 "await-promise": [true, "Bluebird"],
5 "no-inferrable-types": true,
6 "eofline": true,
7 "indent": [true, "spaces"],
8 "ter-indent": [
9 true,
10 2,
11 {
12 "SwitchCase": 1
13 }
14 ],
15 "max-line-length": [true, 140],
16 "no-unused-variable": false, // Memory issues
17 "no-floating-promises": false
18 }
19}
diff --git a/yarn.lock b/yarn.lock
index 76ce7ed27..6e75dab9a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3,49 +3,21 @@
3 3
4 4
5"@babel/code-frame@^7.0.0": 5"@babel/code-frame@^7.0.0":
6 version "7.5.5" 6 version "7.8.3"
7 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" 7 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
8 integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== 8 integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
9 dependencies: 9 dependencies:
10 "@babel/highlight" "^7.0.0" 10 "@babel/highlight" "^7.8.3"
11 11
12"@babel/highlight@^7.0.0": 12"@babel/highlight@^7.8.3":
13 version "7.5.0" 13 version "7.8.3"
14 resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" 14 resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
15 integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== 15 integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==
16 dependencies: 16 dependencies:
17 chalk "^2.0.0" 17 chalk "^2.0.0"
18 esutils "^2.0.2" 18 esutils "^2.0.2"
19 js-tokens "^4.0.0" 19 js-tokens "^4.0.0"
20 20
21"@nodelib/fs.scandir@2.1.3":
22 version "2.1.3"
23 resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
24 integrity sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==
25 dependencies:
26 "@nodelib/fs.stat" "2.0.3"
27 run-parallel "^1.1.9"
28
29"@nodelib/fs.stat@2.0.3", "@nodelib/fs.stat@^2.0.2":
30 version "2.0.3"
31 resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz#34dc5f4cabbc720f4e60f75a747e7ecd6c175bd3"
32 integrity sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==
33
34"@nodelib/fs.walk@^1.2.3":
35 version "1.2.4"
36 resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz#011b9202a70a6366e436ca5c065844528ab04976"
37 integrity sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==
38 dependencies:
39 "@nodelib/fs.scandir" "2.1.3"
40 fastq "^1.6.0"
41
42"@samverschueren/stream-to-observable@^0.3.0":
43 version "0.3.0"
44 resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
45 integrity sha512-MI4Xx6LHs4Webyvi6EbspgyAb4D2Q2VtnCQ1blOJcoLS6mVa8lNN2rkIy1CVxfTUpoyIbCTkXES1rLXztFD1lg==
46 dependencies:
47 any-observable "^0.3.0"
48
49"@types/apicache@^1.2.0": 21"@types/apicache@^1.2.0":
50 version "1.2.2" 22 version "1.2.2"
51 resolved "https://registry.yarnpkg.com/@types/apicache/-/apicache-1.2.2.tgz#b820659b1d95e66ec0e71dcd0317e9d30f0c154b" 23 resolved "https://registry.yarnpkg.com/@types/apicache/-/apicache-1.2.2.tgz#b820659b1d95e66ec0e71dcd0317e9d30f0c154b"
@@ -59,9 +31,9 @@
59 integrity sha512-TU1X8jmAU2BjwKryBFV/GDezz7Ge0xu9ZuYC7dy6wKj4hnL0JcxeseCOr/G2JkGylff6hdUBrR+Ee5ApAQeU5g== 31 integrity sha512-TU1X8jmAU2BjwKryBFV/GDezz7Ge0xu9ZuYC7dy6wKj4hnL0JcxeseCOr/G2JkGylff6hdUBrR+Ee5ApAQeU5g==
60 32
61"@types/async@^3.0.0": 33"@types/async@^3.0.0":
62 version "3.0.3" 34 version "3.0.7"
63 resolved "https://registry.yarnpkg.com/@types/async/-/async-3.0.3.tgz#ea3694128c757580e4f9328cd941b81d9c3e9bf6" 35 resolved "https://registry.yarnpkg.com/@types/async/-/async-3.0.7.tgz#ef2733a3d027a81cd86524d0650db55f5d8cdff6"
64 integrity sha512-FrIcC67Zpko1jO8K4d30C41/KVhAABbMbaSxccvXacxPcKbDBav+8WoFzv72BA2zJvyX4T9PFz0we1hcNymgGA== 36 integrity sha512-Oqf/gYXRnUkYL0xYB5gCLr5Ft/3yckVWie8W8TPngOeT56n+NrnHRtu9xbTvFXzxA7vfaK+gPdCjFiFCyBfEPg==
65 37
66"@types/bcrypt@^3.0.0": 38"@types/bcrypt@^3.0.0":
67 version "3.0.0" 39 version "3.0.0"
@@ -75,15 +47,10 @@
75 dependencies: 47 dependencies:
76 "@types/node" "*" 48 "@types/node" "*"
77 49
78"@types/bluebird@*", "@types/bluebird@3.5.27": 50"@types/bluebird@3.5.29":
79 version "3.5.27" 51 version "3.5.29"
80 resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.27.tgz#61eb4d75dc6bfbce51cf49ee9bbebe941b2cb5d0" 52 resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6"
81 integrity sha512-6BmYWSBea18+tSjjSC3QIyV93ZKAeNWGM7R6aYt1ryTZXrlHF+QLV0G2yV0viEGVyRkyQsWfMoJ0k/YghBX5sQ== 53 integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==
82
83"@types/bluebird@3.5.21":
84 version "3.5.21"
85 resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.21.tgz#567615589cc913e84a28ecf9edb031732bdf2634"
86 integrity sha512-6UNEwyw+6SGMC/WMI0ld0PS4st7Qq51qgguFrFizOSpGvZiqe9iswztFSdZvwJBEhLOy2JaxNE6VC7yMAlbfyQ==
87 54
88"@types/body-parser@*", "@types/body-parser@^1.16.3": 55"@types/body-parser@*", "@types/body-parser@^1.16.3":
89 version "1.17.1" 56 version "1.17.1"
@@ -93,12 +60,11 @@
93 "@types/connect" "*" 60 "@types/connect" "*"
94 "@types/node" "*" 61 "@types/node" "*"
95 62
96"@types/bull@3.4.0": 63"@types/bull@3.12.0":
97 version "3.4.0" 64 version "3.12.0"
98 resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.4.0.tgz#18ffefefa4dd1cfbdbdc8ca7df56c934459f6b9d" 65 resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.12.0.tgz#683d7a08db64823076c4b5ac00eea73e5b6947fa"
99 integrity sha512-NVD2X+cUu1qNv6blsOfCr2fVsD3+O13U19dFuy9Du7PWfn1/gjFZEDk220uBuRSH5JyaP4nV6S8BLjsT5/bXUg== 66 integrity sha512-oKj9X8bxBF7OyAsCPGg2hu9msQPM/WwIRJfUHd0xzmKDMYOBepzbWdIuQDoX1xyvDskdjbW2Io7chbxqARae7A==
100 dependencies: 67 dependencies:
101 "@types/bluebird" "*"
102 "@types/ioredis" "*" 68 "@types/ioredis" "*"
103 69
104"@types/bytes@^3.0.0": 70"@types/bytes@^3.0.0":
@@ -126,9 +92,9 @@
126 "@types/chai" "*" 92 "@types/chai" "*"
127 93
128"@types/chai@*", "@types/chai@^4.0.4": 94"@types/chai@*", "@types/chai@^4.0.4":
129 version "4.2.7" 95 version "4.2.8"
130 resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d" 96 resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.8.tgz#c8d645506db0d15f4aafd4dfa873f443ad87ea59"
131 integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g== 97 integrity sha512-U1bQiWbln41Yo6EeHMr+34aUhvrMVyrhn9lYfPSpLTCrZlGxU4Rtn1bocX+0p2Fc/Jkd2FanCEXdw0WNfHHM0w==
132 98
133"@types/color-name@^1.1.1": 99"@types/color-name@^1.1.1":
134 version "1.1.1" 100 version "1.1.1"
@@ -152,10 +118,10 @@
152 resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80" 118 resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80"
153 integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw== 119 integrity sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==
154 120
155"@types/events@*": 121"@types/eslint-visitor-keys@^1.0.0":
156 version "3.0.0" 122 version "1.0.0"
157 resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" 123 resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
158 integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== 124 integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
159 125
160"@types/express-rate-limit@^3.3.0": 126"@types/express-rate-limit@^3.3.0":
161 version "3.3.3" 127 version "3.3.3"
@@ -165,9 +131,9 @@
165 "@types/express" "*" 131 "@types/express" "*"
166 132
167"@types/express-serve-static-core@*": 133"@types/express-serve-static-core@*":
168 version "4.17.1" 134 version "4.17.2"
169 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.1.tgz#82be64a77211b205641e0209096fd3afb62481d3" 135 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz#f6f41fa35d42e79dbf6610eccbb2637e6008a0cf"
170 integrity sha512-9e7jj549ZI+RxY21Cl0t8uBnWyb22HzILupyHZjYEVK//5TT/1bZodU+yUbLnPdoYViBBnNWbxp4zYjGV0zUGw== 136 integrity sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==
171 dependencies: 137 dependencies:
172 "@types/node" "*" 138 "@types/node" "*"
173 "@types/range-parser" "*" 139 "@types/range-parser" "*"
@@ -195,22 +161,18 @@
195 dependencies: 161 dependencies:
196 "@types/node" "*" 162 "@types/node" "*"
197 163
198"@types/glob@^7.1.1":
199 version "7.1.1"
200 resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
201 integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
202 dependencies:
203 "@types/events" "*"
204 "@types/minimatch" "*"
205 "@types/node" "*"
206
207"@types/ioredis@*": 164"@types/ioredis@*":
208 version "4.14.3" 165 version "4.14.6"
209 resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.14.3.tgz#6a6089296d6fb90bbaee96d36b19d480efff026a" 166 resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.14.6.tgz#446403ab0360bf12d010ba8bbd0670b292f12143"
210 integrity sha512-WM9xJkP+ckvr8DFy2bFEPWEp7454L/4cBT11qhqPqyzdyZ+8DxUfkf/yo+rQGQJbld0agY2PI5Jb3xxC7KZs6g== 167 integrity sha512-VUbEZaeCfdiqfd3UDtmPpwewCBdbnjpMZtarKuZV7XwkhqgBZN208WQpsD3hT0BJqEx3GPApFnIVnIOq/eBpbA==
211 dependencies: 168 dependencies:
212 "@types/node" "*" 169 "@types/node" "*"
213 170
171"@types/json-schema@^7.0.3":
172 version "7.0.4"
173 resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
174 integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
175
214"@types/json5@^0.0.29": 176"@types/json5@^0.0.29":
215 version "0.0.29" 177 version "0.0.29"
216 resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" 178 resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -257,11 +219,6 @@
257 resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" 219 resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
258 integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== 220 integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
259 221
260"@types/minimatch@*":
261 version "3.0.3"
262 resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
263 integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
264
265"@types/mkdirp@^0.5.1": 222"@types/mkdirp@^0.5.1":
266 version "0.5.2" 223 version "0.5.2"
267 resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f" 224 resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-0.5.2.tgz#503aacfe5cc2703d5484326b1b27efa67a339c1f"
@@ -269,10 +226,10 @@
269 dependencies: 226 dependencies:
270 "@types/node" "*" 227 "@types/node" "*"
271 228
272"@types/mocha@^5.0.0": 229"@types/mocha@^7.0.1":
273 version "5.2.7" 230 version "7.0.1"
274 resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" 231 resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.1.tgz#5d7ec2a789a1f77c59b7ad071b9d50bf1abbfc9e"
275 integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== 232 integrity sha512-L/Nw/2e5KUaprNJoRA33oly+M8X8n0K+FwLTbYqwTcR14wdPWeRkigBLfSFpN/Asf9ENZTMZwLxjtjeYucAA4Q==
276 233
277"@types/morgan@^1.7.32": 234"@types/morgan@^1.7.32":
278 version "1.7.37" 235 version "1.7.37"
@@ -282,26 +239,26 @@
282 "@types/express" "*" 239 "@types/express" "*"
283 240
284"@types/multer@^1.3.3": 241"@types/multer@^1.3.3":
285 version "1.3.10" 242 version "1.4.0"
286 resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.3.10.tgz#d7afbd916f688fceb4460320e62a8ad1ab3e3cad" 243 resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.0.tgz#a4a83bee02696f61f63b65a87f9ebe8bdf12b767"
287 integrity sha512-3hECfz+W0ix/LvPanp87mjO3kOyDnJYTpY9y7gdBxXnYXqEcj21pD0lW7KEUFFr8CHrDF5Mhh7o241KLEXDRoQ== 244 integrity sha512-mF3lGy1HTixLELNGufKTvLWGUZKd0Amz/nZYj79nzCXWye2wTlgIo4CZ+mze7xMkezcWmQviV9uXYRMpZ0qk1w==
288 dependencies: 245 dependencies:
289 "@types/express" "*" 246 "@types/express" "*"
290 247
291"@types/node@*": 248"@types/node@*":
292 version "13.1.4" 249 version "13.5.3"
293 resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.4.tgz#4cfd90175a200ee9b02bd6b1cd19bc349741607e" 250 resolved "https://registry.yarnpkg.com/@types/node/-/node-13.5.3.tgz#37f1f539b7535b9fb4ef77d59db1847a837b7f17"
294 integrity sha512-Lue/mlp2egZJoHXZr4LndxDAd7i/7SQYhV0EjWfb/a4/OZ6tuVwMCVPiwkU5nsEipxEf7hmkSU7Em5VQ8P5NGA== 251 integrity sha512-ZPnWX9PW992w6DUsz3JIXHaSb5v7qmKCVzC3km6SxcDGxk7zmLfYaCJTbktIa5NeywJkkZDhGldKqDIvC5DRrA==
295 252
296"@types/node@^10.0.8": 253"@types/node@^10.0.8":
297 version "10.17.13" 254 version "10.17.14"
298 resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.13.tgz#ccebcdb990bd6139cd16e84c39dc2fb1023ca90c" 255 resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541"
299 integrity sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg== 256 integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw==
300 257
301"@types/node@^12.12.3": 258"@types/node@^12.12.3":
302 version "12.12.24" 259 version "12.12.26"
303 resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.24.tgz#d4606afd8cf6c609036b854360367d1b2c78931f" 260 resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9"
304 integrity sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug== 261 integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA==
305 262
306"@types/nodemailer@^6.2.0": 263"@types/nodemailer@^6.2.0":
307 version "6.4.0" 264 version "6.4.0"
@@ -363,9 +320,9 @@
363 form-data "^2.5.0" 320 form-data "^2.5.0"
364 321
365"@types/sax@^1.2.0": 322"@types/sax@^1.2.0":
366 version "1.2.0" 323 version "1.2.1"
367 resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.0.tgz#6025e0b7fc7cd5f3d83808a6809730bac798565d" 324 resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.1.tgz#e0248be936ece791a82db1a57f3fb5f7c87e8172"
368 integrity sha512-D8ef/GGUjiHuUOiXV6tkJw6Zq2Sm8vcBScJSvj+monDI5YncJ6M3oNIXR7EtmWPVqJw0jsZF2ARN/X5gvGmQSA== 325 integrity sha512-dqYdvN7Sbw8QT/0Ci5rhjE4/iCMJEM0Y9rHpCu+gGXD9Lwbz28t6HI2yegsB6BoV1sShRMU6lAmAcgRjmFy7LA==
369 dependencies: 326 dependencies:
370 "@types/node" "*" 327 "@types/node" "*"
371 328
@@ -377,10 +334,10 @@
377 "@types/express-serve-static-core" "*" 334 "@types/express-serve-static-core" "*"
378 "@types/mime" "*" 335 "@types/mime" "*"
379 336
380"@types/sharp@^0.23.0": 337"@types/sharp@^0.24.0":
381 version "0.23.1" 338 version "0.24.0"
382 resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.23.1.tgz#1e02560371d6603adc121389512f0745028aa507" 339 resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.24.0.tgz#28abfeac45b4dcb472305503105322e97a6c2672"
383 integrity sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ== 340 integrity sha512-+0WeyJajTSoIacBzonsq856whNJC+cN9FNEs0yZ6hFq/V1CZmlqM8vBRy7TKZunH+gIO7SwDCzgXYWRRbzqfDA==
384 dependencies: 341 dependencies:
385 "@types/node" "*" 342 "@types/node" "*"
386 343
@@ -438,13 +395,56 @@
438 "@types/parse-torrent" "*" 395 "@types/parse-torrent" "*"
439 "@types/simple-peer" "*" 396 "@types/simple-peer" "*"
440 397
441"@types/ws@^6.0.0": 398"@types/ws@^7.2.1":
442 version "6.0.4" 399 version "7.2.1"
443 resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1" 400 resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.1.tgz#b800f2b8aee694e2b581113643e20d79dd3b8556"
444 integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg== 401 integrity sha512-UEmRNbXFGvfs/sLncf01GuVv6U1mZP3Df0iXWx4kUlikJxbFyFADp95mDn1XDTE2mXpzzoHcKlfFcbytLq4vaA==
445 dependencies: 402 dependencies:
446 "@types/node" "*" 403 "@types/node" "*"
447 404
405"@typescript-eslint/eslint-plugin@^2.18.0":
406 version "2.18.0"
407 resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.18.0.tgz#f8cf272dfb057ecf1ea000fea1e0b3f06a32f9cb"
408 integrity sha512-kuO8WQjV+RCZvAXVRJfXWiJ8iYEtfHlKgcqqqXg9uUkIolEHuUaMmm8/lcO4xwCOtaw6mY0gStn2Lg4/eUXXYQ==
409 dependencies:
410 "@typescript-eslint/experimental-utils" "2.18.0"
411 eslint-utils "^1.4.3"
412 functional-red-black-tree "^1.0.1"
413 regexpp "^3.0.0"
414 tsutils "^3.17.1"
415
416"@typescript-eslint/experimental-utils@2.18.0":
417 version "2.18.0"
418 resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.18.0.tgz#e4eab839082030282496c1439bbf9fdf2a4f3da8"
419 integrity sha512-J6MopKPHuJYmQUkANLip7g9I82ZLe1naCbxZZW3O2sIxTiq/9YYoOELEKY7oPg0hJ0V/AQ225h2z0Yp+RRMXhw==
420 dependencies:
421 "@types/json-schema" "^7.0.3"
422 "@typescript-eslint/typescript-estree" "2.18.0"
423 eslint-scope "^5.0.0"
424
425"@typescript-eslint/parser@^2.10.0":
426 version "2.18.0"
427 resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.18.0.tgz#d5f7fc1839abd4a985394e40e9d2454bd56aeb1f"
428 integrity sha512-SJJPxFMEYEWkM6pGfcnjLU+NJIPo+Ko1QrCBL+i0+zV30ggLD90huEmMMhKLHBpESWy9lVEeWlQibweNQzyc+A==
429 dependencies:
430 "@types/eslint-visitor-keys" "^1.0.0"
431 "@typescript-eslint/experimental-utils" "2.18.0"
432 "@typescript-eslint/typescript-estree" "2.18.0"
433 eslint-visitor-keys "^1.1.0"
434
435"@typescript-eslint/typescript-estree@2.18.0":
436 version "2.18.0"
437 resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.18.0.tgz#cfbd16ed1b111166617d718619c19b62764c8460"
438 integrity sha512-gVHylf7FDb8VSi2ypFuEL3hOtoC4HkZZ5dOjXvVjoyKdRrvXAOPSzpNRnKMfaUUEiSLP8UF9j9X9EDLxC0lfZg==
439 dependencies:
440 debug "^4.1.1"
441 eslint-visitor-keys "^1.1.0"
442 glob "^7.1.6"
443 is-glob "^4.0.1"
444 lodash "^4.17.15"
445 semver "^6.3.0"
446 tsutils "^3.17.1"
447
448abbrev@1: 448abbrev@1:
449 version "1.1.1" 449 version "1.1.1"
450 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 450 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@@ -458,6 +458,16 @@ accepts@~1.3.4, accepts@~1.3.7:
458 mime-types "~2.1.24" 458 mime-types "~2.1.24"
459 negotiator "0.6.2" 459 negotiator "0.6.2"
460 460
461acorn-jsx@^5.1.0:
462 version "5.1.0"
463 resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
464 integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
465
466acorn@^7.1.0:
467 version "7.1.0"
468 resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
469 integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==
470
461addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.4.2: 471addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.4.2:
462 version "1.5.1" 472 version "1.5.1"
463 resolved "https://registry.yarnpkg.com/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz#bfada13fd6aeeeac19f1e9f7d84b4bbab45e5208" 473 resolved "https://registry.yarnpkg.com/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz#bfada13fd6aeeeac19f1e9f7d84b4bbab45e5208"
@@ -473,20 +483,12 @@ after@0.8.2:
473 resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" 483 resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
474 integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= 484 integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
475 485
476aggregate-error@^3.0.0: 486ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
477 version "3.0.1" 487 version "6.11.0"
478 resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" 488 resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9"
479 integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== 489 integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==
480 dependencies: 490 dependencies:
481 clean-stack "^2.0.0" 491 fast-deep-equal "^3.1.1"
482 indent-string "^4.0.0"
483
484ajv@^6.5.5:
485 version "6.10.2"
486 resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
487 integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
488 dependencies:
489 fast-deep-equal "^2.0.1"
490 fast-json-stable-stringify "^2.0.0" 492 fast-json-stable-stringify "^2.0.0"
491 json-schema-traverse "^0.4.1" 493 json-schema-traverse "^0.4.1"
492 uri-js "^4.2.2" 494 uri-js "^4.2.2"
@@ -503,10 +505,12 @@ ansi-colors@3.2.3:
503 resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" 505 resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813"
504 integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== 506 integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==
505 507
506ansi-escapes@^3.0.0: 508ansi-escapes@^4.2.1:
507 version "3.2.0" 509 version "4.3.0"
508 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" 510 resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
509 integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== 511 integrity sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==
512 dependencies:
513 type-fest "^0.8.1"
510 514
511ansi-regex@^2.0.0: 515ansi-regex@^2.0.0:
512 version "2.1.1" 516 version "2.1.1"
@@ -528,11 +532,6 @@ ansi-regex@^5.0.0:
528 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" 532 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
529 integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== 533 integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
530 534
531ansi-styles@^2.2.1:
532 version "2.2.1"
533 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
534 integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
535
536ansi-styles@^3.2.0, ansi-styles@^3.2.1: 535ansi-styles@^3.2.0, ansi-styles@^3.2.1:
537 version "3.2.1" 536 version "3.2.1"
538 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 537 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -548,11 +547,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
548 "@types/color-name" "^1.1.1" 547 "@types/color-name" "^1.1.1"
549 color-convert "^2.0.1" 548 color-convert "^2.0.1"
550 549
551any-observable@^0.3.0:
552 version "0.3.0"
553 resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
554 integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==
555
556any-promise@^1.3.0: 550any-promise@^1.3.0:
557 version "1.3.0" 551 version "1.3.0"
558 resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 552 resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -567,9 +561,9 @@ anymatch@~3.1.1:
567 picomatch "^2.0.4" 561 picomatch "^2.0.4"
568 562
569apicache@^1.4.0: 563apicache@^1.4.0:
570 version "1.5.2" 564 version "1.5.3"
571 resolved "https://registry.yarnpkg.com/apicache/-/apicache-1.5.2.tgz#2cb0697d9b1b612b505b1a44face66d48b1d1404" 565 resolved "https://registry.yarnpkg.com/apicache/-/apicache-1.5.3.tgz#8977b358bf7d579d55fe3d183c907ae5dbcfb357"
572 integrity sha512-jO8ie/Zqmr3MxLQSVNsHiQo5R1tbbP1TnpI6xOnRJv9wUOSP+YnZkULWmdo3fE7PHBSxIQzgIsEHGa6H5hKH9Q== 566 integrity sha512-n1h39Bt7tMiJMV0u0tFlhigig8Uo/wJmKoj6WE/OwvZ+WbFchn7jnXleotZOzZTUBtr0Tg9iJshHnJDAGQbAEQ==
573 567
574append-field@^1.0.0: 568append-field@^1.0.0:
575 version "1.0.0" 569 version "1.0.0"
@@ -606,10 +600,22 @@ array-flatten@1.1.1:
606 resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 600 resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
607 integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 601 integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
608 602
609array-union@^2.1.0: 603array-includes@^3.0.3:
610 version "2.1.0" 604 version "3.1.1"
611 resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 605 resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.1.tgz#cdd67e6852bdf9c1215460786732255ed2459348"
612 integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 606 integrity sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==
607 dependencies:
608 define-properties "^1.1.3"
609 es-abstract "^1.17.0"
610 is-string "^1.0.5"
611
612array.prototype.flat@^1.2.1:
613 version "1.2.3"
614 resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b"
615 integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==
616 dependencies:
617 define-properties "^1.1.3"
618 es-abstract "^1.17.0-next.1"
613 619
614arraybuffer.slice@~0.0.7: 620arraybuffer.slice@~0.0.7:
615 version "0.0.7" 621 version "0.0.7"
@@ -638,6 +644,11 @@ assertion-error@^1.1.0:
638 resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" 644 resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
639 integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== 645 integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==
640 646
647astral-regex@^1.0.0:
648 version "1.0.0"
649 resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
650 integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
651
641async-limiter@~1.0.0: 652async-limiter@~1.0.0:
642 version "1.0.1" 653 version "1.0.1"
643 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" 654 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
@@ -651,9 +662,9 @@ async-lru@^1.1.1:
651 lru "^3.1.0" 662 lru "^3.1.0"
652 663
653async@>=0.2.9, async@^3.0.1, async@^3.1.0: 664async@>=0.2.9, async@^3.0.1, async@^3.1.0:
654 version "3.1.0" 665 version "3.1.1"
655 resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772" 666 resolved "https://registry.yarnpkg.com/async/-/async-3.1.1.tgz#dd3542db03de837979c9ebbca64ca01b06dc98df"
656 integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ== 667 integrity sha512-X5Dj8hK1pJNC2Wzo2Rcp9FBVdJMGRR/S7V+lH46s8GVFhtbo5O4Le5GECCF/8PISVdkUA6mMPvgz7qTTD1rf1g==
657 668
658async@^2.6.1: 669async@^2.6.1:
659 version "2.6.3" 670 version "2.6.3"
@@ -683,9 +694,9 @@ aws-sign2@~0.7.0:
683 integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= 694 integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
684 695
685aws4@^1.8.0: 696aws4@^1.8.0:
686 version "1.9.0" 697 version "1.9.1"
687 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" 698 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
688 integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== 699 integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
689 700
690backo2@1.0.2: 701backo2@1.0.2:
691 version "1.0.2" 702 version "1.0.2"
@@ -903,9 +914,9 @@ body-parser@1.19.0, body-parser@^1.12.4:
903 type-is "~1.6.17" 914 type-is "~1.6.17"
904 915
905bowser@^2.7.0: 916bowser@^2.7.0:
906 version "2.8.1" 917 version "2.9.0"
907 resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.8.1.tgz#35b74165e17b80ba8af6aa4736c2861b001fc09e" 918 resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.9.0.tgz#3bed854233b419b9a7422d9ee3e85504373821c9"
908 integrity sha512-FxxltGKqMHkVa3KtpA+kdnxH0caHPDewccyrK3vW1bsMw6Zco4vRPmMunowX0pXlDZqhxkKSpToADQI2Sk4OeQ== 919 integrity sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==
909 920
910boxen@^1.2.1: 921boxen@^1.2.1:
911 version "1.3.0" 922 version "1.3.0"
@@ -928,7 +939,7 @@ brace-expansion@^1.1.7:
928 balanced-match "^1.0.0" 939 balanced-match "^1.0.0"
929 concat-map "0.0.1" 940 concat-map "0.0.1"
930 941
931braces@^3.0.1, braces@~3.0.2: 942braces@~3.0.2:
932 version "3.0.2" 943 version "3.0.2"
933 resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 944 resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
934 integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 945 integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
@@ -980,11 +991,6 @@ bufferutil@^4.0.0:
980 dependencies: 991 dependencies:
981 node-gyp-build "~3.7.0" 992 node-gyp-build "~3.7.0"
982 993
983builtin-modules@^1.1.1:
984 version "1.1.1"
985 resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
986 integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
987
988bull@^3.4.2: 994bull@^3.4.2:
989 version "3.12.1" 995 version "3.12.1"
990 resolved "https://registry.yarnpkg.com/bull/-/bull-3.12.1.tgz#ced62d0afca81c9264b44f1b6f39243df5d2e73f" 996 resolved "https://registry.yarnpkg.com/bull/-/bull-3.12.1.tgz#ced62d0afca81c9264b44f1b6f39243df5d2e73f"
@@ -1019,29 +1025,15 @@ call-me-maybe@^1.0.1:
1019 resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" 1025 resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
1020 integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= 1026 integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
1021 1027
1022caller-callsite@^2.0.0:
1023 version "2.0.0"
1024 resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
1025 integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
1026 dependencies:
1027 callsites "^2.0.0"
1028
1029caller-path@^2.0.0:
1030 version "2.0.0"
1031 resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
1032 integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
1033 dependencies:
1034 caller-callsite "^2.0.0"
1035
1036callsite@1.0.0: 1028callsite@1.0.0:
1037 version "1.0.0" 1029 version "1.0.0"
1038 resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" 1030 resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20"
1039 integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= 1031 integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA=
1040 1032
1041callsites@^2.0.0: 1033callsites@^3.0.0:
1042 version "2.0.0" 1034 version "3.1.0"
1043 resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" 1035 resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
1044 integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= 1036 integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
1045 1037
1046camelcase@^4.0.0: 1038camelcase@^4.0.0:
1047 version "4.1.0" 1039 version "4.1.0"
@@ -1109,18 +1101,7 @@ chai@^4.1.1:
1109 pathval "^1.1.0" 1101 pathval "^1.1.0"
1110 type-detect "^4.0.5" 1102 type-detect "^4.0.5"
1111 1103
1112chalk@^1.0.0, chalk@^1.1.3: 1104chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.2:
1113 version "1.1.3"
1114 resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
1115 integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=
1116 dependencies:
1117 ansi-styles "^2.2.1"
1118 escape-string-regexp "^1.0.2"
1119 has-ansi "^2.0.0"
1120 strip-ansi "^3.0.0"
1121 supports-color "^2.0.0"
1122
1123chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
1124 version "2.4.2" 1105 version "2.4.2"
1125 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 1106 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
1126 integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 1107 integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1137,6 +1118,11 @@ chalk@^3.0.0:
1137 ansi-styles "^4.1.0" 1118 ansi-styles "^4.1.0"
1138 supports-color "^7.1.0" 1119 supports-color "^7.1.0"
1139 1120
1121chardet@^0.7.0:
1122 version "0.7.0"
1123 resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
1124 integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
1125
1140charenc@~0.0.1: 1126charenc@~0.0.1:
1141 version "0.0.2" 1127 version "0.0.2"
1142 resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" 1128 resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
@@ -1234,30 +1220,22 @@ circular-json@^0.5.9:
1234 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" 1220 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
1235 integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== 1221 integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==
1236 1222
1237clean-stack@^2.0.0:
1238 version "2.2.0"
1239 resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
1240 integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
1241
1242cli-boxes@^1.0.0: 1223cli-boxes@^1.0.0:
1243 version "1.0.0" 1224 version "1.0.0"
1244 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" 1225 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
1245 integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= 1226 integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
1246 1227
1247cli-cursor@^2.0.0, cli-cursor@^2.1.0: 1228cli-cursor@^3.1.0:
1248 version "2.1.0" 1229 version "3.1.0"
1249 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" 1230 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
1250 integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= 1231 integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
1251 dependencies: 1232 dependencies:
1252 restore-cursor "^2.0.0" 1233 restore-cursor "^3.1.0"
1253 1234
1254cli-truncate@^0.2.1: 1235cli-width@^2.0.0:
1255 version "0.2.1" 1236 version "2.2.0"
1256 resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" 1237 resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
1257 integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ= 1238 integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
1258 dependencies:
1259 slice-ansi "0.0.4"
1260 string-width "^1.0.1"
1261 1239
1262cliui@^5.0.0: 1240cliui@^5.0.0:
1263 version "5.0.0" 1241 version "5.0.0"
@@ -1386,7 +1364,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
1386 dependencies: 1364 dependencies:
1387 delayed-stream "~1.0.0" 1365 delayed-stream "~1.0.0"
1388 1366
1389commander@^2.12.1, commander@^2.20.0, commander@^2.7.1: 1367commander@^2.20.0, commander@^2.7.1:
1390 version "2.20.3" 1368 version "2.20.3"
1391 resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 1369 resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
1392 integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 1370 integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -1439,9 +1417,9 @@ concat-stream@^1.5.2:
1439 typedarray "^0.0.6" 1417 typedarray "^0.0.6"
1440 1418
1441concurrently@^5.0.0: 1419concurrently@^5.0.0:
1442 version "5.0.2" 1420 version "5.1.0"
1443 resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.0.2.tgz#4d2911018c0f15ddec34a8e668fc48dced7f3b1e" 1421 resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.1.0.tgz#05523986ba7aaf4b58a49ddd658fab88fa783132"
1444 integrity sha512-iUNVI6PzKO0RVXV9pHWM0khvEbELxf3XLIoChaV6hHyoIaJuxQWZiOwlNysnJX5khsfvIK66+OJqRdbYrdsR1g== 1422 integrity sha512-9ViZMu3OOCID3rBgU31mjBftro2chOop0G2u1olq1OuwRBVRw/GxHTg80TVJBUTJfoswMmEUeuOg1g1yu1X2dA==
1445 dependencies: 1423 dependencies:
1446 chalk "^2.4.2" 1424 chalk "^2.4.2"
1447 date-fns "^2.0.1" 1425 date-fns "^2.0.1"
@@ -1454,9 +1432,9 @@ concurrently@^5.0.0:
1454 yargs "^13.3.0" 1432 yargs "^13.3.0"
1455 1433
1456config@^3.0.0: 1434config@^3.0.0:
1457 version "3.2.4" 1435 version "3.2.5"
1458 resolved "https://registry.yarnpkg.com/config/-/config-3.2.4.tgz#e60a908582991e800852f9cb60fcf424f3274a6c" 1436 resolved "https://registry.yarnpkg.com/config/-/config-3.2.5.tgz#ab10ab88b61a873fbf9a5f0c6b4a22750422f243"
1459 integrity sha512-H1XIGfnU1EAkfjSLn9ZvYDRx9lOezDViuzLDgiJ/lMeqjYe3q6iQfpcLt2NInckJgpAeekbNhQkmnnbdEDs9rw== 1437 integrity sha512-8itpjyR01lAJanhAlPncBngYRZez/LoRLW8wnGi+6SEcsUyA1wvHvbpIrAJYDJT+W9BScnj4mYoUgbtp9I+0+Q==
1460 dependencies: 1438 dependencies:
1461 json5 "^1.0.1" 1439 json5 "^1.0.1"
1462 1440
@@ -1477,6 +1455,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
1477 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 1455 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
1478 integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= 1456 integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
1479 1457
1458contains-path@^0.1.0:
1459 version "0.1.0"
1460 resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
1461 integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=
1462
1480content-disposition@0.5.3: 1463content-disposition@0.5.3:
1481 version "0.5.3" 1464 version "0.5.3"
1482 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 1465 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@@ -1535,16 +1518,6 @@ cors@^2.8.1, cors@^2.8.5:
1535 object-assign "^4" 1518 object-assign "^4"
1536 vary "^1" 1519 vary "^1"
1537 1520
1538cosmiconfig@^5.2.1:
1539 version "5.2.1"
1540 resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
1541 integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
1542 dependencies:
1543 import-fresh "^2.0.0"
1544 is-directory "^0.3.1"
1545 js-yaml "^3.13.1"
1546 parse-json "^4.0.0"
1547
1548create-error-class@^3.0.0: 1521create-error-class@^3.0.0:
1549 version "3.0.2" 1522 version "3.0.2"
1550 resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" 1523 resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
@@ -1587,6 +1560,17 @@ cross-spawn@^5.0.1:
1587 shebang-command "^1.2.0" 1560 shebang-command "^1.2.0"
1588 which "^1.2.9" 1561 which "^1.2.9"
1589 1562
1563cross-spawn@^6.0.5:
1564 version "6.0.5"
1565 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
1566 integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
1567 dependencies:
1568 nice-try "^1.0.4"
1569 path-key "^2.0.1"
1570 semver "^5.5.0"
1571 shebang-command "^1.2.0"
1572 which "^1.2.9"
1573
1590cross-spawn@^7.0.0: 1574cross-spawn@^7.0.0:
1591 version "7.0.1" 1575 version "7.0.1"
1592 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" 1576 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14"
@@ -1631,17 +1615,12 @@ dasherize@2.0.0:
1631 resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" 1615 resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308"
1632 integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= 1616 integrity sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg=
1633 1617
1634date-fns@^1.27.2:
1635 version "1.30.1"
1636 resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
1637 integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
1638
1639date-fns@^2.0.1: 1618date-fns@^2.0.1:
1640 version "2.8.1" 1619 version "2.9.0"
1641 resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.8.1.tgz#2109362ccb6c87c3ca011e9e31f702bc09e4123b" 1620 resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.9.0.tgz#d0b175a5c37ed5f17b97e2272bbc1fa5aec677d2"
1642 integrity sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg== 1621 integrity sha512-khbFLu/MlzLjEzy9Gh8oY1hNt/Dvxw3J6Rbc28cVoYWQaC1S3YI4xwkF9ZWcjDLscbZlY9hISMr66RFzZagLsA==
1643 1622
1644debug@2.6.9, debug@^2.2.0: 1623debug@2.6.9, debug@^2.2.0, debug@^2.6.9:
1645 version "2.6.9" 1624 version "2.6.9"
1646 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 1625 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
1647 integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 1626 integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -1686,11 +1665,6 @@ decompress-response@^4.2.0:
1686 dependencies: 1665 dependencies:
1687 mimic-response "^2.0.0" 1666 mimic-response "^2.0.0"
1688 1667
1689dedent@^0.7.0:
1690 version "0.7.0"
1691 resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
1692 integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=
1693
1694deep-eql@0.1.3: 1668deep-eql@0.1.3:
1695 version "0.1.3" 1669 version "0.1.3"
1696 resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 1670 resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2"
@@ -1715,6 +1689,11 @@ deep-extend@^0.6.0:
1715 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" 1689 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
1716 integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== 1690 integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
1717 1691
1692deep-is@~0.1.3:
1693 version "0.1.3"
1694 resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
1695 integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
1696
1718deep-object-diff@^1.1.0: 1697deep-object-diff@^1.1.0:
1719 version "1.1.0" 1698 version "1.1.0"
1720 resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" 1699 resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a"
@@ -1727,20 +1706,6 @@ define-properties@^1.1.2, define-properties@^1.1.3:
1727 dependencies: 1706 dependencies:
1728 object-keys "^1.0.12" 1707 object-keys "^1.0.12"
1729 1708
1730del@^5.0.0:
1731 version "5.1.0"
1732 resolved "https://registry.yarnpkg.com/del/-/del-5.1.0.tgz#d9487c94e367410e6eff2925ee58c0c84a75b3a7"
1733 integrity sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==
1734 dependencies:
1735 globby "^10.0.1"
1736 graceful-fs "^4.2.2"
1737 is-glob "^4.0.1"
1738 is-path-cwd "^2.2.0"
1739 is-path-inside "^3.0.1"
1740 p-map "^3.0.0"
1741 rimraf "^3.0.0"
1742 slash "^3.0.0"
1743
1744delayed-stream@~1.0.0: 1709delayed-stream@~1.0.0:
1745 version "1.0.0" 1710 version "1.0.0"
1746 resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 1711 resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -1799,29 +1764,29 @@ diff@3.5.0:
1799 integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 1764 integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
1800 1765
1801diff@^4.0.1: 1766diff@^4.0.1:
1802 version "4.0.1" 1767 version "4.0.2"
1803 resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" 1768 resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
1804 integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== 1769 integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
1805
1806dir-glob@^3.0.1:
1807 version "3.0.1"
1808 resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
1809 integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
1810 dependencies:
1811 path-type "^4.0.0"
1812 1770
1813dns-prefetch-control@0.2.0: 1771dns-prefetch-control@0.2.0:
1814 version "0.2.0" 1772 version "0.2.0"
1815 resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz#73988161841f3dcc81f47686d539a2c702c88624" 1773 resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.2.0.tgz#73988161841f3dcc81f47686d539a2c702c88624"
1816 integrity sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q== 1774 integrity sha512-hvSnros73+qyZXhHFjx2CMLwoj3Fe7eR9EJsFsqmcI1bB2OBWL/+0YzaEaKssCHnj/6crawNnUyw74Gm2EKe+Q==
1817 1775
1818doctrine@0.7.2: 1776doctrine@1.5.0:
1819 version "0.7.2" 1777 version "1.5.0"
1820 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" 1778 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
1821 integrity sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM= 1779 integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=
1822 dependencies: 1780 dependencies:
1823 esutils "^1.1.6" 1781 esutils "^2.0.2"
1824 isarray "0.0.1" 1782 isarray "^1.0.0"
1783
1784doctrine@^3.0.0:
1785 version "3.0.0"
1786 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
1787 integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
1788 dependencies:
1789 esutils "^2.0.2"
1825 1790
1826dont-sniff-mimetype@1.1.0: 1791dont-sniff-mimetype@1.1.0:
1827 version "1.1.0" 1792 version "1.1.0"
@@ -1873,11 +1838,6 @@ ee-first@1.1.1:
1873 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 1838 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
1874 integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 1839 integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
1875 1840
1876elegant-spinner@^1.0.1:
1877 version "1.0.1"
1878 resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e"
1879 integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=
1880
1881emoji-regex@^7.0.1: 1841emoji-regex@^7.0.1:
1882 version "7.0.3" 1842 version "7.0.3"
1883 resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 1843 resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
@@ -1988,21 +1948,21 @@ engine.io@~3.4.0:
1988 ws "^7.1.2" 1948 ws "^7.1.2"
1989 1949
1990env-variable@0.0.x: 1950env-variable@0.0.x:
1991 version "0.0.5" 1951 version "0.0.6"
1992 resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" 1952 resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808"
1993 integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== 1953 integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==
1994 1954
1995error-ex@^1.3.1: 1955error-ex@^1.2.0, error-ex@^1.3.1:
1996 version "1.3.2" 1956 version "1.3.2"
1997 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 1957 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
1998 integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 1958 integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
1999 dependencies: 1959 dependencies:
2000 is-arrayish "^0.2.1" 1960 is-arrayish "^0.2.1"
2001 1961
2002es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1: 1962es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2:
2003 version "1.17.0" 1963 version "1.17.4"
2004 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0.tgz#f42a517d0036a5591dbb2c463591dc8bb50309b1" 1964 resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
2005 integrity sha512-yYkE07YF+6SIBmg1MsJ9dlub5L48Ek7X0qz+c/CPCHS9EBXfESorzng4cJQjJW5/pB6vDF41u7F8vUhLVDqIug== 1965 integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
2006 dependencies: 1966 dependencies:
2007 es-to-primitive "^1.2.1" 1967 es-to-primitive "^1.2.1"
2008 function-bind "^1.1.1" 1968 function-bind "^1.1.1"
@@ -2071,20 +2031,190 @@ escape-html@^1.0.3, escape-html@~1.0.3:
2071 resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 2031 resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
2072 integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 2032 integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
2073 2033
2074escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5: 2034escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5:
2075 version "1.0.5" 2035 version "1.0.5"
2076 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 2036 resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
2077 integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 2037 integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
2078 2038
2039eslint-config-standard-with-typescript@^12.0.1:
2040 version "12.0.1"
2041 resolved "https://registry.yarnpkg.com/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-12.0.1.tgz#60f941a3a942b50393715ca336e1c7ba76e3ab04"
2042 integrity sha512-v0DDNzsb36Oun3N04Y27Ca9DfF+S9Orrdtqa5anUUpwIu/MMqCRxYAcKdD0Uao+Gzqz9EjaFYjBKZCPFyXH5jw==
2043 dependencies:
2044 "@typescript-eslint/parser" "^2.10.0"
2045 eslint-config-standard "^14.1.0"
2046
2047eslint-config-standard@^14.1.0:
2048 version "14.1.0"
2049 resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz#b23da2b76fe5a2eba668374f246454e7058f15d4"
2050 integrity sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==
2051
2052eslint-import-resolver-node@^0.3.2:
2053 version "0.3.3"
2054 resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz#dbaa52b6b2816b50bc6711af75422de808e98404"
2055 integrity sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==
2056 dependencies:
2057 debug "^2.6.9"
2058 resolve "^1.13.1"
2059
2060eslint-module-utils@^2.4.1:
2061 version "2.5.2"
2062 resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz#7878f7504824e1b857dd2505b59a8e5eda26a708"
2063 integrity sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==
2064 dependencies:
2065 debug "^2.6.9"
2066 pkg-dir "^2.0.0"
2067
2068eslint-plugin-es@^3.0.0:
2069 version "3.0.0"
2070 resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b"
2071 integrity sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==
2072 dependencies:
2073 eslint-utils "^2.0.0"
2074 regexpp "^3.0.0"
2075
2076eslint-plugin-import@^2.20.1:
2077 version "2.20.1"
2078 resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz#802423196dcb11d9ce8435a5fc02a6d3b46939b3"
2079 integrity sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==
2080 dependencies:
2081 array-includes "^3.0.3"
2082 array.prototype.flat "^1.2.1"
2083 contains-path "^0.1.0"
2084 debug "^2.6.9"
2085 doctrine "1.5.0"
2086 eslint-import-resolver-node "^0.3.2"
2087 eslint-module-utils "^2.4.1"
2088 has "^1.0.3"
2089 minimatch "^3.0.4"
2090 object.values "^1.1.0"
2091 read-pkg-up "^2.0.0"
2092 resolve "^1.12.0"
2093
2094eslint-plugin-node@^11.0.0:
2095 version "11.0.0"
2096 resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz#365944bb0804c5d1d501182a9bc41a0ffefed726"
2097 integrity sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==
2098 dependencies:
2099 eslint-plugin-es "^3.0.0"
2100 eslint-utils "^2.0.0"
2101 ignore "^5.1.1"
2102 minimatch "^3.0.4"
2103 resolve "^1.10.1"
2104 semver "^6.1.0"
2105
2106eslint-plugin-promise@^4.2.1:
2107 version "4.2.1"
2108 resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
2109 integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
2110
2111eslint-plugin-standard@^4.0.1:
2112 version "4.0.1"
2113 resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4"
2114 integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==
2115
2116eslint-scope@^5.0.0:
2117 version "5.0.0"
2118 resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
2119 integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
2120 dependencies:
2121 esrecurse "^4.1.0"
2122 estraverse "^4.1.1"
2123
2124eslint-utils@^1.4.3:
2125 version "1.4.3"
2126 resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
2127 integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
2128 dependencies:
2129 eslint-visitor-keys "^1.1.0"
2130
2131eslint-utils@^2.0.0:
2132 version "2.0.0"
2133 resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd"
2134 integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==
2135 dependencies:
2136 eslint-visitor-keys "^1.1.0"
2137
2138eslint-visitor-keys@^1.1.0:
2139 version "1.1.0"
2140 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
2141 integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
2142
2143eslint@^6.8.0:
2144 version "6.8.0"
2145 resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
2146 integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
2147 dependencies:
2148 "@babel/code-frame" "^7.0.0"
2149 ajv "^6.10.0"
2150 chalk "^2.1.0"
2151 cross-spawn "^6.0.5"
2152 debug "^4.0.1"
2153 doctrine "^3.0.0"
2154 eslint-scope "^5.0.0"
2155 eslint-utils "^1.4.3"
2156 eslint-visitor-keys "^1.1.0"
2157 espree "^6.1.2"
2158 esquery "^1.0.1"
2159 esutils "^2.0.2"
2160 file-entry-cache "^5.0.1"
2161 functional-red-black-tree "^1.0.1"
2162 glob-parent "^5.0.0"
2163 globals "^12.1.0"
2164 ignore "^4.0.6"
2165 import-fresh "^3.0.0"
2166 imurmurhash "^0.1.4"
2167 inquirer "^7.0.0"
2168 is-glob "^4.0.0"
2169 js-yaml "^3.13.1"
2170 json-stable-stringify-without-jsonify "^1.0.1"
2171 levn "^0.3.0"
2172 lodash "^4.17.14"
2173 minimatch "^3.0.4"
2174 mkdirp "^0.5.1"
2175 natural-compare "^1.4.0"
2176 optionator "^0.8.3"
2177 progress "^2.0.0"
2178 regexpp "^2.0.1"
2179 semver "^6.1.2"
2180 strip-ansi "^5.2.0"
2181 strip-json-comments "^3.0.1"
2182 table "^5.2.3"
2183 text-table "^0.2.0"
2184 v8-compile-cache "^2.0.3"
2185
2186espree@^6.1.2:
2187 version "6.1.2"
2188 resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d"
2189 integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==
2190 dependencies:
2191 acorn "^7.1.0"
2192 acorn-jsx "^5.1.0"
2193 eslint-visitor-keys "^1.1.0"
2194
2079esprima@^4.0.0: 2195esprima@^4.0.0:
2080 version "4.0.1" 2196 version "4.0.1"
2081 resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 2197 resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
2082 integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 2198 integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
2083 2199
2084esutils@^1.1.6: 2200esquery@^1.0.1:
2085 version "1.1.6" 2201 version "1.0.1"
2086 resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375" 2202 resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708"
2087 integrity sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U= 2203 integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==
2204 dependencies:
2205 estraverse "^4.0.0"
2206
2207esrecurse@^4.1.0:
2208 version "4.2.1"
2209 resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
2210 integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==
2211 dependencies:
2212 estraverse "^4.1.0"
2213
2214estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1:
2215 version "4.3.0"
2216 resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
2217 integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
2088 2218
2089esutils@^2.0.2: 2219esutils@^2.0.2:
2090 version "2.0.3" 2220 version "2.0.3"
@@ -2117,21 +2247,6 @@ execa@^0.7.0:
2117 signal-exit "^3.0.0" 2247 signal-exit "^3.0.0"
2118 strip-eof "^1.0.0" 2248 strip-eof "^1.0.0"
2119 2249
2120execa@^2.0.3:
2121 version "2.1.0"
2122 resolved "https://registry.yarnpkg.com/execa/-/execa-2.1.0.tgz#e5d3ecd837d2a60ec50f3da78fd39767747bbe99"
2123 integrity sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==
2124 dependencies:
2125 cross-spawn "^7.0.0"
2126 get-stream "^5.0.0"
2127 is-stream "^2.0.0"
2128 merge-stream "^2.0.0"
2129 npm-run-path "^3.0.0"
2130 onetime "^5.1.0"
2131 p-finally "^2.0.0"
2132 signal-exit "^3.0.2"
2133 strip-final-newline "^2.0.0"
2134
2135execa@~3.2.0: 2250execa@~3.2.0:
2136 version "3.2.0" 2251 version "3.2.0"
2137 resolved "https://registry.yarnpkg.com/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a" 2252 resolved "https://registry.yarnpkg.com/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a"
@@ -2172,13 +2287,13 @@ express-rate-limit@^4.0.4:
2172 resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-4.0.4.tgz#a495338ae9e58c856b66d1346ec0d86f43ba2e43" 2287 resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-4.0.4.tgz#a495338ae9e58c856b66d1346ec0d86f43ba2e43"
2173 integrity sha512-DLRj2vMO7Xgai8qWKU9O6ZztF2bdDmfFNFi9k3G9BPzJ+7MG7eWaaBikbe0eBpNGSxU8JziwW0PQKG78aNWa6g== 2288 integrity sha512-DLRj2vMO7Xgai8qWKU9O6ZztF2bdDmfFNFi9k3G9BPzJ+7MG7eWaaBikbe0eBpNGSxU8JziwW0PQKG78aNWa6g==
2174 2289
2175express-validator@^6.1.1: 2290express-validator@^6.4.0:
2176 version "6.3.1" 2291 version "6.4.0"
2177 resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-6.3.1.tgz#5ad6ca3ce6141f33638608d006d26c217500f375" 2292 resolved "https://registry.yarnpkg.com/express-validator/-/express-validator-6.4.0.tgz#634f96b60d53112409e270c038ab818a36f56e47"
2178 integrity sha512-YQHQKP/zlUTN6d38uWwXgK3At5phK6R24pOB/ImWisMUz/U/1AC3ZXMgiZYhtH4ViYJ6UAiV0/nj8s1Qs3kmvw== 2293 integrity sha512-Fs+x0yDOSiUV+o5jIRloMyBxqpSzJiMM8KQW1IRVv2l49F6ATU0F9uPa+3K6vXNlLlhUjauv2FCGLFPMaNr24w==
2179 dependencies: 2294 dependencies:
2180 lodash "^4.17.15" 2295 lodash "^4.17.15"
2181 validator "^11.1.0" 2296 validator "^12.1.0"
2182 2297
2183express@^4.12.4, express@^4.13.3, express@^4.17.1: 2298express@^4.12.4, express@^4.13.3, express@^4.17.1:
2184 version "4.17.1" 2299 version "4.17.1"
@@ -2228,6 +2343,15 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.2:
2228 resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 2343 resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
2229 integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 2344 integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
2230 2345
2346external-editor@^3.0.3:
2347 version "3.1.0"
2348 resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
2349 integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
2350 dependencies:
2351 chardet "^0.7.0"
2352 iconv-lite "^0.4.24"
2353 tmp "^0.0.33"
2354
2231extsprintf@1.3.0: 2355extsprintf@1.3.0:
2232 version "1.3.0" 2356 version "1.3.0"
2233 resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 2357 resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
@@ -2243,39 +2367,26 @@ eyes@0.1.x:
2243 resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" 2367 resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
2244 integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= 2368 integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
2245 2369
2246fast-deep-equal@^2.0.1: 2370fast-deep-equal@^3.1.1:
2247 version "2.0.1"
2248 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
2249 integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
2250
2251fast-glob@^3.0.3:
2252 version "3.1.1" 2371 version "3.1.1"
2253 resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.1.1.tgz#87ee30e9e9f3eb40d6f254a7997655da753d7c82" 2372 resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
2254 integrity sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g== 2373 integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
2255 dependencies:
2256 "@nodelib/fs.stat" "^2.0.2"
2257 "@nodelib/fs.walk" "^1.2.3"
2258 glob-parent "^5.1.0"
2259 merge2 "^1.3.0"
2260 micromatch "^4.0.2"
2261 2374
2262fast-json-stable-stringify@^2.0.0: 2375fast-json-stable-stringify@^2.0.0:
2263 version "2.1.0" 2376 version "2.1.0"
2264 resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 2377 resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
2265 integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 2378 integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
2266 2379
2380fast-levenshtein@~2.0.6:
2381 version "2.0.6"
2382 resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
2383 integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
2384
2267fast-safe-stringify@^2.0.4: 2385fast-safe-stringify@^2.0.4:
2268 version "2.0.7" 2386 version "2.0.7"
2269 resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" 2387 resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
2270 integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== 2388 integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
2271 2389
2272fastq@^1.6.0:
2273 version "1.6.0"
2274 resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2"
2275 integrity sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==
2276 dependencies:
2277 reusify "^1.0.0"
2278
2279feature-policy@0.3.0: 2390feature-policy@0.3.0:
2280 version "0.3.0" 2391 version "0.3.0"
2281 resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069" 2392 resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
@@ -2286,20 +2397,19 @@ fecha@^2.3.3:
2286 resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" 2397 resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
2287 integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== 2398 integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==
2288 2399
2289figures@^1.7.0: 2400figures@^3.0.0:
2290 version "1.7.0" 2401 version "3.1.0"
2291 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" 2402 resolved "https://registry.yarnpkg.com/figures/-/figures-3.1.0.tgz#4b198dd07d8d71530642864af2d45dd9e459c4ec"
2292 integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= 2403 integrity sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==
2293 dependencies: 2404 dependencies:
2294 escape-string-regexp "^1.0.5" 2405 escape-string-regexp "^1.0.5"
2295 object-assign "^4.1.0"
2296 2406
2297figures@^2.0.0: 2407file-entry-cache@^5.0.1:
2298 version "2.0.0" 2408 version "5.0.1"
2299 resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" 2409 resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
2300 integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= 2410 integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
2301 dependencies: 2411 dependencies:
2302 escape-string-regexp "^1.0.5" 2412 flat-cache "^2.0.1"
2303 2413
2304filestream@^5.0.0: 2414filestream@^5.0.0:
2305 version "5.0.0" 2415 version "5.0.0"
@@ -2336,6 +2446,13 @@ find-up@3.0.0, find-up@^3.0.0:
2336 dependencies: 2446 dependencies:
2337 locate-path "^3.0.0" 2447 locate-path "^3.0.0"
2338 2448
2449find-up@^2.0.0, find-up@^2.1.0:
2450 version "2.1.0"
2451 resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
2452 integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
2453 dependencies:
2454 locate-path "^2.0.0"
2455
2339find-up@^4.1.0: 2456find-up@^4.1.0:
2340 version "4.1.0" 2457 version "4.1.0"
2341 resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" 2458 resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
@@ -2344,6 +2461,15 @@ find-up@^4.1.0:
2344 locate-path "^5.0.0" 2461 locate-path "^5.0.0"
2345 path-exists "^4.0.0" 2462 path-exists "^4.0.0"
2346 2463
2464flat-cache@^2.0.1:
2465 version "2.0.1"
2466 resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
2467 integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
2468 dependencies:
2469 flatted "^2.0.0"
2470 rimraf "2.6.3"
2471 write "1.0.3"
2472
2347flat@^4.1.0: 2473flat@^4.1.0:
2348 version "4.1.0" 2474 version "4.1.0"
2349 resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" 2475 resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2"
@@ -2358,6 +2484,11 @@ flat@^5.0.0:
2358 dependencies: 2484 dependencies:
2359 is-buffer "~2.0.4" 2485 is-buffer "~2.0.4"
2360 2486
2487flatted@^2.0.0:
2488 version "2.0.1"
2489 resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
2490 integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
2491
2361fluent-ffmpeg@^2.1.0: 2492fluent-ffmpeg@^2.1.0:
2362 version "2.1.2" 2493 version "2.1.2"
2363 resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz#c952de2240f812ebda0aa8006d7776ee2acf7d74" 2494 resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz#c952de2240f812ebda0aa8006d7776ee2acf7d74"
@@ -2447,9 +2578,9 @@ fs-minipass@^1.2.5:
2447 minipass "^2.6.0" 2578 minipass "^2.6.0"
2448 2579
2449fs-minipass@^2.0.0: 2580fs-minipass@^2.0.0:
2450 version "2.0.0" 2581 version "2.1.0"
2451 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.0.0.tgz#a6415edab02fae4b9e9230bc87ee2e4472003cd1" 2582 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
2452 integrity sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A== 2583 integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
2453 dependencies: 2584 dependencies:
2454 minipass "^3.0.0" 2585 minipass "^3.0.0"
2455 2586
@@ -2468,6 +2599,11 @@ function-bind@^1.1.1:
2468 resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 2599 resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
2469 integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 2600 integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
2470 2601
2602functional-red-black-tree@^1.0.1:
2603 version "1.0.1"
2604 resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
2605 integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
2606
2471gauge@~2.7.3: 2607gauge@~2.7.3:
2472 version "2.7.4" 2608 version "2.7.4"
2473 resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" 2609 resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@@ -2497,15 +2633,10 @@ get-func-name@^2.0.0:
2497 resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 2633 resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
2498 integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 2634 integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
2499 2635
2500get-own-enumerable-property-symbols@^3.0.0:
2501 version "3.0.2"
2502 resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
2503 integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==
2504
2505get-port@^5.0.0: 2636get-port@^5.0.0:
2506 version "5.1.0" 2637 version "5.1.1"
2507 resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.0.tgz#a8f6510d0000f07d5c65653a4b0ae648fe832683" 2638 resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
2508 integrity sha512-bjioH1E9bTQUvgaB6VycVy1QVbTZI41yTnF9qkZz6ixgy/uhCH6D63bKeZ6Code/07JYA61MeI94jSdHss8PNA== 2639 integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
2509 2640
2510get-stdin@^7.0.0: 2641get-stdin@^7.0.0:
2511 version "7.0.0" 2642 version "7.0.0"
@@ -2536,7 +2667,7 @@ github-from-package@0.0.0:
2536 resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" 2667 resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
2537 integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= 2668 integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
2538 2669
2539glob-parent@^5.1.0, glob-parent@~5.1.0: 2670glob-parent@^5.0.0, glob-parent@~5.1.0:
2540 version "5.1.0" 2671 version "5.1.0"
2541 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" 2672 resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2"
2542 integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== 2673 integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==
@@ -2567,7 +2698,7 @@ glob@7.1.3:
2567 once "^1.3.0" 2698 once "^1.3.0"
2568 path-is-absolute "^1.0.0" 2699 path-is-absolute "^1.0.0"
2569 2700
2570glob@^7.0.3, glob@^7.1.1, glob@^7.1.3: 2701glob@^7.0.3, glob@^7.1.3, glob@^7.1.6:
2571 version "7.1.6" 2702 version "7.1.6"
2572 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 2703 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
2573 integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 2704 integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -2586,19 +2717,12 @@ global-dirs@^0.1.0:
2586 dependencies: 2717 dependencies:
2587 ini "^1.3.4" 2718 ini "^1.3.4"
2588 2719
2589globby@^10.0.1: 2720globals@^12.1.0:
2590 version "10.0.2" 2721 version "12.3.0"
2591 resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" 2722 resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13"
2592 integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== 2723 integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==
2593 dependencies: 2724 dependencies:
2594 "@types/glob" "^7.1.1" 2725 type-fest "^0.8.1"
2595 array-union "^2.1.0"
2596 dir-glob "^3.0.1"
2597 fast-glob "^3.0.3"
2598 glob "^7.1.3"
2599 ignore "^5.1.1"
2600 merge2 "^1.2.3"
2601 slash "^3.0.0"
2602 2726
2603got@^6.7.1: 2727got@^6.7.1:
2604 version "6.7.1" 2728 version "6.7.1"
@@ -2617,7 +2741,7 @@ got@^6.7.1:
2617 unzip-response "^2.0.1" 2741 unzip-response "^2.0.1"
2618 url-parse-lax "^1.0.0" 2742 url-parse-lax "^1.0.0"
2619 2743
2620graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2: 2744graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
2621 version "4.2.3" 2745 version "4.2.3"
2622 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" 2746 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
2623 integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== 2747 integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
@@ -2640,13 +2764,6 @@ har-validator@~5.1.0:
2640 ajv "^6.5.5" 2764 ajv "^6.5.5"
2641 har-schema "^2.0.0" 2765 har-schema "^2.0.0"
2642 2766
2643has-ansi@^2.0.0:
2644 version "2.0.0"
2645 resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
2646 integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=
2647 dependencies:
2648 ansi-regex "^2.0.0"
2649
2650has-binary2@~1.0.2: 2767has-binary2@~1.0.2:
2651 version "1.0.3" 2768 version "1.0.3"
2652 resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" 2769 resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
@@ -2826,7 +2943,7 @@ i@0.3.x:
2826 resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d" 2943 resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
2827 integrity sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0= 2944 integrity sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=
2828 2945
2829iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.24: 2946iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.24:
2830 version "0.4.24" 2947 version "0.4.24"
2831 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 2948 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
2832 integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 2949 integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -2850,6 +2967,11 @@ ignore-walk@^3.0.1:
2850 dependencies: 2967 dependencies:
2851 minimatch "^3.0.4" 2968 minimatch "^3.0.4"
2852 2969
2970ignore@^4.0.6:
2971 version "4.0.6"
2972 resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
2973 integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
2974
2853ignore@^5.1.1: 2975ignore@^5.1.1:
2854 version "5.1.4" 2976 version "5.1.4"
2855 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" 2977 resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
@@ -2862,13 +2984,13 @@ immediate-chunk-store@^2.0.0:
2862 dependencies: 2984 dependencies:
2863 queue-microtask "^1.1.2" 2985 queue-microtask "^1.1.2"
2864 2986
2865import-fresh@^2.0.0: 2987import-fresh@^3.0.0:
2866 version "2.0.0" 2988 version "3.2.1"
2867 resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" 2989 resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
2868 integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= 2990 integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
2869 dependencies: 2991 dependencies:
2870 caller-path "^2.0.0" 2992 parent-module "^1.0.0"
2871 resolve-from "^3.0.0" 2993 resolve-from "^4.0.0"
2872 2994
2873import-lazy@^2.1.0: 2995import-lazy@^2.1.0:
2874 version "2.1.0" 2996 version "2.1.0"
@@ -2880,16 +3002,6 @@ imurmurhash@^0.1.4:
2880 resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 3002 resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
2881 integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= 3003 integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
2882 3004
2883indent-string@^3.0.0:
2884 version "3.2.0"
2885 resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
2886 integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
2887
2888indent-string@^4.0.0:
2889 version "4.0.0"
2890 resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
2891 integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
2892
2893indexof@0.0.1: 3005indexof@0.0.1:
2894 version "0.0.1" 3006 version "0.0.1"
2895 resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" 3007 resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
@@ -2923,6 +3035,25 @@ ini@^1.3.4, ini@~1.3.0:
2923 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 3035 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
2924 integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== 3036 integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
2925 3037
3038inquirer@^7.0.0:
3039 version "7.0.4"
3040 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703"
3041 integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==
3042 dependencies:
3043 ansi-escapes "^4.2.1"
3044 chalk "^2.4.2"
3045 cli-cursor "^3.1.0"
3046 cli-width "^2.0.0"
3047 external-editor "^3.0.3"
3048 figures "^3.0.0"
3049 lodash "^4.17.15"
3050 mute-stream "0.0.8"
3051 run-async "^2.2.0"
3052 rxjs "^6.5.3"
3053 string-width "^4.1.0"
3054 strip-ansi "^5.1.0"
3055 through "^2.3.6"
3056
2926ioredis@^4.14.1: 3057ioredis@^4.14.1:
2927 version "4.14.1" 3058 version "4.14.1"
2928 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318" 3059 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318"
@@ -3036,11 +3167,6 @@ is-date-object@^1.0.1:
3036 resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" 3167 resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e"
3037 integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== 3168 integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==
3038 3169
3039is-directory@^0.3.1:
3040 version "0.3.1"
3041 resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
3042 integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
3043
3044is-extglob@^2.1.1: 3170is-extglob@^2.1.1:
3045 version "2.1.1" 3171 version "2.1.1"
3046 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 3172 resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -3073,7 +3199,7 @@ is-generator@^1.0.2:
3073 resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" 3199 resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3"
3074 integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM= 3200 integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM=
3075 3201
3076is-glob@^4.0.1, is-glob@~4.0.1: 3202is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
3077 version "4.0.1" 3203 version "4.0.1"
3078 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 3204 resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
3079 integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 3205 integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
@@ -3105,23 +3231,11 @@ is-number@^7.0.0:
3105 resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 3231 resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
3106 integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 3232 integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
3107 3233
3108is-obj@^1.0.0, is-obj@^1.0.1: 3234is-obj@^1.0.0:
3109 version "1.0.1" 3235 version "1.0.1"
3110 resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" 3236 resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
3111 integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= 3237 integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
3112 3238
3113is-observable@^1.1.0:
3114 version "1.1.0"
3115 resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e"
3116 integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==
3117 dependencies:
3118 symbol-observable "^1.1.0"
3119
3120is-path-cwd@^2.2.0:
3121 version "2.2.0"
3122 resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
3123 integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
3124
3125is-path-inside@^1.0.0: 3239is-path-inside@^1.0.0:
3126 version "1.0.1" 3240 version "1.0.1"
3127 resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" 3241 resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
@@ -3129,11 +3243,6 @@ is-path-inside@^1.0.0:
3129 dependencies: 3243 dependencies:
3130 path-is-inside "^1.0.1" 3244 path-is-inside "^1.0.1"
3131 3245
3132is-path-inside@^3.0.1:
3133 version "3.0.2"
3134 resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.2.tgz#f5220fc82a3e233757291dddc9c5877f2a1f3017"
3135 integrity sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==
3136
3137is-promise@^2.1, is-promise@^2.1.0: 3246is-promise@^2.1, is-promise@^2.1.0:
3138 version "2.1.0" 3247 version "2.1.0"
3139 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" 3248 resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -3151,11 +3260,6 @@ is-regex@^1.0.5:
3151 dependencies: 3260 dependencies:
3152 has "^1.0.3" 3261 has "^1.0.3"
3153 3262
3154is-regexp@^1.0.0:
3155 version "1.0.0"
3156 resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
3157 integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
3158
3159is-retry-allowed@^1.0.0: 3263is-retry-allowed@^1.0.0:
3160 version "1.2.0" 3264 version "1.2.0"
3161 resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" 3265 resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
@@ -3171,6 +3275,11 @@ is-stream@^2.0.0:
3171 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" 3275 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
3172 integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== 3276 integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==
3173 3277
3278is-string@^1.0.5:
3279 version "1.0.5"
3280 resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
3281 integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==
3282
3174is-symbol@^1.0.2: 3283is-symbol@^1.0.2:
3175 version "1.0.3" 3284 version "1.0.3"
3176 resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" 3285 resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
@@ -3198,7 +3307,7 @@ isarray@2.0.1:
3198 resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" 3307 resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
3199 integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4= 3308 integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
3200 3309
3201isarray@~1.0.0: 3310isarray@^1.0.0, isarray@~1.0.0:
3202 version "1.0.0" 3311 version "1.0.0"
3203 resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 3312 resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
3204 integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 3313 integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
@@ -3208,10 +3317,10 @@ isexe@^2.0.0:
3208 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 3317 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
3209 integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 3318 integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
3210 3319
3211iso-639-3@^1.0.1: 3320iso-639-3@^2.0.0:
3212 version "1.2.0" 3321 version "2.0.0"
3213 resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.2.0.tgz#eee1f5e6ca2bbb33e3ecc910857c1c12e8b295be" 3322 resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-2.0.0.tgz#5844c6b885cbeac3571d407de5b5fdcb92f3505f"
3214 integrity sha512-jNvD2P4JHNckQH7pc0R0SQ4oPCpyEtgs0nTtjB+DZCUDdygz0cOAxlcnq5KgNjjsqMHbR4Sbgwz2+DflzAZvlQ== 3323 integrity sha512-Pp+ctEs/pna6/rj05a5VR3qYxJHBZi95wp20C6Snf/WeghrkR/4G44LPJFqlbyo67XntkcUaxwrGmMeyY+F4mA==
3215 3324
3216isstream@0.1.x, isstream@~0.1.2: 3325isstream@0.1.x, isstream@~0.1.2:
3217 version "0.1.2" 3326 version "0.1.2"
@@ -3260,6 +3369,11 @@ json-schema@0.2.3:
3260 resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 3369 resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
3261 integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= 3370 integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
3262 3371
3372json-stable-stringify-without-jsonify@^1.0.1:
3373 version "1.0.1"
3374 resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
3375 integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
3376
3263json-stringify-safe@~5.0.1: 3377json-stringify-safe@~5.0.1:
3264 version "5.0.1" 3378 version "5.0.1"
3265 resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 3379 resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -3280,9 +3394,9 @@ jsonfile@^4.0.0:
3280 graceful-fs "^4.1.6" 3394 graceful-fs "^4.1.6"
3281 3395
3282jsonld@~2.0.1: 3396jsonld@~2.0.1:
3283 version "2.0.1" 3397 version "2.0.2"
3284 resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-2.0.1.tgz#c08760fb00f429496b45f5c9984bf9be408d3980" 3398 resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-2.0.2.tgz#f5d0631de828ce6dee3a14f08cbdc6a53174cd15"
3285 integrity sha512-37NIP09U0AnLu94b7ktsgHxZflAqDH8wn2kcLeAjxcL3sbuwJZ1IxIIbWyjSzrKojADWnoZM/btAWlrcGDMgJA== 3399 integrity sha512-/TQzRe75/3h2khu57IUojha5oat+M82bm8RYw0jLhlmmPrW/kTWAZ9nGzKPfZWnPYnVVJJMQVc/pU8HCmpv9xg==
3286 dependencies: 3400 dependencies:
3287 canonicalize "^1.0.1" 3401 canonicalize "^1.0.1"
3288 lru-cache "^5.1.1" 3402 lru-cache "^5.1.1"
@@ -3356,6 +3470,14 @@ latest-version@^3.0.0:
3356 dependencies: 3470 dependencies:
3357 package-json "^4.0.0" 3471 package-json "^4.0.0"
3358 3472
3473levn@^0.3.0, levn@~0.3.0:
3474 version "0.3.0"
3475 resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
3476 integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
3477 dependencies:
3478 prelude-ls "~1.1.2"
3479 type-check "~0.3.2"
3480
3359libxmljs@0.19.7: 3481libxmljs@0.19.7:
3360 version "0.19.7" 3482 version "0.19.7"
3361 resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.7.tgz#96c2151b0b73f33dd29917edec82902587004e5a" 3483 resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.7.tgz#96c2151b0b73f33dd29917edec82902587004e5a"
@@ -3365,70 +3487,6 @@ libxmljs@0.19.7:
3365 nan "~2.14.0" 3487 nan "~2.14.0"
3366 node-pre-gyp "~0.11.0" 3488 node-pre-gyp "~0.11.0"
3367 3489
3368lint-staged@^9.2.0:
3369 version "9.5.0"
3370 resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-9.5.0.tgz#290ec605252af646d9b74d73a0fa118362b05a33"
3371 integrity sha512-nawMob9cb/G1J98nb8v3VC/E8rcX1rryUYXVZ69aT9kde6YWX+uvNOEHY5yf2gcWcTJGiD0kqXmCnS3oD75GIA==
3372 dependencies:
3373 chalk "^2.4.2"
3374 commander "^2.20.0"
3375 cosmiconfig "^5.2.1"
3376 debug "^4.1.1"
3377 dedent "^0.7.0"
3378 del "^5.0.0"
3379 execa "^2.0.3"
3380 listr "^0.14.3"
3381 log-symbols "^3.0.0"
3382 micromatch "^4.0.2"
3383 normalize-path "^3.0.0"
3384 please-upgrade-node "^3.1.1"
3385 string-argv "^0.3.0"
3386 stringify-object "^3.3.0"
3387
3388listr-silent-renderer@^1.1.1:
3389 version "1.1.1"
3390 resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
3391 integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
3392
3393listr-update-renderer@^0.5.0:
3394 version "0.5.0"
3395 resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2"
3396 integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==
3397 dependencies:
3398 chalk "^1.1.3"
3399 cli-truncate "^0.2.1"
3400 elegant-spinner "^1.0.1"
3401 figures "^1.7.0"
3402 indent-string "^3.0.0"
3403 log-symbols "^1.0.2"
3404 log-update "^2.3.0"
3405 strip-ansi "^3.0.1"
3406
3407listr-verbose-renderer@^0.5.0:
3408 version "0.5.0"
3409 resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db"
3410 integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==
3411 dependencies:
3412 chalk "^2.4.1"
3413 cli-cursor "^2.1.0"
3414 date-fns "^1.27.2"
3415 figures "^2.0.0"
3416
3417listr@^0.14.3:
3418 version "0.14.3"
3419 resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
3420 integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==
3421 dependencies:
3422 "@samverschueren/stream-to-observable" "^0.3.0"
3423 is-observable "^1.1.0"
3424 is-promise "^2.1.0"
3425 is-stream "^1.1.0"
3426 listr-silent-renderer "^1.1.1"
3427 listr-update-renderer "^0.5.0"
3428 listr-verbose-renderer "^0.5.0"
3429 p-map "^2.0.0"
3430 rxjs "^6.3.3"
3431
3432load-ip-set@^2.1.0: 3490load-ip-set@^2.1.0:
3433 version "2.1.0" 3491 version "2.1.0"
3434 resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-2.1.0.tgz#2d50b737cae41de4e413d213991d4083a3e1784b" 3492 resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-2.1.0.tgz#2d50b737cae41de4e413d213991d4083a3e1784b"
@@ -3440,6 +3498,24 @@ load-ip-set@^2.1.0:
3440 simple-get "^3.0.0" 3498 simple-get "^3.0.0"
3441 split "^1.0.0" 3499 split "^1.0.0"
3442 3500
3501load-json-file@^2.0.0:
3502 version "2.0.0"
3503 resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8"
3504 integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=
3505 dependencies:
3506 graceful-fs "^4.1.2"
3507 parse-json "^2.2.0"
3508 pify "^2.0.0"
3509 strip-bom "^3.0.0"
3510
3511locate-path@^2.0.0:
3512 version "2.0.0"
3513 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
3514 integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
3515 dependencies:
3516 p-locate "^2.0.0"
3517 path-exists "^3.0.0"
3518
3443locate-path@^3.0.0: 3519locate-path@^3.0.0:
3444 version "3.0.0" 3520 version "3.0.0"
3445 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 3521 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -3492,29 +3568,6 @@ log-symbols@2.2.0:
3492 dependencies: 3568 dependencies:
3493 chalk "^2.0.1" 3569 chalk "^2.0.1"
3494 3570
3495log-symbols@^1.0.2:
3496 version "1.0.2"
3497 resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
3498 integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=
3499 dependencies:
3500 chalk "^1.0.0"
3501
3502log-symbols@^3.0.0:
3503 version "3.0.0"
3504 resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
3505 integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
3506 dependencies:
3507 chalk "^2.4.2"
3508
3509log-update@^2.3.0:
3510 version "2.3.0"
3511 resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708"
3512 integrity sha1-iDKP19HOeTiykoN0bwsbwSayRwg=
3513 dependencies:
3514 ansi-escapes "^3.0.0"
3515 cli-cursor "^2.0.0"
3516 wrap-ansi "^3.0.1"
3517
3518logform@^2.1.1: 3571logform@^2.1.1:
3519 version "2.1.2" 3572 version "2.1.2"
3520 resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" 3573 resolved "https://registry.yarnpkg.com/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360"
@@ -3669,24 +3722,11 @@ merge-stream@^2.0.0:
3669 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" 3722 resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
3670 integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== 3723 integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
3671 3724
3672merge2@^1.2.3, merge2@^1.3.0:
3673 version "1.3.0"
3674 resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.3.0.tgz#5b366ee83b2f1582c48f87e47cf1a9352103ca81"
3675 integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==
3676
3677methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: 3725methods@^1.1.1, methods@^1.1.2, methods@~1.1.2:
3678 version "1.1.2" 3726 version "1.1.2"
3679 resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 3727 resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
3680 integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 3728 integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
3681 3729
3682micromatch@^4.0.2:
3683 version "4.0.2"
3684 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
3685 integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
3686 dependencies:
3687 braces "^3.0.1"
3688 picomatch "^2.0.5"
3689
3690mime-db@1.43.0: 3730mime-db@1.43.0:
3691 version "1.43.0" 3731 version "1.43.0"
3692 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" 3732 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
@@ -3709,11 +3749,6 @@ mime@^2.4.0:
3709 resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" 3749 resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
3710 integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== 3750 integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
3711 3751
3712mimic-fn@^1.0.0:
3713 version "1.2.0"
3714 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
3715 integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
3716
3717mimic-fn@^2.1.0: 3752mimic-fn@^2.1.0:
3718 version "2.1.0" 3753 version "2.1.0"
3719 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" 3754 resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@@ -3789,9 +3824,9 @@ mocha-parallel-tests@^2.2.1:
3789 yargs "^13.2.4" 3824 yargs "^13.2.4"
3790 3825
3791mocha@^7.0.0: 3826mocha@^7.0.0:
3792 version "7.0.0" 3827 version "7.0.1"
3793 resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.0.tgz#c60d14bf3de9601f549b3ff5be657eb8381c54bf" 3828 resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.0.1.tgz#276186d35a4852f6249808c6dd4a1376cbf6c6ce"
3794 integrity sha512-CirsOPbO3jU86YKjjMzFLcXIb5YiGLUrjrXFHoJ3e2z9vWiaZVCZQ2+gtRGMPWF+nFhN6AWwLM/juzAQ6KRkbA== 3829 integrity sha512-9eWmWTdHLXh72rGrdZjNbG3aa1/3NRPpul1z0D979QpEnFdCG0Q5tv834N+94QEN2cysfV72YocQ3fn87s70fg==
3795 dependencies: 3830 dependencies:
3796 ansi-colors "3.2.3" 3831 ansi-colors "3.2.3"
3797 browser-stdout "1.3.1" 3832 browser-stdout "1.3.1"
@@ -3893,7 +3928,7 @@ multistream@^4.0.0:
3893 dependencies: 3928 dependencies:
3894 readable-stream "^3.4.0" 3929 readable-stream "^3.4.0"
3895 3930
3896mute-stream@~0.0.4: 3931mute-stream@0.0.8, mute-stream@~0.0.4:
3897 version "0.0.8" 3932 version "0.0.8"
3898 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" 3933 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
3899 integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== 3934 integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
@@ -3908,6 +3943,11 @@ napi-build-utils@^1.0.1:
3908 resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" 3943 resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508"
3909 integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== 3944 integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==
3910 3945
3946natural-compare@^1.4.0:
3947 version "1.4.0"
3948 resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
3949 integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
3950
3911ncp@1.0.x: 3951ncp@1.0.x:
3912 version "1.0.1" 3952 version "1.0.1"
3913 resolved "https://registry.yarnpkg.com/ncp/-/ncp-1.0.1.tgz#d15367e5cb87432ba117d2bf80fdf45aecfb4246" 3953 resolved "https://registry.yarnpkg.com/ncp/-/ncp-1.0.1.tgz#d15367e5cb87432ba117d2bf80fdf45aecfb4246"
@@ -3942,6 +3982,11 @@ next-tick@1, next-tick@~1.0.0:
3942 resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" 3982 resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
3943 integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= 3983 integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
3944 3984
3985nice-try@^1.0.4:
3986 version "1.0.5"
3987 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
3988 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
3989
3945nocache@2.1.0: 3990nocache@2.1.0:
3946 version "2.1.0" 3991 version "2.1.0"
3947 resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f" 3992 resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
@@ -3962,10 +4007,10 @@ node-environment-flags@1.0.6:
3962 object.getownpropertydescriptors "^2.0.3" 4007 object.getownpropertydescriptors "^2.0.3"
3963 semver "^5.7.0" 4008 semver "^5.7.0"
3964 4009
3965node-forge@^0.8.1: 4010node-forge@^0.9.1:
3966 version "0.8.5" 4011 version "0.9.1"
3967 resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" 4012 resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
3968 integrity sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q== 4013 integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
3969 4014
3970node-gyp-build@~3.7.0: 4015node-gyp-build@~3.7.0:
3971 version "3.7.0" 4016 version "3.7.0"
@@ -4083,12 +4128,13 @@ npm-normalize-package-bin@^1.0.1:
4083 integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== 4128 integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
4084 4129
4085npm-packlist@^1.1.6: 4130npm-packlist@^1.1.6:
4086 version "1.4.7" 4131 version "1.4.8"
4087 resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.7.tgz#9e954365a06b80b18111ea900945af4f88ed4848" 4132 resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
4088 integrity sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ== 4133 integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
4089 dependencies: 4134 dependencies:
4090 ignore-walk "^3.0.1" 4135 ignore-walk "^3.0.1"
4091 npm-bundled "^1.0.1" 4136 npm-bundled "^1.0.1"
4137 npm-normalize-package-bin "^1.0.1"
4092 4138
4093npm-run-path@^2.0.0: 4139npm-run-path@^2.0.0:
4094 version "2.0.2" 4140 version "2.0.2"
@@ -4097,13 +4143,6 @@ npm-run-path@^2.0.0:
4097 dependencies: 4143 dependencies:
4098 path-key "^2.0.0" 4144 path-key "^2.0.0"
4099 4145
4100npm-run-path@^3.0.0:
4101 version "3.1.0"
4102 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-3.1.0.tgz#7f91be317f6a466efed3c9f2980ad8a4ee8b0fa5"
4103 integrity sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==
4104 dependencies:
4105 path-key "^3.0.0"
4106
4107npm-run-path@^4.0.0: 4146npm-run-path@^4.0.0:
4108 version "4.0.1" 4147 version "4.0.1"
4109 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" 4148 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -4173,7 +4212,7 @@ object.assign@4.1.0, object.assign@^4.1.0:
4173 has-symbols "^1.0.0" 4212 has-symbols "^1.0.0"
4174 object-keys "^1.0.11" 4213 object-keys "^1.0.11"
4175 4214
4176object.getownpropertydescriptors@^2.0.3: 4215object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0:
4177 version "2.1.0" 4216 version "2.1.0"
4178 resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" 4217 resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
4179 integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== 4218 integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
@@ -4181,6 +4220,16 @@ object.getownpropertydescriptors@^2.0.3:
4181 define-properties "^1.1.3" 4220 define-properties "^1.1.3"
4182 es-abstract "^1.17.0-next.1" 4221 es-abstract "^1.17.0-next.1"
4183 4222
4223object.values@^1.1.0:
4224 version "1.1.1"
4225 resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
4226 integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
4227 dependencies:
4228 define-properties "^1.1.3"
4229 es-abstract "^1.17.0-next.1"
4230 function-bind "^1.1.1"
4231 has "^1.0.3"
4232
4184on-finished@^2.3.0, on-finished@~2.3.0: 4233on-finished@^2.3.0, on-finished@~2.3.0:
4185 version "2.3.0" 4234 version "2.3.0"
4186 resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 4235 resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -4205,13 +4254,6 @@ one-time@0.0.4:
4205 resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" 4254 resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e"
4206 integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= 4255 integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=
4207 4256
4208onetime@^2.0.0:
4209 version "2.0.1"
4210 resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
4211 integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
4212 dependencies:
4213 mimic-fn "^1.0.0"
4214
4215onetime@^5.1.0: 4257onetime@^5.1.0:
4216 version "5.1.0" 4258 version "5.1.0"
4217 resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" 4259 resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
@@ -4241,6 +4283,18 @@ opn@^6.0.0:
4241 dependencies: 4283 dependencies:
4242 is-wsl "^1.1.0" 4284 is-wsl "^1.1.0"
4243 4285
4286optionator@^0.8.3:
4287 version "0.8.3"
4288 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
4289 integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
4290 dependencies:
4291 deep-is "~0.1.3"
4292 fast-levenshtein "~2.0.6"
4293 levn "~0.3.0"
4294 prelude-ls "~1.1.2"
4295 type-check "~0.3.2"
4296 word-wrap "~1.2.3"
4297
4244os-homedir@^1.0.0: 4298os-homedir@^1.0.0:
4245 version "1.0.2" 4299 version "1.0.2"
4246 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 4300 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
@@ -4269,6 +4323,13 @@ p-finally@^2.0.0:
4269 resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" 4323 resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
4270 integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== 4324 integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==
4271 4325
4326p-limit@^1.1.0:
4327 version "1.3.0"
4328 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
4329 integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
4330 dependencies:
4331 p-try "^1.0.0"
4332
4272p-limit@^2.0.0, p-limit@^2.2.0: 4333p-limit@^2.0.0, p-limit@^2.2.0:
4273 version "2.2.2" 4334 version "2.2.2"
4274 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" 4335 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e"
@@ -4276,6 +4337,13 @@ p-limit@^2.0.0, p-limit@^2.2.0:
4276 dependencies: 4337 dependencies:
4277 p-try "^2.0.0" 4338 p-try "^2.0.0"
4278 4339
4340p-locate@^2.0.0:
4341 version "2.0.0"
4342 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
4343 integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
4344 dependencies:
4345 p-limit "^1.1.0"
4346
4279p-locate@^3.0.0: 4347p-locate@^3.0.0:
4280 version "3.0.0" 4348 version "3.0.0"
4281 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 4349 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -4290,18 +4358,6 @@ p-locate@^4.1.0:
4290 dependencies: 4358 dependencies:
4291 p-limit "^2.2.0" 4359 p-limit "^2.2.0"
4292 4360
4293p-map@^2.0.0:
4294 version "2.1.0"
4295 resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
4296 integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
4297
4298p-map@^3.0.0:
4299 version "3.0.0"
4300 resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d"
4301 integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==
4302 dependencies:
4303 aggregate-error "^3.0.0"
4304
4305p-timeout@^3.1.0: 4361p-timeout@^3.1.0:
4306 version "3.2.0" 4362 version "3.2.0"
4307 resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" 4363 resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
@@ -4309,6 +4365,11 @@ p-timeout@^3.1.0:
4309 dependencies: 4365 dependencies:
4310 p-finally "^1.0.0" 4366 p-finally "^1.0.0"
4311 4367
4368p-try@^1.0.0:
4369 version "1.0.0"
4370 resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
4371 integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
4372
4312p-try@^2.0.0: 4373p-try@^2.0.0:
4313 version "2.2.0" 4374 version "2.2.0"
4314 resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 4375 resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -4336,6 +4397,20 @@ packet-reader@1.0.0:
4336 resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 4397 resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
4337 integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== 4398 integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
4338 4399
4400parent-module@^1.0.0:
4401 version "1.0.1"
4402 resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
4403 integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
4404 dependencies:
4405 callsites "^3.0.0"
4406
4407parse-json@^2.2.0:
4408 version "2.2.0"
4409 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
4410 integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
4411 dependencies:
4412 error-ex "^1.2.0"
4413
4339parse-json@^4.0.0: 4414parse-json@^4.0.0:
4340 version "4.0.0" 4415 version "4.0.0"
4341 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" 4416 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -4408,7 +4483,7 @@ path-is-inside@^1.0.1:
4408 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 4483 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
4409 integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 4484 integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
4410 4485
4411path-key@^2.0.0: 4486path-key@^2.0.0, path-key@^2.0.1:
4412 version "2.0.1" 4487 version "2.0.1"
4413 resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" 4488 resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
4414 integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= 4489 integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
@@ -4428,10 +4503,12 @@ path-to-regexp@0.1.7:
4428 resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 4503 resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
4429 integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 4504 integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
4430 4505
4431path-type@^4.0.0: 4506path-type@^2.0.0:
4432 version "4.0.0" 4507 version "2.0.0"
4433 resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 4508 resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73"
4434 integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 4509 integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=
4510 dependencies:
4511 pify "^2.0.0"
4435 4512
4436pathval@^1.1.0: 4513pathval@^1.1.0:
4437 version "1.1.0" 4514 version "1.1.0"
@@ -4448,14 +4525,14 @@ peek-stream@^1.1.1:
4448 through2 "^2.0.3" 4525 through2 "^2.0.3"
4449 4526
4450pem@^1.12.3: 4527pem@^1.12.3:
4451 version "1.14.3" 4528 version "1.14.4"
4452 resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.3.tgz#347e5a5c194a5f7612b88083e45042fcc4fb4901" 4529 resolved "https://registry.yarnpkg.com/pem/-/pem-1.14.4.tgz#a68c70c6e751ccc5b3b5bcd7af78b0aec1177ff9"
4453 integrity sha512-Q+AMVMD3fzeVvZs5PHeI+pVt0hgZY2fjhkliBW43qyONLgCXPVk1ryim43F9eupHlNGLJNT5T/NNrzhUdiC5Zg== 4530 integrity sha512-v8lH3NpirgiEmbOqhx0vwQTxwi0ExsiWBGYh0jYNq7K6mQuO4gI6UEFlr6fLAdv9TPXRt6GqiwE37puQdIDS8g==
4454 dependencies: 4531 dependencies:
4455 es6-promisify "^6.0.0" 4532 es6-promisify "^6.0.0"
4456 md5 "^2.2.1" 4533 md5 "^2.2.1"
4457 os-tmpdir "^1.0.1" 4534 os-tmpdir "^1.0.1"
4458 which "^1.3.1" 4535 which "^2.0.2"
4459 4536
4460performance-now@^2.1.0: 4537performance-now@^2.1.0:
4461 version "2.1.0" 4538 version "2.1.0"
@@ -4485,10 +4562,10 @@ pg-packet-stream@^1.1.0:
4485 resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914" 4562 resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914"
4486 integrity sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg== 4563 integrity sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg==
4487 4564
4488pg-pool@^2.0.9: 4565pg-pool@^2.0.10:
4489 version "2.0.9" 4566 version "2.0.10"
4490 resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.9.tgz#7ed69a27e204f99e9804a851404db6aa908a6dea" 4567 resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.10.tgz#842ee23b04e86824ce9d786430f8365082d81c4a"
4491 integrity sha512-gNiuIEKNCT3OnudQM2kvgSnXsLkSpd6mS/fRnqs6ANtrke6j8OY5l9mnAryf1kgwJMWLg0C1N1cYTZG1xmEYHQ== 4568 integrity sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg==
4492 4569
4493pg-types@^2.1.0: 4570pg-types@^2.1.0:
4494 version "2.2.0" 4571 version "2.2.0"
@@ -4502,15 +4579,15 @@ pg-types@^2.1.0:
4502 postgres-interval "^1.1.0" 4579 postgres-interval "^1.1.0"
4503 4580
4504pg@^7.4.1: 4581pg@^7.4.1:
4505 version "7.17.0" 4582 version "7.18.1"
4506 resolved "https://registry.yarnpkg.com/pg/-/pg-7.17.0.tgz#1fcf82238dcbebea63e192c944345c25c86992fc" 4583 resolved "https://registry.yarnpkg.com/pg/-/pg-7.18.1.tgz#67f59c47a99456fcb34f9fe53662b79d4a992f6d"
4507 integrity sha512-70Q4ZzIdPgwMPb3zUIzAUwigNJ4v5vsWdMED6OzXMfOECeYTvTm7iSC3FpKizu/R1BHL8Do3bLs6ltGfOTAnqg== 4584 integrity sha512-1KtKBKg/zWrjEEv//klBbVOPGucuc7HHeJf6OEMueVcUeyF3yueHf+DvhVwBjIAe9/97RAydO/lWjkcMwssuEw==
4508 dependencies: 4585 dependencies:
4509 buffer-writer "2.0.0" 4586 buffer-writer "2.0.0"
4510 packet-reader "1.0.0" 4587 packet-reader "1.0.0"
4511 pg-connection-string "0.1.3" 4588 pg-connection-string "0.1.3"
4512 pg-packet-stream "^1.1.0" 4589 pg-packet-stream "^1.1.0"
4513 pg-pool "^2.0.9" 4590 pg-pool "^2.0.10"
4514 pg-types "^2.1.0" 4591 pg-types "^2.1.0"
4515 pgpass "1.x" 4592 pgpass "1.x"
4516 semver "4.3.2" 4593 semver "4.3.2"
@@ -4522,7 +4599,7 @@ pgpass@1.x:
4522 dependencies: 4599 dependencies:
4523 split "^1.0.0" 4600 split "^1.0.0"
4524 4601
4525picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7: 4602picomatch@^2.0.4, picomatch@^2.0.7:
4526 version "2.2.1" 4603 version "2.2.1"
4527 resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" 4604 resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
4528 integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== 4605 integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==
@@ -4532,11 +4609,23 @@ piece-length@^2.0.1:
4532 resolved "https://registry.yarnpkg.com/piece-length/-/piece-length-2.0.1.tgz#dbed4e78976955f34466d0a65304d0cb21914ac9" 4609 resolved "https://registry.yarnpkg.com/piece-length/-/piece-length-2.0.1.tgz#dbed4e78976955f34466d0a65304d0cb21914ac9"
4533 integrity sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug== 4610 integrity sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==
4534 4611
4612pify@^2.0.0:
4613 version "2.3.0"
4614 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
4615 integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
4616
4535pify@^3.0.0: 4617pify@^3.0.0:
4536 version "3.0.0" 4618 version "3.0.0"
4537 resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" 4619 resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
4538 integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 4620 integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
4539 4621
4622pkg-dir@^2.0.0:
4623 version "2.0.0"
4624 resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
4625 integrity sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=
4626 dependencies:
4627 find-up "^2.1.0"
4628
4540pkginfo@0.3.x: 4629pkginfo@0.3.x:
4541 version "0.3.1" 4630 version "0.3.1"
4542 resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21" 4631 resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
@@ -4547,13 +4636,6 @@ pkginfo@0.x.x:
4547 resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff" 4636 resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
4548 integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8= 4637 integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
4549 4638
4550please-upgrade-node@^3.1.1:
4551 version "3.2.0"
4552 resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
4553 integrity sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==
4554 dependencies:
4555 semver-compare "^1.0.0"
4556
4557postgres-array@~2.0.0: 4639postgres-array@~2.0.0:
4558 version "2.0.0" 4640 version "2.0.0"
4559 resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" 4641 resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
@@ -4597,6 +4679,11 @@ prebuild-install@^5.3.3:
4597 tunnel-agent "^0.6.0" 4679 tunnel-agent "^0.6.0"
4598 which-pm-runs "^1.0.0" 4680 which-pm-runs "^1.0.0"
4599 4681
4682prelude-ls@~1.1.2:
4683 version "1.1.2"
4684 resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
4685 integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
4686
4600prepend-http@^1.0.1: 4687prepend-http@^1.0.1:
4601 version "1.0.4" 4688 version "1.0.4"
4602 resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" 4689 resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
@@ -4607,6 +4694,11 @@ process-nextick-args@~2.0.0:
4607 resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" 4694 resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
4608 integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== 4695 integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
4609 4696
4697progress@^2.0.0:
4698 version "2.0.3"
4699 resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
4700 integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
4701
4610promise.prototype.finally@^3.1.1: 4702promise.prototype.finally@^3.1.1:
4611 version "3.1.2" 4703 version "3.1.2"
4612 resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" 4704 resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067"
@@ -4775,12 +4867,29 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
4775 strip-json-comments "~2.0.1" 4867 strip-json-comments "~2.0.1"
4776 4868
4777rdf-canonize@^1.0.2: 4869rdf-canonize@^1.0.2:
4778 version "1.0.3" 4870 version "1.1.0"
4779 resolved "https://registry.yarnpkg.com/rdf-canonize/-/rdf-canonize-1.0.3.tgz#71dc56bb808a39d12e3ca17674c15f881cad648a" 4871 resolved "https://registry.yarnpkg.com/rdf-canonize/-/rdf-canonize-1.1.0.tgz#61d1609bbdb3234b8f38c9c34ad889bf670e089d"
4780 integrity sha512-piLMOB5Q6LJSVx2XzmdpHktYVb8TmVTy8coXJBFtdkcMC96DknZOuzpAYqCWx2ERZX7xEW+mMi8/wDuMJS/95w== 4872 integrity sha512-DV06OnhVfl2zcZJQCt+YvU+hoZVgpyQpNFLeAmghq8RJybUxD3B4LRzlBquYS5k+LLd8/c3g5Gnhkqjw5qRMvg==
4873 dependencies:
4874 node-forge "^0.9.1"
4875 semver "^6.3.0"
4876
4877read-pkg-up@^2.0.0:
4878 version "2.0.0"
4879 resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
4880 integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=
4881 dependencies:
4882 find-up "^2.0.0"
4883 read-pkg "^2.0.0"
4884
4885read-pkg@^2.0.0:
4886 version "2.0.0"
4887 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8"
4888 integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=
4781 dependencies: 4889 dependencies:
4782 node-forge "^0.8.1" 4890 load-json-file "^2.0.0"
4783 semver "^5.6.0" 4891 normalize-package-data "^2.3.2"
4892 path-type "^2.0.0"
4784 4893
4785read-pkg@^4.0.1: 4894read-pkg@^4.0.1:
4786 version "4.0.1" 4895 version "4.0.1"
@@ -4832,9 +4941,9 @@ readable-stream@^2.0.0, readable-stream@^2.0.6, readable-stream@^2.2.2, readable
4832 util-deprecate "~1.0.1" 4941 util-deprecate "~1.0.1"
4833 4942
4834readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: 4943readable-stream@^3.0.0, readable-stream@^3.0.1, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0:
4835 version "3.4.0" 4944 version "3.5.0"
4836 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" 4945 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606"
4837 integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== 4946 integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==
4838 dependencies: 4947 dependencies:
4839 inherits "^2.0.3" 4948 inherits "^2.0.3"
4840 string_decoder "^1.1.1" 4949 string_decoder "^1.1.1"
@@ -4907,6 +5016,16 @@ reflect-metadata@^0.1.12:
4907 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 5016 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
4908 integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== 5017 integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
4909 5018
5019regexpp@^2.0.1:
5020 version "2.0.1"
5021 resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
5022 integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
5023
5024regexpp@^3.0.0:
5025 version "3.0.0"
5026 resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e"
5027 integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==
5028
4910registry-auth-token@^3.0.1: 5029registry-auth-token@^3.0.1:
4911 version "3.4.0" 5030 version "3.4.0"
4912 resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" 5031 resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
@@ -4974,10 +5093,10 @@ resolve-from@^2.0.0:
4974 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" 5093 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
4975 integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c= 5094 integrity sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=
4976 5095
4977resolve-from@^3.0.0: 5096resolve-from@^4.0.0:
4978 version "3.0.0" 5097 version "4.0.0"
4979 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" 5098 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
4980 integrity sha1-six699nWiBvItuZTM17rywoYh0g= 5099 integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
4981 5100
4982resolve-pkg@^1.0.0: 5101resolve-pkg@^1.0.0:
4983 version "1.0.0" 5102 version "1.0.0"
@@ -4986,19 +5105,19 @@ resolve-pkg@^1.0.0:
4986 dependencies: 5105 dependencies:
4987 resolve-from "^2.0.0" 5106 resolve-from "^2.0.0"
4988 5107
4989resolve@^1.10.0, resolve@^1.3.2: 5108resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1:
4990 version "1.14.2" 5109 version "1.15.0"
4991 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" 5110 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
4992 integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== 5111 integrity sha512-+hTmAldEGE80U2wJJDC1lebb5jWqvTYAfm3YZ1ckk1gBr0MnCqUKlwK1e+anaFljIl+F5tR5IoZcm4ZDA1zMQw==
4993 dependencies: 5112 dependencies:
4994 path-parse "^1.0.6" 5113 path-parse "^1.0.6"
4995 5114
4996restore-cursor@^2.0.0: 5115restore-cursor@^3.1.0:
4997 version "2.0.0" 5116 version "3.1.0"
4998 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" 5117 resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
4999 integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= 5118 integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
5000 dependencies: 5119 dependencies:
5001 onetime "^2.0.0" 5120 onetime "^5.1.0"
5002 signal-exit "^3.0.2" 5121 signal-exit "^3.0.2"
5003 5122
5004retry-as-promised@^3.2.0: 5123retry-as-promised@^3.2.0:
@@ -5008,16 +5127,18 @@ retry-as-promised@^3.2.0:
5008 dependencies: 5127 dependencies:
5009 any-promise "^1.3.0" 5128 any-promise "^1.3.0"
5010 5129
5011reusify@^1.0.0:
5012 version "1.0.4"
5013 resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
5014 integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
5015
5016revalidator@0.1.x: 5130revalidator@0.1.x:
5017 version "0.1.8" 5131 version "0.1.8"
5018 resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" 5132 resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
5019 integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs= 5133 integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs=
5020 5134
5135rimraf@2.6.3:
5136 version "2.6.3"
5137 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
5138 integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
5139 dependencies:
5140 glob "^7.1.3"
5141
5021rimraf@2.x.x, rimraf@^2.6.1, rimraf@^2.6.3: 5142rimraf@2.x.x, rimraf@^2.6.1, rimraf@^2.6.3:
5022 version "2.7.1" 5143 version "2.7.1"
5023 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" 5144 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -5026,18 +5147,25 @@ rimraf@2.x.x, rimraf@^2.6.1, rimraf@^2.6.3:
5026 glob "^7.1.3" 5147 glob "^7.1.3"
5027 5148
5028rimraf@^3.0.0: 5149rimraf@^3.0.0:
5029 version "3.0.0" 5150 version "3.0.1"
5030 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" 5151 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.1.tgz#48d3d4cb46c80d388ab26cd61b1b466ae9ae225a"
5031 integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg== 5152 integrity sha512-IQ4ikL8SjBiEDZfk+DFVwqRK8md24RWMEJkdSlgNLkyyAImcjf8SWvU1qFMDOb4igBClbTQ/ugPqXcRwdFTxZw==
5032 dependencies: 5153 dependencies:
5033 glob "^7.1.3" 5154 glob "^7.1.3"
5034 5155
5156run-async@^2.2.0:
5157 version "2.3.0"
5158 resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
5159 integrity sha1-A3GrSuC91yDUFm19/aZP96RFpsA=
5160 dependencies:
5161 is-promise "^2.1.0"
5162
5035run-parallel-limit@^1.0.3: 5163run-parallel-limit@^1.0.3:
5036 version "1.0.5" 5164 version "1.0.5"
5037 resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.5.tgz#c29a4fd17b4df358cb52a8a697811a63c984f1b7" 5165 resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.5.tgz#c29a4fd17b4df358cb52a8a697811a63c984f1b7"
5038 integrity sha512-NsY+oDngvrvMxKB3G8ijBzIema6aYbQMD2bHOamvN52BysbIGTnEY2xsNyfrcr9GhY995/t/0nQN3R3oZvaDlg== 5166 integrity sha512-NsY+oDngvrvMxKB3G8ijBzIema6aYbQMD2bHOamvN52BysbIGTnEY2xsNyfrcr9GhY995/t/0nQN3R3oZvaDlg==
5039 5167
5040run-parallel@^1.0.0, run-parallel@^1.1.2, run-parallel@^1.1.6, run-parallel@^1.1.9: 5168run-parallel@^1.0.0, run-parallel@^1.1.2, run-parallel@^1.1.6:
5041 version "1.1.9" 5169 version "1.1.9"
5042 resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" 5170 resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
5043 integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== 5171 integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
@@ -5052,7 +5180,7 @@ rusha@^0.8.1:
5052 resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.13.tgz#9a084e7b860b17bff3015b92c67a6a336191513a" 5180 resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.13.tgz#9a084e7b860b17bff3015b92c67a6a336191513a"
5053 integrity sha1-mghOe4YLF7/zAVuSxnpqM2GRUTo= 5181 integrity sha1-mghOe4YLF7/zAVuSxnpqM2GRUTo=
5054 5182
5055rxjs@^6.3.3, rxjs@^6.5.2: 5183rxjs@^6.5.2, rxjs@^6.5.3:
5056 version "6.5.4" 5184 version "6.5.4"
5057 resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" 5185 resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
5058 integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q== 5186 integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
@@ -5089,11 +5217,6 @@ scripty@^1.5.0:
5089 lodash "^4.17.11" 5217 lodash "^4.17.11"
5090 resolve-pkg "^1.0.0" 5218 resolve-pkg "^1.0.0"
5091 5219
5092semver-compare@^1.0.0:
5093 version "1.0.0"
5094 resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
5095 integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w=
5096
5097semver-diff@^2.0.0: 5220semver-diff@^2.0.0:
5098 version "2.1.0" 5221 version "2.1.0"
5099 resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" 5222 resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
@@ -5101,7 +5224,7 @@ semver-diff@^2.0.0:
5101 dependencies: 5224 dependencies:
5102 semver "^5.0.3" 5225 semver "^5.0.3"
5103 5226
5104"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: 5227"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.7.0, semver@^5.7.1:
5105 version "5.7.1" 5228 version "5.7.1"
5106 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 5229 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
5107 integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 5230 integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
@@ -5111,11 +5234,16 @@ semver@4.3.2:
5111 resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" 5234 resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
5112 integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= 5235 integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
5113 5236
5114semver@^6.3.0: 5237semver@^6.1.0, semver@^6.1.2, semver@^6.3.0:
5115 version "6.3.0" 5238 version "6.3.0"
5116 resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 5239 resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
5117 integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 5240 integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
5118 5241
5242semver@^7.1.1:
5243 version "7.1.2"
5244 resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.2.tgz#847bae5bce68c5d08889824f02667199b70e3d87"
5245 integrity sha512-BJs9T/H8sEVHbeigqzIEo57Iu/3DG6c4QoqTfbQB3BPA4zgzAomh/Fk9E7QtjWQ8mx2dgA9YCfSF4y9k9bHNpQ==
5246
5119send@0.17.1: 5247send@0.17.1:
5120 version "0.17.1" 5248 version "0.17.1"
5121 resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 5249 resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -5188,17 +5316,17 @@ setprototypeof@1.1.1:
5188 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 5316 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
5189 integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 5317 integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
5190 5318
5191sharp@^0.23.3: 5319sharp@^0.24.0:
5192 version "0.23.4" 5320 version "0.24.0"
5193 resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.23.4.tgz#ca36067cb6ff7067fa6c77b01651cb9a890f8eb3" 5321 resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.24.0.tgz#1200f4bb36ccc2bb36a78f0bcba0302cf1f7a5fd"
5194 integrity sha512-fJMagt6cT0UDy9XCsgyLi0eiwWWhQRxbwGmqQT6sY8Av4s0SVsT/deg8fobBQCTDU5iXRgz0rAeXoE2LBZ8g+Q== 5322 integrity sha512-kUtQE6+HJnNqO0H6ueOBtRXahktuqydIBaFMvhDelf/KaK9j/adEdjf4Y3+bbjYOa5i6hi2EAa2Y2G9umP4s2g==
5195 dependencies: 5323 dependencies:
5196 color "^3.1.2" 5324 color "^3.1.2"
5197 detect-libc "^1.0.3" 5325 detect-libc "^1.0.3"
5198 nan "^2.14.0" 5326 nan "^2.14.0"
5199 npmlog "^4.1.2" 5327 npmlog "^4.1.2"
5200 prebuild-install "^5.3.3" 5328 prebuild-install "^5.3.3"
5201 semver "^6.3.0" 5329 semver "^7.1.1"
5202 simple-get "^3.1.0" 5330 simple-get "^3.1.0"
5203 tar "^5.0.5" 5331 tar "^5.0.5"
5204 tunnel-agent "^0.6.0" 5332 tunnel-agent "^0.6.0"
@@ -5299,15 +5427,14 @@ sitemap@^5.0.0:
5299 sax "^1.2.4" 5427 sax "^1.2.4"
5300 xmlbuilder "^13.0.2" 5428 xmlbuilder "^13.0.2"
5301 5429
5302slash@^3.0.0: 5430slice-ansi@^2.1.0:
5303 version "3.0.0" 5431 version "2.1.0"
5304 resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 5432 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
5305 integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 5433 integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
5306 5434 dependencies:
5307slice-ansi@0.0.4: 5435 ansi-styles "^3.2.0"
5308 version "0.0.4" 5436 astral-regex "^1.0.0"
5309 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" 5437 is-fullwidth-code-point "^2.0.0"
5310 integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
5311 5438
5312smtp-connection@4.0.2: 5439smtp-connection@4.0.2:
5313 version "4.0.2" 5440 version "4.0.2"
@@ -5575,11 +5702,6 @@ streamsearch@0.1.2:
5575 resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 5702 resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
5576 integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= 5703 integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
5577 5704
5578string-argv@^0.3.0:
5579 version "0.3.1"
5580 resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
5581 integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
5582
5583string-width@^1.0.1: 5705string-width@^1.0.1:
5584 version "1.0.2" 5706 version "1.0.2"
5585 resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 5707 resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -5658,15 +5780,6 @@ string_decoder@~1.1.1:
5658 dependencies: 5780 dependencies:
5659 safe-buffer "~5.1.0" 5781 safe-buffer "~5.1.0"
5660 5782
5661stringify-object@^3.3.0:
5662 version "3.3.0"
5663 resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
5664 integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==
5665 dependencies:
5666 get-own-enumerable-property-symbols "^3.0.0"
5667 is-obj "^1.0.1"
5668 is-regexp "^1.0.0"
5669
5670strip-ansi@^3.0.0, strip-ansi@^3.0.1: 5783strip-ansi@^3.0.0, strip-ansi@^3.0.1:
5671 version "3.0.1" 5784 version "3.0.1"
5672 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 5785 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -5715,6 +5828,11 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1:
5715 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 5828 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
5716 integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 5829 integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
5717 5830
5831strip-json-comments@^3.0.1:
5832 version "3.0.1"
5833 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
5834 integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
5835
5718superagent@^3.8.3: 5836superagent@^3.8.3:
5719 version "3.8.3" 5837 version "3.8.3"
5720 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" 5838 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
@@ -5746,11 +5864,6 @@ supports-color@6.0.0:
5746 dependencies: 5864 dependencies:
5747 has-flag "^3.0.0" 5865 has-flag "^3.0.0"
5748 5866
5749supports-color@^2.0.0:
5750 version "2.0.0"
5751 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
5752 integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
5753
5754supports-color@^5.3.0, supports-color@^5.5.0: 5867supports-color@^5.3.0, supports-color@^5.5.0:
5755 version "5.5.0" 5868 version "5.5.0"
5756 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 5869 resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -5772,16 +5885,15 @@ supports-color@^7.1.0:
5772 dependencies: 5885 dependencies:
5773 has-flag "^4.0.0" 5886 has-flag "^4.0.0"
5774 5887
5775swagger-cli@^2.2.0: 5888swagger-cli@^3.0.1:
5776 version "2.3.5" 5889 version "3.0.1"
5777 resolved "https://registry.yarnpkg.com/swagger-cli/-/swagger-cli-2.3.5.tgz#a7ae08ae9abe4cc4aaab0334c57166a2cb377fd3" 5890 resolved "https://registry.yarnpkg.com/swagger-cli/-/swagger-cli-3.0.1.tgz#ed49c5844e9c10d1fd97d8501611846356ca48ed"
5778 integrity sha512-UL3S053zT0P9prYJj2zaiokER2KxJh2GWTJ12SbBAJZnlUKJrUZn3qK+zVc/i/bUcyrRRttA4cp8Lzk6cNm0nw== 5891 integrity sha512-RkCH3ylYmtNj5cIQCQtP+f01mTcwkGEKXsnIN+Vyiqf8M0SdgPfyjuNJ78hj47xGXCDysCBINQHtP0p8phYSBA==
5779 dependencies: 5892 dependencies:
5780 chalk "^3.0.0" 5893 chalk "^3.0.0"
5781 js-yaml "^3.13.1" 5894 js-yaml "^3.13.1"
5782 mkdirp "^0.5.1"
5783 swagger-parser "^8.0.4" 5895 swagger-parser "^8.0.4"
5784 yargs "^15.0.2" 5896 yargs "^15.1.0"
5785 5897
5786swagger-methods@^2.0.1: 5898swagger-methods@^2.0.1:
5787 version "2.0.2" 5899 version "2.0.2"
@@ -5801,10 +5913,15 @@ swagger-parser@^8.0.4:
5801 swagger-methods "^2.0.1" 5913 swagger-methods "^2.0.1"
5802 z-schema "^4.2.2" 5914 z-schema "^4.2.2"
5803 5915
5804symbol-observable@^1.1.0: 5916table@^5.2.3:
5805 version "1.2.0" 5917 version "5.4.6"
5806 resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" 5918 resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
5807 integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== 5919 integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
5920 dependencies:
5921 ajv "^6.10.2"
5922 lodash "^4.17.14"
5923 slice-ansi "^2.1.0"
5924 string-width "^3.0.0"
5808 5925
5809tar-fs@^2.0.0: 5926tar-fs@^2.0.0:
5810 version "2.0.0" 5927 version "2.0.0"
@@ -5864,6 +5981,11 @@ text-hex@1.0.x:
5864 resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" 5981 resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5"
5865 integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== 5982 integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==
5866 5983
5984text-table@^0.2.0:
5985 version "0.2.0"
5986 resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
5987 integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
5988
5867thirty-two@^1.0.1: 5989thirty-two@^1.0.1:
5868 version "1.0.2" 5990 version "1.0.2"
5869 resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a" 5991 resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
@@ -5893,7 +6015,7 @@ through2@^2.0.3:
5893 readable-stream "~2.3.6" 6015 readable-stream "~2.3.6"
5894 xtend "~4.0.1" 6016 xtend "~4.0.1"
5895 6017
5896through@2: 6018through@2, through@^2.3.6:
5897 version "2.3.8" 6019 version "2.3.8"
5898 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 6020 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
5899 integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 6021 integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -5916,7 +6038,7 @@ timers-ext@^0.1.5:
5916 es5-ext "~0.10.46" 6038 es5-ext "~0.10.46"
5917 next-tick "1" 6039 next-tick "1"
5918 6040
5919tmp@0.0.x: 6041tmp@0.0.x, tmp@^0.0.33:
5920 version "0.0.33" 6042 version "0.0.33"
5921 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 6043 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
5922 integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== 6044 integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
@@ -6006,16 +6128,16 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
6006 resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" 6128 resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
6007 integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== 6129 integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
6008 6130
6009ts-node@8.5.4: 6131ts-node@8.6.2:
6010 version "8.5.4" 6132 version "8.6.2"
6011 resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.5.4.tgz#a152add11fa19c221d0b48962c210cf467262ab2" 6133 resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35"
6012 integrity sha512-izbVCRV68EasEPQ8MSIGBNK9dc/4sYJJKYA+IarMQct1RtEot6Xp0bXuClsbUSnKpg50ho+aOAx8en5c+y4OFw== 6134 integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==
6013 dependencies: 6135 dependencies:
6014 arg "^4.1.0" 6136 arg "^4.1.0"
6015 diff "^4.0.1" 6137 diff "^4.0.1"
6016 make-error "^1.1.1" 6138 make-error "^1.1.1"
6017 source-map-support "^0.5.6" 6139 source-map-support "^0.5.6"
6018 yn "^3.0.0" 6140 yn "3.1.1"
6019 6141
6020tsconfig-paths@^3.9.0: 6142tsconfig-paths@^3.9.0:
6021 version "3.9.0" 6143 version "3.9.0"
@@ -6027,59 +6149,12 @@ tsconfig-paths@^3.9.0:
6027 minimist "^1.2.0" 6149 minimist "^1.2.0"
6028 strip-bom "^3.0.0" 6150 strip-bom "^3.0.0"
6029 6151
6030tslib@1.9.0: 6152tslib@^1.8.1, tslib@^1.9.0:
6031 version "1.9.0"
6032 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
6033 integrity sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==
6034
6035tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
6036 version "1.10.0" 6153 version "1.10.0"
6037 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" 6154 resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
6038 integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== 6155 integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
6039 6156
6040tslint-config-standard@^9.0.0: 6157tsutils@^3.17.1:
6041 version "9.0.0"
6042 resolved "https://registry.yarnpkg.com/tslint-config-standard/-/tslint-config-standard-9.0.0.tgz#349a94819d93d5f8d803e3c71cb58ef38eff88e0"
6043 integrity sha512-CAw9J743RnPMemQV/XQ4YyNreC+A1NItACfkm+cBedrOkz6CQfwlnbKn8anUXBfoa4Zo4tjAhblRbsMNcSLfSw==
6044 dependencies:
6045 tslint-eslint-rules "^5.3.1"
6046
6047tslint-eslint-rules@^5.3.1:
6048 version "5.4.0"
6049 resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.4.0.tgz#e488cc9181bf193fe5cd7bfca213a7695f1737b5"
6050 integrity sha512-WlSXE+J2vY/VPgIcqQuijMQiel+UtmXS+4nvK4ZzlDiqBfXse8FAvkNnTcYhnQyOTW5KFM+uRRGXxYhFpuBc6w==
6051 dependencies:
6052 doctrine "0.7.2"
6053 tslib "1.9.0"
6054 tsutils "^3.0.0"
6055
6056tslint@^5.7.0:
6057 version "5.20.1"
6058 resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d"
6059 integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==
6060 dependencies:
6061 "@babel/code-frame" "^7.0.0"
6062 builtin-modules "^1.1.1"
6063 chalk "^2.3.0"
6064 commander "^2.12.1"
6065 diff "^4.0.1"
6066 glob "^7.1.1"
6067 js-yaml "^3.13.1"
6068 minimatch "^3.0.4"
6069 mkdirp "^0.5.1"
6070 resolve "^1.3.2"
6071 semver "^5.3.0"
6072 tslib "^1.8.0"
6073 tsutils "^2.29.0"
6074
6075tsutils@^2.29.0:
6076 version "2.29.0"
6077 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
6078 integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
6079 dependencies:
6080 tslib "^1.8.1"
6081
6082tsutils@^3.0.0:
6083 version "3.17.1" 6158 version "3.17.1"
6084 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" 6159 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
6085 integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== 6160 integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
@@ -6103,6 +6178,13 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
6103 resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 6178 resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
6104 integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= 6179 integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
6105 6180
6181type-check@~0.3.2:
6182 version "0.3.2"
6183 resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
6184 integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
6185 dependencies:
6186 prelude-ls "~1.1.2"
6187
6106type-detect@0.1.1: 6188type-detect@0.1.1:
6107 version "0.1.1" 6189 version "0.1.1"
6108 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 6190 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822"
@@ -6113,6 +6195,11 @@ type-detect@^4.0.0, type-detect@^4.0.5:
6113 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 6195 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
6114 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 6196 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
6115 6197
6198type-fest@^0.8.1:
6199 version "0.8.1"
6200 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
6201 integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
6202
6116type-is@1.6.15: 6203type-is@1.6.15:
6117 version "1.6.15" 6204 version "1.6.15"
6118 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 6205 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
@@ -6152,9 +6239,9 @@ typedarray@^0.0.6:
6152 integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 6239 integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
6153 6240
6154typescript@^3.7.2: 6241typescript@^3.7.2:
6155 version "3.7.4" 6242 version "3.7.5"
6156 resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.4.tgz#1743a5ec5fef6a1fa9f3e4708e33c81c73876c19" 6243 resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
6157 integrity sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw== 6244 integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
6158 6245
6159uint64be@^2.0.2: 6246uint64be@^2.0.2:
6160 version "2.0.2" 6247 version "2.0.2"
@@ -6272,12 +6359,14 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
6272 integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 6359 integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
6273 6360
6274util.promisify@^1.0.0: 6361util.promisify@^1.0.0:
6275 version "1.0.0" 6362 version "1.0.1"
6276 resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" 6363 resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
6277 integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== 6364 integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
6278 dependencies: 6365 dependencies:
6279 define-properties "^1.1.2" 6366 define-properties "^1.1.3"
6280 object.getownpropertydescriptors "^2.0.3" 6367 es-abstract "^1.17.2"
6368 has-symbols "^1.0.1"
6369 object.getownpropertydescriptors "^2.1.0"
6281 6370
6282utile@0.3.x: 6371utile@0.3.x:
6283 version "0.3.0" 6372 version "0.3.0"
@@ -6305,9 +6394,14 @@ uue@^3.1.0:
6305 extend "~3.0.0" 6394 extend "~3.0.0"
6306 6395
6307uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3: 6396uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3:
6308 version "3.3.3" 6397 version "3.4.0"
6309 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" 6398 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
6310 integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== 6399 integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
6400
6401v8-compile-cache@^2.0.3:
6402 version "2.1.0"
6403 resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
6404 integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
6311 6405
6312validate-npm-package-license@^3.0.1: 6406validate-npm-package-license@^3.0.1:
6313 version "3.0.4" 6407 version "3.0.4"
@@ -6322,15 +6416,15 @@ validator@^10.11.0:
6322 resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" 6416 resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228"
6323 integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== 6417 integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==
6324 6418
6325validator@^11.0.0, validator@^11.1.0: 6419validator@^11.0.0:
6326 version "11.1.0" 6420 version "11.1.0"
6327 resolved "https://registry.yarnpkg.com/validator/-/validator-11.1.0.tgz#ac18cac42e0aa5902b603d7a5d9b7827e2346ac4" 6421 resolved "https://registry.yarnpkg.com/validator/-/validator-11.1.0.tgz#ac18cac42e0aa5902b603d7a5d9b7827e2346ac4"
6328 integrity sha512-qiQ5ktdO7CD6C/5/mYV4jku/7qnqzjrxb3C/Q5wR3vGGinHTgJZN/TdFT3ZX4vXhX2R1PXx42fB1cn5W+uJ4lg== 6422 integrity sha512-qiQ5ktdO7CD6C/5/mYV4jku/7qnqzjrxb3C/Q5wR3vGGinHTgJZN/TdFT3ZX4vXhX2R1PXx42fB1cn5W+uJ4lg==
6329 6423
6330validator@^12.1.0: 6424validator@^12.1.0:
6331 version "12.1.0" 6425 version "12.2.0"
6332 resolved "https://registry.yarnpkg.com/validator/-/validator-12.1.0.tgz#a3a7315d5238cbc15e46ad8d5e479aafa7119925" 6426 resolved "https://registry.yarnpkg.com/validator/-/validator-12.2.0.tgz#660d47e96267033fd070096c3b1a6f2db4380a0a"
6333 integrity sha512-gIC2RBuFRi574Rb9vewGCJ7TCLxHXNx6EKthEgs+Iz0pYa9a9Te1VLG/bGLsAyGWrqR5FfR7tbFUI7FEF2LiGA== 6427 integrity sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==
6334 6428
6335vary@^1, vary@~1.1.2: 6429vary@^1, vary@~1.1.2:
6336 version "1.1.2" 6430 version "1.1.2"
@@ -6423,14 +6517,14 @@ which-pm-runs@^1.0.0:
6423 resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" 6517 resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
6424 integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= 6518 integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
6425 6519
6426which@1.3.1, which@^1.1.1, which@^1.2.9, which@^1.3.1: 6520which@1.3.1, which@^1.1.1, which@^1.2.9:
6427 version "1.3.1" 6521 version "1.3.1"
6428 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 6522 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
6429 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 6523 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
6430 dependencies: 6524 dependencies:
6431 isexe "^2.0.0" 6525 isexe "^2.0.0"
6432 6526
6433which@^2.0.1: 6527which@^2.0.1, which@^2.0.2:
6434 version "2.0.2" 6528 version "2.0.2"
6435 resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 6529 resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
6436 integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 6530 integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
@@ -6499,13 +6593,10 @@ wkx@^0.4.8:
6499 dependencies: 6593 dependencies:
6500 "@types/node" "*" 6594 "@types/node" "*"
6501 6595
6502wrap-ansi@^3.0.1: 6596word-wrap@~1.2.3:
6503 version "3.0.1" 6597 version "1.2.3"
6504 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" 6598 resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
6505 integrity sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo= 6599 integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
6506 dependencies:
6507 string-width "^2.1.1"
6508 strip-ansi "^4.0.0"
6509 6600
6510wrap-ansi@^5.1.0: 6601wrap-ansi@^5.1.0:
6511 version "5.1.0" 6602 version "5.1.0"
@@ -6539,6 +6630,13 @@ write-file-atomic@^2.0.0:
6539 imurmurhash "^0.1.4" 6630 imurmurhash "^0.1.4"
6540 signal-exit "^3.0.2" 6631 signal-exit "^3.0.2"
6541 6632
6633write@1.0.3:
6634 version "1.0.3"
6635 resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
6636 integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
6637 dependencies:
6638 mkdirp "^0.5.1"
6639
6542ws@^7.0.0, ws@^7.1.2: 6640ws@^7.0.0, ws@^7.1.2:
6543 version "7.2.1" 6641 version "7.2.1"
6544 resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e" 6642 resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.1.tgz#03ed52423cd744084b2cf42ed197c8b65a936b8e"
@@ -6566,20 +6664,6 @@ xhr2@^0.1.4:
6566 resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" 6664 resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f"
6567 integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= 6665 integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8=
6568 6666
6569xliff@^4.0.0:
6570 version "4.3.2"
6571 resolved "https://registry.yarnpkg.com/xliff/-/xliff-4.3.2.tgz#ef9655abce99f4c60efbc8b6d019c3c55e543315"
6572 integrity sha512-NmI1Q1Zx8tyMl87XmoQnOPQaR7hvgzHhGArskwmUB6Tvyo0PxPkMq59wlyOtV/fNFEhobQqtW/TkpXvuF0RFng==
6573 dependencies:
6574 xml-js "1.6.11"
6575
6576xml-js@1.6.11:
6577 version "1.6.11"
6578 resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9"
6579 integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==
6580 dependencies:
6581 sax "^1.2.4"
6582
6583xml2js@^0.4.4: 6667xml2js@^0.4.4:
6584 version "0.4.23" 6668 version "0.4.23"
6585 resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" 6669 resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
@@ -6679,7 +6763,7 @@ yargs@13.3.0, yargs@^13.2.4, yargs@^13.3.0:
6679 y18n "^4.0.0" 6763 y18n "^4.0.0"
6680 yargs-parser "^13.1.1" 6764 yargs-parser "^13.1.1"
6681 6765
6682yargs@^15.0.2: 6766yargs@^15.1.0:
6683 version "15.1.0" 6767 version "15.1.0"
6684 resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" 6768 resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219"
6685 integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== 6769 integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==
@@ -6701,15 +6785,15 @@ yeast@0.1.2:
6701 resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" 6785 resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
6702 integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= 6786 integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
6703 6787
6704yn@^3.0.0: 6788yn@3.1.1:
6705 version "3.1.1" 6789 version "3.1.1"
6706 resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" 6790 resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
6707 integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== 6791 integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
6708 6792
6709youtube-dl@^2.0.0: 6793youtube-dl@^3.0.2:
6710 version "2.3.0" 6794 version "3.0.2"
6711 resolved "https://registry.yarnpkg.com/youtube-dl/-/youtube-dl-2.3.0.tgz#193d59164e809b2c619b348b7e98b37e6abcf620" 6795 resolved "https://registry.yarnpkg.com/youtube-dl/-/youtube-dl-3.0.2.tgz#66236bfbdc93127efe3a7f02894ec544b23e8aa7"
6712 integrity sha512-92oQDJYaSqLbXZI9GWPalq8fuJdlJGsB6mAQ+uEl127kbCpF5HXea5F046kPxUHQ1OnQ3jHqx0wqZ6ELijVMEw== 6796 integrity sha512-LFFfpsYbRLpqKsnb4gzbnyN7fm190tJw3gJVSvfoEfnb/xYIPNT6i9G3jdzPDp/U5cwB3OSq63nUa7rUwxXAGA==
6713 dependencies: 6797 dependencies:
6714 debug "~4.1.1" 6798 debug "~4.1.1"
6715 execa "~3.2.0" 6799 execa "~3.2.0"