aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.github/CONTRIBUTING.md26
-rw-r--r--.github/FUNDING.yml1
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml4
-rw-r--r--ARCHITECTURE.md45
-rw-r--r--CHANGELOG.md10
-rw-r--r--FAQ.md2
-rw-r--r--README.md16
-rw-r--r--client/e2e/src/po/my-account.ts72
-rw-r--r--client/e2e/src/po/video-update.po.ts20
-rw-r--r--client/e2e/src/po/video-watch.po.ts43
-rw-r--r--client/e2e/src/videos.e2e-spec.ts159
-rw-r--r--client/proxy.config.json5
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.html22
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.scss14
-rw-r--r--client/src/app/+about/about-follows/about-follows.component.ts103
-rw-r--r--client/src/app/+about/about-routing.module.ts10
-rw-r--r--client/src/app/+about/about.component.html4
-rw-r--r--client/src/app/+about/about.module.ts2
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.html36
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.scss36
-rw-r--r--client/src/app/+accounts/account-video-channels/account-video-channels.component.ts71
-rw-r--r--client/src/app/+accounts/account-videos/account-videos.component.ts4
-rw-r--r--client/src/app/+accounts/accounts-routing.module.ts6
-rw-r--r--client/src/app/+accounts/accounts.component.html4
-rw-r--r--client/src/app/+admin/admin.module.ts3
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html12
-rw-r--r--client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts36
-rw-r--r--client/src/app/+admin/follows/followers-list/followers-list.component.ts2
-rw-r--r--client/src/app/+admin/follows/following-add/following-add.component.ts2
-rw-r--r--client/src/app/+admin/follows/following-list/following-list.component.ts2
-rw-r--r--client/src/app/+admin/follows/index.ts1
-rw-r--r--client/src/app/+admin/follows/shared/index.ts1
-rw-r--r--client/src/app/+admin/users/user-edit/user-edit.ts5
-rw-r--r--client/src/app/+my-account/my-account-history/my-account-history.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts1
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html36
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss24
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts73
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts2
-rw-r--r--client/src/app/+my-account/my-account-settings/my-account-settings.component.html3
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html7
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.ts5
-rw-r--r--client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts15
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts3
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html10
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts4
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts47
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html2
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.html25
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.scss66
-rw-r--r--client/src/app/+signup/+register/custom-stepper.component.ts19
-rw-r--r--client/src/app/+signup/+register/register-routing.module.ts (renamed from client/src/app/signup/signup-routing.module.ts)17
-rw-r--r--client/src/app/+signup/+register/register-step-channel.component.html54
-rw-r--r--client/src/app/+signup/+register/register-step-channel.component.ts56
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.html73
-rw-r--r--client/src/app/+signup/+register/register-step-user.component.ts54
-rw-r--r--client/src/app/+signup/+register/register.component.html47
-rw-r--r--client/src/app/+signup/+register/register.component.scss81
-rw-r--r--client/src/app/+signup/+register/register.component.ts89
-rw-r--r--client/src/app/+signup/+register/register.module.ts33
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html (renamed from client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html)0
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss (renamed from client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss)0
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts (renamed from client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts)0
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html18
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts (renamed from client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts)22
-rw-r--r--client/src/app/+signup/+verify-account/verify-account-routing.module.ts (renamed from client/src/app/+verify-account/verify-account-routing.module.ts)8
-rw-r--r--client/src/app/+signup/+verify-account/verify-account.module.ts25
-rw-r--r--client/src/app/+signup/shared/signup-shared.module.ts21
-rw-r--r--client/src/app/+signup/shared/signup-success.component.html16
-rw-r--r--client/src/app/+signup/shared/signup-success.component.scss76
-rw-r--r--client/src/app/+signup/shared/signup-success.component.ts10
-rw-r--r--client/src/app/+verify-account/index.ts2
-rw-r--r--client/src/app/+verify-account/verify-account-email/verify-account-email.component.html15
-rw-r--r--client/src/app/+verify-account/verify-account.module.ts27
-rw-r--r--client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts5
-rw-r--r--client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts2
-rw-r--r--client/src/app/app-routing.module.ts6
-rw-r--r--client/src/app/app.module.ts2
-rw-r--r--client/src/app/core/core.module.ts3
-rw-r--r--client/src/app/core/routing/redirect.service.ts9
-rw-r--r--client/src/app/core/routing/unlogged-guard.service.ts25
-rw-r--r--client/src/app/core/server/server.service.ts13
-rw-r--r--client/src/app/menu/menu.component.html2
-rw-r--r--client/src/app/search/search.component.html2
-rw-r--r--client/src/app/search/search.component.scss12
-rw-r--r--client/src/app/search/search.module.ts5
-rw-r--r--client/src/app/shared/actor/actor.model.ts2
-rw-r--r--client/src/app/shared/buttons/button.component.scss13
-rw-r--r--client/src/app/shared/buttons/button.component.ts2
-rw-r--r--client/src/app/shared/buttons/delete-button.component.html2
-rw-r--r--client/src/app/shared/buttons/edit-button.component.html2
-rw-r--r--client/src/app/shared/forms/form-validators/user-validators.service.ts33
-rw-r--r--client/src/app/shared/forms/peertube-checkbox.component.scss3
-rw-r--r--client/src/app/shared/forms/reactive-file.component.html7
-rw-r--r--client/src/app/shared/forms/reactive-file.component.scss10
-rw-r--r--client/src/app/shared/forms/reactive-file.component.ts2
-rw-r--r--client/src/app/shared/images/image-upload.component.html9
-rw-r--r--client/src/app/shared/images/image-upload.component.scss18
-rw-r--r--client/src/app/shared/images/preview-upload.component.html13
-rw-r--r--client/src/app/shared/images/preview-upload.component.scss27
-rw-r--r--client/src/app/shared/images/preview-upload.component.ts (renamed from client/src/app/shared/images/image-upload.component.ts)17
-rw-r--r--client/src/app/shared/instance/follow.service.ts (renamed from client/src/app/+admin/follows/shared/follow.service.ts)8
-rw-r--r--client/src/app/shared/misc/loader.component.html2
-rw-r--r--client/src/app/shared/misc/loader.component.scss14
-rw-r--r--client/src/app/shared/shared.module.ts20
-rw-r--r--client/src/app/shared/users/user-notifications.component.scss1
-rw-r--r--client/src/app/shared/users/user.model.ts1
-rw-r--r--client/src/app/shared/users/user.service.ts38
-rw-r--r--client/src/app/shared/video-channel/video-channel.service.ts17
-rw-r--r--client/src/app/shared/video/abstract-video-list.html20
-rw-r--r--client/src/app/shared/video/abstract-video-list.scss36
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts80
-rw-r--r--client/src/app/shared/video/modals/video-download.component.html5
-rw-r--r--client/src/app/shared/video/modals/video-download.component.ts13
-rw-r--r--client/src/app/shared/video/video-details.model.ts3
-rw-r--r--client/src/app/shared/video/video-edit.model.ts5
-rw-r--r--client/src/app/shared/video/video.model.ts2
-rw-r--r--client/src/app/shared/video/videos-selection.component.ts2
-rw-r--r--client/src/app/signup/index.ts3
-rw-r--r--client/src/app/signup/signup.component.html72
-rw-r--r--client/src/app/signup/signup.component.scss39
-rw-r--r--client/src/app/signup/signup.component.ts78
-rw-r--r--client/src/app/signup/signup.module.ts24
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html14
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.ts8
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html2
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts2
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html2
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts2
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-send.ts1
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-upload.component.html23
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss17
-rw-r--r--client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts108
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.html2
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.ts16
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.html192
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.scss68
-rw-r--r--client/src/app/videos/+video-watch/modal/video-share.component.ts91
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html2
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.scss1
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts108
-rw-r--r--client/src/app/videos/video-list/video-local.component.ts2
-rw-r--r--client/src/app/videos/video-list/video-overview.component.html6
-rw-r--r--client/src/app/videos/video-list/video-overview.component.scss60
-rw-r--r--client/src/app/videos/video-list/video-recently-added.component.ts3
-rw-r--r--client/src/app/videos/video-list/video-trending.component.ts2
-rw-r--r--client/src/app/videos/video-list/video-user-subscriptions.component.ts3
-rw-r--r--client/src/assets/player/peertube-player-local-storage.ts2
-rw-r--r--client/src/assets/player/peertube-player-manager.ts36
-rw-r--r--client/src/assets/player/utils.ts51
-rw-r--r--client/src/assets/player/videojs-components/peertube-link-button.ts2
-rw-r--r--client/src/sass/application.scss118
-rw-r--r--client/src/sass/bootstrap.scss138
-rw-r--r--client/src/sass/include/_miniature.scss97
-rw-r--r--client/src/sass/include/_mixins.scss25
-rw-r--r--client/src/sass/include/_variables.scss4
-rw-r--r--client/src/sass/player/_player-variables.scss8
-rw-r--r--client/src/sass/player/context-menu.scss4
-rw-r--r--client/src/sass/player/peertube-skin.scss12
-rw-r--r--client/src/sass/player/settings-menu.scss2
-rw-r--r--client/src/standalone/videos/embed-api.ts130
-rw-r--r--client/src/standalone/videos/embed.html2
-rw-r--r--client/src/standalone/videos/embed.ts205
-rw-r--r--config/default.yaml11
-rw-r--r--config/production.yaml.example13
-rw-r--r--config/test-2.yaml1
-rw-r--r--config/test.yaml2
-rw-r--r--package.json42
-rwxr-xr-xscripts/clean/server/test.sh9
-rwxr-xr-xscripts/create-transcoding-job.ts13
-rwxr-xr-xscripts/setup/cli.sh15
-rwxr-xr-xscripts/test.sh2
-rwxr-xr-xscripts/travis.sh9
-rw-r--r--server/assets/default-audio-background.jpgbin0 -> 14048 bytes
-rw-r--r--server/controllers/api/accounts.ts16
-rw-r--r--server/controllers/api/config.ts6
-rw-r--r--server/controllers/api/users/index.ts37
-rw-r--r--server/controllers/api/users/me.ts16
-rw-r--r--server/controllers/api/video-channel.ts37
-rw-r--r--server/controllers/api/video-playlist.ts6
-rw-r--r--server/controllers/api/videos/index.ts54
-rw-r--r--server/controllers/static.ts2
-rw-r--r--server/helpers/core-utils.ts5
-rw-r--r--server/helpers/custom-validators/accounts.ts11
-rw-r--r--server/helpers/custom-validators/activitypub/video-comments.ts3
-rw-r--r--server/helpers/custom-validators/video-channels.ts9
-rw-r--r--server/helpers/express-utils.ts13
-rw-r--r--server/helpers/ffmpeg-utils.ts180
-rw-r--r--server/helpers/logger.ts27
-rw-r--r--server/initializers/checker-before-init.ts1
-rw-r--r--server/initializers/config.ts15
-rw-r--r--server/initializers/constants.ts70
-rw-r--r--server/initializers/installer.ts4
-rw-r--r--server/initializers/migrations/0100-activitypub.ts9
-rw-r--r--server/initializers/migrations/0385-remove-actor-uuid.ts19
-rw-r--r--server/initializers/migrations/0390-user-pending-email.ts25
-rw-r--r--server/lib/activitypub/actor.ts33
-rw-r--r--server/lib/activitypub/crawl.ts19
-rw-r--r--server/lib/activitypub/process/process-announce.ts15
-rw-r--r--server/lib/activitypub/process/process-create.ts29
-rw-r--r--server/lib/activitypub/process/process-delete.ts8
-rw-r--r--server/lib/activitypub/process/process-update.ts4
-rw-r--r--server/lib/activitypub/video-comments.ts4
-rw-r--r--server/lib/emailer.ts66
-rw-r--r--server/lib/files-cache/videos-preview-cache.ts2
-rw-r--r--server/lib/job-queue/handlers/video-file-import.ts4
-rw-r--r--server/lib/job-queue/handlers/video-import.ts1
-rw-r--r--server/lib/job-queue/handlers/video-transcoding.ts56
-rw-r--r--server/lib/thumbnail.ts8
-rw-r--r--server/lib/user.ts91
-rw-r--r--server/lib/video-channel.ts16
-rw-r--r--server/lib/video-transcoding.ts97
-rw-r--r--server/middlewares/validators/feeds.ts13
-rw-r--r--server/middlewares/validators/users.ts75
-rw-r--r--server/middlewares/validators/videos/video-channels.ts16
-rw-r--r--server/middlewares/validators/videos/video-playlists.ts15
-rw-r--r--server/middlewares/validators/videos/videos.ts8
-rw-r--r--server/models/account/account.ts19
-rw-r--r--server/models/account/user.ts6
-rw-r--r--server/models/activitypub/actor.ts14
-rw-r--r--server/models/video/thumbnail.ts8
-rw-r--r--server/models/video/video-channel.ts34
-rw-r--r--server/models/video/video-file.ts5
-rw-r--r--server/models/video/video.ts23
-rw-r--r--server/tests/api/activitypub/client.ts11
-rw-r--r--server/tests/api/activitypub/fetch.ts17
-rw-r--r--server/tests/api/activitypub/refresher.ts46
-rw-r--r--server/tests/api/activitypub/security.ts106
-rw-r--r--server/tests/api/check-params/config.ts4
-rw-r--r--server/tests/api/check-params/users.ts45
-rw-r--r--server/tests/api/check-params/video-channels.ts36
-rw-r--r--server/tests/api/check-params/video-playlists.ts12
-rw-r--r--server/tests/api/index-1.ts3
-rw-r--r--server/tests/api/index-2.ts2
-rw-r--r--server/tests/api/index-3.ts1
-rw-r--r--server/tests/api/index-4.ts2
-rw-r--r--server/tests/api/index.ts12
-rw-r--r--server/tests/api/notifications/index.ts2
-rw-r--r--server/tests/api/notifications/user-notifications.ts41
-rw-r--r--server/tests/api/redundancy/redundancy.ts34
-rw-r--r--server/tests/api/search/search-activitypub-video-channels.ts39
-rw-r--r--server/tests/api/search/search-activitypub-videos.ts31
-rw-r--r--server/tests/api/search/search-videos.ts10
-rw-r--r--server/tests/api/server/config.ts38
-rw-r--r--server/tests/api/server/contact-form.ts7
-rw-r--r--server/tests/api/server/email.ts38
-rw-r--r--server/tests/api/server/follow-constraints.ts46
-rw-r--r--server/tests/api/server/follows-moderation.ts20
-rw-r--r--server/tests/api/server/follows.ts61
-rw-r--r--server/tests/api/server/handle-down.ts76
-rw-r--r--server/tests/api/server/jobs.ts2
-rw-r--r--server/tests/api/server/logs.ts2
-rw-r--r--server/tests/api/travis-1.sh10
-rw-r--r--server/tests/api/travis-2.sh9
-rw-r--r--server/tests/api/travis-3.sh8
-rw-r--r--server/tests/api/travis-4.sh9
-rw-r--r--server/tests/api/users/blocklist.ts28
-rw-r--r--server/tests/api/users/user-subscriptions.ts34
-rw-r--r--server/tests/api/users/users-multiple-servers.ts54
-rw-r--r--server/tests/api/users/users-verification.ts65
-rw-r--r--server/tests/api/users/users.ts30
-rw-r--r--server/tests/api/videos/multiple-servers.ts45
-rw-r--r--server/tests/api/videos/services.ts14
-rw-r--r--server/tests/api/videos/single-server.ts23
-rw-r--r--server/tests/api/videos/video-abuse.ts9
-rw-r--r--server/tests/api/videos/video-change-ownership.ts8
-rw-r--r--server/tests/api/videos/video-channels.ts116
-rw-r--r--server/tests/api/videos/video-comments.ts6
-rw-r--r--server/tests/api/videos/video-hls.ts54
-rw-r--r--server/tests/api/videos/video-playlists.ts37
-rw-r--r--server/tests/api/videos/video-privacy.ts26
-rw-r--r--server/tests/api/videos/video-transcoder.ts89
-rw-r--r--server/tests/api/videos/videos-views-cleaner.ts16
-rw-r--r--server/tests/cli/optimize-old-videos.ts10
-rw-r--r--server/tests/cli/peertube.ts155
-rw-r--r--server/tests/feeds/feeds.ts76
-rw-r--r--server/tests/fixtures/preview.jpgbin4215 -> 6868 bytes
-rw-r--r--server/tests/fixtures/sample.oggbin0 -> 105243 bytes
-rw-r--r--server/tests/fixtures/video_short1-preview.webm.jpgbin10181 -> 22654 bytes
-rw-r--r--server/tools/cli.ts122
-rw-r--r--server/tools/package.json14
-rw-r--r--server/tools/peertube-auth.ts17
-rw-r--r--server/tools/peertube-get-access-token.ts18
-rw-r--r--server/tools/peertube-import-videos.ts171
-rw-r--r--server/tools/peertube-repl.ts11
-rw-r--r--server/tools/peertube-upload.ts91
-rw-r--r--server/tools/peertube-watch.ts47
-rw-r--r--[-rwxr-xr-x]server/tools/peertube.ts7
-rw-r--r--server/tools/tsconfig.json4
-rw-r--r--server/tools/yarn.lock2063
-rw-r--r--shared/core-utils/miscs/date.ts67
-rw-r--r--shared/extra-utils/miscs/miscs.ts8
-rw-r--r--shared/extra-utils/miscs/sql.ts35
-rw-r--r--shared/extra-utils/search/videos.ts19
-rw-r--r--shared/extra-utils/server/config.ts4
-rw-r--r--shared/extra-utils/server/follows.ts26
-rw-r--r--shared/extra-utils/server/servers.ts4
-rw-r--r--shared/extra-utils/users/accounts.ts4
-rw-r--r--shared/extra-utils/users/user-notifications.ts2
-rw-r--r--shared/extra-utils/users/users.ts50
-rw-r--r--shared/extra-utils/videos/video-channels.ts37
-rw-r--r--shared/extra-utils/videos/video-playlists.ts4
-rw-r--r--shared/extra-utils/videos/videos.ts9
-rw-r--r--shared/models/activitypub/activitypub-actor.ts1
-rw-r--r--shared/models/actors/account.model.ts1
-rw-r--r--shared/models/actors/actor.model.ts1
-rw-r--r--shared/models/server/custom-config.model.ts2
-rw-r--r--shared/models/users/user-register.model.ts12
-rw-r--r--shared/models/users/user.model.ts1
-rw-r--r--shared/models/videos/channel/video-channel-update.model.ts4
-rw-r--r--shared/models/videos/channel/video-channel.model.ts1
-rw-r--r--shared/models/videos/playlist/video-playlist-update.model.ts4
-rw-r--r--shared/models/videos/video-resolution.enum.ts10
-rw-r--r--shared/models/videos/video.model.ts3
-rw-r--r--support/doc/api/openapi.yaml391
-rw-r--r--support/doc/api/quickstart.md2
-rw-r--r--support/doc/dependencies.md8
-rw-r--r--support/doc/development/client/code.md67
-rw-r--r--support/doc/development/client/components-tree.pngbin22104 -> 0 bytes
-rw-r--r--support/doc/development/client/components-tree.svg2
-rw-r--r--support/doc/development/client/components-tree.xml1
-rw-r--r--support/doc/development/server/code.md58
-rw-r--r--support/doc/development/server/peertube-architecture-server.xml1
-rw-r--r--support/doc/development/server/upload-video.pngbin34643 -> 0 bytes
-rw-r--r--support/doc/production.md3
-rw-r--r--support/doc/tools.md15
-rw-r--r--support/docker/production/.env3
-rw-r--r--support/docker/production/config/custom-environment-variables.yaml3
-rw-r--r--support/docker/production/docker-compose.yml7
-rw-r--r--tsconfig.json1
-rw-r--r--yarn.lock2883
334 files changed, 8777 insertions, 4762 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index b3847b8d7..bbf06c87f 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -9,8 +9,6 @@ Interested in contributing? Awesome!
9 * [Write documentation](#write-documentation) 9 * [Write documentation](#write-documentation)
10 * [Develop](#develop) 10 * [Develop](#develop)
11 * [Improve the website](#improve-the-website) 11 * [Improve the website](#improve-the-website)
12 * [Troubleshooting](#troubleshooting)
13 * [Tutorials](#tutorials)
14 12
15## Translate 13## Translate
16 14
@@ -61,10 +59,12 @@ Make sure that you have followed
61[the steps](/support/doc/dependencies.md) 59[the steps](/support/doc/dependencies.md)
62to install the dependencies. 60to install the dependencies.
63 61
64Then clone the sources and install node modules: 62Fork the github repository,
63and then clone the sources and install node modules:
65 64
66``` 65```
67$ git clone https://github.com/Chocobozzz/PeerTube 66$ git clone https://github.com/Chocobozzz/PeerTube
67$ git remote add me git@github.com:YOUR_GITHUB_USERNAME/PeerTube.git
68$ cd PeerTube 68$ cd PeerTube
69$ yarn install --pure-lockfile 69$ yarn install --pure-lockfile
70``` 70```
@@ -73,6 +73,12 @@ Note that development is done on the `develop` branch. If you want to hack on
73Peertube, you should switch to that branch. Also note that you have to repeat 73Peertube, you should switch to that branch. Also note that you have to repeat
74the `yarn install --pure-lockfile` command. 74the `yarn install --pure-lockfile` command.
75 75
76When you create a new branch you should also tell to use your repo for upload
77not default one. To do just do:
78```
79$ git push --set-upstream me <your branch name>
80```
81
76Then, create a postgres database and user with the values set in the 82Then, create a postgres database and user with the values set in the
77`config/default.yaml` file. For instance, if you do not change the values 83`config/default.yaml` file. For instance, if you do not change the values
78there, the following commands would create a new database called `peertube_dev` 84there, the following commands would create a new database called `peertube_dev`
@@ -101,7 +107,7 @@ You can get a complete PeerTube development setup with Gitpod, a free one-click
101 107
102### Server side 108### Server side
103 109
104You can find a documentation of the server code/architecture [here](/support/doc/development/server/code.md). 110You can find a documentation of the server code/architecture [here](https://docs.joinpeertube.org/#/contribute-architecture?id=server-code).
105 111
106To develop on the server-side: 112To develop on the server-side:
107 113
@@ -115,8 +121,8 @@ restart.
115 121
116### Client side 122### Client side
117 123
118You can find a documentation of the server code/architecture 124You can find a documentation of the client code/architecture
119[here](/support/doc/development/client/code.md). 125[here](https://docs.joinpeertube.org/#/contribute-architecture?id=client-code).
120 126
121 127
122To develop on the client side: 128To develop on the client side:
@@ -193,11 +199,3 @@ $ npm run mocha -- --exit --require ts-node/register/type-check --bail server/te
193 199
194Instance configurations are in `config/test-{1,2,3,4,5,6}.yaml`. 200Instance configurations are in `config/test-{1,2,3,4,5,6}.yaml`.
195Note that only instance 2 has transcoding enabled. 201Note that only instance 2 has transcoding enabled.
196
197### Troubleshooting
198
199Please check out the issues and [list of common errors](https://docs.joinpeertube.org/lang/en/devdocs/troubleshooting.html).
200
201### Tutorials
202
203Please check out the related section in the [development documentation](https://docs.joinpeertube.org/lang/en/devdocs/index.html#tutorials). Contribute tutorials at [framagit.org/framasoft/peertube/documentation](https://framagit.org/framasoft/peertube/documentation).
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..cece65761
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
custom: https://framasoft.org/en/#soutenir
diff --git a/.gitignore b/.gitignore
index 8cd04eea1..3a91facb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
1# NPM instalation 1# NPM instalation
2/node_modules/ 2/node_modules/
3/server/tools/node_modules
3*npm-debug.log 4*npm-debug.log
4 5
5# Testing 6# Testing
diff --git a/.travis.yml b/.travis.yml
index 5fa41fb43..8b3ec94d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -29,8 +29,8 @@ install:
29 - CC=gcc-4.9 CXX=g++-4.9 yarn install 29 - CC=gcc-4.9 CXX=g++-4.9 yarn install
30 30
31before_script: 31before_script:
32 - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.0.2-64bit-static.tar.xz" 32 - wget --no-check-certificate "https://download.cpy.re/ffmpeg/ffmpeg-release-4.0.3-64bit-static.tar.xz"
33 - tar xf ffmpeg-release-4.0.2-64bit-static.tar.xz 33 - tar xf ffmpeg-release-4.0.3-64bit-static.tar.xz
34 - mkdir -p $HOME/bin 34 - mkdir -p $HOME/bin
35 - cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin 35 - cp ffmpeg-*/{ffmpeg,ffprobe} $HOME/bin
36 - export PATH=$HOME/bin:$PATH 36 - export PATH=$HOME/bin:$PATH
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index f3254d2d6..000000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,45 +0,0 @@
1# Architecture
2
3## Vocabulary
4
5 - **Fediverse:** several servers following one another, several users
6 following each other. Designates federated communities in general.
7 - **Vidiverse:** same as Fediverse, but federating videos specifically.
8 - **Instance:** a server which runs PeerTube in the fediverse.
9 - **Origin instance:** the instance on which the video was uploaded and which
10 is seeding (through the WebSeed protocol) the video.
11 - **Cache instance:** an instance that decided to make available a WebSeed
12 of its own for a video originating from another instance. It sends a `ptCache`
13 activity to notify the origin instance, which will then update its list of
14 WebSeeds for the video.
15 - **Following:** the action of a PeerTube instance which will follow another
16 instance (subscribe to its videos).
17
18## Base
19
20### Communications
21 * All the communication between the instances are signed with [Linked Data
22 Signatures](https://w3c-dvcg.github.io/ld-signatures/) with the private key
23 of the account that authored the action.
24 * We use the [ActivityPub](https://www.w3.org/TR/activitypub/) protocol (only
25 server-server for now). Object models could be found in
26 [shared/models/activitypub
27 directory](/shared/models/activitypub).
28 * All the requests are retried several times if they fail.
29
30### Instance
31 * An instance has a websocket tracker which is responsible for all videos
32 uploaded by its users.
33 * An instance has an administrator that can follow other instances.
34 * An instance can be configured to follow back automatically.
35 * An instance can blacklist other instances (only used in "follow back"
36 mode).
37 * An instance cannot choose which other instances follow it, but it can
38 decide to **reject all** followers.
39 * After having uploaded a video, the instance seeds it (WebSeed protocol).
40 * If a user wants to watch a video, they ask its instance the magnet URI and
41 the frontend adds the torrent (with WebTorrent), creates the HTML5 video
42 player and streams the file into it.
43 * A user watching a video seeds it too (BitTorrent). Thus another user who is
44 watching the same video can get the data from the origin server and other
45 users watching it.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf8833833..3a8bf716f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -344,8 +344,8 @@ and update your [.env](https://github.com/Chocobozzz/PeerTube/blob/develop/suppo
344 344
345### Maintenance 345### Maintenance
346 346
347 * Improve REST API documentation: https://docs.joinpeertube.org/api.html ([@rigelk](https://github.com/rigelk)) 347 * Improve REST API documentation ([@rigelk](https://github.com/rigelk))
348 * Add basic ActivityPub documentation: https://docs.joinpeertube.org/lang/en/devdocs/federation.html ([@rigelk](https://github.com/rigelk)) 348 * Add basic ActivityPub documentation ([@rigelk](https://github.com/rigelk))
349 * Add CLI option to run PeerTube without client ([@rigelk](https://github.com/rigelk)) 349 * Add CLI option to run PeerTube without client ([@rigelk](https://github.com/rigelk))
350 * Add manpage to peertube CLI ([@rigelk](https://github.com/rigelk)) 350 * Add manpage to peertube CLI ([@rigelk](https://github.com/rigelk))
351 * Make backups of files in optimize-old-videos script ([@Nutomic](https://github.com/nutomic)) 351 * Make backups of files in optimize-old-videos script ([@Nutomic](https://github.com/nutomic))
@@ -430,8 +430,8 @@ and update your [.env](https://github.com/Chocobozzz/PeerTube/blob/develop/suppo
430 430
431### Maintenance 431### Maintenance
432 432
433 * Improve REST API documentation: https://docs.joinpeertube.org/api.html ([@rigelk](https://github.com/rigelk)) 433 * Improve REST API documentation ([@rigelk](https://github.com/rigelk))
434 * Add basic ActivityPub documentation: https://docs.joinpeertube.org/lang/en/devdocs/federation.html ([@rigelk](https://github.com/rigelk)) 434 * Add basic ActivityPub documentation ([@rigelk](https://github.com/rigelk))
435 * Add CLI option to run PeerTube without client ([@rigelk](https://github.com/rigelk)) 435 * Add CLI option to run PeerTube without client ([@rigelk](https://github.com/rigelk))
436 * Add manpage to peertube CLI ([@rigelk](https://github.com/rigelk)) 436 * Add manpage to peertube CLI ([@rigelk](https://github.com/rigelk))
437 * Make backups of files in optimize-old-videos script ([@Nutomic](https://github.com/nutomic)) 437 * Make backups of files in optimize-old-videos script ([@Nutomic](https://github.com/nutomic))
@@ -645,7 +645,7 @@ This release could contain bugs. Don't expect a stable v1.1.0 until December :)
645 645
646### Features 646### Features
647 647
648 * Video redundancy system (experimental, see [the doc](https://docs.joinpeertube.org/lang/en/devdocs/architecture.html#redundancy-between-instances)) 648 * Video redundancy system (experimental)
649 * Add peertube script (see [the doc](/support/doc/tools.md#cli-wrapper)) ([@rigelk](https://github.com/rigelk)) 649 * Add peertube script (see [the doc](/support/doc/tools.md#cli-wrapper)) ([@rigelk](https://github.com/rigelk))
650 * Improve download modal ([@rigelk](https://github.com/rigelk)) 650 * Improve download modal ([@rigelk](https://github.com/rigelk))
651 * Add redirect after login ([@BO41](https://github.com/BO41)) 651 * Add redirect after login ([@BO41](https://github.com/BO41))
diff --git a/FAQ.md b/FAQ.md
index 7d8be96a7..1a3b1847b 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -58,7 +58,7 @@ is named "Framatube".
58 58
59Yes, the origin server always seeds videos uploaded on it thanks to 59Yes, the origin server always seeds videos uploaded on it thanks to
60[Webseed](http://www.bittorrent.org/beps/bep_0019.html). 60[Webseed](http://www.bittorrent.org/beps/bep_0019.html).
61It can also be helped by other servers using [redundancy](https://docs.joinpeertube.org/lang/en/devdocs/architecture.html#redundancy-between-instances). 61It can also be helped by other servers using [redundancy](https://docs.joinpeertube.org/#/contribute-architecture?id=redundancy-between-instances).
62 62
63 63
64## What is WebSeed? 64## What is WebSeed?
diff --git a/README.md b/README.md
index a5060cf0d..3edaeef2f 100644
--- a/README.md
+++ b/README.md
@@ -115,7 +115,7 @@ Be it as a user or an instance administrator, you can decide what your experienc
115 115
116<h3 align="right">Communities that help each other</h3> 116<h3 align="right">Communities that help each other</h3>
117<p align="right"> 117<p align="right">
118In addition to visitors using WebTorrent to share the load among them, instances can help each other by caching one another's videos. This way even small instances have a way to show content to a wider audience, as they will be shouldered by friend instances (more about that in our <a href="https://docs.joinpeertube.org/lang/en/devdocs/architecture.html#redundancy-between-instances">redundancy guide</a>). 118In addition to visitors using WebTorrent to share the load among them, instances can help each other by caching one another's videos. This way even small instances have a way to show content to a wider audience, as they will be shouldered by friend instances (more about that in our <a href="https://docs.joinpeertube.org/#/contribute-architecture?id=redundancy-between-instances">redundancy guide</a>).
119</p> 119</p>
120<p align="right"> 120<p align="right">
121Content creators can get help from their viewers in the simplest way possible: a support button showing a message linking to their donation accounts or really anything else. No more pay-per-view and advertisements that hurt visitors and <strike>incentivize</strike> alter creativity (more about that in our <a href="./FAQ.md">FAQ</a>). 121Content creators can get help from their viewers in the simplest way possible: a support button showing a message linking to their donation accounts or really anything else. No more pay-per-view and advertisements that hurt visitors and <strike>incentivize</strike> alter creativity (more about that in our <a href="./FAQ.md">FAQ</a>).
@@ -151,9 +151,9 @@ Feel free to reach out if you have any questions or ideas! :speech_balloon:
151 * **yarn >= 1.x** 151 * **yarn >= 1.x**
152 * **FFmpeg >= 3.x** 152 * **FFmpeg >= 3.x**
153 153
154See the [production guide](/support/doc/production.md), which is the recommended way. 154See the [production guide](/support/doc/production.md), which is the recommended way to install or upgrade PeerTube. For hardware requirements, see [Should I have a big server to run PeerTube?](https://github.com/Chocobozzz/PeerTube/blob/develop/FAQ.md#should-i-have-a-big-server-to-run-peertube) in the FAQ.
155 155
156See the [community packages](https://docs.joinpeertube.org/lang/en/docs/install.html), which cover various platforms (including [YunoHost](https://install-app.yunohost.org/?app=peertube) and [Docker](/support/doc/docker.md)). 156See the [community packages](https://docs.joinpeertube.org/#/install-unofficial), which cover various platforms (including [YunoHost](https://install-app.yunohost.org/?app=peertube) and [Docker](/support/doc/docker.md)).
157 157
158:book: Documentation 158:book: Documentation
159---------------------------------------------------------------- 159----------------------------------------------------------------
@@ -162,13 +162,13 @@ If you have a question, please try to find the answer in the [FAQ](/FAQ.md) firs
162 162
163### User documentation 163### User documentation
164 164
165See the [user documentation](https://docs.joinpeertube.org/lang/en/userdocs/). 165See the [user documentation](https://docs.joinpeertube.org/#/use-setup-account).
166 166
167### Admin documentation 167### Admin documentation
168 168
169See [how to create your own instance](#package-create-your-own-instance). 169See [how to create your own instance](#package-create-your-own-instance).
170 170
171See the more general [admin documentation](https://docs.joinpeertube.org/lang/en/docs/). 171See the more general [admin documentation](https://docs.joinpeertube.org/#/admin-following-instances).
172 172
173#### Tools 173#### Tools
174 174
@@ -178,13 +178,13 @@ See the more general [admin documentation](https://docs.joinpeertube.org/lang/en
178 178
179### Technical documentation 179### Technical documentation
180 180
181See the [architecture blueprint](https://docs.joinpeertube.org/lang/en/devdocs/architecture.html) for a more detailed explanation of the architectural choices. 181See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-architecture) for a more detailed explanation of the architectural choices.
182 182
183See our REST API documentation: 183See our REST API documentation:
184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) 184 * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml)
185 * Spec explorer: [docs.joinpeertube.org/api.html](http://docs.joinpeertube.org/api.html) 185 * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html)
186 186
187See our [ActivityPub documentation](https://docs.joinpeertube.org/lang/en/devdocs/federation.html). 187See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub).
188 188
189:heart: Supports of our crowdfunding 189:heart: Supports of our crowdfunding
190---------------------------------------------------------------- 190----------------------------------------------------------------
diff --git a/client/e2e/src/po/my-account.ts b/client/e2e/src/po/my-account.ts
new file mode 100644
index 000000000..e49372983
--- /dev/null
+++ b/client/e2e/src/po/my-account.ts
@@ -0,0 +1,72 @@
1import { by, element } from 'protractor'
2
3export class MyAccountPage {
4
5 navigateToMyVideos () {
6 return element(by.css('a[href="/my-account/videos"]')).click()
7 }
8
9 navigateToMyPlaylists () {
10 return element(by.css('a[href="/my-account/video-playlists"]')).click()
11 }
12
13 navigateToMyHistory () {
14 return element(by.css('a[href="/my-account/history/videos"]')).click()
15 }
16
17 // My account Videos
18
19 getLastVideoName () {
20 return this.getAllVideoNameElements().first().getText()
21 }
22
23 removeLastVideo () {
24 return this.getLastVideoElement().element(by.css('my-delete-button')).click()
25 }
26
27 validRemove () {
28 return element(by.css('.action-button-submit')).click()
29 }
30
31 countVideos () {
32 return this.getAllVideoNameElements().count()
33 }
34
35 // My account playlists
36
37 getLastUpdatedPlaylistName () {
38 return this.getLastUpdatedPlaylist().element(by.css('.miniature-name')).getText()
39 }
40
41 getLastUpdatedPlaylistVideosText () {
42 return this.getLastUpdatedPlaylist().element(by.css('.miniature-playlist-info-overlay')).getText()
43 }
44
45 clickOnLastUpdatedPlaylist () {
46 return this.getLastUpdatedPlaylist().element(by.css('.miniature-thumbnail')).click()
47 }
48
49 countTotalPlaylistElements () {
50 return element.all(by.css('my-video-playlist-element-miniature')).count()
51 }
52
53 playPlaylist () {
54 return element(by.css('.playlist-info .miniature-thumbnail')).click()
55 }
56
57 // My account Videos
58
59 private getLastVideoElement () {
60 return element.all(by.css('.video')).first()
61 }
62
63 private getAllVideoNameElements () {
64 return element.all(by.css('.video-miniature-name'))
65 }
66
67 // My account playlists
68
69 private getLastUpdatedPlaylist () {
70 return element.all(by.css('my-video-playlist-miniature')).first()
71 }
72}
diff --git a/client/e2e/src/po/video-update.po.ts b/client/e2e/src/po/video-update.po.ts
new file mode 100644
index 000000000..4de3b1b1d
--- /dev/null
+++ b/client/e2e/src/po/video-update.po.ts
@@ -0,0 +1,20 @@
1import { by, element } from 'protractor'
2
3export class VideoUpdatePage {
4
5 async updateName (videoName: string) {
6 const nameInput = element(by.css('input#name'))
7 await nameInput.clear()
8 await nameInput.sendKeys(videoName)
9 }
10
11 async validUpdate () {
12 const submitButton = await this.getSubmitButton()
13
14 return submitButton.click()
15 }
16
17 private getSubmitButton () {
18 return element(by.css('.submit-button:not(.disabled) input'))
19 }
20}
diff --git a/client/e2e/src/po/video-watch.po.ts b/client/e2e/src/po/video-watch.po.ts
index 5f61d5668..9bb0a3919 100644
--- a/client/e2e/src/po/video-watch.po.ts
+++ b/client/e2e/src/po/video-watch.po.ts
@@ -1,4 +1,4 @@
1import { browser, by, element } from 'protractor' 1import { browser, by, element, ElementFinder, ExpectedConditions } from 'protractor'
2 2
3export class VideoWatchPage { 3export class VideoWatchPage {
4 async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) { 4 async goOnVideosList (isMobileDevice: boolean, isSafari: boolean) {
@@ -44,6 +44,10 @@ export class VideoWatchPage {
44 .then(seconds => parseInt(seconds, 10)) 44 .then(seconds => parseInt(seconds, 10))
45 } 45 }
46 46
47 getVideoName () {
48 return this.getVideoNameElement().getText()
49 }
50
47 async playAndPauseVideo (isAutoplay: boolean, isMobileDevice: boolean) { 51 async playAndPauseVideo (isAutoplay: boolean, isMobileDevice: boolean) {
48 if (isAutoplay === false) { 52 if (isAutoplay === false) {
49 const playButton = element(by.css('.vjs-big-play-button')) 53 const playButton = element(by.css('.vjs-big-play-button'))
@@ -101,4 +105,41 @@ export class VideoWatchPage {
101 async goOnP2PMediaLoaderEmbed () { 105 async goOnP2PMediaLoaderEmbed () {
102 return browser.get('https://peertube2.cpy.re/videos/embed/969bf103-7818-43b5-94a0-de159e13de50?mode=p2p-media-loader') 106 return browser.get('https://peertube2.cpy.re/videos/embed/969bf103-7818-43b5-94a0-de159e13de50?mode=p2p-media-loader')
103 } 107 }
108
109 async clickOnUpdate () {
110 const dropdown = element(by.css('my-video-actions-dropdown .action-button'))
111 await dropdown.click()
112
113 const items: ElementFinder[] = await element.all(by.css('my-video-actions-dropdown .dropdown-menu .dropdown-item'))
114
115 for (const item of items) {
116 const href = await item.getAttribute('href')
117
118 if (href && href.includes('/update/')) {
119 await item.click()
120 return
121 }
122 }
123 }
124
125 async clickOnSave () {
126 return element(by.css('.action-button-save')).click()
127 }
128
129 async saveToWatchLater () {
130 return element.all(by.css('my-video-add-to-playlist .playlist')).first().click()
131 }
132
133 waitUntilVideoName (name: string, maxTime: number) {
134 const elem = this.getVideoNameElement()
135
136 return browser.wait(ExpectedConditions.textToBePresentInElement(elem, name), maxTime)
137 }
138
139 private getVideoNameElement () {
140 // We have 2 video info name block, pick the first that is not empty
141 return element.all(by.css('.video-bottom .video-info-name'))
142 .filter(e => e.getText().then(t => !!t))
143 .first()
144 }
104} 145}
diff --git a/client/e2e/src/videos.e2e-spec.ts b/client/e2e/src/videos.e2e-spec.ts
index 25521cad9..c19ab3092 100644
--- a/client/e2e/src/videos.e2e-spec.ts
+++ b/client/e2e/src/videos.e2e-spec.ts
@@ -2,35 +2,56 @@ import { VideoWatchPage } from './po/video-watch.po'
2import { VideoUploadPage } from './po/video-upload.po' 2import { VideoUploadPage } from './po/video-upload.po'
3import { LoginPage } from './po/login.po' 3import { LoginPage } from './po/login.po'
4import { browser } from 'protractor' 4import { browser } from 'protractor'
5import { VideoUpdatePage } from './po/video-update.po'
6import { MyAccountPage } from './po/my-account'
7
8async function skipIfUploadNotSupported () {
9 if (await isMobileDevice() || await isSafari()) {
10 console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
11 return true
12 }
13
14 return false
15}
16
17async function isMobileDevice () {
18 const caps = await browser.getCapabilities()
19 return caps.get('realMobile') === 'true' || caps.get('realMobile') === true
20}
21
22async function isSafari () {
23 const caps = await browser.getCapabilities()
24 return caps.get('browserName') && caps.get('browserName').toLowerCase() === 'safari'
25}
5 26
6describe('Videos workflow', () => { 27describe('Videos workflow', () => {
7 let videoWatchPage: VideoWatchPage 28 let videoWatchPage: VideoWatchPage
8 let pageUploadPage: VideoUploadPage 29 let videoUploadPage: VideoUploadPage
30 let videoUpdatePage: VideoUpdatePage
31 let myAccountPage: MyAccountPage
9 let loginPage: LoginPage 32 let loginPage: LoginPage
33
10 const videoName = new Date().getTime() + ' video' 34 const videoName = new Date().getTime() + ' video'
11 let isMobileDevice = false 35 let videoWatchUrl: string
12 let isSafari = false
13 36
14 beforeEach(async () => { 37 beforeEach(async () => {
15 videoWatchPage = new VideoWatchPage() 38 videoWatchPage = new VideoWatchPage()
16 pageUploadPage = new VideoUploadPage() 39 videoUploadPage = new VideoUploadPage()
40 videoUpdatePage = new VideoUpdatePage()
41 myAccountPage = new MyAccountPage()
17 loginPage = new LoginPage() 42 loginPage = new LoginPage()
18 43
19 const caps = await browser.getCapabilities() 44 if (await isMobileDevice()) {
20 isMobileDevice = caps.get('realMobile') === 'true' || caps.get('realMobile') === true
21 isSafari = caps.get('browserName') && caps.get('browserName').toLowerCase() === 'safari'
22
23 if (isMobileDevice) {
24 console.log('Mobile device detected.') 45 console.log('Mobile device detected.')
25 } 46 }
26 47
27 if (isSafari) { 48 if (await isSafari()) {
28 console.log('Safari detected.') 49 console.log('Safari detected.')
29 } 50 }
30 }) 51 })
31 52
32 it('Should log in', () => { 53 it('Should log in', async () => {
33 if (isMobileDevice || isSafari) { 54 if (await isMobileDevice() || await isSafari()) {
34 console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.') 55 console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
35 return 56 return
36 } 57 }
@@ -39,24 +60,18 @@ describe('Videos workflow', () => {
39 }) 60 })
40 61
41 it('Should upload a video', async () => { 62 it('Should upload a video', async () => {
42 if (isMobileDevice || isSafari) { 63 if (await skipIfUploadNotSupported()) return
43 console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
44 return
45 }
46 64
47 await pageUploadPage.navigateTo() 65 await videoUploadPage.navigateTo()
48 66
49 await pageUploadPage.uploadVideo() 67 await videoUploadPage.uploadVideo()
50 return pageUploadPage.validSecondUploadStep(videoName) 68 return videoUploadPage.validSecondUploadStep(videoName)
51 }) 69 })
52 70
53 it('Should list videos', async () => { 71 it('Should list videos', async () => {
54 await videoWatchPage.goOnVideosList(isMobileDevice, isSafari) 72 await videoWatchPage.goOnVideosList(await isMobileDevice(), await isSafari())
55 73
56 if (isMobileDevice || isSafari) { 74 if (await skipIfUploadNotSupported()) return
57 console.log('Skipping because we are on a real device or Safari and BrowserStack does not support file upload.')
58 return
59 }
60 75
61 const videoNames = videoWatchPage.getVideosListName() 76 const videoNames = videoWatchPage.getVideosListName()
62 expect(videoNames).toContain(videoName) 77 expect(videoNames).toContain(videoName)
@@ -65,14 +80,16 @@ describe('Videos workflow', () => {
65 it('Should go on video watch page', async () => { 80 it('Should go on video watch page', async () => {
66 let videoNameToExcept = videoName 81 let videoNameToExcept = videoName
67 82
68 if (isMobileDevice || isSafari) videoNameToExcept = await videoWatchPage.clickOnFirstVideo() 83 if (await isMobileDevice() || await isSafari()) videoNameToExcept = await videoWatchPage.clickOnFirstVideo()
69 else await videoWatchPage.clickOnVideo(videoName) 84 else await videoWatchPage.clickOnVideo(videoName)
70 85
71 return videoWatchPage.waitWatchVideoName(videoNameToExcept, isMobileDevice, isSafari) 86 return videoWatchPage.waitWatchVideoName(videoNameToExcept, await isMobileDevice(), await isSafari())
72 }) 87 })
73 88
74 it('Should play the video', async () => { 89 it('Should play the video', async () => {
75 await videoWatchPage.playAndPauseVideo(true, isMobileDevice) 90 videoWatchUrl = await browser.getCurrentUrl()
91
92 await videoWatchPage.playAndPauseVideo(true, await isMobileDevice())
76 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) 93 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
77 }) 94 })
78 95
@@ -81,7 +98,7 @@ describe('Videos workflow', () => {
81 98
82 await videoWatchPage.goOnAssociatedEmbed() 99 await videoWatchPage.goOnAssociatedEmbed()
83 100
84 await videoWatchPage.playAndPauseVideo(false, isMobileDevice) 101 await videoWatchPage.playAndPauseVideo(false, await isMobileDevice())
85 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) 102 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
86 103
87 await browser.waitForAngularEnabled(true) 104 await browser.waitForAngularEnabled(true)
@@ -92,9 +109,93 @@ describe('Videos workflow', () => {
92 109
93 await videoWatchPage.goOnP2PMediaLoaderEmbed() 110 await videoWatchPage.goOnP2PMediaLoaderEmbed()
94 111
95 await videoWatchPage.playAndPauseVideo(false, isMobileDevice) 112 await videoWatchPage.playAndPauseVideo(false, await isMobileDevice())
96 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2) 113 expect(videoWatchPage.getWatchVideoPlayerCurrentTime()).toBeGreaterThanOrEqual(2)
97 114
98 await browser.waitForAngularEnabled(true) 115 await browser.waitForAngularEnabled(true)
99 }) 116 })
117
118 it('Should update the video', async () => {
119 if (await skipIfUploadNotSupported()) return
120
121 await browser.get(videoWatchUrl)
122
123 await videoWatchPage.clickOnUpdate()
124
125 await videoUpdatePage.updateName('my new name')
126
127 await videoUpdatePage.validUpdate()
128
129 const name = await videoWatchPage.getVideoName()
130 expect(name).toEqual('my new name')
131 })
132
133 it('Should add the video in my playlist', async () => {
134 if (await skipIfUploadNotSupported()) return
135
136 await videoWatchPage.clickOnSave()
137 await videoWatchPage.saveToWatchLater()
138
139 await videoUploadPage.navigateTo()
140
141 await videoUploadPage.uploadVideo()
142 await videoUploadPage.validSecondUploadStep('second video')
143
144 await videoWatchPage.clickOnSave()
145 await videoWatchPage.saveToWatchLater()
146 })
147
148 it('Should have the watch later playlist in my account', async () => {
149 if (await skipIfUploadNotSupported()) return
150
151 await myAccountPage.navigateToMyPlaylists()
152
153 const name = await myAccountPage.getLastUpdatedPlaylistName()
154 expect(name).toEqual('Watch later')
155
156 const videosNumberText = await myAccountPage.getLastUpdatedPlaylistVideosText()
157 expect(videosNumberText).toEqual('2 videos')
158
159 await myAccountPage.clickOnLastUpdatedPlaylist()
160
161 const count = await myAccountPage.countTotalPlaylistElements()
162 expect(count).toEqual(2)
163 })
164
165 it('Should watch the playlist', async () => {
166 if (await skipIfUploadNotSupported()) return
167
168 await myAccountPage.playPlaylist()
169
170 await videoWatchPage.waitUntilVideoName('second video', 20000 * 1000)
171 })
172
173 it('Should have the video in my account', async () => {
174 if (await skipIfUploadNotSupported()) return
175
176 await myAccountPage.navigateToMyVideos()
177
178 const lastVideoName = await myAccountPage.getLastVideoName()
179 expect(lastVideoName).toEqual('second video')
180 })
181
182 it('Should delete the last video', async () => {
183 if (await skipIfUploadNotSupported()) return
184
185 await myAccountPage.removeLastVideo()
186 await myAccountPage.validRemove()
187
188 const count = await myAccountPage.countVideos()
189 expect(count).toEqual(1)
190 })
191
192 it('Should delete the first video', async () => {
193 if (await skipIfUploadNotSupported()) return
194
195 await myAccountPage.removeLastVideo()
196 await myAccountPage.validRemove()
197
198 const count = await myAccountPage.countVideos()
199 expect(count).toEqual(0)
200 })
100}) 201})
diff --git a/client/proxy.config.json b/client/proxy.config.json
index e5f0dfd61..4a72f1826 100644
--- a/client/proxy.config.json
+++ b/client/proxy.config.json
@@ -8,7 +8,8 @@
8 "secure": false 8 "secure": false
9 }, 9 },
10 "/socket.io": { 10 "/socket.io": {
11 "target": "http://localhost:9000", 11 "target": "ws://localhost:9000",
12 "secure": false 12 "secure": false,
13 "ws": true
13 } 14 }
14} 15}
diff --git a/client/src/app/+about/about-follows/about-follows.component.html b/client/src/app/+about/about-follows/about-follows.component.html
new file mode 100644
index 000000000..18689bbf7
--- /dev/null
+++ b/client/src/app/+about/about-follows/about-follows.component.html
@@ -0,0 +1,22 @@
1<div class="row" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
2 <div class="col-xl-6 col-md-12">
3 <div i18n class="subtitle">Followers</div>
4
5 <div i18n class="no-results" *ngIf="followersPagination.totalItems === 0">This instance does not have followers.</div>
6
7 <a *ngFor="let follower of followers" [href]="buildLink(follower)" target="_blank" rel="noopener noreferrer">
8 {{ follower }}
9 </a>
10 </div>
11
12 <div class="col-xl-6 col-md-12">
13 <div i18n class="subtitle">Followings</div>
14
15 <div i18n class="no-results" *ngIf="followingsPagination.totalItems === 0">This instance does not have followings.</div>
16
17 <a *ngFor="let following of followings" [href]="buildLink(following)" target="_blank" rel="noopener noreferrer">
18 {{ following }}
19 </a>
20 </div>
21
22</div>
diff --git a/client/src/app/+about/about-follows/about-follows.component.scss b/client/src/app/+about/about-follows/about-follows.component.scss
new file mode 100644
index 000000000..e0d597a96
--- /dev/null
+++ b/client/src/app/+about/about-follows/about-follows.component.scss
@@ -0,0 +1,14 @@
1@import '_variables';
2@import '_mixins';
3
4.subtitle {
5 font-size: 18px;
6 font-weight: $font-semibold;
7 margin-bottom: 20px;
8}
9
10a {
11 display: block;
12 width: fit-content;
13 margin-top: 3px;
14}
diff --git a/client/src/app/+about/about-follows/about-follows.component.ts b/client/src/app/+about/about-follows/about-follows.component.ts
new file mode 100644
index 000000000..f0e1375d6
--- /dev/null
+++ b/client/src/app/+about/about-follows/about-follows.component.ts
@@ -0,0 +1,103 @@
1import { Component, OnInit } from '@angular/core'
2import { FollowService } from '@app/shared/instance/follow.service'
3import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
4import { Notifier } from '@app/core'
5import { RestService } from '@app/shared'
6import { SortMeta } from 'primeng/api'
7
8@Component({
9 selector: 'my-about-follows',
10 templateUrl: './about-follows.component.html',
11 styleUrls: [ './about-follows.component.scss' ]
12})
13
14export class AboutFollowsComponent implements OnInit {
15 followers: string[] = []
16 followings: string[] = []
17
18 followersPagination: ComponentPagination = {
19 currentPage: 1,
20 itemsPerPage: 40,
21 totalItems: null
22 }
23
24 followingsPagination: ComponentPagination = {
25 currentPage: 1,
26 itemsPerPage: 40,
27 totalItems: null
28 }
29
30 sort: SortMeta = {
31 field: 'createdAt',
32 order: -1
33 }
34
35 constructor (
36 private restService: RestService,
37 private notifier: Notifier,
38 private followService: FollowService
39 ) { }
40
41 ngOnInit () {
42 this.loadMoreFollowers()
43
44 this.loadMoreFollowings()
45 }
46
47 onNearOfBottom () {
48 this.onNearOfFollowersBottom()
49
50 this.onNearOfFollowingsBottom()
51 }
52
53 onNearOfFollowersBottom () {
54 if (!hasMoreItems(this.followersPagination)) return
55
56 this.followersPagination.currentPage += 1
57 this.loadMoreFollowers()
58 }
59
60 onNearOfFollowingsBottom () {
61 if (!hasMoreItems(this.followingsPagination)) return
62
63 this.followingsPagination.currentPage += 1
64 this.loadMoreFollowings()
65 }
66
67 buildLink (host: string) {
68 return window.location.protocol + '//' + host
69 }
70
71 private loadMoreFollowers () {
72 const pagination = this.restService.componentPaginationToRestPagination(this.followersPagination)
73
74 this.followService.getFollowers(pagination, this.sort)
75 .subscribe(
76 resultList => {
77 const newFollowers = resultList.data.map(r => r.follower.host)
78 this.followers = this.followers.concat(newFollowers)
79
80 this.followersPagination.totalItems = resultList.total
81 },
82
83 err => this.notifier.error(err.message)
84 )
85 }
86
87 private loadMoreFollowings () {
88 const pagination = this.restService.componentPaginationToRestPagination(this.followingsPagination)
89
90 this.followService.getFollowing(pagination, this.sort)
91 .subscribe(
92 resultList => {
93 const newFollowings = resultList.data.map(r => r.following.host)
94 this.followings = this.followings.concat(newFollowings)
95
96 this.followingsPagination.totalItems = resultList.total
97 },
98
99 err => this.notifier.error(err.message)
100 )
101 }
102
103}
diff --git a/client/src/app/+about/about-routing.module.ts b/client/src/app/+about/about-routing.module.ts
index c83c62c7f..33e5070cb 100644
--- a/client/src/app/+about/about-routing.module.ts
+++ b/client/src/app/+about/about-routing.module.ts
@@ -4,6 +4,7 @@ import { MetaGuard } from '@ngx-meta/core'
4import { AboutComponent } from './about.component' 4import { 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'
7 8
8const aboutRoutes: Routes = [ 9const aboutRoutes: Routes = [
9 { 10 {
@@ -33,6 +34,15 @@ const aboutRoutes: Routes = [
33 title: 'About PeerTube' 34 title: 'About PeerTube'
34 } 35 }
35 } 36 }
37 },
38 {
39 path: 'follows',
40 component: AboutFollowsComponent,
41 data: {
42 meta: {
43 title: 'About follows'
44 }
45 }
36 } 46 }
37 ] 47 ]
38 } 48 }
diff --git a/client/src/app/+about/about.component.html b/client/src/app/+about/about.component.html
index 8c50835c1..0c4a5156d 100644
--- a/client/src/app/+about/about.component.html
+++ b/client/src/app/+about/about.component.html
@@ -5,10 +5,12 @@
5 <a i18n routerLink="instance" routerLinkActive="active" class="title-page">Instance</a> 5 <a i18n routerLink="instance" routerLinkActive="active" class="title-page">Instance</a>
6 6
7 <a i18n routerLink="peertube" routerLinkActive="active" class="title-page">PeerTube</a> 7 <a i18n routerLink="peertube" routerLinkActive="active" class="title-page">PeerTube</a>
8
9 <a i18n routerLink="follows" routerLinkActive="active" class="title-page">Follows</a>
8 </div> 10 </div>
9 </div> 11 </div>
10 12
11 <div class="margin-content"> 13 <div class="margin-content">
12 <router-outlet></router-outlet> 14 <router-outlet></router-outlet>
13 </div> 15 </div>
14</div> \ No newline at end of file 16</div>
diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts
index 9c6b29740..49a7a52f8 100644
--- a/client/src/app/+about/about.module.ts
+++ b/client/src/app/+about/about.module.ts
@@ -6,6 +6,7 @@ import { SharedModule } from '../shared'
6import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' 6import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
7import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' 7import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
8import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' 8import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
9import { AboutFollowsComponent } from '@app/+about/about-follows/about-follows.component'
9 10
10@NgModule({ 11@NgModule({
11 imports: [ 12 imports: [
@@ -17,6 +18,7 @@ import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-a
17 AboutComponent, 18 AboutComponent,
18 AboutInstanceComponent, 19 AboutInstanceComponent,
19 AboutPeertubeComponent, 20 AboutPeertubeComponent,
21 AboutFollowsComponent,
20 ContactAdminModalComponent 22 ContactAdminModalComponent
21 ], 23 ],
22 24
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
index c3ef1d894..e9c8179b7 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.html
@@ -1,11 +1,25 @@
1<div *ngIf="account" class="row"> 1<div class="margin-content">
2 <a 2
3 *ngFor="let videoChannel of videoChannels" [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" 3 <div class="no-results" i18n *ngIf="channelPagination.totalItems === 0">This account does not have channels.</div>
4 class="video-channel" i18n-title title="See this video channel" 4
5 > 5 <div class="channels" myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true">
6 <img [src]="videoChannel.avatarUrl" alt="Avatar" /> 6 <div class="section channel" *ngFor="let videoChannel of videoChannels">
7 7 <div class="section-title">
8 <div class="video-channel-display-name">{{ videoChannel.displayName }}</div> 8 <a [routerLink]="[ '/video-channels', videoChannel.nameWithHost ]" i18n-title title="See this video channel">
9 <div i18n class="video-channel-followers">{{ videoChannel.followersCount }} subscribers</div> 9 <img [src]="videoChannel.avatarUrl" alt="Avatar" />
10 </a> 10
11</div> \ No newline at end of file 11 <div>{{ videoChannel.displayName }}</div>
12 <div i18n class="followers">{{ videoChannel.followersCount }} subscribers</div>
13 </a>
14
15 <my-subscribe-button [videoChannel]="videoChannel"></my-subscribe-button>
16 </div>
17
18 <div *ngIf="getVideosOf(videoChannel)" class="videos">
19 <div class="no-results" i18n *ngIf="getVideosOf(videoChannel).length === 0">This channel does not have videos.</div>
20
21 <my-video-miniature *ngFor="let video of getVideosOf(videoChannel)" [video]="video" [user]="user" [displayVideoActions]="false"></my-video-miniature>
22 </div>
23 </div>
24 </div>
25</div>
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
index 0c6de2efa..98931f0c2 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.scss
@@ -1,30 +1,28 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3@import '_miniature';
3 4
4.row { 5.margin-content {
5 justify-content: center; 6 @include adapt-margin-content-width;
6} 7}
7 8
8a.video-channel { 9.section {
9 @include disable-default-a-behaviour; 10 @include miniature-rows;
10 11
11 display: inline-block; 12 overflow: visible; // For the subscribe dropdown
12 text-align: center; 13 padding-top: 0 !important;
13 color: var(--mainForegroundColor);
14 margin: 10px 30px;
15 14
16 img { 15 .section-title {
17 @include avatar(80px); 16 align-items: center;
18
19 margin-bottom: 10px;
20 } 17 }
21 18
22 .video-channel-display-name { 19 .videos {
23 font-size: 20px; 20 overflow: hidden;
24 font-weight: $font-bold;
25 }
26 21
27 .video-channel-followers { 22 .no-results {
28 font-size: 15px; 23 height: 50px;
24 }
29 } 25 }
30} \ No newline at end of file 26}
27
28
diff --git a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
index 44f5626bb..a8d4237e8 100644
--- a/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
+++ b/client/src/app/+accounts/account-video-channels/account-video-channels.component.ts
@@ -3,9 +3,14 @@ import { ActivatedRoute } from '@angular/router'
3import { Account } from '@app/shared/account/account.model' 3import { Account } from '@app/shared/account/account.model'
4import { AccountService } from '@app/shared/account/account.service' 4import { AccountService } from '@app/shared/account/account.service'
5import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' 5import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
6import { flatMap, map, tap } from 'rxjs/operators' 6import { concatMap, map, switchMap, tap } from 'rxjs/operators'
7import { Subscription } from 'rxjs' 7import { from, Subscription } from 'rxjs'
8import { VideoChannel } from '@app/shared/video-channel/video-channel.model' 8import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
9import { Video } from '@app/shared/video/video.model'
10import { AuthService } from '@app/core'
11import { VideoService } from '@app/shared/video/video.service'
12import { VideoSortField } from '@app/shared/video/sort-field.type'
13import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
9 14
10@Component({ 15@Component({
11 selector: 'my-account-video-channels', 16 selector: 'my-account-video-channels',
@@ -15,27 +20,73 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
15export class AccountVideoChannelsComponent implements OnInit, OnDestroy { 20export class AccountVideoChannelsComponent implements OnInit, OnDestroy {
16 account: Account 21 account: Account
17 videoChannels: VideoChannel[] = [] 22 videoChannels: VideoChannel[] = []
23 videos: { [id: number]: Video[] } = {}
24
25 channelPagination: ComponentPagination = {
26 currentPage: 1,
27 itemsPerPage: 2
28 }
29
30 videosPagination: ComponentPagination = {
31 currentPage: 1,
32 itemsPerPage: 12
33 }
34 videosSort: VideoSortField = '-publishedAt'
18 35
19 private accountSub: Subscription 36 private accountSub: Subscription
20 37
21 constructor ( 38 constructor (
22 protected route: ActivatedRoute, 39 private route: ActivatedRoute,
40 private authService: AuthService,
23 private accountService: AccountService, 41 private accountService: AccountService,
24 private videoChannelService: VideoChannelService 42 private videoChannelService: VideoChannelService,
43 private videoService: VideoService
25 ) { } 44 ) { }
26 45
46 get user () {
47 return this.authService.getUser()
48 }
49
27 ngOnInit () { 50 ngOnInit () {
28 // Parent get the account for us 51 // Parent get the account for us
29 this.accountSub = this.accountService.accountLoaded 52 this.accountSub = this.accountService.accountLoaded
30 .pipe( 53 .subscribe(account => {
31 tap(account => this.account = account), 54 this.account = account
32 flatMap(account => this.videoChannelService.listAccountVideoChannels(account)), 55
33 map(res => res.data) 56 this.loadMoreChannels()
34 ) 57 })
35 .subscribe(videoChannels => this.videoChannels = videoChannels)
36 } 58 }
37 59
38 ngOnDestroy () { 60 ngOnDestroy () {
39 if (this.accountSub) this.accountSub.unsubscribe() 61 if (this.accountSub) this.accountSub.unsubscribe()
40 } 62 }
63
64 loadMoreChannels () {
65 this.videoChannelService.listAccountVideoChannels(this.account, this.channelPagination)
66 .pipe(
67 tap(res => this.channelPagination.totalItems = res.total),
68 switchMap(res => from(res.data)),
69 concatMap(videoChannel => {
70 return this.videoService.getVideoChannelVideos(videoChannel, this.videosPagination, this.videosSort)
71 .pipe(map(data => ({ videoChannel, videos: data.videos })))
72 })
73 )
74 .subscribe(({ videoChannel, videos }) => {
75 this.videoChannels.push(videoChannel)
76
77 this.videos[videoChannel.id] = videos
78 })
79 }
80
81 getVideosOf (videoChannel: VideoChannel) {
82 return this.videos[ videoChannel.id ]
83 }
84
85 onNearOfBottom () {
86 if (!hasMoreItems(this.channelPagination)) return
87
88 this.channelPagination.currentPage += 1
89
90 this.loadMoreChannels()
91 }
41} 92}
diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts
index 0d579fa0c..5a99aadce 100644
--- a/client/src/app/+accounts/account-videos/account-videos.component.ts
+++ b/client/src/app/+accounts/account-videos/account-videos.component.ts
@@ -29,6 +29,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
29 private accountSub: Subscription 29 private accountSub: Subscription
30 30
31 constructor ( 31 constructor (
32 protected i18n: I18n,
32 protected router: Router, 33 protected router: Router,
33 protected serverService: ServerService, 34 protected serverService: ServerService,
34 protected route: ActivatedRoute, 35 protected route: ActivatedRoute,
@@ -36,13 +37,10 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
36 protected notifier: Notifier, 37 protected notifier: Notifier,
37 protected confirmService: ConfirmService, 38 protected confirmService: ConfirmService,
38 protected screenService: ScreenService, 39 protected screenService: ScreenService,
39 private i18n: I18n,
40 private accountService: AccountService, 40 private accountService: AccountService,
41 private videoService: VideoService 41 private videoService: VideoService
42 ) { 42 ) {
43 super() 43 super()
44
45 this.titlePage = this.i18n('Published videos')
46 } 44 }
47 45
48 ngOnInit () { 46 ngOnInit () {
diff --git a/client/src/app/+accounts/accounts-routing.module.ts b/client/src/app/+accounts/accounts-routing.module.ts
index 531d763c4..45b24eb55 100644
--- a/client/src/app/+accounts/accounts-routing.module.ts
+++ b/client/src/app/+accounts/accounts-routing.module.ts
@@ -8,13 +8,17 @@ import { AccountVideoChannelsComponent } from './account-video-channels/account-
8 8
9const accountsRoutes: Routes = [ 9const accountsRoutes: Routes = [
10 { 10 {
11 path: 'peertube',
12 redirectTo: '/videos/local'
13 },
14 {
11 path: ':accountId', 15 path: ':accountId',
12 component: AccountsComponent, 16 component: AccountsComponent,
13 canActivateChild: [ MetaGuard ], 17 canActivateChild: [ MetaGuard ],
14 children: [ 18 children: [
15 { 19 {
16 path: '', 20 path: '',
17 redirectTo: 'videos', 21 redirectTo: 'video-channels',
18 pathMatch: 'full' 22 pathMatch: 'full'
19 }, 23 },
20 { 24 {
diff --git a/client/src/app/+accounts/accounts.component.html b/client/src/app/+accounts/accounts.component.html
index c1377c1ea..038e18c4b 100644
--- a/client/src/app/+accounts/accounts.component.html
+++ b/client/src/app/+accounts/accounts.component.html
@@ -26,10 +26,10 @@
26 </div> 26 </div>
27 27
28 <div class="links"> 28 <div class="links">
29 <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
30
31 <a i18n routerLink="video-channels" routerLinkActive="active" class="title-page">Video channels</a> 29 <a i18n routerLink="video-channels" routerLinkActive="active" class="title-page">Video channels</a>
32 30
31 <a i18n routerLink="videos" routerLinkActive="active" class="title-page">Videos</a>
32
33 <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a> 33 <a i18n routerLink="about" routerLinkActive="active" class="title-page">About</a>
34 </div> 34 </div>
35 </div> 35 </div>
diff --git a/client/src/app/+admin/admin.module.ts b/client/src/app/+admin/admin.module.ts
index 71a4dfc4a..9ab883f60 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, FollowService } from './follows' 8import { FollowersListComponent, FollowingAddComponent, FollowsComponent } 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 {
@@ -66,7 +66,6 @@ import { DebugComponent, DebugService } from '@app/+admin/system/debug'
66 ], 66 ],
67 67
68 providers: [ 68 providers: [
69 FollowService,
70 RedundancyService, 69 RedundancyService,
71 JobService, 70 JobService,
72 LogsService, 71 LogsService,
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 637484622..d5b625d9c 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
@@ -287,6 +287,14 @@
287 </div> 287 </div>
288 288
289 <div class="form-group"> 289 <div class="form-group">
290 <my-peertube-checkbox
291 inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles"
292 i18n-labelText labelText="Allow audio files upload"
293 i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload"
294 ></my-peertube-checkbox>
295 </div>
296
297 <div class="form-group">
290 <label i18n for="transcodingThreads">Transcoding threads</label> 298 <label i18n for="transcodingThreads">Transcoding threads</label>
291 <div class="peertube-select-container"> 299 <div class="peertube-select-container">
292 <select id="transcodingThreads" formControlName="threads"> 300 <select id="transcodingThreads" formControlName="threads">
@@ -301,8 +309,8 @@
301 <ng-container formGroupName="resolutions"> 309 <ng-container formGroupName="resolutions">
302 <div class="form-group" *ngFor="let resolution of resolutions"> 310 <div class="form-group" *ngFor="let resolution of resolutions">
303 <my-peertube-checkbox 311 <my-peertube-checkbox
304 [inputName]="getResolutionKey(resolution)" [formControlName]="resolution" 312 [inputName]="getResolutionKey(resolution.id)" [formControlName]="resolution.id"
305 i18n-labelText labelText="Resolution {{resolution}} enabled" 313 i18n-labelText labelText="Resolution {{resolution.label}} enabled"
306 ></my-peertube-checkbox> 314 ></my-peertube-checkbox>
307 </div> 315 </div>
308 </ng-container> 316 </ng-container>
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
index e64750713..055bae851 100644
--- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
+++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
@@ -15,7 +15,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
15export class EditCustomConfigComponent extends FormReactive implements OnInit { 15export class EditCustomConfigComponent extends FormReactive implements OnInit {
16 customConfig: CustomConfig 16 customConfig: CustomConfig
17 17
18 resolutions: string[] = [] 18 resolutions: { id: string, label: string }[] = []
19 transcodingThreadOptions: { label: string, value: number }[] = [] 19 transcodingThreadOptions: { label: string, value: number }[] = []
20 20
21 constructor ( 21 constructor (
@@ -30,11 +30,30 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
30 super() 30 super()
31 31
32 this.resolutions = [ 32 this.resolutions = [
33 this.i18n('240p'), 33 {
34 this.i18n('360p'), 34 id: '240p',
35 this.i18n('480p'), 35 label: this.i18n('240p')
36 this.i18n('720p'), 36 },
37 this.i18n('1080p') 37 {
38 id: '360p',
39 label: this.i18n('360p')
40 },
41 {
42 id: '480p',
43 label: this.i18n('480p')
44 },
45 {
46 id: '720p',
47 label: this.i18n('720p')
48 },
49 {
50 id: '1080p',
51 label: this.i18n('1080p')
52 },
53 {
54 id: '2160p',
55 label: this.i18n('2160p')
56 }
38 ] 57 ]
39 58
40 this.transcodingThreadOptions = [ 59 this.transcodingThreadOptions = [
@@ -116,6 +135,7 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
116 enabled: null, 135 enabled: null,
117 threads: this.customConfigValidatorsService.TRANSCODING_THREADS, 136 threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
118 allowAdditionalExtensions: null, 137 allowAdditionalExtensions: null,
138 allowAudioFiles: null,
119 resolutions: {} 139 resolutions: {}
120 }, 140 },
121 autoBlacklist: { 141 autoBlacklist: {
@@ -139,8 +159,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
139 } 159 }
140 } 160 }
141 for (const resolution of this.resolutions) { 161 for (const resolution of this.resolutions) {
142 defaultValues.transcoding.resolutions[resolution] = 'false' 162 defaultValues.transcoding.resolutions[resolution.id] = 'false'
143 formGroupData.transcoding.resolutions[resolution] = null 163 formGroupData.transcoding.resolutions[resolution.id] = null
144 } 164 }
145 165
146 this.buildForm(formGroupData) 166 this.buildForm(formGroupData)
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
index b78cdf656..e25d9ab66 100644
--- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts
+++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts
@@ -3,7 +3,7 @@ import { ConfirmService, Notifier } from '@app/core'
3import { SortMeta } from 'primeng/primeng' 3import { SortMeta } from 'primeng/primeng'
4import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' 4import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
5import { RestPagination, RestTable } from '../../../shared' 5import { RestPagination, RestTable } from '../../../shared'
6import { FollowService } from '../shared' 6import { FollowService } from '@app/shared/instance/follow.service'
7import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8 8
9@Component({ 9@Component({
diff --git a/client/src/app/+admin/follows/following-add/following-add.component.ts b/client/src/app/+admin/follows/following-add/following-add.component.ts
index 2bb249746..308bbb0c5 100644
--- a/client/src/app/+admin/follows/following-add/following-add.component.ts
+++ b/client/src/app/+admin/follows/following-add/following-add.component.ts
@@ -3,7 +3,7 @@ import { Router } from '@angular/router'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { ConfirmService } from '../../../core' 4import { ConfirmService } from '../../../core'
5import { validateHost } from '../../../shared' 5import { validateHost } from '../../../shared'
6import { FollowService } from '../shared' 6import { FollowService } from '@app/shared/instance/follow.service'
7import { I18n } from '@ngx-translate/i18n-polyfill' 7import { I18n } from '@ngx-translate/i18n-polyfill'
8 8
9@Component({ 9@Component({
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts
index 4517a721e..ded616624 100644
--- a/client/src/app/+admin/follows/following-list/following-list.component.ts
+++ b/client/src/app/+admin/follows/following-list/following-list.component.ts
@@ -4,7 +4,7 @@ import { SortMeta } from 'primeng/primeng'
4import { ActorFollow } from '../../../../../../shared/models/actors/follow.model' 4import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
5import { ConfirmService } from '../../../core/confirm/confirm.service' 5import { ConfirmService } from '../../../core/confirm/confirm.service'
6import { RestPagination, RestTable } from '../../../shared' 6import { RestPagination, RestTable } from '../../../shared'
7import { FollowService } from '../shared' 7import { FollowService } from '@app/shared/instance/follow.service'
8import { I18n } from '@ngx-translate/i18n-polyfill' 8import { I18n } from '@ngx-translate/i18n-polyfill'
9 9
10@Component({ 10@Component({
diff --git a/client/src/app/+admin/follows/index.ts b/client/src/app/+admin/follows/index.ts
index 7849a06e7..e94f33710 100644
--- a/client/src/app/+admin/follows/index.ts
+++ b/client/src/app/+admin/follows/index.ts
@@ -1,6 +1,5 @@
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 './shared'
5export * from './follows.component' 4export * from './follows.component'
6export * from './follows.routes' 5export * from './follows.routes'
diff --git a/client/src/app/+admin/follows/shared/index.ts b/client/src/app/+admin/follows/shared/index.ts
deleted file mode 100644
index 78d456def..000000000
--- a/client/src/app/+admin/follows/shared/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './follow.service'
diff --git a/client/src/app/+admin/users/user-edit/user-edit.ts b/client/src/app/+admin/users/user-edit/user-edit.ts
index adce1b2d4..ee6d2c489 100644
--- a/client/src/app/+admin/users/user-edit/user-edit.ts
+++ b/client/src/app/+admin/users/user-edit/user-edit.ts
@@ -7,7 +7,8 @@ import { UserAdminFlag } from '@shared/models/users/user-flag.model'
7export abstract class UserEdit extends FormReactive { 7export abstract class UserEdit extends FormReactive {
8 videoQuotaOptions: { value: string, label: string }[] = [] 8 videoQuotaOptions: { value: string, label: string }[] = []
9 videoQuotaDailyOptions: { value: string, label: string }[] = [] 9 videoQuotaDailyOptions: { value: string, label: string }[] = []
10 roles = Object.keys(USER_ROLE_LABELS).map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] })) 10 roles = Object.keys(USER_ROLE_LABELS)
11 .map(key => ({ value: key.toString(), label: USER_ROLE_LABELS[key] }))
11 username: string 12 username: string
12 userId: number 13 userId: number
13 14
@@ -27,7 +28,7 @@ export abstract class UserEdit extends FormReactive {
27 const transcodingConfig = this.serverService.getConfig().transcoding 28 const transcodingConfig = this.serverService.getConfig().transcoding
28 29
29 const resolutions = transcodingConfig.enabledResolutions 30 const resolutions = transcodingConfig.enabledResolutions
30 const higherResolution = VideoResolution.H_1080P 31 const higherResolution = VideoResolution.H_4K
31 let multiplier = 0 32 let multiplier = 0
32 33
33 for (const resolution of resolutions) { 34 for (const resolution of resolutions) {
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
index 73340d21a..13607119e 100644
--- a/client/src/app/+my-account/my-account-history/my-account-history.component.ts
+++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
@@ -27,6 +27,7 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
27 videosHistoryEnabled: boolean 27 videosHistoryEnabled: boolean
28 28
29 constructor ( 29 constructor (
30 protected i18n: I18n,
30 protected router: Router, 31 protected router: Router,
31 protected serverService: ServerService, 32 protected serverService: ServerService,
32 protected route: ActivatedRoute, 33 protected route: ActivatedRoute,
@@ -34,7 +35,6 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
34 protected userService: UserService, 35 protected userService: UserService,
35 protected notifier: Notifier, 36 protected notifier: Notifier,
36 protected screenService: ScreenService, 37 protected screenService: ScreenService,
37 protected i18n: I18n,
38 private confirmService: ConfirmService, 38 private confirmService: ConfirmService,
39 private videoService: VideoService, 39 private videoService: VideoService,
40 private userHistoryService: UserHistoryService 40 private userHistoryService: UserHistoryService
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts
new file mode 100644
index 000000000..f42af361e
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts
@@ -0,0 +1 @@
export * from './my-account-change-email.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
new file mode 100644
index 000000000..5492cdf22
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html
@@ -0,0 +1,36 @@
1<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
2<div *ngIf="success" class="alert alert-success">{{ success }}</div>
3
4<div i18n class="current-email">
5 Your current email is <span class="email">{{ user.email }}</span>
6</div>
7
8<div i18n class="pending-email" *ngIf="user.pendingEmail">
9 <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification
10</div>
11
12<form role="form" class="change-email" (ngSubmit)="changeEmail()" [formGroup]="form">
13
14 <div class="form-group">
15 <label i18n for="new-email">New email</label>
16 <input
17 type="email" id="new-email" i18n-placeholder placeholder="Your new email"
18 formControlName="new-email" [ngClass]="{ 'input-error': formErrors['new-email'] }"
19 >
20 <div *ngIf="formErrors['new-email']" class="form-error">
21 {{ formErrors['new-email'] }}
22 </div>
23 </div>
24
25 <div class="form-group">
26 <input
27 type="password" id="password" i18n-placeholder placeholder="Your password"
28 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
29 >
30 <div *ngIf="formErrors['password']" class="form-error">
31 {{ formErrors['password'] }}
32 </div>
33 </div>
34
35 <input type="submit" i18n-value value="Change email" [disabled]="!form.valid">
36</form>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss
new file mode 100644
index 000000000..81eba3ec9
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss
@@ -0,0 +1,24 @@
1@import '_variables';
2@import '_mixins';
3
4input[type=password],
5input[type=email] {
6 @include peertube-input-text(340px);
7
8 display: block;
9}
10
11input[type=submit] {
12 @include peertube-button;
13 @include orange-button;
14}
15
16.current-email,
17.pending-email {
18 font-size: 16px;
19 margin: 15px 0;
20
21 .email {
22 font-weight: $font-semibold;
23 }
24}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
new file mode 100644
index 000000000..ec7cf935c
--- /dev/null
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts
@@ -0,0 +1,73 @@
1import { Component, OnInit } from '@angular/core'
2import { AuthService, Notifier, ServerService } from '@app/core'
3import { FormReactive, UserService } from '../../../shared'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
6import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
7import { User } from '../../../../../../shared'
8import { tap } from 'rxjs/operators'
9
10@Component({
11 selector: 'my-account-change-email',
12 templateUrl: './my-account-change-email.component.html',
13 styleUrls: [ './my-account-change-email.component.scss' ]
14})
15export class MyAccountChangeEmailComponent extends FormReactive implements OnInit {
16 error: string = null
17 success: string = null
18 user: User = null
19
20 constructor (
21 protected formValidatorService: FormValidatorService,
22 private userValidatorsService: UserValidatorsService,
23 private notifier: Notifier,
24 private authService: AuthService,
25 private userService: UserService,
26 private serverService: ServerService,
27 private i18n: I18n
28 ) {
29 super()
30 }
31
32 ngOnInit () {
33 this.buildForm({
34 'new-email': this.userValidatorsService.USER_EMAIL,
35 'password': this.userValidatorsService.USER_PASSWORD
36 })
37
38 this.user = this.authService.getUser()
39 }
40
41 changeEmail () {
42 this.error = null
43 this.success = null
44
45 const password = this.form.value[ 'password' ]
46 const email = this.form.value[ 'new-email' ]
47
48 this.userService.changeEmail(password, email)
49 .pipe(
50 tap(() => this.authService.refreshUserInformation())
51 )
52 .subscribe(
53 () => {
54 this.form.reset()
55
56 if (this.serverService.getConfig().signup.requiresEmailVerification) {
57 this.success = this.i18n('Please check your emails to verify your new email.')
58 } else {
59 this.success = this.i18n('Email updated.')
60 }
61 },
62
63 err => {
64 if (err.status === 401) {
65 this.error = this.i18n('You current password is invalid.')
66 return
67 }
68
69 this.error = err.message
70 }
71 )
72 }
73}
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
index ae797d1bc..a39061ee3 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html
@@ -2,7 +2,7 @@
2 2
3<form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> 3<form role="form" (ngSubmit)="changePassword()" [formGroup]="form">
4 4
5 <label i18n for="new-password">Change password</label> 5 <label i18n for="current-password">Change password</label>
6 <input 6 <input
7 type="password" id="current-password" i18n-placeholder placeholder="Current password" 7 type="password" id="current-password" i18n-placeholder placeholder="Current password"
8 formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }" 8 formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }"
diff --git a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
index a9503ed1b..fcad5a6c2 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
+++ b/client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
@@ -30,7 +30,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
30 30
31 ngOnInit () { 31 ngOnInit () {
32 this.buildForm({ 32 this.buildForm({
33 'display-name': this.userValidatorsService.USER_DISPLAY_NAME, 33 'display-name': this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
34 description: this.userValidatorsService.USER_DESCRIPTION 34 description: this.userValidatorsService.USER_DESCRIPTION
35 }) 35 })
36 36
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
index ad64f28fe..f93d41110 100644
--- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
+++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html
@@ -13,6 +13,9 @@
13<div i18n class="account-title">Password</div> 13<div i18n class="account-title">Password</div>
14<my-account-change-password></my-account-change-password> 14<my-account-change-password></my-account-change-password>
15 15
16<div i18n class="account-title">Email</div>
17<my-account-change-email></my-account-change-email>
18
16<div i18n class="account-title">Video settings</div> 19<div i18n class="account-title">Video settings</div>
17<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> 20<my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
18 21
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
index 81fb11f45..f87df87df 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html
@@ -61,5 +61,12 @@ When you will upload a video in this channel, the video support field will be au
61 </div> 61 </div>
62 </div> 62 </div>
63 63
64 <div class="form-group" *ngIf="isBulkUpdateVideosDisplayed()">
65 <my-peertube-checkbox
66 inputName="bulkVideosSupportUpdate" formControlName="bulkVideosSupportUpdate"
67 i18n-labelText labelText="Overwrite support field of all videos of this channel"
68 ></my-peertube-checkbox>
69 </div>
70
64 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid"> 71 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
65</form> 72</form>
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 4dc65dd99..7479442d1 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
@@ -11,4 +11,9 @@ export abstract class MyAccountVideoChannelEdit extends FormReactive {
11 11
12 // FIXME: We need this method so angular does not complain in the child template 12 // FIXME: We need this method so angular does not complain in the child template
13 onAvatarChange (formData: FormData) { /* empty */ } 13 onAvatarChange (formData: FormData) { /* empty */ }
14
15 // Should be implemented by the child
16 isBulkUpdateVideosDisplayed () {
17 return false
18 }
14} 19}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
index da4fb645a..081e956d2 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
@@ -20,6 +20,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
20 videoChannelToUpdate: VideoChannel 20 videoChannelToUpdate: VideoChannel
21 21
22 private paramsSub: Subscription 22 private paramsSub: Subscription
23 private oldSupportField: string
23 24
24 constructor ( 25 constructor (
25 protected formValidatorService: FormValidatorService, 26 protected formValidatorService: FormValidatorService,
@@ -39,7 +40,8 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
39 this.buildForm({ 40 this.buildForm({
40 'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME, 41 'display-name': this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME,
41 description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION, 42 description: this.videoChannelValidatorsService.VIDEO_CHANNEL_DESCRIPTION,
42 support: this.videoChannelValidatorsService.VIDEO_CHANNEL_SUPPORT 43 support: this.videoChannelValidatorsService.VIDEO_CHANNEL_SUPPORT,
44 bulkVideosSupportUpdate: null
43 }) 45 })
44 46
45 this.paramsSub = this.route.params.subscribe(routeParams => { 47 this.paramsSub = this.route.params.subscribe(routeParams => {
@@ -49,6 +51,8 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
49 videoChannelToUpdate => { 51 videoChannelToUpdate => {
50 this.videoChannelToUpdate = videoChannelToUpdate 52 this.videoChannelToUpdate = videoChannelToUpdate
51 53
54 this.oldSupportField = videoChannelToUpdate.support
55
52 this.form.patchValue({ 56 this.form.patchValue({
53 'display-name': videoChannelToUpdate.displayName, 57 'display-name': videoChannelToUpdate.displayName,
54 description: videoChannelToUpdate.description, 58 description: videoChannelToUpdate.description,
@@ -72,7 +76,8 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
72 const videoChannelUpdate: VideoChannelUpdate = { 76 const videoChannelUpdate: VideoChannelUpdate = {
73 displayName: body['display-name'], 77 displayName: body['display-name'],
74 description: body.description || null, 78 description: body.description || null,
75 support: body.support || null 79 support: body.support || null,
80 bulkVideosSupportUpdate: body.bulkVideosSupportUpdate || false
76 } 81 }
77 82
78 this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe( 83 this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe(
@@ -118,4 +123,10 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
118 getFormButtonTitle () { 123 getFormButtonTitle () {
119 return this.i18n('Update') 124 return this.i18n('Update')
120 } 125 }
126
127 isBulkUpdateVideosDisplayed () {
128 if (this.oldSupportField === undefined) return false
129
130 return this.oldSupportField !== this.form.value['support']
131 }
121} 132}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
index 87a10961f..8aed8b513 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
@@ -7,7 +7,6 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
7import { VideoPlaylistValidatorsService } from '@app/shared' 7import { VideoPlaylistValidatorsService } from '@app/shared'
8import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' 8import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
10import { VideoConstant } from '@shared/models'
11import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' 10import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
12import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' 11import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
13 12
@@ -18,7 +17,6 @@ import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
18}) 17})
19export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylistEdit implements OnInit { 18export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylistEdit implements OnInit {
20 error: string 19 error: string
21 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
22 20
23 constructor ( 21 constructor (
24 protected formValidatorService: FormValidatorService, 22 protected formValidatorService: FormValidatorService,
@@ -47,6 +45,7 @@ export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylis
47 }) 45 })
48 46
49 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) 47 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
48 .catch(err => console.error('Cannot populate user video channels.', err))
50 49
51 this.serverService.videoPlaylistPrivaciesLoaded.subscribe( 50 this.serverService.videoPlaylistPrivaciesLoaded.subscribe(
52 () => { 51 () => {
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
index 303fc46f7..82321459f 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
@@ -57,10 +57,12 @@
57 </div> 57 </div>
58 58
59 <div class="form-group"> 59 <div class="form-group">
60 <my-image-upload 60 <label i18n>Playlist thumbnail</label>
61 i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile" 61
62 previewWidth="200px" previewHeight="110px" 62 <my-preview-upload
63 ></my-image-upload> 63 i18n-inputLabel inputLabel="Edit" inputName="thumbnailfile" formControlName="thumbnailfile"
64 previewWidth="223px" previewHeight="122px"
65 ></my-preview-upload>
64 </div> 66 </div>
65 </div> 67 </div>
66 </div> 68 </div>
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
index fbfb4c8f7..e94188786 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
@@ -1,12 +1,12 @@
1import { FormReactive } from '@app/shared' 1import { FormReactive } from '@app/shared'
2import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
3import { ServerService } from '@app/core'
4import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' 2import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model'
3import { VideoConstant, VideoPlaylistPrivacy } from '@shared/models'
5 4
6export abstract class MyAccountVideoPlaylistEdit extends FormReactive { 5export abstract class MyAccountVideoPlaylistEdit extends FormReactive {
7 // Declare it here to avoid errors in create template 6 // Declare it here to avoid errors in create template
8 videoPlaylistToUpdate: VideoPlaylist 7 videoPlaylistToUpdate: VideoPlaylist
9 userVideoChannels: { id: number, label: string }[] = [] 8 userVideoChannels: { id: number, label: string }[] = []
9 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
10 10
11 abstract isCreation (): boolean 11 abstract isCreation (): boolean
12 abstract getFormButtonTitle (): string 12 abstract getFormButtonTitle (): string
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
index 4887fdfb4..917ad7258 100644
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
@@ -9,9 +9,8 @@ import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
10import { VideoPlaylistValidatorsService } from '@app/shared' 10import { VideoPlaylistValidatorsService } from '@app/shared'
11import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' 11import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
12import { VideoConstant } from '@shared/models'
13import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
14import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 12import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
13import { delayWhen, map, switchMap } from 'rxjs/operators'
15 14
16@Component({ 15@Component({
17 selector: 'my-account-video-playlist-update', 16 selector: 'my-account-video-playlist-update',
@@ -21,7 +20,6 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
21export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylistEdit implements OnInit, OnDestroy { 20export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylistEdit implements OnInit, OnDestroy {
22 error: string 21 error: string
23 videoPlaylistToUpdate: VideoPlaylist 22 videoPlaylistToUpdate: VideoPlaylist
24 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
25 23
26 private paramsSub: Subscription 24 private paramsSub: Subscription
27 25
@@ -53,31 +51,24 @@ export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylis
53 }) 51 })
54 52
55 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) 53 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
56 54 .catch(err => console.error('Cannot populate user video channels.', err))
57 this.paramsSub = this.route.params.subscribe(routeParams => { 55
58 const videoPlaylistId = routeParams['videoPlaylistId'] 56 this.paramsSub = this.route.params
59 57 .pipe(
60 this.videoPlaylistService.getVideoPlaylist(videoPlaylistId).subscribe( 58 map(routeParams => routeParams['videoPlaylistId']),
61 videoPlaylistToUpdate => { 59 switchMap(videoPlaylistId => this.videoPlaylistService.getVideoPlaylist(videoPlaylistId)),
62 this.videoPlaylistToUpdate = videoPlaylistToUpdate 60 delayWhen(() => this.serverService.videoPlaylistPrivaciesLoaded)
63 61 )
64 this.hydrateFormFromPlaylist() 62 .subscribe(
65 63 videoPlaylistToUpdate => {
66 this.serverService.videoPlaylistPrivaciesLoaded.subscribe( 64 this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies()
67 () => { 65 this.videoPlaylistToUpdate = videoPlaylistToUpdate
68 this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies() 66
69 .filter(p => { 67 this.hydrateFormFromPlaylist()
70 // If the playlist is not private, we cannot put it in private anymore 68 },
71 return this.videoPlaylistToUpdate.privacy.id === VideoPlaylistPrivacy.PRIVATE || 69
72 p.id !== VideoPlaylistPrivacy.PRIVATE 70 err => this.error = err.message
73 }) 71 )
74 }
75 )
76 },
77
78 err => this.error = err.message
79 )
80 })
81 } 72 }
82 73
83 ngOnDestroy () { 74 ngOnDestroy () {
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
index 84d464800..2854093c4 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
@@ -20,7 +20,7 @@
20 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button> 20 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
21 21
22 <my-button i18n-label label="Change ownership" 22 <my-button i18n-label label="Change ownership"
23 className="action-button-change-ownership" 23 className="action-button-change-ownership grey-button"
24 icon="im-with-her" 24 icon="im-with-her"
25 (click)="changeOwnership($event, video)" 25 (click)="changeOwnership($event, video)"
26 ></my-button> 26 ></my-button>
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index 4a18a9968..ca5b1f7cb 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -36,6 +36,7 @@ import {
36 MyAccountVideoPlaylistElementsComponent 36 MyAccountVideoPlaylistElementsComponent
37} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' 37} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
38import { DragDropModule } from '@angular/cdk/drag-drop' 38import { DragDropModule } from '@angular/cdk/drag-drop'
39import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email'
39 40
40@NgModule({ 41@NgModule({
41 imports: [ 42 imports: [
@@ -54,7 +55,10 @@ import { DragDropModule } from '@angular/cdk/drag-drop'
54 MyAccountChangePasswordComponent, 55 MyAccountChangePasswordComponent,
55 MyAccountVideoSettingsComponent, 56 MyAccountVideoSettingsComponent,
56 MyAccountProfileComponent, 57 MyAccountProfileComponent,
58 MyAccountChangeEmailComponent,
59
57 MyAccountVideosComponent, 60 MyAccountVideosComponent,
61
58 VideoChangeOwnershipComponent, 62 VideoChangeOwnershipComponent,
59 MyAccountOwnershipComponent, 63 MyAccountOwnershipComponent,
60 MyAccountAcceptOwnershipComponent, 64 MyAccountAcceptOwnershipComponent,
diff --git a/client/src/app/+signup/+register/custom-stepper.component.html b/client/src/app/+signup/+register/custom-stepper.component.html
new file mode 100644
index 000000000..bf507fc4f
--- /dev/null
+++ b/client/src/app/+signup/+register/custom-stepper.component.html
@@ -0,0 +1,25 @@
1<section class="container">
2 <header>
3 <ng-container *ngFor="let step of steps; let i = index; let isLast = last;">
4 <div
5 class="step-info" [ngClass]="{ active: selectedIndex === i, completed: isCompleted(step) }"
6 (click)="onClick(i)"
7 >
8 <div class="step-index">
9 <ng-container *ngIf="!isCompleted(step)">{{ i + 1 }}</ng-container>
10 <my-global-icon *ngIf="isCompleted(step)" iconName="tick"></my-global-icon>
11 </div>
12
13 <div class="step-label">{{ step.label }}</div>
14 </div>
15
16 <!-- Do no display if this is the last child -->
17 <div *ngIf="!isLast" class="connector"></div>
18 </ng-container>
19 </header>
20
21 <div [style.display]="selected ? 'block' : 'none'">
22 <ng-container [ngTemplateOutlet]="selected.content"></ng-container>
23 </div>
24
25</section>
diff --git a/client/src/app/+signup/+register/custom-stepper.component.scss b/client/src/app/+signup/+register/custom-stepper.component.scss
new file mode 100644
index 000000000..2371c8ae5
--- /dev/null
+++ b/client/src/app/+signup/+register/custom-stepper.component.scss
@@ -0,0 +1,66 @@
1@import '_variables';
2@import '_mixins';
3
4$grey-color: #9CA3AB;
5$index-block-height: 32px;
6
7header {
8 display: flex;
9 justify-content: space-between;
10 font-size: 15px;
11 margin-bottom: 30px;
12
13 .step-info {
14 color: $grey-color;
15 display: flex;
16 flex-direction: column;
17 align-items: center;
18 width: $index-block-height;
19
20 .step-index {
21 display: flex;
22 justify-content: center;
23 align-items: center;
24 width: $index-block-height;
25 height: $index-block-height;
26 border-radius: 100px;
27 border: 2px solid $grey-color;
28 margin-bottom: 10px;
29
30 my-global-icon {
31 @include apply-svg-color(var(--mainBackgroundColor));
32
33 width: 22px;
34 height: 22px;
35 }
36 }
37
38 .step-label {
39 width: max-content;
40 }
41
42 &.active,
43 &.completed {
44 .step-index {
45 border-color: var(--mainColor);
46 background-color: var(--mainColor);
47 color: var(--mainBackgroundColor);
48 }
49
50 .step-label {
51 color: var(--mainColor);
52 }
53 }
54
55 &.completed {
56 cursor: pointer;
57 }
58 }
59
60 .connector {
61 flex: auto;
62 margin: $index-block-height/2 10px 0 10px;
63 height: 2px;
64 background-color: $grey-color;
65 }
66}
diff --git a/client/src/app/+signup/+register/custom-stepper.component.ts b/client/src/app/+signup/+register/custom-stepper.component.ts
new file mode 100644
index 000000000..2ae40f3a9
--- /dev/null
+++ b/client/src/app/+signup/+register/custom-stepper.component.ts
@@ -0,0 +1,19 @@
1import { Component } from '@angular/core'
2import { CdkStep, CdkStepper } from '@angular/cdk/stepper'
3
4@Component({
5 selector: 'my-custom-stepper',
6 templateUrl: './custom-stepper.component.html',
7 styleUrls: [ './custom-stepper.component.scss' ],
8 providers: [ { provide: CdkStepper, useExisting: CustomStepperComponent } ]
9})
10export class CustomStepperComponent extends CdkStepper {
11
12 onClick (index: number): void {
13 this.selectedIndex = index
14 }
15
16 isCompleted (step: CdkStep) {
17 return step.stepControl && step.stepControl.dirty && step.stepControl.valid
18 }
19}
diff --git a/client/src/app/signup/signup-routing.module.ts b/client/src/app/+signup/+register/register-routing.module.ts
index 820d16d4d..e3a5001dc 100644
--- a/client/src/app/signup/signup-routing.module.ts
+++ b/client/src/app/+signup/+register/register-routing.module.ts
@@ -1,17 +1,18 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouterModule, Routes } from '@angular/router'
3import { MetaGuard } from '@ngx-meta/core' 3import { MetaGuard } from '@ngx-meta/core'
4import { SignupComponent } from './signup.component' 4import { RegisterComponent } from './register.component'
5import { ServerConfigResolver } from '@app/core/routing/server-config-resolver.service' 5import { ServerConfigResolver } from '@app/core/routing/server-config-resolver.service'
6import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
6 7
7const signupRoutes: Routes = [ 8const registerRoutes: Routes = [
8 { 9 {
9 path: 'signup', 10 path: '',
10 component: SignupComponent, 11 component: RegisterComponent,
11 canActivate: [ MetaGuard ], 12 canActivate: [ MetaGuard, UnloggedGuard ],
12 data: { 13 data: {
13 meta: { 14 meta: {
14 title: 'Signup' 15 title: 'Register'
15 } 16 }
16 }, 17 },
17 resolve: { 18 resolve: {
@@ -21,7 +22,7 @@ const signupRoutes: Routes = [
21] 22]
22 23
23@NgModule({ 24@NgModule({
24 imports: [ RouterModule.forChild(signupRoutes) ], 25 imports: [ RouterModule.forChild(registerRoutes) ],
25 exports: [ RouterModule ] 26 exports: [ RouterModule ]
26}) 27})
27export class SignupRoutingModule {} 28export class RegisterRoutingModule {}
diff --git a/client/src/app/+signup/+register/register-step-channel.component.html b/client/src/app/+signup/+register/register-step-channel.component.html
new file mode 100644
index 000000000..253374f87
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-channel.component.html
@@ -0,0 +1,54 @@
1<form role="form" [formGroup]="form">
2
3 <div class="channel-explanations">
4 <p i18n>
5 A channel is an entity in which you upload your videos. Creating several of them helps you to organize and separate your content.<br />
6 For example, you could decide to have a channel to publish your piano concerts, and another channel in which you publish your videos talking about ecology.
7 </p>
8
9 <p>
10 Other users can decide to subscribe any channel they want, to be notified when you publish a new video.
11 </p>
12 </div>
13
14 <div class="form-group">
15 <label for="displayName" i18n>Channel display name</label>
16
17 <div class="input-group">
18 <input
19 type="text" id="displayName"
20 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
21 >
22 </div>
23
24 <div *ngIf="formErrors.displayName" class="form-error">
25 {{ formErrors.displayName }}
26 </div>
27 </div>
28
29 <div class="form-group">
30 <label for="name" i18n>Channel name</label>
31
32 <div class="input-group">
33 <input
34 type="text" id="name" i18n-placeholder placeholder="Example: my_super_channel"
35 formControlName="name" [ngClass]="{ 'input-error': formErrors['name'] }"
36 >
37 <div class="input-group-append">
38 <span class="input-group-text">@{{ instanceHost }}</span>
39 </div>
40 </div>
41
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.
44 </div>
45
46 <div *ngIf="formErrors.name" class="form-error">
47 {{ formErrors.name }}
48 </div>
49
50 <div *ngIf="isSameThanUsername()" class="form-error" i18n>
51 Channel name cannot be the same than your account name. You can click on the first step to update your account name.
52 </div>
53 </div>
54</form>
diff --git a/client/src/app/+signup/+register/register-step-channel.component.ts b/client/src/app/+signup/+register/register-step-channel.component.ts
new file mode 100644
index 000000000..e434b91a7
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-channel.component.ts
@@ -0,0 +1,56 @@
1import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
2import { AuthService } from '@app/core'
3import { FormReactive, UserService, VideoChannelValidatorsService } from '@app/shared'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { FormGroup } from '@angular/forms'
6import { pairwise } from 'rxjs/operators'
7import { concat, of } from 'rxjs'
8
9@Component({
10 selector: 'my-register-step-channel',
11 templateUrl: './register-step-channel.component.html',
12 styleUrls: [ './register.component.scss' ]
13})
14export class RegisterStepChannelComponent extends FormReactive implements OnInit {
15 @Input() username: string
16 @Output() formBuilt = new EventEmitter<FormGroup>()
17
18 constructor (
19 protected formValidatorService: FormValidatorService,
20 private authService: AuthService,
21 private userService: UserService,
22 private videoChannelValidatorsService: VideoChannelValidatorsService
23 ) {
24 super()
25 }
26
27 get instanceHost () {
28 return window.location.host
29 }
30
31 ngOnInit () {
32 this.buildForm({
33 displayName: this.videoChannelValidatorsService.VIDEO_CHANNEL_DISPLAY_NAME,
34 name: this.videoChannelValidatorsService.VIDEO_CHANNEL_NAME
35 })
36
37 setTimeout(() => this.formBuilt.emit(this.form))
38
39 concat(
40 of(''),
41 this.form.get('displayName').valueChanges
42 ).pipe(pairwise())
43 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
44 }
45
46 isSameThanUsername () {
47 return this.username && this.username === this.form.value['name']
48 }
49
50 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
51 const name = this.form.value['name'] || ''
52
53 const newName = this.userService.getNewUsername(oldDisplayName, newDisplayName, name)
54 this.form.patchValue({ name: newName })
55 }
56}
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html
new file mode 100644
index 000000000..47b3be8cc
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-user.component.html
@@ -0,0 +1,73 @@
1<form role="form" [formGroup]="form">
2
3 <div class="form-group">
4 <label for="displayName" i18n>Display name</label>
5
6 <div class="input-group">
7 <input
8 type="text" id="displayName" placeholder="John Doe"
9 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
10 >
11 </div>
12
13 <div *ngIf="formErrors.displayName" class="form-error">
14 {{ formErrors.displayName }}
15 </div>
16 </div>
17
18 <div class="form-group">
19 <label for="username" i18n>Username</label>
20
21 <div class="input-group">
22 <input
23 type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
24 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
25 >
26 <div class="input-group-append">
27 <span class="input-group-text">@{{ instanceHost }}</span>
28 </div>
29 </div>
30
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.
33 </div>
34
35 <div *ngIf="formErrors.username" class="form-error">
36 {{ formErrors.username }}
37 </div>
38 </div>
39
40 <div class="form-group">
41 <label for="email" i18n>Email</label>
42 <input
43 type="text" id="email" i18n-placeholder placeholder="Email"
44 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
45 >
46 <div *ngIf="formErrors.email" class="form-error">
47 {{ formErrors.email }}
48 </div>
49 </div>
50
51 <div class="form-group">
52 <label for="password" i18n>Password</label>
53 <input
54 type="password" id="password" i18n-placeholder placeholder="Password"
55 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
56 >
57 <div *ngIf="formErrors.password" class="form-error">
58 {{ formErrors.password }}
59 </div>
60 </div>
61
62 <div class="form-group form-group-terms">
63 <my-peertube-checkbox
64 inputName="terms" formControlName="terms"
65 i18n-labelHtml
66 labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance"
67 ></my-peertube-checkbox>
68
69 <div *ngIf="formErrors.terms" class="form-error">
70 {{ formErrors.terms }}
71 </div>
72 </div>
73</form>
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts
new file mode 100644
index 000000000..3b71fd3c4
--- /dev/null
+++ b/client/src/app/+signup/+register/register-step-user.component.ts
@@ -0,0 +1,54 @@
1import { Component, EventEmitter, OnInit, Output } from '@angular/core'
2import { AuthService } from '@app/core'
3import { FormReactive, UserService, UserValidatorsService } from '@app/shared'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { FormGroup } from '@angular/forms'
6import { pairwise } from 'rxjs/operators'
7import { concat, of } from 'rxjs'
8
9@Component({
10 selector: 'my-register-step-user',
11 templateUrl: './register-step-user.component.html',
12 styleUrls: [ './register.component.scss' ]
13})
14export class RegisterStepUserComponent extends FormReactive implements OnInit {
15 @Output() formBuilt = new EventEmitter<FormGroup>()
16
17 constructor (
18 protected formValidatorService: FormValidatorService,
19 private authService: AuthService,
20 private userService: UserService,
21 private userValidatorsService: UserValidatorsService
22 ) {
23 super()
24 }
25
26 get instanceHost () {
27 return window.location.host
28 }
29
30 ngOnInit () {
31 this.buildForm({
32 displayName: this.userValidatorsService.USER_DISPLAY_NAME_REQUIRED,
33 username: this.userValidatorsService.USER_USERNAME,
34 password: this.userValidatorsService.USER_PASSWORD,
35 email: this.userValidatorsService.USER_EMAIL,
36 terms: this.userValidatorsService.USER_TERMS
37 })
38
39 setTimeout(() => this.formBuilt.emit(this.form))
40
41 concat(
42 of(''),
43 this.form.get('displayName').valueChanges
44 ).pipe(pairwise())
45 .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue))
46 }
47
48 private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) {
49 const username = this.form.value['username'] || ''
50
51 const newUsername = this.userService.getNewUsername(oldDisplayName, newDisplayName, username)
52 this.form.patchValue({ username: newUsername })
53 }
54}
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html
new file mode 100644
index 000000000..d7e47c1a8
--- /dev/null
+++ b/client/src/app/+signup/+register/register.component.html
@@ -0,0 +1,47 @@
1<div class="margin-content">
2
3 <div i18n class="title-page title-page-single">
4 Create an account
5 </div>
6
7 <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success>
8 <div *ngIf="info" class="alert alert-info">{{ info }}</div>
9
10 <div class="wrapper" *ngIf="!signupDone">
11 <div>
12 <my-custom-stepper linear *ngIf="!signupDone">
13 <cdk-step [stepControl]="formStepUser" i18n-label label="User information">
14 <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user>
15
16 <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button>
17 </cdk-step>
18
19 <cdk-step [stepControl]="formStepChannel" i18n-label label="Channel information">
20 <my-register-step-channel (formBuilt)="onChannelFormBuilt($event)" [username]="getUsername()"></my-register-step-channel>
21
22 <button i18n cdkStepperNext (click)="signup()"
23 [disabled]="!formStepChannel || !formStepChannel.valid || hasSameChannelAndAccountNames()"
24 >
25 Create my account
26 </button>
27 </cdk-step>
28
29 <cdk-step i18n-label label="Done" editable="false">
30 <div *ngIf="!signupDone && !error" class="done-loader">
31 <my-loader [loading]="true"></my-loader>
32
33 <div i18n>PeerTube is creating your account...</div>
34 </div>
35
36 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
37 </cdk-step>
38 </my-custom-stepper>
39 </div>
40
41 <div>
42 <label i18n>Features found on this instance</label>
43 <my-instance-features-table></my-instance-features-table>
44 </div>
45 </div>
46
47</div>
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss
new file mode 100644
index 000000000..8d14992e7
--- /dev/null
+++ b/client/src/app/+signup/+register/register.component.scss
@@ -0,0 +1,81 @@
1@import '_variables';
2@import '_mixins';
3
4.alert {
5 font-size: 15px;
6 text-align: center;
7}
8
9.wrapper {
10 display: flex;
11 justify-content: space-between;
12 flex-wrap: wrap;
13
14 & > div {
15 margin-bottom: 40px;
16 width: 450px;
17
18 @media screen and (max-width: 500px) {
19 width: auto;
20 }
21 }
22}
23
24my-instance-features-table {
25 display: block;
26
27 margin-bottom: 40px;
28}
29
30.form-group-terms {
31 margin: 30px 0;
32}
33
34.input-group {
35 @include peertube-input-group(400px);
36}
37
38.input-group-append {
39 height: 30px;
40}
41
42input:not([type=submit]) {
43 @include peertube-input-text(400px);
44
45 display: block;
46
47 &#username,
48 &#name {
49 width: auto !important;
50 flex-grow: 1;
51 }
52}
53
54input[type=submit],
55button {
56 @include peertube-button;
57 @include orange-button;
58}
59
60.name-information {
61 margin-top: 10px;
62}
63
64.done-loader {
65 display: flex;
66 justify-content: center;
67 flex-direction: column;
68 align-items: center;
69
70 my-loader {
71 margin-bottom: 20px;
72
73 /deep/ .loader div {
74 border-color: var(--mainColor) transparent transparent transparent;
75 }
76
77 & + div {
78 font-size: 15px;
79 }
80 }
81}
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts
new file mode 100644
index 000000000..cd6059728
--- /dev/null
+++ b/client/src/app/+signup/+register/register.component.ts
@@ -0,0 +1,89 @@
1import { Component } from '@angular/core'
2import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
3import { UserService, UserValidatorsService } from '@app/shared'
4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { UserRegister } from '@shared/models/users/user-register.model'
6import { FormGroup } from '@angular/forms'
7
8@Component({
9 selector: 'my-register',
10 templateUrl: './register.component.html',
11 styleUrls: [ './register.component.scss' ]
12})
13export class RegisterComponent {
14 info: string = null
15 error: string = null
16 success: string = null
17 signupDone = false
18
19 formStepUser: FormGroup
20 formStepChannel: FormGroup
21
22 constructor (
23 private authService: AuthService,
24 private userValidatorsService: UserValidatorsService,
25 private notifier: Notifier,
26 private userService: UserService,
27 private serverService: ServerService,
28 private redirectService: RedirectService,
29 private i18n: I18n
30 ) {
31 }
32
33 get requiresEmailVerification () {
34 return this.serverService.getConfig().signup.requiresEmailVerification
35 }
36
37 hasSameChannelAndAccountNames () {
38 return this.getUsername() === this.getChannelName()
39 }
40
41 getUsername () {
42 if (!this.formStepUser) return undefined
43
44 return this.formStepUser.value['username']
45 }
46
47 getChannelName () {
48 if (!this.formStepChannel) return undefined
49
50 return this.formStepChannel.value['name']
51 }
52
53 onUserFormBuilt (form: FormGroup) {
54 this.formStepUser = form
55 }
56
57 onChannelFormBuilt (form: FormGroup) {
58 this.formStepChannel = form
59 }
60
61 signup () {
62 this.error = null
63
64 const body: UserRegister = Object.assign(this.formStepUser.value, { channel: this.formStepChannel.value })
65
66 this.userService.signup(body).subscribe(
67 () => {
68 this.signupDone = true
69
70 if (this.requiresEmailVerification) {
71 this.info = this.i18n('Now please check your emails to verify your account and complete signup.')
72 return
73 }
74
75 // Auto login
76 this.authService.login(body.username, body.password)
77 .subscribe(
78 () => {
79 this.success = this.i18n('You are now logged in as {{username}}!', { username: body.username })
80 },
81
82 err => this.error = err.message
83 )
84 },
85
86 err => this.error = err.message
87 )
88 }
89}
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts
new file mode 100644
index 000000000..46336cbd0
--- /dev/null
+++ b/client/src/app/+signup/+register/register.module.ts
@@ -0,0 +1,33 @@
1import { NgModule } from '@angular/core'
2import { RegisterRoutingModule } from './register-routing.module'
3import { RegisterComponent } from './register.component'
4import { SharedModule } from '@app/shared'
5import { CdkStepperModule } from '@angular/cdk/stepper'
6import { RegisterStepChannelComponent } from './register-step-channel.component'
7import { RegisterStepUserComponent } from './register-step-user.component'
8import { CustomStepperComponent } from './custom-stepper.component'
9import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module'
10
11@NgModule({
12 imports: [
13 RegisterRoutingModule,
14 SharedModule,
15 CdkStepperModule,
16 SignupSharedModule
17 ],
18
19 declarations: [
20 RegisterComponent,
21 CustomStepperComponent,
22 RegisterStepChannelComponent,
23 RegisterStepUserComponent
24 ],
25
26 exports: [
27 RegisterComponent
28 ],
29
30 providers: [
31 ]
32})
33export class RegisterModule { }
diff --git a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html
index 2e4180632..2e4180632 100644
--- a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html
+++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html
diff --git a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss
index efec6b706..efec6b706 100644
--- a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss
+++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.scss
diff --git a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
index cfd471fa4..cfd471fa4 100644
--- a/client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html
new file mode 100644
index 000000000..47519c943
--- /dev/null
+++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html
@@ -0,0 +1,18 @@
1<div class="margin-content">
2 <div i18n class="title-page title-page-single">
3 Verify account email confirmation
4 </div>
5
6 <my-signup-success i18n *ngIf="!isPendingEmail && success" message="Your email has been verified and you may now login.">
7 </my-signup-success>
8
9 <div i18n class="alert alert-success" *ngIf="isPendingEmail && success">
10 Email updated.
11 </div>
12
13 <div *ngIf="failed">
14 <span i18n>An error occurred.</span>
15
16 <a i18n routerLink="/verify-account/ask-send-email" [queryParams]="{ isPendingEmail: isPendingEmail }">Request new verification email.</a>
17 </div>
18</div>
diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
index f9ecf664b..054f04310 100644
--- a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts
@@ -1,7 +1,7 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { I18n } from '@ngx-translate/i18n-polyfill' 3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { Notifier } from '@app/core' 4import { AuthService, Notifier } from '@app/core'
5import { UserService } from '@app/shared' 5import { UserService } from '@app/shared'
6 6
7@Component({ 7@Component({
@@ -11,12 +11,15 @@ import { UserService } from '@app/shared'
11 11
12export class VerifyAccountEmailComponent implements OnInit { 12export class VerifyAccountEmailComponent implements OnInit {
13 success = false 13 success = false
14 failed = false
15 isPendingEmail = false
14 16
15 private userId: number 17 private userId: number
16 private verificationString: string 18 private verificationString: string
17 19
18 constructor ( 20 constructor (
19 private userService: UserService, 21 private userService: UserService,
22 private authService: AuthService,
20 private notifier: Notifier, 23 private notifier: Notifier,
21 private router: Router, 24 private router: Router,
22 private route: ActivatedRoute, 25 private route: ActivatedRoute,
@@ -25,8 +28,12 @@ export class VerifyAccountEmailComponent implements OnInit {
25 } 28 }
26 29
27 ngOnInit () { 30 ngOnInit () {
28 this.userId = this.route.snapshot.queryParams['userId'] 31 const queryParams = this.route.snapshot.queryParams
29 this.verificationString = this.route.snapshot.queryParams['verificationString'] 32 this.userId = queryParams['userId']
33 this.verificationString = queryParams['verificationString']
34 this.isPendingEmail = queryParams['isPendingEmail'] === 'true'
35
36 console.log(this.isPendingEmail)
30 37
31 if (!this.userId || !this.verificationString) { 38 if (!this.userId || !this.verificationString) {
32 this.notifier.error(this.i18n('Unable to find user id or verification string.')) 39 this.notifier.error(this.i18n('Unable to find user id or verification string.'))
@@ -36,16 +43,17 @@ export class VerifyAccountEmailComponent implements OnInit {
36 } 43 }
37 44
38 verifyEmail () { 45 verifyEmail () {
39 this.userService.verifyEmail(this.userId, this.verificationString) 46 this.userService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail)
40 .subscribe( 47 .subscribe(
41 () => { 48 () => {
49 this.authService.refreshUserInformation()
50
42 this.success = true 51 this.success = true
43 setTimeout(() => {
44 this.router.navigate([ '/login' ])
45 }, 2000)
46 }, 52 },
47 53
48 err => { 54 err => {
55 this.failed = true
56
49 this.notifier.error(err.message) 57 this.notifier.error(err.message)
50 } 58 }
51 ) 59 )
diff --git a/client/src/app/+verify-account/verify-account-routing.module.ts b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts
index a038f0336..16d5fe0d0 100644
--- a/client/src/app/+verify-account/verify-account-routing.module.ts
+++ b/client/src/app/+signup/+verify-account/verify-account-routing.module.ts
@@ -1,12 +1,8 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouterModule, Routes } from '@angular/router'
3
4import { MetaGuard } from '@ngx-meta/core' 3import { MetaGuard } from '@ngx-meta/core'
5 4import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component'
6import { VerifyAccountEmailComponent } from '@app/+verify-account/verify-account-email/verify-account-email.component' 5import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component'
7import {
8 VerifyAccountAskSendEmailComponent
9} from '@app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component'
10 6
11const verifyAccountRoutes: Routes = [ 7const verifyAccountRoutes: Routes = [
12 { 8 {
diff --git a/client/src/app/+signup/+verify-account/verify-account.module.ts b/client/src/app/+signup/+verify-account/verify-account.module.ts
new file mode 100644
index 000000000..9fe14e81e
--- /dev/null
+++ b/client/src/app/+signup/+verify-account/verify-account.module.ts
@@ -0,0 +1,25 @@
1import { NgModule } from '@angular/core'
2import { VerifyAccountRoutingModule } from './verify-account-routing.module'
3import { VerifyAccountEmailComponent } from './verify-account-email/verify-account-email.component'
4import { VerifyAccountAskSendEmailComponent } from './verify-account-ask-send-email/verify-account-ask-send-email.component'
5import { SharedModule } from '@app/shared'
6import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module'
7
8@NgModule({
9 imports: [
10 VerifyAccountRoutingModule,
11 SharedModule,
12 SignupSharedModule
13 ],
14
15 declarations: [
16 VerifyAccountEmailComponent,
17 VerifyAccountAskSendEmailComponent
18 ],
19
20 exports: [],
21
22 providers: []
23})
24export class VerifyAccountModule {
25}
diff --git a/client/src/app/+signup/shared/signup-shared.module.ts b/client/src/app/+signup/shared/signup-shared.module.ts
new file mode 100644
index 000000000..cd21fdef3
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-shared.module.ts
@@ -0,0 +1,21 @@
1import { NgModule } from '@angular/core'
2import { SignupSuccessComponent } from '../shared/signup-success.component'
3import { SharedModule } from '@app/shared'
4
5@NgModule({
6 imports: [
7 SharedModule
8 ],
9
10 declarations: [
11 SignupSuccessComponent
12 ],
13
14 exports: [
15 SignupSuccessComponent
16 ],
17
18 providers: [
19 ]
20})
21export class SignupSharedModule { }
diff --git a/client/src/app/+signup/shared/signup-success.component.html b/client/src/app/+signup/shared/signup-success.component.html
new file mode 100644
index 000000000..e35f858c6
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success.component.html
@@ -0,0 +1,16 @@
1<!-- Thanks: Amit Singh Sansoya from https://codepen.io/amit3200/pen/zWMJOO -->
2
3<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 130.2 130.2">
4 <circle class="path circle" fill="none" stroke="#73AF55" stroke-width="6" stroke-miterlimit="10" cx="65.1" cy="65.1" r="62.1"/>
5 <polyline class="path check" fill="none" stroke="#73AF55" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" points="100.2,40.2 51.5,88.8 29.8,67.5 "/>
6</svg>
7
8<p class="bottom-message">Welcome on PeerTube!</p>
9
10<div *ngIf="message" class="alert alert-success">
11 <p>{{ message }}</p>
12
13 <p i18n>
14 If you need help to use PeerTube, you can take a look to the <a href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">documentation</a>.
15 </p>
16</div>
diff --git a/client/src/app/+signup/shared/signup-success.component.scss b/client/src/app/+signup/shared/signup-success.component.scss
new file mode 100644
index 000000000..fbc27c8bc
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success.component.scss
@@ -0,0 +1,76 @@
1svg {
2 width: 100px;
3 display: block;
4 margin: 40px auto 0;
5}
6
7.path {
8 stroke-dasharray: 1000;
9 stroke-dashoffset: 0;
10
11 &.circle {
12 -webkit-animation: dash .9s ease-in-out;
13 animation: dash .9s ease-in-out;
14 }
15
16 &.line {
17 stroke-dashoffset: 1000;
18 -webkit-animation: dash .9s .35s ease-in-out forwards;
19 animation: dash .9s .35s ease-in-out forwards;
20 }
21
22 &.check {
23 stroke-dashoffset: -100;
24 -webkit-animation: dash-check .9s .35s ease-in-out forwards;
25 animation: dash-check .9s .35s ease-in-out forwards;
26 }
27}
28
29.bottom-message {
30 text-align: center;
31 margin: 20px 0 60px;
32 font-size: 1.25em;
33 color: #73AF55;
34}
35
36.alert {
37 font-size: 15px;
38 text-align: center;
39}
40
41
42@-webkit-keyframes dash {
43 0% {
44 stroke-dashoffset: 1000;
45 }
46 100% {
47 stroke-dashoffset: 0;
48 }
49}
50
51@keyframes dash {
52 0% {
53 stroke-dashoffset: 1000;
54 }
55 100% {
56 stroke-dashoffset: 0;
57 }
58}
59
60@-webkit-keyframes dash-check {
61 0% {
62 stroke-dashoffset: -100;
63 }
64 100% {
65 stroke-dashoffset: 900;
66 }
67}
68
69@keyframes dash-check {
70 0% {
71 stroke-dashoffset: -100;
72 }
73 100% {
74 stroke-dashoffset: 900;
75 }
76}
diff --git a/client/src/app/+signup/shared/signup-success.component.ts b/client/src/app/+signup/shared/signup-success.component.ts
new file mode 100644
index 000000000..19fb5922a
--- /dev/null
+++ b/client/src/app/+signup/shared/signup-success.component.ts
@@ -0,0 +1,10 @@
1import { Component, Input } from '@angular/core'
2
3@Component({
4 selector: 'my-signup-success',
5 templateUrl: './signup-success.component.html',
6 styleUrls: [ './signup-success.component.scss' ]
7})
8export class SignupSuccessComponent {
9 @Input() message: string
10}
diff --git a/client/src/app/+verify-account/index.ts b/client/src/app/+verify-account/index.ts
deleted file mode 100644
index 733f5ba77..000000000
--- a/client/src/app/+verify-account/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1export * from '@app/+verify-account/verify-account-routing.module'
2export * from '@app/+verify-account/verify-account.module'
diff --git a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html
deleted file mode 100644
index a83d4a3c2..000000000
--- a/client/src/app/+verify-account/verify-account-email/verify-account-email.component.html
+++ /dev/null
@@ -1,15 +0,0 @@
1<div class="margin-content">
2 <div i18n class="title-page title-page-single">
3 Verify account email confirmation
4 </div>
5
6 <div i18n *ngIf="success; else verificationError">
7 Your email has been verified and you may now login. Redirecting...
8 </div>
9 <ng-template #verificationError>
10 <div>
11 <span i18n>An error occurred. </span>
12 <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a>
13 </div>
14 </ng-template>
15</div>
diff --git a/client/src/app/+verify-account/verify-account.module.ts b/client/src/app/+verify-account/verify-account.module.ts
deleted file mode 100644
index 9092c6b4f..000000000
--- a/client/src/app/+verify-account/verify-account.module.ts
+++ /dev/null
@@ -1,27 +0,0 @@
1import { NgModule } from '@angular/core'
2
3import { VerifyAccountRoutingModule } from '@app/+verify-account/verify-account-routing.module'
4import { VerifyAccountEmailComponent } from '@app/+verify-account/verify-account-email/verify-account-email.component'
5import {
6 VerifyAccountAskSendEmailComponent
7} from '@app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component'
8import { SharedModule } from '@app/shared'
9
10@NgModule({
11 imports: [
12 VerifyAccountRoutingModule,
13 SharedModule
14 ],
15
16 declarations: [
17 VerifyAccountEmailComponent,
18 VerifyAccountAskSendEmailComponent
19 ],
20
21 exports: [
22 ],
23
24 providers: [
25 ]
26})
27export class VerifyAccountModule { }
diff --git a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
index 907aefae1..7990044a2 100644
--- a/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
+++ b/client/src/app/+video-channels/video-channel-playlists/video-channel-playlists.component.ts
@@ -5,7 +5,7 @@ import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
5import { Subscription } from 'rxjs' 5import { Subscription } from 'rxjs'
6import { Notifier } from '@app/core' 6import { Notifier } from '@app/core'
7import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 7import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
8import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 8import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
10 10
11@Component({ 11@Component({
@@ -46,8 +46,7 @@ export class VideoChannelPlaylistsComponent implements OnInit, OnDestroy {
46 } 46 }
47 47
48 onNearOfBottom () { 48 onNearOfBottom () {
49 // Last page 49 if (!hasMoreItems(this.pagination)) return
50 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
51 50
52 this.pagination.currentPage += 1 51 this.pagination.currentPage += 1
53 this.loadVideoPlaylists() 52 this.loadVideoPlaylists()
diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
index 5e60b34b4..629fd4450 100644
--- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
+++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
@@ -29,6 +29,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
29 private videoChannelSub: Subscription 29 private videoChannelSub: Subscription
30 30
31 constructor ( 31 constructor (
32 protected i18n: I18n,
32 protected router: Router, 33 protected router: Router,
33 protected serverService: ServerService, 34 protected serverService: ServerService,
34 protected route: ActivatedRoute, 35 protected route: ActivatedRoute,
@@ -36,7 +37,6 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
36 protected notifier: Notifier, 37 protected notifier: Notifier,
37 protected confirmService: ConfirmService, 38 protected confirmService: ConfirmService,
38 protected screenService: ScreenService, 39 protected screenService: ScreenService,
39 private i18n: I18n,
40 private videoChannelService: VideoChannelService, 40 private videoChannelService: VideoChannelService,
41 private videoService: VideoService 41 private videoService: VideoService
42 ) { 42 ) {
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index db8888dba..7ca51f226 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -16,7 +16,7 @@ const routes: Routes = [
16 }, 16 },
17 { 17 {
18 path: 'verify-account', 18 path: 'verify-account',
19 loadChildren: './+verify-account/verify-account.module#VerifyAccountModule' 19 loadChildren: './+signup/+verify-account/verify-account.module#VerifyAccountModule'
20 }, 20 },
21 { 21 {
22 path: 'accounts', 22 path: 'accounts',
@@ -31,6 +31,10 @@ const routes: Routes = [
31 loadChildren: './+about/about.module#AboutModule' 31 loadChildren: './+about/about.module#AboutModule'
32 }, 32 },
33 { 33 {
34 path: 'signup',
35 loadChildren: './+signup/+register/register.module#RegisterModule'
36 },
37 {
34 path: '', 38 path: '',
35 component: AppComponent // Avoid 404, app component will redirect dynamically 39 component: AppComponent // Avoid 404, app component will redirect dynamically
36 }, 40 },
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 0bbc2e08b..1e2936a37 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -14,7 +14,6 @@ import { HeaderComponent } from './header'
14import { LoginModule } from './login' 14import { LoginModule } from './login'
15import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu' 15import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu'
16import { SharedModule } from './shared' 16import { SharedModule } from './shared'
17import { SignupModule } from './signup'
18import { VideosModule } from './videos' 17import { VideosModule } from './videos'
19import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' 18import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n'
20import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' 19import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
@@ -53,7 +52,6 @@ export function metaFactory (serverService: ServerService): MetaLoader {
53 CoreModule, 52 CoreModule,
54 LoginModule, 53 LoginModule,
55 ResetPasswordModule, 54 ResetPasswordModule,
56 SignupModule,
57 SearchModule, 55 SearchModule,
58 SharedModule, 56 SharedModule,
59 VideosModule, 57 VideosModule,
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts
index d3e72afb4..06fa8fcf1 100644
--- a/client/src/app/core/core.module.ts
+++ b/client/src/app/core/core.module.ts
@@ -20,6 +20,7 @@ import { Notifier } from './notification'
20import { MessageService } from 'primeng/api' 20import { MessageService } from 'primeng/api'
21import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service' 21import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service'
22import { ServerConfigResolver } from './routing/server-config-resolver.service' 22import { ServerConfigResolver } from './routing/server-config-resolver.service'
23import { UnloggedGuard } from '@app/core/routing/unlogged-guard.service'
23 24
24@NgModule({ 25@NgModule({
25 imports: [ 26 imports: [
@@ -58,6 +59,8 @@ import { ServerConfigResolver } from './routing/server-config-resolver.service'
58 ThemeService, 59 ThemeService,
59 LoginGuard, 60 LoginGuard,
60 UserRightGuard, 61 UserRightGuard,
62 UnloggedGuard,
63
61 RedirectService, 64 RedirectService,
62 Notifier, 65 Notifier,
63 MessageService, 66 MessageService,
diff --git a/client/src/app/core/routing/redirect.service.ts b/client/src/app/core/routing/redirect.service.ts
index e1db4097b..571822b76 100644
--- a/client/src/app/core/routing/redirect.service.ts
+++ b/client/src/app/core/routing/redirect.service.ts
@@ -42,7 +42,14 @@ export class RedirectService {
42 } 42 }
43 43
44 redirectToPreviousRoute () { 44 redirectToPreviousRoute () {
45 if (this.previousUrl) return this.router.navigateByUrl(this.previousUrl) 45 const exceptions = [
46 '/verify-account'
47 ]
48
49 if (this.previousUrl) {
50 const isException = exceptions.find(e => this.previousUrl.startsWith(e))
51 if (!isException) return this.router.navigateByUrl(this.previousUrl)
52 }
46 53
47 return this.redirectToHomepage() 54 return this.redirectToHomepage()
48 } 55 }
diff --git a/client/src/app/core/routing/unlogged-guard.service.ts b/client/src/app/core/routing/unlogged-guard.service.ts
new file mode 100644
index 000000000..3132a1a77
--- /dev/null
+++ b/client/src/app/core/routing/unlogged-guard.service.ts
@@ -0,0 +1,25 @@
1import { Injectable } from '@angular/core'
2import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router'
3import { AuthService } from '../auth/auth.service'
4import { RedirectService } from './redirect.service'
5
6@Injectable()
7export class UnloggedGuard implements CanActivate, CanActivateChild {
8
9 constructor (
10 private router: Router,
11 private auth: AuthService,
12 private redirectService: RedirectService
13 ) {}
14
15 canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
16 if (this.auth.isLoggedIn() === false) return true
17
18 this.redirectService.redirectToHomepage()
19 return false
20 }
21
22 canActivateChild (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
23 return this.canActivate(route, state)
24 }
25}
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 3a8a535fd..689f25a40 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -10,6 +10,7 @@ import { isDefaultLocale, peertubeTranslate } from '../../../../../shared/models
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 { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' 12import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
13import { cloneDeep } from 'lodash-es'
13 14
14@Injectable() 15@Injectable()
15export class ServerService { 16export class ServerService {
@@ -160,27 +161,27 @@ export class ServerService {
160 } 161 }
161 162
162 getConfig () { 163 getConfig () {
163 return this.config 164 return cloneDeep(this.config)
164 } 165 }
165 166
166 getVideoCategories () { 167 getVideoCategories () {
167 return this.videoCategories 168 return cloneDeep(this.videoCategories)
168 } 169 }
169 170
170 getVideoLicences () { 171 getVideoLicences () {
171 return this.videoLicences 172 return cloneDeep(this.videoLicences)
172 } 173 }
173 174
174 getVideoLanguages () { 175 getVideoLanguages () {
175 return this.videoLanguages 176 return cloneDeep(this.videoLanguages)
176 } 177 }
177 178
178 getVideoPrivacies () { 179 getVideoPrivacies () {
179 return this.videoPrivacies 180 return cloneDeep(this.videoPrivacies)
180 } 181 }
181 182
182 getVideoPlaylistPrivacies () { 183 getVideoPlaylistPrivacies () {
183 return this.videoPlaylistPrivacies 184 return cloneDeep(this.videoPlaylistPrivacies)
184 } 185 }
185 186
186 private loadAttributeEnum ( 187 private loadAttributeEnum (
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html
index e80e6b803..588cb8548 100644
--- a/client/src/app/menu/menu.component.html
+++ b/client/src/app/menu/menu.component.html
@@ -63,7 +63,7 @@
63 63
64 <a routerLink="/videos/overview" routerLinkActive="active"> 64 <a routerLink="/videos/overview" routerLinkActive="active">
65 <my-global-icon iconName="globe"></my-global-icon> 65 <my-global-icon iconName="globe"></my-global-icon>
66 <ng-container i18n>Overview</ng-container> 66 <ng-container i18n>Discover</ng-container>
67 </a> 67 </a>
68 68
69 <a routerLink="/videos/trending" routerLinkActive="active"> 69 <a routerLink="/videos/trending" routerLinkActive="active">
diff --git a/client/src/app/search/search.component.html b/client/src/app/search/search.component.html
index 0a9f78cb2..055f64cc8 100644
--- a/client/src/app/search/search.component.html
+++ b/client/src/app/search/search.component.html
@@ -20,7 +20,7 @@
20 </div> 20 </div>
21 </div> 21 </div>
22 22
23 <div class="results-filter" [ngbCollapse]="isSearchFilterCollapsed"> 23 <div class="results-filter collapse-transition" [ngbCollapse]="isSearchFilterCollapsed">
24 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters> 24 <my-search-filters [advancedSearch]="advancedSearch" (filtered)="onFiltered()"></my-search-filters>
25 </div> 25 </div>
26 </div> 26 </div>
diff --git a/client/src/app/search/search.component.scss b/client/src/app/search/search.component.scss
index 4e3ce1c96..3343a276d 100644
--- a/client/src/app/search/search.component.scss
+++ b/client/src/app/search/search.component.scss
@@ -35,18 +35,6 @@
35 } 35 }
36 } 36 }
37 } 37 }
38
39 .results-filter {
40 // Animation when we show/hide the filters
41 transition: max-height 0.3s;
42 display: block !important;
43 overflow: hidden !important;
44 max-height: 0;
45
46 &.show {
47 max-height: 1500px;
48 }
49 }
50 } 38 }
51 39
52 .entry { 40 .entry {
diff --git a/client/src/app/search/search.module.ts b/client/src/app/search/search.module.ts
index 0411fbe24..8b791621e 100644
--- a/client/src/app/search/search.module.ts
+++ b/client/src/app/search/search.module.ts
@@ -4,14 +4,11 @@ import { SearchComponent } from '@app/search/search.component'
4import { SearchService } from '@app/search/search.service' 4import { SearchService } from '@app/search/search.service'
5import { SearchRoutingModule } from '@app/search/search-routing.module' 5import { SearchRoutingModule } from '@app/search/search-routing.module'
6import { SearchFiltersComponent } from '@app/search/search-filters.component' 6import { SearchFiltersComponent } from '@app/search/search-filters.component'
7import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'
8 7
9@NgModule({ 8@NgModule({
10 imports: [ 9 imports: [
11 SearchRoutingModule, 10 SearchRoutingModule,
12 SharedModule, 11 SharedModule
13
14 NgbCollapseModule
15 ], 12 ],
16 13
17 declarations: [ 14 declarations: [
diff --git a/client/src/app/shared/actor/actor.model.ts b/client/src/app/shared/actor/actor.model.ts
index adecec1fc..5a517c975 100644
--- a/client/src/app/shared/actor/actor.model.ts
+++ b/client/src/app/shared/actor/actor.model.ts
@@ -4,7 +4,6 @@ import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
4 4
5export abstract class Actor implements ActorServer { 5export abstract class Actor implements ActorServer {
6 id: number 6 id: number
7 uuid: string
8 url: string 7 url: string
9 name: string 8 name: string
10 host: string 9 host: string
@@ -35,7 +34,6 @@ export abstract class Actor implements ActorServer {
35 34
36 protected constructor (hash: ActorServer) { 35 protected constructor (hash: ActorServer) {
37 this.id = hash.id 36 this.id = hash.id
38 this.uuid = hash.uuid
39 this.url = hash.url 37 this.url = hash.url
40 this.name = hash.name 38 this.name = hash.name
41 this.host = hash.host 39 this.host = hash.host
diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/buttons/button.component.scss
index 04199a2a9..99d7f51c1 100644
--- a/client/src/app/shared/buttons/button.component.scss
+++ b/client/src/app/shared/buttons/button.component.scss
@@ -5,16 +5,9 @@
5 @include peertube-button-link; 5 @include peertube-button-link;
6 @include button-with-icon(21px, 0, -2px); 6 @include button-with-icon(21px, 0, -2px);
7 7
8 font-weight: $font-semibold; 8 // FIXME: Firefox does not apply global .orange-button icon color
9 color: $grey-foreground-color; 9 &.orange-button {
10 background-color: $grey-background-color; 10 @include apply-svg-color(#fff)
11
12 &:hover {
13 background-color: $grey-background-hover-color;
14 }
15
16 my-global-icon {
17 @include apply-svg-color($grey-foreground-color);
18 } 11 }
19} 12}
20 13
diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/buttons/button.component.ts
index c2b69d31a..cf334e8d5 100644
--- a/client/src/app/shared/buttons/button.component.ts
+++ b/client/src/app/shared/buttons/button.component.ts
@@ -9,7 +9,7 @@ import { GlobalIconName } from '@app/shared/images/global-icon.component'
9 9
10export class ButtonComponent { 10export class ButtonComponent {
11 @Input() label = '' 11 @Input() label = ''
12 @Input() className: string = undefined 12 @Input() className = 'grey-button'
13 @Input() icon: GlobalIconName = undefined 13 @Input() icon: GlobalIconName = undefined
14 @Input() title: string = undefined 14 @Input() title: string = undefined
15 15
diff --git a/client/src/app/shared/buttons/delete-button.component.html b/client/src/app/shared/buttons/delete-button.component.html
index b4acb9d32..25196fbd5 100644
--- a/client/src/app/shared/buttons/delete-button.component.html
+++ b/client/src/app/shared/buttons/delete-button.component.html
@@ -1,4 +1,4 @@
1<span class="action-button action-button-delete" [title]="title" role="button"> 1<span class="action-button action-button-delete grey-button" [title]="title" role="button">
2 <my-global-icon iconName="delete"></my-global-icon> 2 <my-global-icon iconName="delete"></my-global-icon>
3 3
4 <span class="button-label" *ngIf="label">{{ label }}</span> 4 <span class="button-label" *ngIf="label">{{ label }}</span>
diff --git a/client/src/app/shared/buttons/edit-button.component.html b/client/src/app/shared/buttons/edit-button.component.html
index da3addbae..3d7cd4780 100644
--- a/client/src/app/shared/buttons/edit-button.component.html
+++ b/client/src/app/shared/buttons/edit-button.component.html
@@ -1,4 +1,4 @@
1<a class="action-button action-button-edit" [routerLink]="routerLink" i18n-title title="Edit"> 1<a class="action-button action-button-edit grey-button" [routerLink]="routerLink" i18n-title title="Edit">
2 <my-global-icon iconName="edit"></my-global-icon> 2 <my-global-icon iconName="edit"></my-global-icon>
3 3
4 <span class="button-label" *ngIf="label">{{ label }}</span> 4 <span class="button-label" *ngIf="label">{{ label }}</span>
diff --git a/client/src/app/shared/forms/form-validators/user-validators.service.ts b/client/src/app/shared/forms/form-validators/user-validators.service.ts
index 6589b2580..2dafb1816 100644
--- a/client/src/app/shared/forms/form-validators/user-validators.service.ts
+++ b/client/src/app/shared/forms/form-validators/user-validators.service.ts
@@ -12,7 +12,7 @@ export class UserValidatorsService {
12 readonly USER_VIDEO_QUOTA: BuildFormValidator 12 readonly USER_VIDEO_QUOTA: BuildFormValidator
13 readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator 13 readonly USER_VIDEO_QUOTA_DAILY: BuildFormValidator
14 readonly USER_ROLE: BuildFormValidator 14 readonly USER_ROLE: BuildFormValidator
15 readonly USER_DISPLAY_NAME: BuildFormValidator 15 readonly USER_DISPLAY_NAME_REQUIRED: BuildFormValidator
16 readonly USER_DESCRIPTION: BuildFormValidator 16 readonly USER_DESCRIPTION: BuildFormValidator
17 readonly USER_TERMS: BuildFormValidator 17 readonly USER_TERMS: BuildFormValidator
18 18
@@ -85,18 +85,7 @@ export class UserValidatorsService {
85 } 85 }
86 } 86 }
87 87
88 this.USER_DISPLAY_NAME = { 88 this.USER_DISPLAY_NAME_REQUIRED = this.getDisplayName(true)
89 VALIDATORS: [
90 Validators.required,
91 Validators.minLength(1),
92 Validators.maxLength(50)
93 ],
94 MESSAGES: {
95 'required': this.i18n('Display name is required.'),
96 'minlength': this.i18n('Display name must be at least 1 character long.'),
97 'maxlength': this.i18n('Display name cannot be more than 50 characters long.')
98 }
99 }
100 89
101 this.USER_DESCRIPTION = { 90 this.USER_DESCRIPTION = {
102 VALIDATORS: [ 91 VALIDATORS: [
@@ -129,4 +118,22 @@ export class UserValidatorsService {
129 } 118 }
130 } 119 }
131 } 120 }
121
122 private getDisplayName (required: boolean) {
123 const control = {
124 VALIDATORS: [
125 Validators.minLength(1),
126 Validators.maxLength(120)
127 ],
128 MESSAGES: {
129 'required': this.i18n('Display name is required.'),
130 'minlength': this.i18n('Display name must be at least 1 character long.'),
131 'maxlength': this.i18n('Display name cannot be more than 50 characters long.')
132 }
133 }
134
135 if (required) control.VALIDATORS.push(Validators.required)
136
137 return control
138 }
132} 139}
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss
index ea321ee65..84ea788af 100644
--- a/client/src/app/shared/forms/peertube-checkbox.component.scss
+++ b/client/src/app/shared/forms/peertube-checkbox.component.scss
@@ -14,9 +14,6 @@
14 14
15 input { 15 input {
16 @include peertube-checkbox(1px); 16 @include peertube-checkbox(1px);
17
18 width: 10px;
19 margin-right: 10px;
20 } 17 }
21 } 18 }
22 19
diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/forms/reactive-file.component.html
index 7d691059d..f6bf5f9ae 100644
--- a/client/src/app/shared/forms/reactive-file.component.html
+++ b/client/src/app/shared/forms/reactive-file.component.html
@@ -1,6 +1,9 @@
1<div class="root"> 1<div class="root">
2 <div class="button-file"> 2 <div class="button-file" [ngClass]="{ 'with-icon': !!icon }">
3 <my-global-icon *ngIf="icon" [iconName]="icon"></my-global-icon>
4
3 <span>{{ inputLabel }}</span> 5 <span>{{ inputLabel }}</span>
6
4 <input 7 <input
5 type="file" 8 type="file"
6 [name]="inputName" [id]="inputName" [accept]="extensions" 9 [name]="inputName" [id]="inputName" [accept]="extensions"
@@ -8,7 +11,5 @@
8 /> 11 />
9 </div> 12 </div>
10 13
11 <div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxFileSize | bytes }})</div>
12
13 <div class="filename" *ngIf="displayFilename === true && filename">{{ filename }}</div> 14 <div class="filename" *ngIf="displayFilename === true && filename">{{ filename }}</div>
14</div> 15</div>
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/forms/reactive-file.component.scss
index d89844264..84c23c1d6 100644
--- a/client/src/app/shared/forms/reactive-file.component.scss
+++ b/client/src/app/shared/forms/reactive-file.component.scss
@@ -8,13 +8,11 @@
8 8
9 .button-file { 9 .button-file {
10 @include peertube-button-file(auto); 10 @include peertube-button-file(auto);
11 @include grey-button;
11 12
12 min-width: 190px; 13 &.with-icon {
13 } 14 @include button-with-icon;
14 15 }
15 .file-constraints {
16 margin-left: 5px;
17 font-size: 13px;
18 } 16 }
19 17
20 .filename { 18 .filename {
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts
index f60c38e8d..b7a821d4f 100644
--- a/client/src/app/shared/forms/reactive-file.component.ts
+++ b/client/src/app/shared/forms/reactive-file.component.ts
@@ -2,6 +2,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@ang
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { Notifier } from '@app/core' 3import { Notifier } from '@app/core'
4import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { GlobalIconName } from '@app/shared/images/global-icon.component'
5 6
6@Component({ 7@Component({
7 selector: 'my-reactive-file', 8 selector: 'my-reactive-file',
@@ -21,6 +22,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
21 @Input() extensions: string[] = [] 22 @Input() extensions: string[] = []
22 @Input() maxFileSize: number 23 @Input() maxFileSize: number
23 @Input() displayFilename = false 24 @Input() displayFilename = false
25 @Input() icon: GlobalIconName
24 26
25 @Output() fileChanged = new EventEmitter<Blob>() 27 @Output() fileChanged = new EventEmitter<Blob>()
26 28
diff --git a/client/src/app/shared/images/image-upload.component.html b/client/src/app/shared/images/image-upload.component.html
deleted file mode 100644
index c09c862c4..000000000
--- a/client/src/app/shared/images/image-upload.component.html
+++ /dev/null
@@ -1,9 +0,0 @@
1<div class="root">
2 <my-reactive-file
3 [inputName]="inputName" [inputLabel]="inputLabel" [extensions]="videoImageExtensions" [maxFileSize]="maxVideoImageSize"
4 (fileChanged)="onFileChanged($event)"
5 ></my-reactive-file>
6
7 <img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" />
8 <div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div>
9</div>
diff --git a/client/src/app/shared/images/image-upload.component.scss b/client/src/app/shared/images/image-upload.component.scss
deleted file mode 100644
index b63963bca..000000000
--- a/client/src/app/shared/images/image-upload.component.scss
+++ /dev/null
@@ -1,18 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.root {
5 height: auto;
6 display: flex;
7 align-items: center;
8
9 .preview {
10 border: 2px solid grey;
11 border-radius: 4px;
12 margin-left: 50px;
13
14 &.no-image {
15 background-color: #ececec;
16 }
17 }
18}
diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/images/preview-upload.component.html
new file mode 100644
index 000000000..5e1d5211b
--- /dev/null
+++ b/client/src/app/shared/images/preview-upload.component.html
@@ -0,0 +1,13 @@
1<div class="root">
2 <div class="preview-container">
3 <my-reactive-file
4 [inputName]="inputName" [inputLabel]="inputLabel" [extensions]="videoImageExtensions" [maxFileSize]="maxVideoImageSize"
5 icon="edit" (fileChanged)="onFileChanged($event)"
6 ></my-reactive-file>
7
8 <img *ngIf="imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" [src]="imageSrc" class="preview" />
9 <div *ngIf="!imageSrc" [ngStyle]="{ width: previewWidth, height: previewHeight }" class="preview no-image"></div>
10 </div>
11
12 <div i18n class="file-constraints">(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxVideoImageSize | bytes }})</div>
13</div>
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/images/preview-upload.component.scss
new file mode 100644
index 000000000..257060239
--- /dev/null
+++ b/client/src/app/shared/images/preview-upload.component.scss
@@ -0,0 +1,27 @@
1@import '_variables';
2@import '_mixins';
3
4.root {
5 height: auto;
6 display: flex;
7 flex-direction: column;
8
9 .preview-container {
10 position: relative;
11
12 my-reactive-file {
13 position: absolute;
14 bottom: 10px;
15 left: 10px;
16 }
17
18 .preview {
19 border: 2px solid grey;
20 border-radius: 4px;
21
22 &.no-image {
23 background-color: #ececec;
24 }
25 }
26 }
27}
diff --git a/client/src/app/shared/images/image-upload.component.ts b/client/src/app/shared/images/preview-upload.component.ts
index 2da1592ff..44b78866e 100644
--- a/client/src/app/shared/images/image-upload.component.ts
+++ b/client/src/app/shared/images/preview-upload.component.ts
@@ -1,27 +1,28 @@
1import { Component, forwardRef, Input } from '@angular/core' 1import { Component, forwardRef, Input, OnInit } from '@angular/core'
2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' 2import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
3import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' 3import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
4import { ServerService } from '@app/core' 4import { ServerService } from '@app/core'
5 5
6@Component({ 6@Component({
7 selector: 'my-image-upload', 7 selector: 'my-preview-upload',
8 styleUrls: [ './image-upload.component.scss' ], 8 styleUrls: [ './preview-upload.component.scss' ],
9 templateUrl: './image-upload.component.html', 9 templateUrl: './preview-upload.component.html',
10 providers: [ 10 providers: [
11 { 11 {
12 provide: NG_VALUE_ACCESSOR, 12 provide: NG_VALUE_ACCESSOR,
13 useExisting: forwardRef(() => ImageUploadComponent), 13 useExisting: forwardRef(() => PreviewUploadComponent),
14 multi: true 14 multi: true
15 } 15 }
16 ] 16 ]
17}) 17})
18export class ImageUploadComponent implements ControlValueAccessor { 18export class PreviewUploadComponent implements OnInit, ControlValueAccessor {
19 @Input() inputLabel: string 19 @Input() inputLabel: string
20 @Input() inputName: string 20 @Input() inputName: string
21 @Input() previewWidth: string 21 @Input() previewWidth: string
22 @Input() previewHeight: string 22 @Input() previewHeight: string
23 23
24 imageSrc: SafeResourceUrl 24 imageSrc: SafeResourceUrl
25 allowedExtensionsMessage = ''
25 26
26 private file: File 27 private file: File
27 28
@@ -38,6 +39,10 @@ export class ImageUploadComponent implements ControlValueAccessor {
38 return this.serverService.getConfig().video.image.size.max 39 return this.serverService.getConfig().video.image.size.max
39 } 40 }
40 41
42 ngOnInit () {
43 this.allowedExtensionsMessage = this.videoImageExtensions.join(', ')
44 }
45
41 onFileChanged (file: File) { 46 onFileChanged (file: File) {
42 this.file = file 47 this.file = file
43 48
diff --git a/client/src/app/+admin/follows/shared/follow.service.ts b/client/src/app/shared/instance/follow.service.ts
index c2b8ef006..5a44c64f1 100644
--- a/client/src/app/+admin/follows/shared/follow.service.ts
+++ b/client/src/app/shared/instance/follow.service.ts
@@ -3,13 +3,13 @@ import { HttpClient, HttpParams } from '@angular/common/http'
3import { Injectable } from '@angular/core' 3import { Injectable } from '@angular/core'
4import { SortMeta } from 'primeng/primeng' 4import { SortMeta } from 'primeng/primeng'
5import { Observable } from 'rxjs' 5import { Observable } from 'rxjs'
6import { ActorFollow, ResultList } from '../../../../../../shared' 6import { ActorFollow, ResultList } from '@shared/index'
7import { environment } from '../../../../environments/environment' 7import { environment } from '../../../environments/environment'
8import { RestExtractor, RestPagination, RestService } from '../../../shared' 8import { RestExtractor, RestPagination, RestService } from '../rest'
9 9
10@Injectable() 10@Injectable()
11export class FollowService { 11export class FollowService {
12 private static BASE_APPLICATION_URL = environment.apiUrl + '/api/v1/server' 12 private static BASE_APPLICATION_URL = 'https://peertube2.cpy.re' + '/api/v1/server'
13 13
14 constructor ( 14 constructor (
15 private authHttp: HttpClient, 15 private authHttp: HttpClient,
diff --git a/client/src/app/shared/misc/loader.component.html b/client/src/app/shared/misc/loader.component.html
index b8b7ad343..ca8ed063e 100644
--- a/client/src/app/shared/misc/loader.component.html
+++ b/client/src/app/shared/misc/loader.component.html
@@ -1,5 +1,5 @@
1<div *ngIf="loading"> 1<div *ngIf="loading">
2 <div class="lds-ring"> 2 <div class="loader">
3 <div></div> 3 <div></div>
4 <div></div> 4 <div></div>
5 <div></div> 5 <div></div>
diff --git a/client/src/app/shared/misc/loader.component.scss b/client/src/app/shared/misc/loader.component.scss
index ddb64f07a..ffac9c707 100644
--- a/client/src/app/shared/misc/loader.component.scss
+++ b/client/src/app/shared/misc/loader.component.scss
@@ -3,14 +3,14 @@
3 3
4// Thanks to https://loading.io/css/ (CC0 License) 4// Thanks to https://loading.io/css/ (CC0 License)
5 5
6.lds-ring { 6.loader {
7 display: inline-block; 7 display: inline-block;
8 position: relative; 8 position: relative;
9 width: 50px; 9 width: 50px;
10 height: 50px; 10 height: 50px;
11} 11}
12 12
13.lds-ring div { 13.loader div {
14 box-sizing: border-box; 14 box-sizing: border-box;
15 display: block; 15 display: block;
16 position: absolute; 16 position: absolute;
@@ -19,23 +19,23 @@
19 margin: 6px; 19 margin: 6px;
20 border: 4px solid; 20 border: 4px solid;
21 border-radius: 50%; 21 border-radius: 50%;
22 animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 22 animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
23 border-color: #999999 transparent transparent transparent; 23 border-color: #999999 transparent transparent transparent;
24} 24}
25 25
26.lds-ring div:nth-child(1) { 26.loader div:nth-child(1) {
27 animation-delay: -0.45s; 27 animation-delay: -0.45s;
28} 28}
29 29
30.lds-ring div:nth-child(2) { 30.loader div:nth-child(2) {
31 animation-delay: -0.3s; 31 animation-delay: -0.3s;
32} 32}
33 33
34.lds-ring div:nth-child(3) { 34.loader div:nth-child(3) {
35 animation-delay: -0.15s; 35 animation-delay: -0.15s;
36} 36}
37 37
38@keyframes lds-ring { 38@keyframes loader {
39 0% { 39 0% {
40 transform: rotate(0deg); 40 transform: rotate(0deg);
41 } 41 }
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index ded65653f..eb57a2fff 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -53,7 +53,14 @@ import { VideoCaptionService } from '@app/shared/video-caption'
53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component' 53import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
54import { VideoImportService } from '@app/shared/video-import/video-import.service' 54import { VideoImportService } from '@app/shared/video-import/video-import.service'
55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component' 55import { ActionDropdownComponent } from '@app/shared/buttons/action-dropdown.component'
56import { NgbDropdownModule, NgbModalModule, NgbPopoverModule, NgbTabsetModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap' 56import {
57 NgbCollapseModule,
58 NgbDropdownModule,
59 NgbModalModule,
60 NgbPopoverModule,
61 NgbTabsetModule,
62 NgbTooltipModule
63} from '@ng-bootstrap/ng-bootstrap'
57import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription' 64import { RemoteSubscribeComponent, SubscribeButtonComponent, UserSubscriptionService } from '@app/shared/user-subscription'
58import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component' 65import { InstanceFeaturesTableComponent } from '@app/shared/instance/instance-features-table.component'
59import { OverviewService } from '@app/shared/overview' 66import { OverviewService } from '@app/shared/overview'
@@ -69,7 +76,7 @@ import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/sha
69import { ConfirmComponent } from '@app/shared/confirm/confirm.component' 76import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
70import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' 77import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component'
71import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 78import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
72import { ImageUploadComponent } from '@app/shared/images/image-upload.component' 79import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component'
73import { GlobalIconComponent } from '@app/shared/images/global-icon.component' 80import { GlobalIconComponent } from '@app/shared/images/global-icon.component'
74import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' 81import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component'
75import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' 82import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component'
@@ -85,6 +92,7 @@ import { VideoBlacklistComponent } from '@app/shared/video/modals/video-blacklis
85import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component' 92import { VideoDownloadComponent } from '@app/shared/video/modals/video-download.component'
86import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' 93import { VideoReportComponent } from '@app/shared/video/modals/video-report.component'
87import { ClipboardModule } from 'ngx-clipboard' 94import { ClipboardModule } from 'ngx-clipboard'
95import { FollowService } from '@app/shared/instance/follow.service'
88 96
89@NgModule({ 97@NgModule({
90 imports: [ 98 imports: [
@@ -99,6 +107,7 @@ import { ClipboardModule } from 'ngx-clipboard'
99 NgbPopoverModule, 107 NgbPopoverModule,
100 NgbTabsetModule, 108 NgbTabsetModule,
101 NgbTooltipModule, 109 NgbTooltipModule,
110 NgbCollapseModule,
102 111
103 ClipboardModule, 112 ClipboardModule,
104 113
@@ -154,7 +163,7 @@ import { ClipboardModule } from 'ngx-clipboard'
154 ConfirmComponent, 163 ConfirmComponent,
155 164
156 GlobalIconComponent, 165 GlobalIconComponent,
157 ImageUploadComponent 166 PreviewUploadComponent
158 ], 167 ],
159 168
160 exports: [ 169 exports: [
@@ -169,6 +178,7 @@ import { ClipboardModule } from 'ngx-clipboard'
169 NgbPopoverModule, 178 NgbPopoverModule,
170 NgbTabsetModule, 179 NgbTabsetModule,
171 NgbTooltipModule, 180 NgbTooltipModule,
181 NgbCollapseModule,
172 182
173 ClipboardModule, 183 ClipboardModule,
174 184
@@ -218,7 +228,7 @@ import { ClipboardModule } from 'ngx-clipboard'
218 ConfirmComponent, 228 ConfirmComponent,
219 229
220 GlobalIconComponent, 230 GlobalIconComponent,
221 ImageUploadComponent, 231 PreviewUploadComponent,
222 232
223 NumberFormatterPipe, 233 NumberFormatterPipe,
224 ObjectLengthPipe, 234 ObjectLengthPipe,
@@ -271,6 +281,8 @@ import { ClipboardModule } from 'ngx-clipboard'
271 281
272 UserNotificationService, 282 UserNotificationService,
273 283
284 FollowService,
285
274 I18n 286 I18n
275 ] 287 ]
276}) 288})
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/users/user-notifications.component.scss
index 88f38d9cf..0c6c70d98 100644
--- a/client/src/app/shared/users/user-notifications.component.scss
+++ b/client/src/app/shared/users/user-notifications.component.scss
@@ -14,6 +14,7 @@
14 font-size: inherit; 14 font-size: inherit;
15 padding: 15px 5px 15px 10px; 15 padding: 15px 5px 15px 10px;
16 border-bottom: 1px solid $separator-border-color; 16 border-bottom: 1px solid $separator-border-color;
17 word-break: break-word;
17 18
18 &.unread { 19 &.unread {
19 background-color: rgba(0, 0, 0, 0.05); 20 background-color: rgba(0, 0, 0, 0.05);
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts
index e3ed2dfbd..14d13959a 100644
--- a/client/src/app/shared/users/user.model.ts
+++ b/client/src/app/shared/users/user.model.ts
@@ -8,6 +8,7 @@ export class User implements UserServerModel {
8 id: number 8 id: number
9 username: string 9 username: string
10 email: string 10 email: string
11 pendingEmail: string | null
11 emailVerified: boolean 12 emailVerified: boolean
12 nsfwPolicy: NSFWPolicyType 13 nsfwPolicy: NSFWPolicyType
13 14
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts
index cc5c051f1..41ee87197 100644
--- a/client/src/app/shared/users/user.service.ts
+++ b/client/src/app/shared/users/user.service.ts
@@ -9,6 +9,7 @@ import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
9import { SortMeta } from 'primeng/api' 9import { SortMeta } from 'primeng/api'
10import { BytesPipe } from 'ngx-pipes' 10import { BytesPipe } from 'ngx-pipes'
11import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
12import { UserRegister } from '@shared/models/users/user-register.model'
12 13
13@Injectable() 14@Injectable()
14export class UserService { 15export class UserService {
@@ -37,6 +38,20 @@ export class UserService {
37 ) 38 )
38 } 39 }
39 40
41 changeEmail (password: string, newEmail: string) {
42 const url = UserService.BASE_USERS_URL + 'me'
43 const body: UserUpdateMe = {
44 currentPassword: password,
45 email: newEmail
46 }
47
48 return this.authHttp.put(url, body)
49 .pipe(
50 map(this.restExtractor.extractDataBool),
51 catchError(err => this.restExtractor.handleError(err))
52 )
53 }
54
40 updateMyProfile (profile: UserUpdateMe) { 55 updateMyProfile (profile: UserUpdateMe) {
41 const url = UserService.BASE_USERS_URL + 'me' 56 const url = UserService.BASE_USERS_URL + 'me'
42 57
@@ -64,7 +79,7 @@ export class UserService {
64 .pipe(catchError(err => this.restExtractor.handleError(err))) 79 .pipe(catchError(err => this.restExtractor.handleError(err)))
65 } 80 }
66 81
67 signup (userCreate: UserCreate) { 82 signup (userCreate: UserRegister) {
68 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate) 83 return this.authHttp.post(UserService.BASE_USERS_URL + 'register', userCreate)
69 .pipe( 84 .pipe(
70 map(this.restExtractor.extractDataBool), 85 map(this.restExtractor.extractDataBool),
@@ -103,10 +118,11 @@ export class UserService {
103 ) 118 )
104 } 119 }
105 120
106 verifyEmail (userId: number, verificationString: string) { 121 verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) {
107 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` 122 const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email`
108 const body = { 123 const body = {
109 verificationString 124 verificationString,
125 isPendingEmail
110 } 126 }
111 127
112 return this.authHttp.post(url, body) 128 return this.authHttp.post(url, body)
@@ -135,6 +151,22 @@ export class UserService {
135 .pipe(catchError(res => this.restExtractor.handleError(res))) 151 .pipe(catchError(res => this.restExtractor.handleError(res)))
136 } 152 }
137 153
154 getNewUsername (oldDisplayName: string, newDisplayName: string, currentUsername: string) {
155 // Don't update display name, the user seems to have changed it
156 if (this.displayNameToUsername(oldDisplayName) !== currentUsername) return currentUsername
157
158 return this.displayNameToUsername(newDisplayName)
159 }
160
161 displayNameToUsername (displayName: string) {
162 if (!displayName) return ''
163
164 return displayName
165 .toLowerCase()
166 .replace(/\s/g, '_')
167 .replace(/[^a-z0-9_.]/g, '')
168 }
169
138 /* ###### Admin methods ###### */ 170 /* ###### Admin methods ###### */
139 171
140 addUser (userCreate: UserCreate) { 172 addUser (userCreate: UserCreate) {
diff --git a/client/src/app/shared/video-channel/video-channel.service.ts b/client/src/app/shared/video-channel/video-channel.service.ts
index d0bec649a..0168d37d9 100644
--- a/client/src/app/shared/video-channel/video-channel.service.ts
+++ b/client/src/app/shared/video-channel/video-channel.service.ts
@@ -2,7 +2,7 @@ import { catchError, map, tap } from 'rxjs/operators'
2import { Injectable } from '@angular/core' 2import { Injectable } from '@angular/core'
3import { Observable, ReplaySubject } from 'rxjs' 3import { Observable, ReplaySubject } from 'rxjs'
4import { RestExtractor } from '../rest/rest-extractor.service' 4import { RestExtractor } from '../rest/rest-extractor.service'
5import { HttpClient } from '@angular/common/http' 5import { HttpClient, HttpParams } from '@angular/common/http'
6import { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos' 6import { VideoChannel as VideoChannelServer, VideoChannelCreate, VideoChannelUpdate } from '../../../../../shared/models/videos'
7import { AccountService } from '../account/account.service' 7import { AccountService } from '../account/account.service'
8import { ResultList } from '../../../../../shared' 8import { ResultList } from '../../../../../shared'
@@ -10,6 +10,8 @@ import { VideoChannel } from './video-channel.model'
10import { environment } from '../../../environments/environment' 10import { environment } from '../../../environments/environment'
11import { Account } from '@app/shared/account/account.model' 11import { Account } from '@app/shared/account/account.model'
12import { Avatar } from '../../../../../shared/models/avatars/avatar.model' 12import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
13import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
14import { RestService } from '@app/shared/rest'
13 15
14@Injectable() 16@Injectable()
15export class VideoChannelService { 17export class VideoChannelService {
@@ -29,6 +31,7 @@ export class VideoChannelService {
29 31
30 constructor ( 32 constructor (
31 private authHttp: HttpClient, 33 private authHttp: HttpClient,
34 private restService: RestService,
32 private restExtractor: RestExtractor 35 private restExtractor: RestExtractor
33 ) { } 36 ) { }
34 37
@@ -41,8 +44,16 @@ export class VideoChannelService {
41 ) 44 )
42 } 45 }
43 46
44 listAccountVideoChannels (account: Account): Observable<ResultList<VideoChannel>> { 47 listAccountVideoChannels (account: Account, componentPagination?: ComponentPagination): Observable<ResultList<VideoChannel>> {
45 return this.authHttp.get<ResultList<VideoChannelServer>>(AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels') 48 const pagination = componentPagination
49 ? this.restService.componentPaginationToRestPagination(componentPagination)
50 : { start: 0, count: 20 }
51
52 let params = new HttpParams()
53 params = this.restService.addRestGetParams(params, pagination)
54
55 const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-channels'
56 return this.authHttp.get<ResultList<VideoChannelServer>>(url, { params })
46 .pipe( 57 .pipe(
47 map(res => VideoChannelService.extractVideoChannels(res)), 58 map(res => VideoChannelService.extractVideoChannels(res)),
48 catchError(err => this.restExtractor.handleError(err)) 59 catchError(err => this.restExtractor.handleError(err))
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index 268677977..efd369bca 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -6,7 +6,7 @@
6 </div> 6 </div>
7 </div> 7 </div>
8 8
9 <my-feed [syndicationItems]="syndicationItems"></my-feed> 9 <my-feed *ngIf="titlePage" [syndicationItems]="syndicationItems"></my-feed>
10 10
11 <div class="moderation-block" *ngIf="displayModerationBlock"> 11 <div class="moderation-block" *ngIf="displayModerationBlock">
12 <my-peertube-checkbox 12 <my-peertube-checkbox
@@ -22,11 +22,17 @@
22 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" 22 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
23 class="videos" 23 class="videos"
24 > 24 >
25 <my-video-miniature 25 <ng-container *ngFor="let video of videos; trackBy: videoById;">
26 *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType" 26 <div class="date-title" *ngIf="getCurrentGroupedDateLabel(video)">
27 [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions" 27 {{ getCurrentGroupedDateLabel(video) }}
28 (videoBlacklisted)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)" 28 </div>
29 > 29
30 </my-video-miniature> 30 <my-video-miniature
31 [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"
32 [displayVideoActions]="displayVideoActions" [displayOptions]="displayOptions"
33 (videoBlacklisted)="removeVideoFromArray(video)" (videoRemoved)="removeVideoFromArray(video)"
34 >
35 </my-video-miniature>
36 </ng-container>
31 </div> 37 </div>
32</div> 38</div>
diff --git a/client/src/app/shared/video/abstract-video-list.scss b/client/src/app/shared/video/abstract-video-list.scss
index 9d481d6e4..98b80fdfd 100644
--- a/client/src/app/shared/video/abstract-video-list.scss
+++ b/client/src/app/shared/video/abstract-video-list.scss
@@ -24,33 +24,19 @@
24 } 24 }
25} 25}
26 26
27.margin-content { 27.date-title {
28 width: $video-miniature-width * 6; 28 font-size: 16px;
29 margin: auto !important; 29 font-weight: $font-semibold;
30 30 margin-bottom: 20px;
31 @media screen and (max-width: 1800px) { 31 margin-top: -10px;
32 width: $video-miniature-width * 5; 32 padding-top: 20px;
33 }
34 33
35 @media screen and (max-width: 1800px - $video-miniature-width) { 34 &:not(:first-child) {
36 width: $video-miniature-width * 4; 35 border-top: 1px solid $separator-border-color;
37 } 36 }
37}
38 38
39 @media screen and (max-width: 1800px - (2* $video-miniature-width)) { 39.margin-content {
40 width: $video-miniature-width * 3; 40 @include adapt-margin-content-width;
41 }
42
43 @media screen and (max-width: 1800px - (3* $video-miniature-width)) {
44 width: $video-miniature-width * 2;
45 }
46
47 @media screen and (max-width: 500px) {
48 width: auto;
49 margin: 0 !important;
50
51 .videos {
52 @include video-miniature-small-screen;
53 }
54 }
55} 41}
56 42
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index fa9d38735..dc8f9cda9 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -11,6 +11,17 @@ import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/vid
11import { Syndication } from '@app/shared/video/syndication.model' 11import { Syndication } from '@app/shared/video/syndication.model'
12import { Notifier, ServerService } from '@app/core' 12import { Notifier, ServerService } from '@app/core'
13import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' 13import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
14import { I18n } from '@ngx-translate/i18n-polyfill'
15import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
16
17enum GroupDate {
18 UNKNOWN = 0,
19 TODAY = 1,
20 YESTERDAY = 2,
21 LAST_WEEK = 3,
22 LAST_MONTH = 4,
23 OLDER = 5
24}
14 25
15export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook { 26export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook {
16 pagination: ComponentPagination = { 27 pagination: ComponentPagination = {
@@ -31,6 +42,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
31 displayModerationBlock = false 42 displayModerationBlock = false
32 titleTooltip: string 43 titleTooltip: string
33 displayVideoActions = true 44 displayVideoActions = true
45 groupByDate = false
34 46
35 disabled = false 47 disabled = false
36 48
@@ -50,11 +62,15 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
50 protected abstract serverService: ServerService 62 protected abstract serverService: ServerService
51 protected abstract screenService: ScreenService 63 protected abstract screenService: ScreenService
52 protected abstract router: Router 64 protected abstract router: Router
65 protected abstract i18n: I18n
53 abstract titlePage: string 66 abstract titlePage: string
54 67
55 private resizeSubscription: Subscription 68 private resizeSubscription: Subscription
56 private angularState: number 69 private angularState: number
57 70
71 private groupedDateLabels: { [id in GroupDate]: string }
72 private groupedDates: { [id: number]: GroupDate } = {}
73
58 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }> 74 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
59 75
60 abstract generateSyndicationList (): void 76 abstract generateSyndicationList (): void
@@ -64,6 +80,15 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
64 } 80 }
65 81
66 ngOnInit () { 82 ngOnInit () {
83 this.groupedDateLabels = {
84 [GroupDate.UNKNOWN]: null,
85 [GroupDate.TODAY]: this.i18n('Today'),
86 [GroupDate.YESTERDAY]: this.i18n('Yesterday'),
87 [GroupDate.LAST_WEEK]: this.i18n('Last week'),
88 [GroupDate.LAST_MONTH]: this.i18n('Last month'),
89 [GroupDate.OLDER]: this.i18n('Older')
90 }
91
67 // Subscribe to route changes 92 // Subscribe to route changes
68 const routeParams = this.route.snapshot.queryParams 93 const routeParams = this.route.snapshot.queryParams
69 this.loadRouteParams(routeParams) 94 this.loadRouteParams(routeParams)
@@ -113,6 +138,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
113 this.pagination.totalItems = totalVideos 138 this.pagination.totalItems = totalVideos
114 this.videos = this.videos.concat(videos) 139 this.videos = this.videos.concat(videos)
115 140
141 if (this.groupByDate) this.buildGroupedDateLabels()
142
116 this.onMoreVideos() 143 this.onMoreVideos()
117 }, 144 },
118 145
@@ -134,6 +161,59 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor
134 this.videos = this.videos.filter(v => v.id !== video.id) 161 this.videos = this.videos.filter(v => v.id !== video.id)
135 } 162 }
136 163
164 buildGroupedDateLabels () {
165 let currentGroupedDate: GroupDate = GroupDate.UNKNOWN
166
167 for (const video of this.videos) {
168 const publishedDate = video.publishedAt
169
170 if (currentGroupedDate <= GroupDate.TODAY && isToday(publishedDate)) {
171 if (currentGroupedDate === GroupDate.TODAY) continue
172
173 currentGroupedDate = GroupDate.TODAY
174 this.groupedDates[ video.id ] = currentGroupedDate
175 continue
176 }
177
178 if (currentGroupedDate <= GroupDate.YESTERDAY && isYesterday(publishedDate)) {
179 if (currentGroupedDate === GroupDate.YESTERDAY) continue
180
181 currentGroupedDate = GroupDate.YESTERDAY
182 this.groupedDates[ video.id ] = currentGroupedDate
183 continue
184 }
185
186 if (currentGroupedDate <= GroupDate.LAST_WEEK && isLastWeek(publishedDate)) {
187 if (currentGroupedDate === GroupDate.LAST_WEEK) continue
188
189 currentGroupedDate = GroupDate.LAST_WEEK
190 this.groupedDates[ video.id ] = currentGroupedDate
191 continue
192 }
193
194 if (currentGroupedDate <= GroupDate.LAST_MONTH && isLastMonth(publishedDate)) {
195 if (currentGroupedDate === GroupDate.LAST_MONTH) continue
196
197 currentGroupedDate = GroupDate.LAST_MONTH
198 this.groupedDates[ video.id ] = currentGroupedDate
199 continue
200 }
201
202 if (currentGroupedDate <= GroupDate.OLDER) {
203 if (currentGroupedDate === GroupDate.OLDER) continue
204
205 currentGroupedDate = GroupDate.OLDER
206 this.groupedDates[ video.id ] = currentGroupedDate
207 }
208 }
209 }
210
211 getCurrentGroupedDateLabel (video: Video) {
212 if (this.groupByDate === false) return undefined
213
214 return this.groupedDateLabels[this.groupedDates[video.id]]
215 }
216
137 // On videos hook for children that want to do something 217 // On videos hook for children that want to do something
138 protected onMoreVideos () { /* empty */ } 218 protected onMoreVideos () { /* empty */ }
139 219
diff --git a/client/src/app/shared/video/modals/video-download.component.html b/client/src/app/shared/video/modals/video-download.component.html
index dd01c1388..935d01330 100644
--- a/client/src/app/shared/video/modals/video-download.component.html
+++ b/client/src/app/shared/video/modals/video-download.component.html
@@ -31,11 +31,6 @@
31 <input type="radio" name="download" id="download-torrent" [(ngModel)]="downloadType" value="torrent"> 31 <input type="radio" name="download" id="download-torrent" [(ngModel)]="downloadType" value="torrent">
32 <label i18n for="download-torrent">Torrent (.torrent file)</label> 32 <label i18n for="download-torrent">Torrent (.torrent file)</label>
33 </div> 33 </div>
34
35 <div class="peertube-radio-container">
36 <input type="radio" name="download" id="download-magnet" [(ngModel)]="downloadType" value="magnet">
37 <label i18n for="download-magnet">Torrent (magnet link)</label>
38 </div>
39 </div> 34 </div>
40 </div> 35 </div>
41 36
diff --git a/client/src/app/shared/video/modals/video-download.component.ts b/client/src/app/shared/video/modals/video-download.component.ts
index d6d10d29e..16f3621b4 100644
--- a/client/src/app/shared/video/modals/video-download.component.ts
+++ b/client/src/app/shared/video/modals/video-download.component.ts
@@ -1,6 +1,6 @@
1import { Component, ElementRef, ViewChild } from '@angular/core' 1import { Component, ElementRef, ViewChild } from '@angular/core'
2import { VideoDetails } from '../../../shared/video/video-details.model' 2import { VideoDetails } from '../../../shared/video/video-details.model'
3import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 3import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'
4import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
6 6
@@ -12,10 +12,11 @@ import { Notifier } from '@app/core'
12export class VideoDownloadComponent { 12export class VideoDownloadComponent {
13 @ViewChild('modal') modal: ElementRef 13 @ViewChild('modal') modal: ElementRef
14 14
15 downloadType: 'direct' | 'torrent' | 'magnet' = 'torrent' 15 downloadType: 'direct' | 'torrent' = 'torrent'
16 resolutionId: number | string = -1 16 resolutionId: number | string = -1
17 17
18 video: VideoDetails 18 video: VideoDetails
19 activeModal: NgbActiveModal
19 20
20 constructor ( 21 constructor (
21 private notifier: Notifier, 22 private notifier: Notifier,
@@ -26,9 +27,7 @@ export class VideoDownloadComponent {
26 show (video: VideoDetails) { 27 show (video: VideoDetails) {
27 this.video = video 28 this.video = video
28 29
29 const m = this.modalService.open(this.modal) 30 this.activeModal = this.modalService.open(this.modal)
30 m.result.then(() => this.onClose())
31 .catch(() => this.onClose())
32 31
33 this.resolutionId = this.video.files[0].resolution.id 32 this.resolutionId = this.video.files[0].resolution.id
34 } 33 }
@@ -39,6 +38,7 @@ export class VideoDownloadComponent {
39 38
40 download () { 39 download () {
41 window.location.assign(this.getLink()) 40 window.location.assign(this.getLink())
41 this.activeModal.close()
42 } 42 }
43 43
44 getLink () { 44 getLink () {
@@ -57,9 +57,6 @@ export class VideoDownloadComponent {
57 57
58 case 'torrent': 58 case 'torrent':
59 return file.torrentDownloadUrl 59 return file.torrentDownloadUrl
60
61 case 'magnet':
62 return file.magnetUri
63 } 60 }
64 } 61 }
65 62
diff --git a/client/src/app/shared/video/video-details.model.ts b/client/src/app/shared/video/video-details.model.ts
index 22f024656..e4d443a06 100644
--- a/client/src/app/shared/video/video-details.model.ts
+++ b/client/src/app/shared/video/video-details.model.ts
@@ -1,5 +1,4 @@
1import { UserRight, VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared' 1import { VideoConstant, VideoDetails as VideoDetailsServerModel, VideoFile, VideoState } from '../../../../../shared'
2import { AuthUser } from '../../core'
3import { Video } from '../../shared/video/video.model' 2import { Video } from '../../shared/video/video.model'
4import { Account } from '@app/shared/account/account.model' 3import { Account } from '@app/shared/account/account.model'
5import { VideoChannel } from '@app/shared/video-channel/video-channel.model' 4import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index 1f633d427..67d8e7711 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -85,6 +85,11 @@ export class VideoEdit implements VideoUpdate {
85 const originallyPublishedAt = new Date(values['originallyPublishedAt']) 85 const originallyPublishedAt = new Date(values['originallyPublishedAt'])
86 this.originallyPublishedAt = originallyPublishedAt.toISOString() 86 this.originallyPublishedAt = originallyPublishedAt.toISOString()
87 } 87 }
88
89 // Use the same file than the preview for the thumbnail
90 if (this.previewfile) {
91 this.thumbnailfile = this.previewfile
92 }
88 } 93 }
89 94
90 toFormPatch () { 95 toFormPatch () {
diff --git a/client/src/app/shared/video/video.model.ts b/client/src/app/shared/video/video.model.ts
index 0cef3eb8f..6f9de9241 100644
--- a/client/src/app/shared/video/video.model.ts
+++ b/client/src/app/shared/video/video.model.ts
@@ -52,7 +52,6 @@ export class Video implements VideoServerModel {
52 52
53 account: { 53 account: {
54 id: number 54 id: number
55 uuid: string
56 name: string 55 name: string
57 displayName: string 56 displayName: string
58 url: string 57 url: string
@@ -62,7 +61,6 @@ export class Video implements VideoServerModel {
62 61
63 channel: { 62 channel: {
64 id: number 63 id: number
65 uuid: string
66 name: string 64 name: string
67 displayName: string 65 displayName: string
68 url: string 66 url: string
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts
index 955ebca9f..d69f7b70e 100644
--- a/client/src/app/shared/video/videos-selection.component.ts
+++ b/client/src/app/shared/video/videos-selection.component.ts
@@ -20,6 +20,7 @@ import { Video } from '@app/shared/video/video.model'
20import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' 20import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive'
21import { VideoSortField } from '@app/shared/video/sort-field.type' 21import { VideoSortField } from '@app/shared/video/sort-field.type'
22import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 22import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
23import { I18n } from '@ngx-translate/i18n-polyfill'
23 24
24export type SelectionType = { [ id: number ]: boolean } 25export type SelectionType = { [ id: number ]: boolean }
25 26
@@ -44,6 +45,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni
44 globalButtonsTemplate: TemplateRef<any> 45 globalButtonsTemplate: TemplateRef<any>
45 46
46 constructor ( 47 constructor (
48 protected i18n: I18n,
47 protected router: Router, 49 protected router: Router,
48 protected route: ActivatedRoute, 50 protected route: ActivatedRoute,
49 protected notifier: Notifier, 51 protected notifier: Notifier,
diff --git a/client/src/app/signup/index.ts b/client/src/app/signup/index.ts
deleted file mode 100644
index b0aca9723..000000000
--- a/client/src/app/signup/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1export * from './signup-routing.module'
2export * from './signup.component'
3export * from './signup.module'
diff --git a/client/src/app/signup/signup.component.html b/client/src/app/signup/signup.component.html
deleted file mode 100644
index 07d24b381..000000000
--- a/client/src/app/signup/signup.component.html
+++ /dev/null
@@ -1,72 +0,0 @@
1<div class="margin-content">
2
3 <div i18n class="title-page title-page-single">
4 Create an account
5 </div>
6
7 <div *ngIf="info" class="alert alert-info">{{ info }}</div>
8 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
9
10 <div class="d-flex justify-content-left flex-wrap">
11 <form role="form" (ngSubmit)="signup()" [formGroup]="form">
12 <div class="form-group">
13 <label for="username" i18n>Username</label>
14
15 <div class="input-group">
16 <input
17 type="text" id="username" i18n-placeholder placeholder="Example: jane_doe"
18 formControlName="username" [ngClass]="{ 'input-error': formErrors['username'] }"
19 >
20 <div class="input-group-append">
21 <span class="input-group-text">@{{ instanceHost }}</span>
22 </div>
23 </div>
24
25 <div *ngIf="formErrors.username" class="form-error">
26 {{ formErrors.username }}
27 </div>
28 </div>
29
30 <div class="form-group">
31 <label for="email" i18n>Email</label>
32 <input
33 type="text" id="email" i18n-placeholder placeholder="Email"
34 formControlName="email" [ngClass]="{ 'input-error': formErrors['email'] }"
35 >
36 <div *ngIf="formErrors.email" class="form-error">
37 {{ formErrors.email }}
38 </div>
39 </div>
40
41 <div class="form-group">
42 <label for="password" i18n>Password</label>
43 <input
44 type="password" id="password" i18n-placeholder placeholder="Password"
45 formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }"
46 >
47 <div *ngIf="formErrors.password" class="form-error">
48 {{ formErrors.password }}
49 </div>
50 </div>
51
52 <div class="form-group form-group-terms">
53 <my-peertube-checkbox
54 inputName="terms" formControlName="terms"
55 i18n-labelHtml labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance"
56 ></my-peertube-checkbox>
57
58 <div *ngIf="formErrors.terms" class="form-error">
59 {{ formErrors.terms }}
60 </div>
61 </div>
62
63 <input type="submit" i18n-value value="Signup" [disabled]="!form.valid || signupDone">
64 </form>
65
66 <div>
67 <label i18n>Features found on this instance</label>
68 <my-instance-features-table></my-instance-features-table>
69 </div>
70 </div>
71
72</div>
diff --git a/client/src/app/signup/signup.component.scss b/client/src/app/signup/signup.component.scss
deleted file mode 100644
index 90e1e8e74..000000000
--- a/client/src/app/signup/signup.component.scss
+++ /dev/null
@@ -1,39 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4my-instance-features-table {
5 display: block;
6
7 margin-bottom: 40px;
8}
9
10form {
11 margin: 0 60px 40px 0;
12}
13
14.form-group-terms {
15 margin: 30px 0;
16}
17
18.input-group {
19 @include peertube-input-group(400px);
20}
21
22.input-group-append {
23 height: 30px;
24}
25
26input:not([type=submit]) {
27 @include peertube-input-text(400px);
28 display: block;
29
30 &#username {
31 width: auto;
32 flex-grow: 1;
33 }
34}
35
36input[type=submit] {
37 @include peertube-button;
38 @include orange-button;
39}
diff --git a/client/src/app/signup/signup.component.ts b/client/src/app/signup/signup.component.ts
deleted file mode 100644
index 13941ec79..000000000
--- a/client/src/app/signup/signup.component.ts
+++ /dev/null
@@ -1,78 +0,0 @@
1import { Component, OnInit } from '@angular/core'
2import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
3import { UserCreate } from '../../../../shared'
4import { FormReactive, UserService, UserValidatorsService } from '../shared'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
7
8@Component({
9 selector: 'my-signup',
10 templateUrl: './signup.component.html',
11 styleUrls: [ './signup.component.scss' ]
12})
13export class SignupComponent extends FormReactive implements OnInit {
14 info: string = null
15 error: string = null
16 signupDone = false
17
18 constructor (
19 protected formValidatorService: FormValidatorService,
20 private authService: AuthService,
21 private userValidatorsService: UserValidatorsService,
22 private notifier: Notifier,
23 private userService: UserService,
24 private serverService: ServerService,
25 private redirectService: RedirectService,
26 private i18n: I18n
27 ) {
28 super()
29 }
30
31 get instanceHost () {
32 return window.location.host
33 }
34
35 get requiresEmailVerification () {
36 return this.serverService.getConfig().signup.requiresEmailVerification
37 }
38
39 ngOnInit () {
40 this.buildForm({
41 username: this.userValidatorsService.USER_USERNAME,
42 password: this.userValidatorsService.USER_PASSWORD,
43 email: this.userValidatorsService.USER_EMAIL,
44 terms: this.userValidatorsService.USER_TERMS
45 })
46 }
47
48 signup () {
49 this.error = null
50
51 const userCreate: UserCreate = this.form.value
52
53 this.userService.signup(userCreate).subscribe(
54 () => {
55 this.signupDone = true
56
57 if (this.requiresEmailVerification) {
58 this.info = this.i18n('Welcome! Now please check your emails to verify your account and complete signup.')
59 return
60 }
61
62 // Auto login
63 this.authService.login(userCreate.username, userCreate.password)
64 .subscribe(
65 () => {
66 this.notifier.success(this.i18n('You are now logged in as {{username}}!', { username: userCreate.username }))
67
68 this.redirectService.redirectToHomepage()
69 },
70
71 err => this.error = err.message
72 )
73 },
74
75 err => this.error = err.message
76 )
77 }
78}
diff --git a/client/src/app/signup/signup.module.ts b/client/src/app/signup/signup.module.ts
deleted file mode 100644
index 61560ddcf..000000000
--- a/client/src/app/signup/signup.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
1import { NgModule } from '@angular/core'
2
3import { SignupRoutingModule } from './signup-routing.module'
4import { SignupComponent } from './signup.component'
5import { SharedModule } from '../shared'
6
7@NgModule({
8 imports: [
9 SignupRoutingModule,
10 SharedModule
11 ],
12
13 declarations: [
14 SignupComponent
15 ],
16
17 exports: [
18 SignupComponent
19 ],
20
21 providers: [
22 ]
23})
24export class SignupModule { }
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html
index 99695204d..28572d611 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -187,18 +187,14 @@
187 <ng-template ngbTabContent> 187 <ng-template ngbTabContent>
188 <div class="row advanced-settings"> 188 <div class="row advanced-settings">
189 <div class="col-md-12 col-xl-8"> 189 <div class="col-md-12 col-xl-8">
190 <div class="form-group">
191 <my-image-upload
192 i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
193 previewWidth="200px" previewHeight="110px"
194 ></my-image-upload>
195 </div>
196 190
197 <div class="form-group"> 191 <div class="form-group">
198 <my-image-upload 192 <label i18n for="previewfile">Video preview</label>
199 i18n-inputLabel inputLabel="Upload preview" inputName="previewfile" formControlName="previewfile" 193
194 <my-preview-upload
195 i18n-inputLabel inputLabel="Edit" inputName="previewfile" formControlName="previewfile"
200 previewWidth="360px" previewHeight="200px" 196 previewWidth="360px" previewHeight="200px"
201 ></my-image-upload> 197 ></my-preview-upload>
202 </div> 198 </div>
203 199
204 <div class="form-group"> 200 <div class="form-group">
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
index 8345645f6..cea352bfb 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -13,6 +13,7 @@ import { VideoCaptionAddModalComponent } from '@app/videos/+video-edit/shared/vi
13import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' 13import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
14import { removeElementFromArray } from '@app/shared/misc/utils' 14import { removeElementFromArray } from '@app/shared/misc/utils'
15import { VideoConstant, VideoPrivacy } from '../../../../../../shared' 15import { VideoConstant, VideoPrivacy } from '../../../../../../shared'
16import { VideoService } from '@app/shared/video/video.service'
16 17
17@Component({ 18@Component({
18 selector: 'my-video-edit', 19 selector: 'my-video-edit',
@@ -23,7 +24,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
23 @Input() form: FormGroup 24 @Input() form: FormGroup
24 @Input() formErrors: { [ id: string ]: string } = {} 25 @Input() formErrors: { [ id: string ]: string } = {}
25 @Input() validationMessages: FormReactiveValidationMessages = {} 26 @Input() validationMessages: FormReactiveValidationMessages = {}
26 @Input() videoPrivacies: VideoConstant<VideoPrivacy>[] = []
27 @Input() userVideoChannels: { id: number, label: string, support: string }[] = [] 27 @Input() userVideoChannels: { id: number, label: string, support: string }[] = []
28 @Input() schedulePublicationPossible = true 28 @Input() schedulePublicationPossible = true
29 @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] 29 @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = []
@@ -34,6 +34,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
34 // So that it can be accessed in the template 34 // So that it can be accessed in the template
35 readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY 35 readonly SPECIAL_SCHEDULED_PRIVACY = VideoEdit.SPECIAL_SCHEDULED_PRIVACY
36 36
37 videoPrivacies: VideoConstant<VideoPrivacy>[] = []
37 videoCategories: VideoConstant<number>[] = [] 38 videoCategories: VideoConstant<number>[] = []
38 videoLicences: VideoConstant<number>[] = [] 39 videoLicences: VideoConstant<number>[] = []
39 videoLanguages: VideoConstant<string>[] = [] 40 videoLanguages: VideoConstant<string>[] = []
@@ -58,6 +59,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
58 private formValidatorService: FormValidatorService, 59 private formValidatorService: FormValidatorService,
59 private videoValidatorsService: VideoValidatorsService, 60 private videoValidatorsService: VideoValidatorsService,
60 private videoCaptionService: VideoCaptionService, 61 private videoCaptionService: VideoCaptionService,
62 private videoService: VideoService,
61 private route: ActivatedRoute, 63 private route: ActivatedRoute,
62 private router: Router, 64 private router: Router,
63 private notifier: Notifier, 65 private notifier: Notifier,
@@ -100,7 +102,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
100 language: this.videoValidatorsService.VIDEO_LANGUAGE, 102 language: this.videoValidatorsService.VIDEO_LANGUAGE,
101 description: this.videoValidatorsService.VIDEO_DESCRIPTION, 103 description: this.videoValidatorsService.VIDEO_DESCRIPTION,
102 tags: null, 104 tags: null,
103 thumbnailfile: null,
104 previewfile: null, 105 previewfile: null,
105 support: this.videoValidatorsService.VIDEO_SUPPORT, 106 support: this.videoValidatorsService.VIDEO_SUPPORT,
106 schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT, 107 schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT,
@@ -133,6 +134,9 @@ export class VideoEditComponent implements OnInit, OnDestroy {
133 this.videoLicences = this.serverService.getVideoLicences() 134 this.videoLicences = this.serverService.getVideoLicences()
134 this.videoLanguages = this.serverService.getVideoLanguages() 135 this.videoLanguages = this.serverService.getVideoLanguages()
135 136
137 const privacies = this.serverService.getVideoPrivacies()
138 this.videoPrivacies = this.videoService.explainedPrivacyLabels(privacies)
139
136 this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) 140 this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id)
137 141
138 this.ngZone.runOutsideAngular(() => { 142 this.ngZone.runOutsideAngular(() => {
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
index 537d7ffa2..7a495fea5 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
+++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
@@ -58,7 +58,7 @@
58<form [hidden]="!hasImportedVideo" novalidate [formGroup]="form"> 58<form [hidden]="!hasImportedVideo" novalidate [formGroup]="form">
59 <my-video-edit 59 <my-video-edit
60 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" 60 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false"
61 [validationMessages]="validationMessages" [videoPrivacies]="explainedVideoPrivacies" [userVideoChannels]="userVideoChannels" 61 [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
62 ></my-video-edit> 62 ></my-video-edit>
63 63
64 <div class="submit-container"> 64 <div class="submit-container">
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
index d2e9f6cfe..e47624dd6 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
@@ -100,8 +100,6 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
100 previewUrl: null 100 previewUrl: null
101 })) 101 }))
102 102
103 this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
104
105 this.hydrateFormFromVideo() 103 this.hydrateFormFromVideo()
106 }, 104 },
107 105
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html
index 984b9d590..e4f19faa8 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html
+++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html
@@ -51,7 +51,7 @@
51<form [hidden]="!hasImportedVideo" novalidate [formGroup]="form"> 51<form [hidden]="!hasImportedVideo" novalidate [formGroup]="form">
52 <my-video-edit 52 <my-video-edit
53 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false" 53 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" [schedulePublicationPossible]="false"
54 [validationMessages]="validationMessages" [videoPrivacies]="explainedVideoPrivacies" [userVideoChannels]="userVideoChannels" 54 [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
55 ></my-video-edit> 55 ></my-video-edit>
56 56
57 <div class="submit-container"> 57 <div class="submit-container">
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts
index 2dffdbf0e..a5578bebd 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts
@@ -91,8 +91,6 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom
91 previewUrl: null 91 previewUrl: null
92 })) 92 }))
93 93
94 this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
95
96 this.hydrateFormFromVideo() 94 this.hydrateFormFromVideo()
97 }, 95 },
98 96
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-send.ts b/client/src/app/videos/+video-edit/video-add-components/video-send.ts
index 8401caeec..580c123a0 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-send.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-send.ts
@@ -14,7 +14,6 @@ import { CanComponentDeactivateResult } from '@app/shared/guards/can-deactivate-
14export abstract class VideoSend extends FormReactive implements OnInit { 14export abstract class VideoSend extends FormReactive implements OnInit {
15 userVideoChannels: { id: number, label: string, support: string }[] = [] 15 userVideoChannels: { id: number, label: string, support: string }[] = []
16 videoPrivacies: VideoConstant<VideoPrivacy>[] = [] 16 videoPrivacies: VideoConstant<VideoPrivacy>[] = []
17 explainedVideoPrivacies: VideoConstant<VideoPrivacy>[] = []
18 videoCaptions: VideoCaptionEdit[] = [] 17 videoCaptions: VideoCaptionEdit[] = []
19 18
20 firstStepPrivacyId = 0 19 firstStepPrivacyId = 0
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
index 536769d2f..0f904affb 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
@@ -26,6 +26,27 @@
26 </select> 26 </select>
27 </div> 27 </div>
28 </div> 28 </div>
29
30 <ng-container *ngIf="isUploadingAudioFile">
31 <div class="form-group audio-preview">
32 <label i18n for="previewfileUpload">Video background image</label>
33
34 <div i18n class="audio-image-info">
35 Image that will be merged with your audio file.
36 <br />
37 The chosen image will be definitive and cannot be modified.
38 </div>
39
40 <my-preview-upload
41 i18n-inputLabel inputLabel="Edit" inputName="previewfileUpload" [(ngModel)]="previewfileUpload"
42 previewWidth="360px" previewHeight="200px"
43 ></my-preview-upload>
44 </div>
45
46 <div class="form-group upload-audio-button">
47 <my-button className="orange-button" i18n-label [label]="getAudioUploadLabel()" icon="upload" (click)="uploadFirstStep(true)"></my-button>
48 </div>
49 </ng-container>
29 </div> 50 </div>
30</div> 51</div>
31 52
@@ -50,7 +71,7 @@
50<form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> 71<form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
51 <my-video-edit 72 <my-video-edit
52 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions" 73 [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions"
53 [validationMessages]="validationMessages" [videoPrivacies]="explainedVideoPrivacies" [userVideoChannels]="userVideoChannels" 74 [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
54 [waitTranscodingEnabled]="waitTranscodingEnabled" 75 [waitTranscodingEnabled]="waitTranscodingEnabled"
55 ></my-video-edit> 76 ></my-video-edit>
56 77
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
index 8adf8f169..684342f09 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
@@ -1,9 +1,20 @@
1@import 'variables'; 1@import 'variables';
2@import 'mixins'; 2@import 'mixins';
3 3
4.first-step-block .form-group-channel { 4.first-step-block {
5 margin-bottom: 20px; 5
6 margin-top: 35px; 6 .form-group-channel {
7 margin-bottom: 20px;
8 margin-top: 35px;
9 }
10
11 .audio-image-info {
12 margin-bottom: 10px;
13 }
14
15 .audio-preview {
16 margin: 30px 0;
17 }
7} 18}
8 19
9.upload-progress-cancel { 20.upload-progress-cancel {
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
index d6d4bad21..69fa13a2f 100644
--- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
+++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
@@ -35,8 +35,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
35 userVideoQuotaUsed = 0 35 userVideoQuotaUsed = 0
36 userVideoQuotaUsedDaily = 0 36 userVideoQuotaUsedDaily = 0
37 37
38 isUploadingAudioFile = false
38 isUploadingVideo = false 39 isUploadingVideo = false
39 isUpdatingVideo = false 40 isUpdatingVideo = false
41
40 videoUploaded = false 42 videoUploaded = false
41 videoUploadObservable: Subscription = null 43 videoUploadObservable: Subscription = null
42 videoUploadPercents = 0 44 videoUploadPercents = 0
@@ -44,7 +46,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
44 id: 0, 46 id: 0,
45 uuid: '' 47 uuid: ''
46 } 48 }
49
47 waitTranscodingEnabled = true 50 waitTranscodingEnabled = true
51 previewfileUpload: File
48 52
49 error: string 53 error: string
50 54
@@ -100,6 +104,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
100 } 104 }
101 } 105 }
102 106
107 getVideoFile () {
108 return this.videofileInput.nativeElement.files[0]
109 }
110
111 getAudioUploadLabel () {
112 const videofile = this.getVideoFile()
113 if (!videofile) return this.i18n('Upload')
114
115 return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name })
116 }
117
103 fileChange () { 118 fileChange () {
104 this.uploadFirstStep() 119 this.uploadFirstStep()
105 } 120 }
@@ -114,38 +129,15 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
114 } 129 }
115 } 130 }
116 131
117 uploadFirstStep () { 132 uploadFirstStep (clickedOnButton = false) {
118 const videofile = this.videofileInput.nativeElement.files[0] 133 const videofile = this.getVideoFile()
119 if (!videofile) return 134 if (!videofile) return
120 135
121 // Check global user quota 136 if (!this.checkGlobalUserQuota(videofile)) return
122 const bytePipes = new BytesPipe() 137 if (!this.checkDailyUserQuota(videofile)) return
123 const videoQuota = this.authService.getUser().videoQuota
124 if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
125 const msg = this.i18n(
126 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
127 {
128 videoSize: bytePipes.transform(videofile.size, 0),
129 videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
130 videoQuota: bytePipes.transform(videoQuota, 0)
131 }
132 )
133 this.notifier.error(msg)
134 return
135 }
136 138
137 // Check daily user quota 139 if (clickedOnButton === false && this.isAudioFile(videofile.name)) {
138 const videoQuotaDaily = this.authService.getUser().videoQuotaDaily 140 this.isUploadingAudioFile = true
139 if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
140 const msg = this.i18n(
141 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
142 {
143 videoSize: bytePipes.transform(videofile.size, 0),
144 quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
145 quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
146 }
147 )
148 this.notifier.error(msg)
149 return 141 return
150 } 142 }
151 143
@@ -180,6 +172,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
180 formData.append('channelId', '' + channelId) 172 formData.append('channelId', '' + channelId)
181 formData.append('videofile', videofile) 173 formData.append('videofile', videofile)
182 174
175 if (this.previewfileUpload) {
176 formData.append('previewfile', this.previewfileUpload)
177 formData.append('thumbnailfile', this.previewfileUpload)
178 }
179
183 this.isUploadingVideo = true 180 this.isUploadingVideo = true
184 this.firstStepDone.emit(name) 181 this.firstStepDone.emit(name)
185 182
@@ -187,11 +184,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
187 name, 184 name,
188 privacy, 185 privacy,
189 nsfw, 186 nsfw,
190 channelId 187 channelId,
188 previewfile: this.previewfileUpload
191 }) 189 })
192 190
193 this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
194
195 this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe( 191 this.videoUploadObservable = this.videoService.uploadVideo(formData).subscribe(
196 event => { 192 event => {
197 if (event.type === HttpEventType.UploadProgress) { 193 if (event.type === HttpEventType.UploadProgress) {
@@ -251,4 +247,52 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
251 } 247 }
252 ) 248 )
253 } 249 }
250
251 private checkGlobalUserQuota (videofile: File) {
252 const bytePipes = new BytesPipe()
253
254 // Check global user quota
255 const videoQuota = this.authService.getUser().videoQuota
256 if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
257 const msg = this.i18n(
258 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})',
259 {
260 videoSize: bytePipes.transform(videofile.size, 0),
261 videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0),
262 videoQuota: bytePipes.transform(videoQuota, 0)
263 }
264 )
265 this.notifier.error(msg)
266
267 return false
268 }
269
270 return true
271 }
272
273 private checkDailyUserQuota (videofile: File) {
274 const bytePipes = new BytesPipe()
275
276 // Check daily user quota
277 const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
278 if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
279 const msg = this.i18n(
280 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})',
281 {
282 videoSize: bytePipes.transform(videofile.size, 0),
283 quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0),
284 quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
285 }
286 )
287 this.notifier.error(msg)
288
289 return false
290 }
291
292 return true
293 }
294
295 private isAudioFile (filename: string) {
296 return filename.endsWith('.mp3') || filename.endsWith('.flac') || filename.endsWith('.ogg')
297 }
254} 298}
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html
index b5cab7ed5..aa148311f 100644
--- a/client/src/app/videos/+video-edit/video-update.component.html
+++ b/client/src/app/videos/+video-edit/video-update.component.html
@@ -7,7 +7,7 @@
7 7
8 <my-video-edit 8 <my-video-edit
9 [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible" 9 [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible"
10 [validationMessages]="validationMessages" [videoPrivacies]="explainedVideoPrivacies" [userVideoChannels]="userVideoChannels" 10 [validationMessages]="validationMessages" [userVideoChannels]="userVideoChannels"
11 [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled" 11 [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled"
12 ></my-video-edit> 12 ></my-video-edit>
13 13
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts
index 10f797d02..81c66ff20 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -3,7 +3,6 @@ import { Component, HostListener, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { LoadingBarService } from '@ngx-loading-bar/core' 4import { LoadingBarService } from '@ngx-loading-bar/core'
5import { Notifier } from '@app/core' 5import { Notifier } from '@app/core'
6import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos'
7import { ServerService } from '../../core' 6import { ServerService } from '../../core'
8import { FormReactive } from '../../shared' 7import { FormReactive } from '../../shared'
9import { VideoEdit } from '../../shared/video/video-edit.model' 8import { VideoEdit } from '../../shared/video/video-edit.model'
@@ -13,6 +12,7 @@ import { FormValidatorService } from '@app/shared/forms/form-validators/form-val
13import { VideoCaptionService } from '@app/shared/video-caption' 12import { VideoCaptionService } from '@app/shared/video-caption'
14import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model' 13import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
15import { VideoDetails } from '@app/shared/video/video-details.model' 14import { VideoDetails } from '@app/shared/video/video-details.model'
15import { VideoPrivacy } from '@shared/models'
16 16
17@Component({ 17@Component({
18 selector: 'my-videos-update', 18 selector: 'my-videos-update',
@@ -23,8 +23,6 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
23 video: VideoEdit 23 video: VideoEdit
24 24
25 isUpdatingVideo = false 25 isUpdatingVideo = false
26 videoPrivacies: VideoConstant<VideoPrivacy>[] = []
27 explainedVideoPrivacies: VideoConstant<VideoPrivacy>[] = []
28 userVideoChannels: { id: number, label: string, support: string }[] = [] 26 userVideoChannels: { id: number, label: string, support: string }[] = []
29 schedulePublicationPossible = false 27 schedulePublicationPossible = false
30 videoCaptions: VideoCaptionEdit[] = [] 28 videoCaptions: VideoCaptionEdit[] = []
@@ -49,9 +47,6 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
49 ngOnInit () { 47 ngOnInit () {
50 this.buildForm({}) 48 this.buildForm({})
51 49
52 this.serverService.videoPrivaciesLoaded
53 .subscribe(() => this.videoPrivacies = this.serverService.getVideoPrivacies())
54
55 this.route.data 50 this.route.data
56 .pipe(map(data => data.videoData)) 51 .pipe(map(data => data.videoData))
57 .subscribe(({ video, videoChannels, videoCaptions }) => { 52 .subscribe(({ video, videoChannels, videoCaptions }) => {
@@ -59,14 +54,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
59 this.userVideoChannels = videoChannels 54 this.userVideoChannels = videoChannels
60 this.videoCaptions = videoCaptions 55 this.videoCaptions = videoCaptions
61 56
62 // We cannot set private a video that was not private 57 this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
63 if (this.video.privacy !== VideoPrivacy.PRIVATE) {
64 this.videoPrivacies = this.videoPrivacies.filter(p => p.id !== VideoPrivacy.PRIVATE)
65 } else { // We can schedule video publication only if it it is private
66 this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
67 }
68
69 this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
70 58
71 const videoFiles = (video as VideoDetails).files 59 const videoFiles = (video as VideoDetails).files
72 if (videoFiles.length > 1) { // Already transcoded 60 if (videoFiles.length > 1) { // Already transcoded
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.html b/client/src/app/videos/+video-watch/modal/video-share.component.html
index 955b2b80c..82e59d04d 100644
--- a/client/src/app/videos/+video-watch/modal/video-share.component.html
+++ b/client/src/app/videos/+video-watch/modal/video-share.component.html
@@ -5,53 +5,167 @@
5 </div> 5 </div>
6 6
7 <div class="modal-body"> 7 <div class="modal-body">
8 <ngb-tabset class="root-tabset bootstrap" (tabChange)="onTabChange($event)">
8 9
9 <div class="start-at"> 10 <ngb-tab i18n-title title="URL" id="url">
10 <my-peertube-checkbox 11 <ng-template ngbTabContent>
11 inputName="startAt" [(ngModel)]="startAtCheckbox" 12
12 i18n-labelText labelText="Start at" 13 <div class="tab-content">
13 ></my-peertube-checkbox> 14 <div class="input-group">
14 15 <input #urlInput (click)="urlInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoUrl()" />
15 <my-timestamp-input 16 <div class="input-group-append">
16 [timestamp]="currentVideoTimestamp" 17 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
17 [maxTimestamp]="video.duration" 18 <span class="glyphicon glyphicon-copy"></span>
18 [disabled]="!startAtCheckbox" 19 </button>
19 [(ngModel)]="currentVideoTimestamp" 20 </div>
20 > 21 </div>
21 </my-timestamp-input> 22 </div>
22 </div> 23
24 </ng-template>
25 </ngb-tab>
26
27 <ngb-tab i18n-title title="QR-Code" id="qrcode">
28 <ng-template ngbTabContent>
29 <div class="tab-content">
30 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode>
31 </div>
32 </ng-template>
33 </ngb-tab>
34
35 <ngb-tab i18n-title title="Embed" id="embed">
36 <ng-template ngbTabContent>
37 <div class="tab-content">
38 <div class="input-group">
39 <input #shareInput (click)="shareInput.select()" type="text" class="form-control readonly" readonly [value]="getVideoIframeCode()" />
40 <div class="input-group-append">
41 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
42 <span class="glyphicon glyphicon-copy"></span>
43 </button>
44 </div>
45 </div>
46
47 <div i18n *ngIf="notSecure()" class="alert alert-warning">
48 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
49 </div>
50 </div>
51 </ng-template>
52 </ngb-tab>
53
54 </ngb-tabset>
23 55
24 <div class="form-group"> 56 <div class="filters">
25 <label i18n>URL</label> 57 <div>
26 <div class="input-group input-group-sm"> 58 <div class="form-group start-at">
27 <input #urlInput (click)="urlInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoUrl()" /> 59 <my-peertube-checkbox
28 <div class="input-group-append"> 60 inputName="startAt" [(ngModel)]="customizations.startAtCheckbox"
29 <button [ngxClipboard]="urlInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary"> 61 i18n-labelText labelText="Start at"
30 <span class="glyphicon glyphicon-copy"></span> 62 ></my-peertube-checkbox>
31 </button> 63
64 <my-timestamp-input
65 [timestamp]="customizations.startAt"
66 [maxTimestamp]="video.duration"
67 [disabled]="!customizations.startAtCheckbox"
68 [(ngModel)]="customizations.startAt"
69 >
70 </my-timestamp-input>
32 </div> 71 </div>
33 </div>
34 </div>
35 72
36 <div class="form-group qr-code-group"> 73 <div *ngIf="videoCaptions.length !== 0" class="form-group video-caption-block">
37 <label i18n>QR-Code</label> 74 <my-peertube-checkbox
38 <ngx-qrcode qrc-element-type="url" [qrc-value]="getVideoUrl()" qrc-errorCorrectionLevel="Q"></ngx-qrcode> 75 inputName="subtitleCheckbox" [(ngModel)]="customizations.subtitleCheckbox"
39 </div> 76 i18n-labelText labelText="Auto select subtitle"
77 ></my-peertube-checkbox>
40 78
41 <div class="form-group"> 79 <div class="peertube-select-container" [ngClass]="{ disabled: !customizations.subtitleCheckbox }">
42 <label i18n>Embed</label> 80 <select [(ngModel)]="customizations.subtitle" [disabled]="!customizations.subtitleCheckbox">
43 <div class="input-group input-group-sm"> 81 <option *ngFor="let caption of videoCaptions" [value]="caption.language.id">{{ caption.language.label }}</option>
44 <input #shareInput (click)="shareInput.select()" type="text" class="form-control input-sm readonly" readonly [value]="getVideoIframeCode()" /> 82 </select>
45 <div class="input-group-append"> 83 </div>
46 <button [ngxClipboard]="shareInput" (click)="activateCopiedMessage()" type="button" class="btn btn-outline-secondary">
47 <span class="glyphicon glyphicon-copy"></span>
48 </button>
49 </div> 84 </div>
50 </div> 85 </div>
51 </div>
52 86
53 <div i18n *ngIf="notSecure()" class="alert alert-warning"> 87 <div (click)="isAdvancedCustomizationCollapsed = !isAdvancedCustomizationCollapsed" role="button" class="advanced-filters-button"
54 The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites). 88 [attr.aria-expanded]="!isAdvancedCustomizationCollapsed" aria-controls="collapseBasic">
89
90 <ng-container *ngIf="isAdvancedCustomizationCollapsed">
91 <span class="glyphicon glyphicon-menu-down"></span>
92
93 <ng-container i18n>
94 More customization
95 </ng-container>
96 </ng-container>
97
98 <ng-container *ngIf="!isAdvancedCustomizationCollapsed">
99 <span class="glyphicon glyphicon-menu-up"></span>
100
101 <ng-container i18n>
102 Less customization
103 </ng-container>
104 </ng-container>
105 </div>
106
107 <div class="advanced-filters collapse-transition" [ngbCollapse]="isAdvancedCustomizationCollapsed">
108 <div>
109 <div class="form-group stop-at">
110 <my-peertube-checkbox
111 inputName="stopAt" [(ngModel)]="customizations.stopAtCheckbox"
112 i18n-labelText labelText="Stop at"
113 ></my-peertube-checkbox>
114
115 <my-timestamp-input
116 [timestamp]="customizations.stopAt"
117 [maxTimestamp]="video.duration"
118 [disabled]="!customizations.stopAtCheckbox"
119 [(ngModel)]="customizations.stopAt"
120 >
121 </my-timestamp-input>
122 </div>
123
124 <div class="form-group">
125 <my-peertube-checkbox
126 inputName="autoplay" [(ngModel)]="customizations.autoplay"
127 i18n-labelText labelText="Autoplay"
128 ></my-peertube-checkbox>
129 </div>
130
131 <div class="form-group">
132 <my-peertube-checkbox
133 inputName="muted" [(ngModel)]="customizations.muted"
134 i18n-labelText labelText="Muted"
135 ></my-peertube-checkbox>
136 </div>
137
138 <div class="form-group">
139 <my-peertube-checkbox
140 inputName="loop" [(ngModel)]="customizations.loop"
141 i18n-labelText labelText="Loop"
142 ></my-peertube-checkbox>
143 </div>
144 </div>
145
146 <ng-container *ngIf="isInEmbedTab()">
147 <div class="form-group">
148 <my-peertube-checkbox
149 inputName="title" [(ngModel)]="customizations.title"
150 i18n-labelText labelText="Display video title"
151 ></my-peertube-checkbox>
152 </div>
153
154 <div class="form-group">
155 <my-peertube-checkbox
156 inputName="warningTitle" [(ngModel)]="customizations.warningTitle"
157 i18n-labelText labelText="Display privacy warning"
158 ></my-peertube-checkbox>
159 </div>
160
161 <div class="form-group">
162 <my-peertube-checkbox
163 inputName="controls" [(ngModel)]="customizations.controls"
164 i18n-labelText labelText="Display player controls"
165 ></my-peertube-checkbox>
166 </div>
167 </ng-container>
168 </div>
55 </div> 169 </div>
56 </div> 170 </div>
57 171
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.scss b/client/src/app/videos/+video-watch/modal/video-share.component.scss
index 472a45920..c48abf9e0 100644
--- a/client/src/app/videos/+video-watch/modal/video-share.component.scss
+++ b/client/src/app/videos/+video-watch/modal/video-share.component.scss
@@ -1,5 +1,9 @@
1@import '~bootstrap/scss/functions'; 1@import '_mixins';
2@import '~bootstrap/scss/variables'; 2@import '_variables';
3
4.peertube-select-container {
5 @include peertube-select-container(200px);
6}
3 7
4.action-button-cancel { 8.action-button-cancel {
5 margin-right: 0 !important; 9 margin-right: 0 !important;
@@ -9,13 +13,65 @@
9 text-align: center; 13 text-align: center;
10} 14}
11 15
12.start-at { 16.tab-content {
17 margin-top: 30px;
13 display: flex; 18 display: flex;
14 justify-content: center; 19 justify-content: center;
15 margin-top: 10px;
16 align-items: center; 20 align-items: center;
21 flex-direction: column;
22}
23
24.alert {
25 margin-top: 20px;
26}
27
28input.readonly {
29 font-size: 15px;
30}
31
32.filters {
33 margin-top: 30px;
34 padding-top: 30px;
35 border-top: 1px solid $separator-border-color;
36
37 .advanced-filters-button {
38 display: flex;
39 justify-content: center;
40 align-items: center;
41 margin-top: 30px;
42 font-size: 16px;
43 font-weight: $font-semibold;
44 cursor: pointer;
45
46 .glyphicon {
47 margin-right: 5px;
48 }
49 }
50
51 .form-group {
52 margin-bottom: 0;
53 height: 34px;
54 display: flex;
55 align-items: center;
56 }
57
58 .video-caption-block {
59 display: flex;
60 align-items: center;
61
62 .peertube-select-container {
63 margin-left: 10px;
64 }
65 }
66
67 .start-at,
68 .stop-at {
69 width: 300px;
70 display: flex;
71 align-items: center;
17 72
18 my-timestamp-input { 73 my-timestamp-input {
19 margin-left: 10px; 74 margin-left: 10px;
75 }
20 } 76 }
21} 77}
diff --git a/client/src/app/videos/+video-watch/modal/video-share.component.ts b/client/src/app/videos/+video-watch/modal/video-share.component.ts
index 6565d7f88..eaaf6b902 100644
--- a/client/src/app/videos/+video-watch/modal/video-share.component.ts
+++ b/client/src/app/videos/+video-watch/modal/video-share.component.ts
@@ -3,8 +3,26 @@ import { Notifier } from '@app/core'
3import { VideoDetails } from '../../../shared/video/video-details.model' 3import { VideoDetails } from '../../../shared/video/video-details.model'
4import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils' 4import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils'
5import { I18n } from '@ngx-translate/i18n-polyfill' 5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { NgbModal } from '@ng-bootstrap/ng-bootstrap' 6import { NgbModal, NgbTabChangeEvent } from '@ng-bootstrap/ng-bootstrap'
7import { durationToString } from '@app/shared/misc/utils' 7import { VideoCaption } from '@shared/models'
8
9type Customizations = {
10 startAtCheckbox: boolean
11 startAt: number
12
13 stopAtCheckbox: boolean
14 stopAt: number
15
16 subtitleCheckbox: boolean
17 subtitle: string
18
19 loop: boolean
20 autoplay: boolean
21 muted: boolean
22 title: boolean
23 warningTitle: boolean
24 controls: boolean
25}
8 26
9@Component({ 27@Component({
10 selector: 'my-video-share', 28 selector: 'my-video-share',
@@ -15,9 +33,13 @@ export class VideoShareComponent {
15 @ViewChild('modal') modal: ElementRef 33 @ViewChild('modal') modal: ElementRef
16 34
17 @Input() video: VideoDetails = null 35 @Input() video: VideoDetails = null
36 @Input() videoCaptions: VideoCaption[] = []
18 37
19 currentVideoTimestamp: number 38 activeId: 'url' | 'qrcode' | 'embed'
20 startAtCheckbox = false 39 customizations: Customizations
40 isAdvancedCustomizationCollapsed = true
41
42 private currentVideoTimestamp: number
21 43
22 constructor ( 44 constructor (
23 private modalService: NgbModal, 45 private modalService: NgbModal,
@@ -26,19 +48,47 @@ export class VideoShareComponent {
26 ) { } 48 ) { }
27 49
28 show (currentVideoTimestamp?: number) { 50 show (currentVideoTimestamp?: number) {
29 this.currentVideoTimestamp = currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0 51 this.currentVideoTimestamp = currentVideoTimestamp
52
53 let subtitle: string
54 if (this.videoCaptions.length !== 0) {
55 subtitle = this.videoCaptions[0].language.id
56 }
57
58 this.customizations = {
59 startAtCheckbox: false,
60 startAt: currentVideoTimestamp ? Math.floor(currentVideoTimestamp) : 0,
61
62 stopAtCheckbox: false,
63 stopAt: this.video.duration,
64
65 subtitleCheckbox: false,
66 subtitle,
67
68 loop: false,
69 autoplay: false,
70 muted: false,
71
72 // Embed options
73 title: true,
74 warningTitle: true,
75 controls: true
76 }
30 77
31 this.modalService.open(this.modal) 78 this.modalService.open(this.modal)
32 } 79 }
33 80
34 getVideoIframeCode () { 81 getVideoIframeCode () {
35 const embedUrl = buildVideoLink(this.getVideoTimestampIfEnabled(), this.video.embedUrl) 82 const options = this.getOptions(this.video.embedUrl)
36 83
84 const embedUrl = buildVideoLink(options)
37 return buildVideoEmbed(embedUrl) 85 return buildVideoEmbed(embedUrl)
38 } 86 }
39 87
40 getVideoUrl () { 88 getVideoUrl () {
41 return buildVideoLink(this.getVideoTimestampIfEnabled()) 89 const options = this.getOptions()
90
91 return buildVideoLink(options)
42 } 92 }
43 93
44 notSecure () { 94 notSecure () {
@@ -49,9 +99,30 @@ export class VideoShareComponent {
49 this.notifier.success(this.i18n('Copied')) 99 this.notifier.success(this.i18n('Copied'))
50 } 100 }
51 101
52 private getVideoTimestampIfEnabled () { 102 onTabChange (event: NgbTabChangeEvent) {
53 if (this.startAtCheckbox === true) return this.currentVideoTimestamp 103 this.activeId = event.nextId as any
104 }
105
106 isInEmbedTab () {
107 return this.activeId === 'embed'
108 }
109
110 private getOptions (baseUrl?: string) {
111 return {
112 baseUrl,
113
114 startTime: this.customizations.startAtCheckbox ? this.customizations.startAt : undefined,
115 stopTime: this.customizations.stopAtCheckbox ? this.customizations.stopAt : undefined,
116
117 subtitle: this.customizations.subtitleCheckbox ? this.customizations.subtitle : undefined,
118
119 loop: this.customizations.loop,
120 autoplay: this.customizations.autoplay,
121 muted: this.customizations.muted,
54 122
55 return undefined 123 title: this.customizations.title,
124 warningTitle: this.customizations.warningTitle,
125 controls: this.customizations.controls
126 }
56 } 127 }
57} 128}
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 2e39b9c6b..6a02f630a 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -219,5 +219,5 @@
219 219
220<ng-template [ngIf]="video !== null"> 220<ng-template [ngIf]="video !== null">
221 <my-video-support #videoSupportModal [video]="video"></my-video-support> 221 <my-video-support #videoSupportModal [video]="video"></my-video-support>
222 <my-video-share #videoShareModal [video]="video"></my-video-share> 222 <my-video-share #videoShareModal [video]="video" [videoCaptions]="videoCaptions"></my-video-share>
223</ng-template> 223</ng-template>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.scss b/client/src/app/videos/+video-watch/video-watch.component.scss
index bada9bae8..35ea0fffd 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.scss
+++ b/client/src/app/videos/+video-watch/video-watch.component.scss
@@ -347,6 +347,7 @@ $player-factor: 1.7; // 16/9
347 /deep/ .other-videos { 347 /deep/ .other-videos {
348 padding-left: 15px; 348 padding-left: 15px;
349 flex-basis: $other-videos-width; 349 flex-basis: $other-videos-width;
350 min-width: $other-videos-width;
350 351
351 .title-page { 352 .title-page {
352 margin-top: 0 !important; 353 margin-top: 0 !important;
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 631504eab..3f1a98f89 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -6,7 +6,7 @@ import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component' 6import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
7import { MetaService } from '@ngx-meta/core' 7import { MetaService } from '@ngx-meta/core'
8import { Notifier, ServerService } from '@app/core' 8import { Notifier, ServerService } from '@app/core'
9import { forkJoin, Subscription } from 'rxjs' 9import { forkJoin, Observable, Subscription } from 'rxjs'
10import { Hotkey, HotkeysService } from 'angular2-hotkeys' 10import { Hotkey, HotkeysService } from 'angular2-hotkeys'
11import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared' 11import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
12import { AuthService, ConfirmService } from '../../core' 12import { AuthService, ConfirmService } from '../../core'
@@ -20,6 +20,7 @@ import { environment } from '../../../environments/environment'
20import { VideoCaptionService } from '@app/shared/video-caption' 20import { VideoCaptionService } from '@app/shared/video-caption'
21import { MarkdownService } from '@app/shared/renderer' 21import { MarkdownService } from '@app/shared/renderer'
22import { 22import {
23 CustomizationOptions,
23 P2PMediaLoaderOptions, 24 P2PMediaLoaderOptions,
24 PeertubePlayerManager, 25 PeertubePlayerManager,
25 PeertubePlayerManagerOptions, 26 PeertubePlayerManagerOptions,
@@ -28,8 +29,9 @@ import {
28import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' 29import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
29import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 30import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
30import { Video } from '@app/shared/video/video.model' 31import { Video } from '@app/shared/video/video.model'
31import { isWebRTCDisabled } from '../../../assets/player/utils' 32import { isWebRTCDisabled, timeToInt } from '../../../assets/player/utils'
32import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component' 33import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
34import { getStoredTheater } from '../../../assets/player/peertube-player-local-storage'
33 35
34@Component({ 36@Component({
35 selector: 'my-video-watch', 37 selector: 'my-video-watch',
@@ -48,9 +50,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
48 playerElement: HTMLVideoElement 50 playerElement: HTMLVideoElement
49 theaterEnabled = false 51 theaterEnabled = false
50 userRating: UserVideoRateType = null 52 userRating: UserVideoRateType = null
51 video: VideoDetails = null
52 descriptionLoading = false 53 descriptionLoading = false
53 54
55 video: VideoDetails = null
56 videoCaptions: VideoCaption[] = []
57
54 playlist: VideoPlaylist = null 58 playlist: VideoPlaylist = null
55 59
56 completeDescriptionShown = false 60 completeDescriptionShown = false
@@ -120,6 +124,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
120 }) 124 })
121 125
122 this.initHotkeys() 126 this.initHotkeys()
127
128 this.theaterEnabled = getStoredTheater()
123 } 129 }
124 130
125 ngOnDestroy () { 131 ngOnDestroy () {
@@ -135,22 +141,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
135 141
136 setLike () { 142 setLike () {
137 if (this.isUserLoggedIn() === false) return 143 if (this.isUserLoggedIn() === false) return
138 if (this.userRating === 'like') { 144
139 // Already liked this video 145 // Already liked this video
140 this.setRating('none') 146 if (this.userRating === 'like') this.setRating('none')
141 } else { 147 else this.setRating('like')
142 this.setRating('like')
143 }
144 } 148 }
145 149
146 setDislike () { 150 setDislike () {
147 if (this.isUserLoggedIn() === false) return 151 if (this.isUserLoggedIn() === false) return
148 if (this.userRating === 'dislike') { 152
149 // Already disliked this video 153 // Already disliked this video
150 this.setRating('none') 154 if (this.userRating === 'dislike') this.setRating('none')
151 } else { 155 else this.setRating('dislike')
152 this.setRating('dislike')
153 }
154 } 156 }
155 157
156 showMoreDescription () { 158 showMoreDescription () {
@@ -249,12 +251,20 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
249 ) 251 )
250 .subscribe(([ video, captionsResult ]) => { 252 .subscribe(([ video, captionsResult ]) => {
251 const queryParams = this.route.snapshot.queryParams 253 const queryParams = this.route.snapshot.queryParams
252 const startTime = queryParams.start
253 const stopTime = queryParams.stop
254 const subtitle = queryParams.subtitle
255 const playerMode = queryParams.mode
256 254
257 this.onVideoFetched(video, captionsResult.data, { startTime, stopTime, subtitle, playerMode }) 255 const urlOptions = {
256 startTime: queryParams.start,
257 stopTime: queryParams.stop,
258
259 muted: queryParams.muted,
260 loop: queryParams.loop,
261 subtitle: queryParams.subtitle,
262
263 playerMode: queryParams.mode,
264 peertubeLink: false
265 }
266
267 this.onVideoFetched(video, captionsResult.data, urlOptions)
258 .catch(err => this.handleError(err)) 268 .catch(err => this.handleError(err))
259 }) 269 })
260 } 270 }
@@ -279,6 +289,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
279 private updateVideoDescription (description: string) { 289 private updateVideoDescription (description: string) {
280 this.video.description = description 290 this.video.description = description
281 this.setVideoDescriptionHTML() 291 this.setVideoDescriptionHTML()
292 .catch(err => console.error(err))
282 } 293 }
283 294
284 private async setVideoDescriptionHTML () { 295 private async setVideoDescriptionHTML () {
@@ -327,9 +338,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
327 private async onVideoFetched ( 338 private async onVideoFetched (
328 video: VideoDetails, 339 video: VideoDetails,
329 videoCaptions: VideoCaption[], 340 videoCaptions: VideoCaption[],
330 urlOptions: { startTime?: number, stopTime?: number, subtitle?: string, playerMode?: string } 341 urlOptions: CustomizationOptions & { playerMode: PlayerMode }
331 ) { 342 ) {
332 this.video = video 343 this.video = video
344 this.videoCaptions = videoCaptions
333 345
334 // Re init attributes 346 // Re init attributes
335 this.descriptionLoading = false 347 this.descriptionLoading = false
@@ -339,7 +351,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
339 351
340 this.videoWatchPlaylist.updatePlaylistIndex(video) 352 this.videoWatchPlaylist.updatePlaylistIndex(video)
341 353
342 let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0) 354 let startTime = timeToInt(urlOptions.startTime) || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
343 // If we are at the end of the video, reset the timer 355 // If we are at the end of the video, reset the timer
344 if (this.video.duration - startTime <= 1) startTime = 0 356 if (this.video.duration - startTime <= 1) startTime = 0
345 357
@@ -378,20 +390,26 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
378 enableHotkeys: true, 390 enableHotkeys: true,
379 inactivityTimeout: 2500, 391 inactivityTimeout: 2500,
380 poster: this.video.previewUrl, 392 poster: this.video.previewUrl,
393
381 startTime, 394 startTime,
382 stopTime: urlOptions.stopTime, 395 stopTime: urlOptions.stopTime,
396 controls: urlOptions.controls,
397 muted: urlOptions.muted,
398 loop: urlOptions.loop,
399 subtitle: urlOptions.subtitle,
400
401 peertubeLink: urlOptions.peertubeLink,
383 402
384 theaterMode: true, 403 theaterMode: true,
385 captions: videoCaptions.length !== 0, 404 captions: videoCaptions.length !== 0,
386 peertubeLink: false,
387 405
388 videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE ? this.videoService.getVideoViewUrl(this.video.uuid) : null, 406 videoViewUrl: this.video.privacy.id !== VideoPrivacy.PRIVATE
407 ? this.videoService.getVideoViewUrl(this.video.uuid)
408 : null,
389 embedUrl: this.video.embedUrl, 409 embedUrl: this.video.embedUrl,
390 410
391 language: this.localeId, 411 language: this.localeId,
392 412
393 subtitle: urlOptions.subtitle,
394
395 userWatching: this.user && this.user.videosHistoryEnabled === true ? { 413 userWatching: this.user && this.user.videosHistoryEnabled === true ? {
396 url: this.videoService.getUserWatchingVideoUrl(this.video.uuid), 414 url: this.videoService.getUserWatchingVideoUrl(this.video.uuid),
397 authorizationHeader: this.authService.getRequestHeaderValue() 415 authorizationHeader: this.authService.getRequestHeaderValue()
@@ -433,7 +451,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
433 451
434 this.zone.runOutsideAngular(async () => { 452 this.zone.runOutsideAngular(async () => {
435 this.player = await PeertubePlayerManager.initialize(mode, options) 453 this.player = await PeertubePlayerManager.initialize(mode, options)
436 this.theaterEnabled = this.player.theaterEnabled
437 454
438 this.player.on('customError', ({ err }: { err: any }) => this.handleError(err)) 455 this.player.on('customError', ({ err }: { err: any }) => this.handleError(err))
439 456
@@ -466,20 +483,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
466 } 483 }
467 484
468 private setRating (nextRating: UserVideoRateType) { 485 private setRating (nextRating: UserVideoRateType) {
469 let method 486 const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
470 switch (nextRating) { 487 like: this.videoService.setVideoLike,
471 case 'like': 488 dislike: this.videoService.setVideoDislike,
472 method = this.videoService.setVideoLike 489 none: this.videoService.unsetVideoLike
473 break
474 case 'dislike':
475 method = this.videoService.setVideoDislike
476 break
477 case 'none':
478 method = this.videoService.unsetVideoLike
479 break
480 } 490 }
481 491
482 method.call(this.videoService, this.video.id) 492 ratingMethods[nextRating].call(this.videoService, this.video.id)
483 .subscribe( 493 .subscribe(
484 () => { 494 () => {
485 // Update the video like attribute 495 // Update the video like attribute
@@ -545,25 +555,29 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
545 private flushPlayer () { 555 private flushPlayer () {
546 // Remove player if it exists 556 // Remove player if it exists
547 if (this.player) { 557 if (this.player) {
548 this.player.dispose() 558 try {
549 this.player = undefined 559 this.player.dispose()
560 this.player = undefined
561 } catch (err) {
562 console.error('Cannot dispose player.', err)
563 }
550 } 564 }
551 } 565 }
552 566
553 private initHotkeys () { 567 private initHotkeys () {
554 this.hotkeys = [ 568 this.hotkeys = [
555 new Hotkey('shift+l', (event: KeyboardEvent): boolean => { 569 new Hotkey('shift+l', () => {
556 this.setLike() 570 this.setLike()
557 return false 571 return false
558 }, undefined, this.i18n('Like the video')), 572 }, undefined, this.i18n('Like the video')),
559 new Hotkey('shift+d', (event: KeyboardEvent): boolean => { 573
574 new Hotkey('shift+d', () => {
560 this.setDislike() 575 this.setDislike()
561 return false 576 return false
562 }, undefined, this.i18n('Dislike the video')), 577 }, undefined, this.i18n('Dislike the video')),
563 new Hotkey('shift+s', (event: KeyboardEvent): boolean => { 578
564 this.subscribeButton.subscribed ? 579 new Hotkey('shift+s', () => {
565 this.subscribeButton.unsubscribe() : 580 this.subscribeButton.subscribed ? this.subscribeButton.unsubscribe() : this.subscribeButton.subscribe()
566 this.subscribeButton.subscribe()
567 return false 581 return false
568 }, undefined, this.i18n('Subscribe to the account')) 582 }, undefined, this.i18n('Subscribe to the account'))
569 ] 583 ]
diff --git a/client/src/app/videos/video-list/video-local.component.ts b/client/src/app/videos/video-list/video-local.component.ts
index 13d4023c2..65543343c 100644
--- a/client/src/app/videos/video-list/video-local.component.ts
+++ b/client/src/app/videos/video-list/video-local.component.ts
@@ -22,13 +22,13 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
22 filter: VideoFilter = 'local' 22 filter: VideoFilter = 'local'
23 23
24 constructor ( 24 constructor (
25 protected i18n: I18n,
25 protected router: Router, 26 protected router: Router,
26 protected serverService: ServerService, 27 protected serverService: ServerService,
27 protected route: ActivatedRoute, 28 protected route: ActivatedRoute,
28 protected notifier: Notifier, 29 protected notifier: Notifier,
29 protected authService: AuthService, 30 protected authService: AuthService,
30 protected screenService: ScreenService, 31 protected screenService: ScreenService,
31 private i18n: I18n,
32 private videoService: VideoService 32 private videoService: VideoService
33 ) { 33 ) {
34 super() 34 super()
diff --git a/client/src/app/videos/video-list/video-overview.component.html b/client/src/app/videos/video-list/video-overview.component.html
index b644dd798..f59de584a 100644
--- a/client/src/app/videos/video-list/video-overview.component.html
+++ b/client/src/app/videos/video-list/video-overview.component.html
@@ -3,7 +3,7 @@
3 <div class="no-results" i18n *ngIf="notResults">No results.</div> 3 <div class="no-results" i18n *ngIf="notResults">No results.</div>
4 4
5 <div class="section" *ngFor="let object of overview.categories"> 5 <div class="section" *ngFor="let object of overview.categories">
6 <div class="section-title" i18n> 6 <div class="section-title">
7 <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a> 7 <a routerLink="/search" [queryParams]="{ categoryOneOf: [ object.category.id ] }">{{ object.category.label }}</a>
8 </div> 8 </div>
9 9
@@ -11,7 +11,7 @@
11 </div> 11 </div>
12 12
13 <div class="section" *ngFor="let object of overview.tags"> 13 <div class="section" *ngFor="let object of overview.tags">
14 <div class="section-title" i18n> 14 <div class="section-title">
15 <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a> 15 <a routerLink="/search" [queryParams]="{ tagsOneOf: [ object.tag ] }">#{{ object.tag }}</a>
16 </div> 16 </div>
17 17
@@ -19,7 +19,7 @@
19 </div> 19 </div>
20 20
21 <div class="section channel" *ngFor="let object of overview.channels"> 21 <div class="section channel" *ngFor="let object of overview.channels">
22 <div class="section-title" i18n> 22 <div class="section-title">
23 <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]"> 23 <a [routerLink]="[ '/video-channels', buildVideoChannelBy(object) ]">
24 <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" /> 24 <img [src]="buildVideoChannelAvatarUrl(object)" alt="Avatar" />
25 25
diff --git a/client/src/app/videos/video-list/video-overview.component.scss b/client/src/app/videos/video-list/video-overview.component.scss
index a24766783..ade6f53b7 100644
--- a/client/src/app/videos/video-list/video-overview.component.scss
+++ b/client/src/app/videos/video-list/video-overview.component.scss
@@ -2,62 +2,10 @@
2@import '_mixins'; 2@import '_mixins';
3@import '_miniature'; 3@import '_miniature';
4 4
5.section { 5.margin-content {
6 max-height: 500px; // 2 rows max 6 @include adapt-margin-content-width;
7 overflow: hidden;
8 padding-top: 10px;
9
10 &:first-child {
11 padding-top: 30px;
12 }
13
14 my-video-miniature {
15 text-align: left;
16 }
17}
18
19.section-title {
20 font-size: 24px;
21 font-weight: $font-semibold;
22 margin-bottom: 10px;
23
24 a {
25 &:hover, &:focus:not(.focus-visible), &:active {
26 text-decoration: none;
27 outline: none;
28 }
29
30 color: var(--mainForegroundColor);
31 }
32} 7}
33 8
34.channel { 9.section {
35 .section-title a { 10 @include miniature-rows;
36 display: flex;
37 width: fit-content;
38 align-items: center;
39
40 img {
41 @include avatar(28px);
42
43 margin-right: 8px;
44 }
45 }
46}
47
48@media screen and (max-width: 500px) {
49 .margin-content {
50 margin: 0 !important;
51 }
52
53 .section-title {
54 font-size: 17px;
55 }
56
57 .section {
58 max-height: initial;
59 overflow: initial;
60
61 @include video-miniature-small-screen;
62 }
63} 11}
diff --git a/client/src/app/videos/video-list/video-recently-added.component.ts b/client/src/app/videos/video-list/video-recently-added.component.ts
index 80cef813e..f54bade98 100644
--- a/client/src/app/videos/video-list/video-recently-added.component.ts
+++ b/client/src/app/videos/video-list/video-recently-added.component.ts
@@ -17,15 +17,16 @@ import { Notifier, ServerService } from '@app/core'
17export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { 17export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
18 titlePage: string 18 titlePage: string
19 sort: VideoSortField = '-publishedAt' 19 sort: VideoSortField = '-publishedAt'
20 groupByDate = true
20 21
21 constructor ( 22 constructor (
23 protected i18n: I18n,
22 protected route: ActivatedRoute, 24 protected route: ActivatedRoute,
23 protected serverService: ServerService, 25 protected serverService: ServerService,
24 protected router: Router, 26 protected router: Router,
25 protected notifier: Notifier, 27 protected notifier: Notifier,
26 protected authService: AuthService, 28 protected authService: AuthService,
27 protected screenService: ScreenService, 29 protected screenService: ScreenService,
28 private i18n: I18n,
29 private videoService: VideoService 30 private videoService: VideoService
30 ) { 31 ) {
31 super() 32 super()
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index e2ad95bc4..a2c819ebe 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -19,13 +19,13 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
19 defaultSort: VideoSortField = '-trending' 19 defaultSort: VideoSortField = '-trending'
20 20
21 constructor ( 21 constructor (
22 protected i18n: I18n,
22 protected router: Router, 23 protected router: Router,
23 protected serverService: ServerService, 24 protected serverService: ServerService,
24 protected route: ActivatedRoute, 25 protected route: ActivatedRoute,
25 protected notifier: Notifier, 26 protected notifier: Notifier,
26 protected authService: AuthService, 27 protected authService: AuthService,
27 protected screenService: ScreenService, 28 protected screenService: ScreenService,
28 private i18n: I18n,
29 private videoService: VideoService 29 private videoService: VideoService
30 ) { 30 ) {
31 super() 31 super()
diff --git a/client/src/app/videos/video-list/video-user-subscriptions.component.ts b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
index 2f0685ccc..3caa371d8 100644
--- a/client/src/app/videos/video-list/video-user-subscriptions.component.ts
+++ b/client/src/app/videos/video-list/video-user-subscriptions.component.ts
@@ -19,15 +19,16 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
19 titlePage: string 19 titlePage: string
20 sort = '-publishedAt' as VideoSortField 20 sort = '-publishedAt' as VideoSortField
21 ownerDisplayType: OwnerDisplayType = 'auto' 21 ownerDisplayType: OwnerDisplayType = 'auto'
22 groupByDate = true
22 23
23 constructor ( 24 constructor (
25 protected i18n: I18n,
24 protected router: Router, 26 protected router: Router,
25 protected serverService: ServerService, 27 protected serverService: ServerService,
26 protected route: ActivatedRoute, 28 protected route: ActivatedRoute,
27 protected notifier: Notifier, 29 protected notifier: Notifier,
28 protected authService: AuthService, 30 protected authService: AuthService,
29 protected screenService: ScreenService, 31 protected screenService: ScreenService,
30 private i18n: I18n,
31 private videoService: VideoService 32 private videoService: VideoService
32 ) { 33 ) {
33 super() 34 super()
diff --git a/client/src/assets/player/peertube-player-local-storage.ts b/client/src/assets/player/peertube-player-local-storage.ts
index 059fca308..f6c5c5419 100644
--- a/client/src/assets/player/peertube-player-local-storage.ts
+++ b/client/src/assets/player/peertube-player-local-storage.ts
@@ -29,7 +29,7 @@ function getStoredTheater () {
29 const value = getLocalStorage('theater-enabled') 29 const value = getLocalStorage('theater-enabled')
30 if (value !== null && value !== undefined) return value === 'true' 30 if (value !== null && value !== undefined) return value === 'true'
31 31
32 return undefined 32 return false
33} 33}
34 34
35function saveVolumeInStore (value: number) { 35function saveVolumeInStore (value: number) {
diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 6cdd54372..083c621d2 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -39,7 +39,19 @@ export type P2PMediaLoaderOptions = {
39 videoFiles: VideoFile[] 39 videoFiles: VideoFile[]
40} 40}
41 41
42export type CommonOptions = { 42export interface CustomizationOptions {
43 startTime: number | string
44 stopTime: number | string
45
46 controls?: boolean
47 muted?: boolean
48 loop?: boolean
49 subtitle?: string
50
51 peertubeLink: boolean
52}
53
54export interface CommonOptions extends CustomizationOptions {
43 playerElement: HTMLVideoElement 55 playerElement: HTMLVideoElement
44 onPlayerElementChange: (element: HTMLVideoElement) => void 56 onPlayerElementChange: (element: HTMLVideoElement) => void
45 57
@@ -48,21 +60,14 @@ export type CommonOptions = {
48 enableHotkeys: boolean 60 enableHotkeys: boolean
49 inactivityTimeout: number 61 inactivityTimeout: number
50 poster: string 62 poster: string
51 startTime: number | string
52 stopTime: number | string
53 63
54 theaterMode: boolean 64 theaterMode: boolean
55 captions: boolean 65 captions: boolean
56 peertubeLink: boolean
57 66
58 videoViewUrl: string 67 videoViewUrl: string
59 embedUrl: string 68 embedUrl: string
60 69
61 language?: string 70 language?: string
62 controls?: boolean
63 muted?: boolean
64 loop?: boolean
65 subtitle?: string
66 71
67 videoCaptions: VideoJSCaption[] 72 videoCaptions: VideoJSCaption[]
68 73
@@ -117,8 +122,17 @@ export class PeertubePlayerManager {
117 videojs(options.common.playerElement, videojsOptions, function (this: any) { 122 videojs(options.common.playerElement, videojsOptions, function (this: any) {
118 const player = this 123 const player = this
119 124
120 player.tech_.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options)) 125 let alreadyFallback = false
121 player.one('error', () => self.maybeFallbackToWebTorrent(mode, player, options)) 126
127 player.tech_.one('error', () => {
128 if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
129 alreadyFallback = true
130 })
131
132 player.one('error', () => {
133 if (!alreadyFallback) self.maybeFallbackToWebTorrent(mode, player, options)
134 alreadyFallback = true
135 })
122 136
123 self.addContextMenu(mode, player, options.common.embedUrl) 137 self.addContextMenu(mode, player, options.common.embedUrl)
124 138
@@ -432,7 +446,7 @@ export class PeertubePlayerManager {
432 label: player.localize('Copy the video URL at the current time'), 446 label: player.localize('Copy the video URL at the current time'),
433 listener: function () { 447 listener: function () {
434 const player = this as videojs.Player 448 const player = this as videojs.Player
435 copyToClipboard(buildVideoLink(player.currentTime())) 449 copyToClipboard(buildVideoLink({ startTime: player.currentTime() }))
436 } 450 }
437 }, 451 },
438 { 452 {
diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts
index 366689962..777abb568 100644
--- a/client/src/assets/player/utils.ts
+++ b/client/src/assets/player/utils.ts
@@ -27,18 +27,55 @@ function isMobile () {
27 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) 27 return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
28} 28}
29 29
30function buildVideoLink (time?: number, url?: string) { 30function buildVideoLink (options: {
31 if (!url) url = window.location.origin + window.location.pathname.replace('/embed/', '/watch/') 31 baseUrl?: string,
32 32
33 if (time) { 33 startTime?: number,
34 const timeInt = Math.floor(time) 34 stopTime?: number,
35 35
36 const params = new URLSearchParams(window.location.search) 36 subtitle?: string,
37 params.set('start', secondsToTime(timeInt))
38 37
39 return url + '?' + params.toString() 38 loop?: boolean,
39 autoplay?: boolean,
40 muted?: boolean,
41
42 // Embed options
43 title?: boolean,
44 warningTitle?: boolean,
45 controls?: boolean
46} = {}) {
47 const { baseUrl } = options
48
49 const url = baseUrl
50 ? baseUrl
51 : window.location.origin + window.location.pathname.replace('/embed/', '/watch/')
52
53 const params = new URLSearchParams(window.location.search)
54
55 if (options.startTime) {
56 const startTimeInt = Math.floor(options.startTime)
57 params.set('start', secondsToTime(startTimeInt))
58 }
59
60 if (options.stopTime) {
61 const stopTimeInt = Math.floor(options.stopTime)
62 params.set('stop', secondsToTime(stopTimeInt))
40 } 63 }
41 64
65 if (options.subtitle) params.set('subtitle', options.subtitle)
66
67 if (options.loop === true) params.set('loop', '1')
68 if (options.autoplay === true) params.set('autoplay', '1')
69 if (options.muted === true) params.set('muted', '1')
70 if (options.title === false) params.set('title', '0')
71 if (options.warningTitle === false) params.set('warningTitle', '0')
72 if (options.controls === false) params.set('controls', '0')
73
74 let hasParams = false
75 params.forEach(() => hasParams = true)
76
77 if (hasParams) return url + '?' + params.toString()
78
42 return url 79 return url
43} 80}
44 81
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 fed8ea33e..4d0ea37f5 100644
--- a/client/src/assets/player/videojs-components/peertube-link-button.ts
+++ b/client/src/assets/player/videojs-components/peertube-link-button.ts
@@ -16,7 +16,7 @@ class PeerTubeLinkButton extends Button {
16 } 16 }
17 17
18 updateHref () { 18 updateHref () {
19 this.el().setAttribute('href', buildVideoLink(this.player().currentTime())) 19 this.el().setAttribute('href', buildVideoLink({ startTime: this.player().currentTime() }))
20 } 20 }
21 21
22 handleClick () { 22 handleClick () {
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index d84766240..c64a8ebf8 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -12,6 +12,7 @@ $assets-path: '../assets/';
12@import './player/index'; 12@import './player/index';
13@import './loading-bar'; 13@import './loading-bar';
14 14
15@import './bootstrap';
15@import './primeng-custom'; 16@import './primeng-custom';
16 17
17[hidden] { 18[hidden] {
@@ -181,128 +182,11 @@ label {
181 font-weight: bold; 182 font-weight: bold;
182} 183}
183 184
184// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
185.glyphicon-refresh-animate {
186 animation: spin .7s infinite linear;
187}
188
189@keyframes spin { 185@keyframes spin {
190 from { transform: scale(1) rotate(0deg);} 186 from { transform: scale(1) rotate(0deg);}
191 to { transform: scale(1) rotate(360deg);} 187 to { transform: scale(1) rotate(360deg);}
192} 188}
193 189
194// Bootstrap customizations
195.dropdown-menu {
196 border-radius: 3px;
197 box-shadow: 0 3px 6px;
198 font-size: 15px;
199
200 .dropdown-item {
201 padding: 3px 15px;
202
203 &:active {
204 color: #000 !important;
205 }
206 }
207
208 button {
209 @include disable-default-a-behaviour;
210 }
211
212 a {
213 @include disable-default-a-behaviour;
214 color: #000 !important;
215 }
216}
217
218.modal {
219 .modal-content {
220 background-color: var(--mainBackgroundColor);
221 }
222
223 .modal-header {
224 border-bottom: none;
225 margin-bottom: 5px;
226
227 .modal-title {
228 font-size: 20px;
229 font-weight: $font-semibold;
230 }
231
232 my-global-icon {
233 @include icon(24px);
234
235 position: relative;
236 top: 3px;
237 float: right;
238
239 margin: 0;
240 padding: 0;
241 opacity: 1;
242 }
243 }
244
245 .inputs {
246 margin-bottom: 0;
247 text-align: right;
248
249 .action-button-cancel {
250 @include peertube-button;
251 @include grey-button;
252
253 display: inline-block;
254 margin-right: 10px;
255 }
256
257 .action-button-submit {
258 @include peertube-button;
259 @include orange-button;
260 }
261 }
262}
263
264// Nav customizations
265.nav .nav-link {
266 display: flex !important;
267 align-items: center;
268 height: 30px !important;
269 padding: 10px 15px !important;
270}
271
272.nav.nav-pills {
273 font-size: 16px !important;
274
275 .nav-link.active {
276 font-weight: $font-semibold !important;
277 }
278
279 a {
280 @include disable-default-a-behaviour;
281
282 color: var(--mainForegroundColor);
283 }
284}
285
286ngb-tabset.bootstrap {
287
288 .nav-link {
289 &, & a {
290 @include disable-default-a-behaviour;
291
292 color: var(--mainForegroundColor) !important;
293 }
294 }
295
296 .nav-pills .nav-link.active {
297 color: #000 !important;
298 }
299}
300
301.nav-tabs .nav-link.active {
302 background-color: var(--mainBackgroundColor) !important;
303 border-bottom: none;
304}
305
306.orange-button { 190.orange-button {
307 @include peertube-button; 191 @include peertube-button;
308 @include orange-button; 192 @include orange-button;
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss
new file mode 100644
index 000000000..12e73278a
--- /dev/null
+++ b/client/src/sass/bootstrap.scss
@@ -0,0 +1,138 @@
1$icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
2@import '_bootstrap';
3
4@import '_variables';
5@import '_mixins';
6
7// Thanks https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d
8.glyphicon-refresh-animate {
9 animation: spin .7s infinite linear;
10}
11
12@keyframes spin {
13 from { transform: scale(1) rotate(0deg);}
14 to { transform: scale(1) rotate(360deg);}
15}
16
17.dropdown-menu {
18 border-radius: 3px;
19 box-shadow: 0 3px 6px;
20 font-size: 15px;
21
22 .dropdown-item {
23 padding: 3px 15px;
24
25 &:active {
26 color: #000 !important;
27 }
28 }
29
30 button {
31 @include disable-default-a-behaviour;
32 }
33
34 a {
35 @include disable-default-a-behaviour;
36 color: #000 !important;
37 }
38}
39
40.modal {
41 .modal-content {
42 background-color: var(--mainBackgroundColor);
43 }
44
45 .modal-header {
46 border-bottom: none;
47 margin-bottom: 5px;
48
49 .modal-title {
50 font-size: 20px;
51 font-weight: $font-semibold;
52 }
53
54 my-global-icon {
55 @include icon(24px);
56
57 position: relative;
58 top: 3px;
59 float: right;
60
61 margin: 0;
62 padding: 0;
63 opacity: 1;
64 }
65 }
66
67 .inputs {
68 margin-bottom: 0;
69 text-align: right;
70
71 .action-button-cancel {
72 @include peertube-button;
73 @include grey-button;
74
75 display: inline-block;
76 margin-right: 10px;
77 }
78
79 .action-button-submit {
80 @include peertube-button;
81 @include orange-button;
82 }
83 }
84}
85
86// Nav customizations
87.nav .nav-link {
88 display: flex !important;
89 align-items: center;
90 height: 30px !important;
91 padding: 10px 15px !important;
92}
93
94.nav.nav-pills {
95 font-size: 16px !important;
96
97 .nav-link.active {
98 font-weight: $font-semibold !important;
99 }
100
101 a {
102 @include disable-default-a-behaviour;
103
104 color: var(--mainForegroundColor);
105 }
106}
107
108ngb-tabset.bootstrap {
109
110 .nav-link {
111 &, & a {
112 @include disable-default-a-behaviour;
113
114 color: var(--mainForegroundColor) !important;
115 }
116 }
117
118 .nav-pills .nav-link.active {
119 color: #000 !important;
120 }
121}
122
123.nav-tabs .nav-link.active {
124 background-color: var(--mainBackgroundColor) !important;
125 border-bottom: none;
126}
127
128.collapse-transition {
129 // Animation when we show/hide the filters
130 transition: max-height 0.3s;
131 display: block !important;
132 overflow: hidden !important;
133 max-height: 0;
134
135 &.show {
136 max-height: 1500px;
137 }
138}
diff --git a/client/src/sass/include/_miniature.scss b/client/src/sass/include/_miniature.scss
index b62187fd2..0c2ee2d0d 100644
--- a/client/src/sass/include/_miniature.scss
+++ b/client/src/sass/include/_miniature.scss
@@ -138,3 +138,100 @@ $play-overlay-width: 18px;
138 } 138 }
139 } 139 }
140} 140}
141
142@mixin miniature-rows {
143 max-height: 540px; // 2 rows max
144 overflow: hidden;
145 padding-top: 10px;
146
147 &:first-child {
148 padding-top: 30px;
149 }
150
151 my-video-miniature {
152 text-align: left;
153 }
154
155 .section-title {
156 font-size: 24px;
157 font-weight: $font-semibold;
158 margin-bottom: 30px;
159 display: flex;
160 justify-content: space-between;
161
162 a {
163 &:hover, &:focus:not(.focus-visible), &:active {
164 text-decoration: none;
165 outline: none;
166 }
167
168 color: var(--mainForegroundColor);
169 }
170 }
171
172 &.channel {
173 .section-title {
174 a {
175 display: flex;
176 width: fit-content;
177 align-items: center;
178
179 img {
180 @include avatar(28px);
181
182 margin-right: 8px;
183 }
184 }
185
186 .followers {
187 color: $grey-foreground-color;
188 font-weight: normal;
189 font-size: 14px;
190 margin-left: 10px;
191 position: relative;
192 top: 2px;
193 }
194 }
195 }
196
197 @media screen and (max-width: $mobile-view) {
198 max-height: initial;
199 overflow: initial;
200
201 @include video-miniature-small-screen;
202
203 .section-title {
204 font-size: 17px;
205 }
206 }
207}
208
209@mixin adapt-margin-content-width {
210 width: $video-miniature-width * 6;
211 margin: auto !important;
212
213 @media screen and (max-width: 1800px) {
214 width: $video-miniature-width * 5;
215 }
216
217 @media screen and (max-width: 1800px - $video-miniature-width) {
218 width: $video-miniature-width * 4;
219 }
220
221 @media screen and (max-width: 1800px - (2* $video-miniature-width)) {
222 width: $video-miniature-width * 3;
223 }
224
225 @media screen and (max-width: 1800px - (3* $video-miniature-width)) {
226 width: $video-miniature-width * 2;
227 }
228
229 @media screen and (max-width: 500px) {
230 width: auto;
231 margin: 0 !important;
232
233 .videos {
234 @include video-miniature-small-screen;
235 }
236 }
237}
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss
index 262a8136f..f608e9299 100644
--- a/client/src/sass/include/_mixins.scss
+++ b/client/src/sass/include/_mixins.scss
@@ -235,6 +235,14 @@
235 position: relative; 235 position: relative;
236 font-size: 15px; 236 font-size: 15px;
237 237
238 &.disabled {
239 background-color: #E5E5E5;
240
241 select {
242 cursor: default;
243 }
244 }
245
238 @media screen and (max-width: $width) { 246 @media screen and (max-width: $width) {
239 width: 100%; 247 width: 100%;
240 } 248 }
@@ -282,16 +290,6 @@
282 } 290 }
283} 291}
284 292
285@mixin peertube-select-disabled-container ($width) {
286 @include peertube-select-container($width);
287
288 background-color: #E5E5E5;
289
290 select {
291 cursor: default;
292 }
293}
294
295// Thanks: https://codepen.io/triss90/pen/XNEdRe/ 293// Thanks: https://codepen.io/triss90/pen/XNEdRe/
296@mixin peertube-radio-container { 294@mixin peertube-radio-container {
297 input[type="radio"] { 295 input[type="radio"] {
@@ -331,7 +329,12 @@
331} 329}
332 330
333@mixin peertube-checkbox ($border-width) { 331@mixin peertube-checkbox ($border-width) {
334 display: none; 332 opacity: 0;
333 position: absolute;
334
335 &:focus + span {
336 outline: 1px solid #1e5180;
337 }
335 338
336 & + span { 339 & + span {
337 position: relative; 340 position: relative;
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss
index c7b205b11..aafeda257 100644
--- a/client/src/sass/include/_variables.scss
+++ b/client/src/sass/include/_variables.scss
@@ -71,7 +71,9 @@ $variables: (
71 --menuForegroundColor: var(--menuForegroundColor), 71 --menuForegroundColor: var(--menuForegroundColor),
72 --submenuColor: var(--submenuColor), 72 --submenuColor: var(--submenuColor),
73 --inputColor: var(--inputColor), 73 --inputColor: var(--inputColor),
74 --inputPlaceholderColor: var(--inputPlaceholderColor) 74 --inputPlaceholderColor: var(--inputPlaceholderColor),
75 --embedForegroundColor: var(--embedForegroundColor),
76 --embedBigPlayBackgroundColor: var(--embedBigPlayBackgroundColor)
75); 77);
76 78
77/*** theme helper ***/ 79/*** theme helper ***/
diff --git a/client/src/sass/player/_player-variables.scss b/client/src/sass/player/_player-variables.scss
index 110129790..4e9e8736c 100644
--- a/client/src/sass/player/_player-variables.scss
+++ b/client/src/sass/player/_player-variables.scss
@@ -10,4 +10,10 @@ $slider-bg-color: lighten($primary-background-color, 33%);
10 10
11$progress-margin: 10px; 11$progress-margin: 10px;
12 12
13$assets-path: '../../assets/' !default; \ No newline at end of file 13$assets-path: '../../assets/' !default;
14
15body {
16 --embedForegroundColor: #{$primary-foreground-color};
17
18 --embedBigPlayBackgroundColor: #{$primary-background-color};
19}
diff --git a/client/src/sass/player/context-menu.scss b/client/src/sass/player/context-menu.scss
index 71d6d1b1d..eeab0ccdf 100644
--- a/client/src/sass/player/context-menu.scss
+++ b/client/src/sass/player/context-menu.scss
@@ -14,7 +14,7 @@ $context-menu-width: 350px;
14 14
15 .vjs-menu-content { 15 .vjs-menu-content {
16 opacity: $primary-foreground-opacity; 16 opacity: $primary-foreground-opacity;
17 color: $primary-foreground-color; 17 color: var(--embedForegroundCsolor);
18 font-size: $font-size !important; 18 font-size: $font-size !important;
19 font-weight: $font-semibold; 19 font-weight: $font-semibold;
20 } 20 }
@@ -30,4 +30,4 @@ $context-menu-width: 350px;
30 background-color: rgba(255, 255, 255, 0.2); 30 background-color: rgba(255, 255, 255, 0.2);
31 } 31 }
32 } 32 }
33} \ No newline at end of file 33}
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss
index e63a2875c..996024ade 100644
--- a/client/src/sass/player/peertube-skin.scss
+++ b/client/src/sass/player/peertube-skin.scss
@@ -10,9 +10,8 @@
10} 10}
11 11
12.video-js.vjs-peertube-skin { 12.video-js.vjs-peertube-skin {
13
14 font-size: $font-size; 13 font-size: $font-size;
15 color: $primary-foreground-color; 14 color: var(--embedForegroundColor);
16 15
17 .vjs-dock-text { 16 .vjs-dock-text {
18 padding-right: 10px; 17 padding-right: 10px;
@@ -114,7 +113,7 @@
114 .vjs-control-bar, 113 .vjs-control-bar,
115 .vjs-big-play-button, 114 .vjs-big-play-button,
116 .vjs-settings-dialog { 115 .vjs-settings-dialog {
117 background-color: rgba($primary-background-color, 0.5); 116 background-color: var(--embedBigPlayBackgroundColor);
118 } 117 }
119 118
120 .vjs-poster { 119 .vjs-poster {
@@ -139,7 +138,8 @@
139 .vjs-theater-control, 138 .vjs-theater-control,
140 .vjs-settings 139 .vjs-settings
141 { 140 {
142 color: $primary-foreground-color !important; 141 color: var(--embedForegroundColor) !important;
142
143 opacity: $primary-foreground-opacity; 143 opacity: $primary-foreground-opacity;
144 transition: opacity .1s; 144 transition: opacity .1s;
145 145
@@ -151,7 +151,7 @@
151 .vjs-current-time, 151 .vjs-current-time,
152 .vjs-duration, 152 .vjs-duration,
153 .vjs-peertube { 153 .vjs-peertube {
154 color: $primary-foreground-color; 154 color: var(--embedForegroundColor);
155 opacity: $primary-foreground-opacity; 155 opacity: $primary-foreground-opacity;
156 } 156 }
157 157
@@ -171,7 +171,7 @@
171 transition: none; 171 transition: none;
172 172
173 .vjs-play-progress { 173 .vjs-play-progress {
174 background: $primary-foreground-color; 174 background: var(--embedForegroundColor);
175 175
176 // Not display the circle if the progress is not hovered 176 // Not display the circle if the progress is not hovered
177 &::before { 177 &::before {
diff --git a/client/src/sass/player/settings-menu.scss b/client/src/sass/player/settings-menu.scss
index 61965c85e..83407b445 100644
--- a/client/src/sass/player/settings-menu.scss
+++ b/client/src/sass/player/settings-menu.scss
@@ -38,7 +38,7 @@ $setting-transition-easing: ease-out;
38 position: absolute; 38 position: absolute;
39 right: .5em; 39 right: .5em;
40 bottom: 3.5em; 40 bottom: 3.5em;
41 color: $primary-foreground-color; 41 color: var(--embedForegroundColor);
42 opacity: $primary-foreground-opacity; 42 opacity: $primary-foreground-opacity;
43 margin: 0 auto; 43 margin: 0 auto;
44 font-size: $font-size !important; 44 font-size: $font-size !important;
diff --git a/client/src/standalone/videos/embed-api.ts b/client/src/standalone/videos/embed-api.ts
new file mode 100644
index 000000000..169e371da
--- /dev/null
+++ b/client/src/standalone/videos/embed-api.ts
@@ -0,0 +1,130 @@
1import './embed.scss'
2
3import * as Channel from 'jschannel'
4import { PeerTubeResolution } from '../player/definitions'
5import { PeerTubeEmbed } from './embed'
6
7/**
8 * Embed API exposes control of the embed player to the outside world via
9 * JSChannels and window.postMessage
10 */
11export class PeerTubeEmbedApi {
12 private channel: Channel.MessagingChannel
13 private isReady = false
14 private resolutions: PeerTubeResolution[] = null
15
16 constructor (private embed: PeerTubeEmbed) {
17 }
18
19 initialize () {
20 this.constructChannel()
21 this.setupStateTracking()
22
23 // We're ready!
24
25 this.notifyReady()
26 }
27
28 private get element () {
29 return this.embed.videoElement
30 }
31
32 private constructChannel () {
33 const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
34
35 channel.bind('play', (txn, params) => this.embed.player.play())
36 channel.bind('pause', (txn, params) => this.embed.player.pause())
37 channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
38 channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
39 channel.bind('getVolume', (txn, value) => this.embed.player.volume())
40 channel.bind('isReady', (txn, params) => this.isReady)
41 channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
42 channel.bind('getResolutions', (txn, params) => this.resolutions)
43 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
44 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
45 channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
46
47 this.channel = channel
48 }
49
50 private setResolution (resolutionId: number) {
51 if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
52
53 // Auto resolution
54 if (resolutionId === -1) {
55 this.embed.player.webtorrent().enableAutoResolution()
56 return
57 }
58
59 this.embed.player.webtorrent().disableAutoResolution()
60 this.embed.player.webtorrent().updateResolution(resolutionId)
61 }
62
63 /**
64 * Let the host know that we're ready to go!
65 */
66 private notifyReady () {
67 this.isReady = true
68 this.channel.notify({ method: 'ready', params: true })
69 }
70
71 private setupStateTracking () {
72 let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
73
74 setInterval(() => {
75 const position = this.element.currentTime
76 const volume = this.element.volume
77
78 this.channel.notify({
79 method: 'playbackStatusUpdate',
80 params: {
81 position,
82 volume,
83 playbackState: currentState
84 }
85 })
86 }, 500)
87
88 this.element.addEventListener('play', ev => {
89 currentState = 'playing'
90 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
91 })
92
93 this.element.addEventListener('pause', ev => {
94 currentState = 'paused'
95 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
96 })
97
98 // PeerTube specific capabilities
99
100 if (this.embed.player.webtorrent) {
101 this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
102 this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
103 }
104 }
105
106 private loadWebTorrentResolutions () {
107 const resolutions = []
108 const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
109
110 for (const videoFile of this.embed.player.webtorrent().videoFiles) {
111 let label = videoFile.resolution.label
112 if (videoFile.fps && videoFile.fps >= 50) {
113 label += videoFile.fps
114 }
115
116 resolutions.push({
117 id: videoFile.resolution.id,
118 label,
119 src: videoFile.magnetUri,
120 active: videoFile.resolution.id === currentResolutionId
121 })
122 }
123
124 this.resolutions = resolutions
125 this.channel.notify({
126 method: 'resolutionUpdate',
127 params: this.resolutions
128 })
129 }
130}
diff --git a/client/src/standalone/videos/embed.html b/client/src/standalone/videos/embed.html
index c3b6e08ca..5a15bf552 100644
--- a/client/src/standalone/videos/embed.html
+++ b/client/src/standalone/videos/embed.html
@@ -11,7 +11,7 @@
11 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" /> 11 <link rel="icon" type="image/png" href="/client/assets/images/favicon.png" />
12 </head> 12 </head>
13 13
14 <body> 14 <body id="custom-css">
15 15
16 <div id="error-block"> 16 <div id="error-block">
17 <h1 id="error-title"></h1> 17 <h1 id="error-title"></h1>
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts
index 707f04253..cfe8e94b1 100644
--- a/client/src/standalone/videos/embed.ts
+++ b/client/src/standalone/videos/embed.ts
@@ -1,9 +1,6 @@
1import './embed.scss' 1import './embed.scss'
2 2
3import * as Channel from 'jschannel'
4
5import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared' 3import { peertubeTranslate, ResultList, ServerConfig, VideoDetails } from '../../../../shared'
6import { PeerTubeResolution } from '../player/definitions'
7import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings' 4import { VideoJSCaption } from '../../assets/player/peertube-videojs-typings'
8import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' 5import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
9import { 6import {
@@ -13,133 +10,9 @@ import {
13 PlayerMode 10 PlayerMode
14} from '../../assets/player/peertube-player-manager' 11} from '../../assets/player/peertube-player-manager'
15import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 12import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
13import { PeerTubeEmbedApi } from './embed-api'
16 14
17/** 15export class PeerTubeEmbed {
18 * Embed API exposes control of the embed player to the outside world via
19 * JSChannels and window.postMessage
20 */
21class PeerTubeEmbedApi {
22 private channel: Channel.MessagingChannel
23 private isReady = false
24 private resolutions: PeerTubeResolution[] = null
25
26 constructor (private embed: PeerTubeEmbed) {
27 }
28
29 initialize () {
30 this.constructChannel()
31 this.setupStateTracking()
32
33 // We're ready!
34
35 this.notifyReady()
36 }
37
38 private get element () {
39 return this.embed.videoElement
40 }
41
42 private constructChannel () {
43 const channel = Channel.build({ window: window.parent, origin: '*', scope: this.embed.scope })
44
45 channel.bind('play', (txn, params) => this.embed.player.play())
46 channel.bind('pause', (txn, params) => this.embed.player.pause())
47 channel.bind('seek', (txn, time) => this.embed.player.currentTime(time))
48 channel.bind('setVolume', (txn, value) => this.embed.player.volume(value))
49 channel.bind('getVolume', (txn, value) => this.embed.player.volume())
50 channel.bind('isReady', (txn, params) => this.isReady)
51 channel.bind('setResolution', (txn, resolutionId) => this.setResolution(resolutionId))
52 channel.bind('getResolutions', (txn, params) => this.resolutions)
53 channel.bind('setPlaybackRate', (txn, playbackRate) => this.embed.player.playbackRate(playbackRate))
54 channel.bind('getPlaybackRate', (txn, params) => this.embed.player.playbackRate())
55 channel.bind('getPlaybackRates', (txn, params) => this.embed.playerOptions.playbackRates)
56
57 this.channel = channel
58 }
59
60 private setResolution (resolutionId: number) {
61 if (resolutionId === -1 && this.embed.player.webtorrent().isAutoResolutionForbidden()) return
62
63 // Auto resolution
64 if (resolutionId === -1) {
65 this.embed.player.webtorrent().enableAutoResolution()
66 return
67 }
68
69 this.embed.player.webtorrent().disableAutoResolution()
70 this.embed.player.webtorrent().updateResolution(resolutionId)
71 }
72
73 /**
74 * Let the host know that we're ready to go!
75 */
76 private notifyReady () {
77 this.isReady = true
78 this.channel.notify({ method: 'ready', params: true })
79 }
80
81 private setupStateTracking () {
82 let currentState: 'playing' | 'paused' | 'unstarted' = 'unstarted'
83
84 setInterval(() => {
85 const position = this.element.currentTime
86 const volume = this.element.volume
87
88 this.channel.notify({
89 method: 'playbackStatusUpdate',
90 params: {
91 position,
92 volume,
93 playbackState: currentState
94 }
95 })
96 }, 500)
97
98 this.element.addEventListener('play', ev => {
99 currentState = 'playing'
100 this.channel.notify({ method: 'playbackStatusChange', params: 'playing' })
101 })
102
103 this.element.addEventListener('pause', ev => {
104 currentState = 'paused'
105 this.channel.notify({ method: 'playbackStatusChange', params: 'paused' })
106 })
107
108 // PeerTube specific capabilities
109
110 if (this.embed.player.webtorrent) {
111 this.embed.player.webtorrent().on('autoResolutionUpdate', () => this.loadWebTorrentResolutions())
112 this.embed.player.webtorrent().on('videoFileUpdate', () => this.loadWebTorrentResolutions())
113 }
114 }
115
116 private loadWebTorrentResolutions () {
117 const resolutions = []
118 const currentResolutionId = this.embed.player.webtorrent().getCurrentResolutionId()
119
120 for (const videoFile of this.embed.player.webtorrent().videoFiles) {
121 let label = videoFile.resolution.label
122 if (videoFile.fps && videoFile.fps >= 50) {
123 label += videoFile.fps
124 }
125
126 resolutions.push({
127 id: videoFile.resolution.id,
128 label,
129 src: videoFile.magnetUri,
130 active: videoFile.resolution.id === currentResolutionId
131 })
132 }
133
134 this.resolutions = resolutions
135 this.channel.notify({
136 method: 'resolutionUpdate',
137 params: this.resolutions
138 })
139 }
140}
141
142class PeerTubeEmbed {
143 videoElement: HTMLVideoElement 16 videoElement: HTMLVideoElement
144 player: any 17 player: any
145 playerOptions: any 18 playerOptions: any
@@ -152,6 +25,12 @@ class PeerTubeEmbed {
152 enableApi = false 25 enableApi = false
153 startTime: number | string = 0 26 startTime: number | string = 0
154 stopTime: number | string 27 stopTime: number | string
28
29 title: boolean
30 warningTitle: boolean
31 bigPlayBackgroundColor: string
32 foregroundColor: string
33
155 mode: PlayerMode 34 mode: PlayerMode
156 scope = 'peertube' 35 scope = 'peertube'
157 36
@@ -245,13 +124,18 @@ class PeerTubeEmbed {
245 this.controls = this.getParamToggle(params, 'controls', true) 124 this.controls = this.getParamToggle(params, 'controls', true)
246 this.muted = this.getParamToggle(params, 'muted', false) 125 this.muted = this.getParamToggle(params, 'muted', false)
247 this.loop = this.getParamToggle(params, 'loop', false) 126 this.loop = this.getParamToggle(params, 'loop', false)
127 this.title = this.getParamToggle(params, 'title', true)
248 this.enableApi = this.getParamToggle(params, 'api', this.enableApi) 128 this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
129 this.warningTitle = this.getParamToggle(params, 'warningTitle', true)
249 130
250 this.scope = this.getParamString(params, 'scope', this.scope) 131 this.scope = this.getParamString(params, 'scope', this.scope)
251 this.subtitle = this.getParamString(params, 'subtitle') 132 this.subtitle = this.getParamString(params, 'subtitle')
252 this.startTime = this.getParamString(params, 'start') 133 this.startTime = this.getParamString(params, 'start')
253 this.stopTime = this.getParamString(params, 'stop') 134 this.stopTime = this.getParamString(params, 'stop')
254 135
136 this.bigPlayBackgroundColor = this.getParamString(params, 'bigPlayBackgroundColor')
137 this.foregroundColor = this.getParamString(params, 'foregroundColor')
138
255 this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent' 139 this.mode = this.getParamString(params, 'mode') === 'p2p-media-loader' ? 'p2p-media-loader' : 'webtorrent'
256 } catch (err) { 140 } catch (err) {
257 console.error('Cannot get params from URL.', err) 141 console.error('Cannot get params from URL.', err)
@@ -276,15 +160,7 @@ class PeerTubeEmbed {
276 } 160 }
277 161
278 const videoInfo: VideoDetails = await videoResponse.json() 162 const videoInfo: VideoDetails = await videoResponse.json()
279 let videoCaptions: VideoJSCaption[] = [] 163 const videoCaptions = await this.buildCaptions(serverTranslations, captionsResponse)
280 if (captionsResponse.ok) {
281 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
282 videoCaptions = data.map(c => ({
283 label: peertubeTranslate(c.language.label, serverTranslations),
284 language: c.language.id,
285 src: window.location.origin + c.captionPath
286 }))
287 }
288 164
289 this.loadParams() 165 this.loadParams()
290 166
@@ -337,33 +213,66 @@ class PeerTubeEmbed {
337 } 213 }
338 214
339 this.player = await PeertubePlayerManager.initialize(this.mode, options) 215 this.player = await PeertubePlayerManager.initialize(this.mode, options)
340
341 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations)) 216 this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
342 217
343 window[ 'videojsPlayer' ] = this.player 218 window[ 'videojsPlayer' ] = this.player
344 219
220 this.buildCSS()
221
222 await this.buildDock(videoInfo, configResponse)
223
224 this.initializeApi()
225 }
226
227 private handleError (err: Error, translations?: { [ id: string ]: string }) {
228 if (err.message.indexOf('from xs param') !== -1) {
229 this.player.dispose()
230 this.videoElement = null
231 this.displayError('This video is not available because the remote instance is not responding.', translations)
232 return
233 }
234 }
235
236 private async buildDock (videoInfo: VideoDetails, configResponse: Response) {
345 if (this.controls) { 237 if (this.controls) {
238 const title = this.title ? videoInfo.name : undefined
239
346 const config: ServerConfig = await configResponse.json() 240 const config: ServerConfig = await configResponse.json()
347 const description = config.tracker.enabled 241 const description = config.tracker.enabled && this.warningTitle
348 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' 242 ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>'
349 : undefined 243 : undefined
350 244
351 this.player.dock({ 245 this.player.dock({
352 title: videoInfo.name, 246 title,
353 description 247 description
354 }) 248 })
355 } 249 }
250 }
356 251
357 this.initializeApi() 252 private buildCSS () {
253 const body = document.getElementById('custom-css')
254
255 if (this.bigPlayBackgroundColor) {
256 body.style.setProperty('--embedBigPlayBackgroundColor', this.bigPlayBackgroundColor)
257 }
258
259 if (this.foregroundColor) {
260 body.style.setProperty('--embedForegroundColor', this.foregroundColor)
261 }
358 } 262 }
359 263
360 private handleError (err: Error, translations?: { [ id: string ]: string }) { 264 private async buildCaptions (serverTranslations: any, captionsResponse: Response): Promise<VideoJSCaption[]> {
361 if (err.message.indexOf('from xs param') !== -1) { 265 if (captionsResponse.ok) {
362 this.player.dispose() 266 const { data } = (await captionsResponse.json()) as ResultList<VideoCaption>
363 this.videoElement = null 267
364 this.displayError('This video is not available because the remote instance is not responding.', translations) 268 return data.map(c => ({
365 return 269 label: peertubeTranslate(c.language.label, serverTranslations),
270 language: c.language.id,
271 src: window.location.origin + c.captionPath
272 }))
366 } 273 }
274
275 return []
367 } 276 }
368} 277}
369 278
diff --git a/config/default.yaml b/config/default.yaml
index 37ef4366f..a213d5b0a 100644
--- a/config/default.yaml
+++ b/config/default.yaml
@@ -53,6 +53,12 @@ smtp:
53 ca_file: null # Used for self signed certificates 53 ca_file: null # Used for self signed certificates
54 from_address: 'admin@example.com' 54 from_address: 'admin@example.com'
55 55
56email:
57 body:
58 signature: "PeerTube"
59 object:
60 prefix: "[PeerTube]"
61
56# From the project root directory 62# From the project root directory
57storage: 63storage:
58 tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... 64 tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
@@ -69,6 +75,8 @@ storage:
69 75
70log: 76log:
71 level: 'info' # debug/info/warning/error 77 level: 'info' # debug/info/warning/error
78 rotation:
79 enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate
72 80
73search: 81search:
74 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance 82 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
@@ -174,6 +182,8 @@ transcoding:
174 enabled: true 182 enabled: true
175 # Allow your users to upload .mkv, .mov, .avi, .flv videos 183 # Allow your users to upload .mkv, .mov, .avi, .flv videos
176 allow_additional_extensions: true 184 allow_additional_extensions: true
185 # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
186 allow_audio_files: true
177 threads: 1 187 threads: 1
178 resolutions: # Only created if the original video has a higher resolution, uses more storage! 188 resolutions: # Only created if the original video has a higher resolution, uses more storage!
179 240p: false 189 240p: false
@@ -181,6 +191,7 @@ transcoding:
181 480p: false 191 480p: false
182 720p: false 192 720p: false
183 1080p: false 193 1080p: false
194 2160p: false
184 # /!\ EXPERIMENTAL /!\ 195 # /!\ EXPERIMENTAL /!\
185 # /!\ Requires ffmpeg >= 4 196 # /!\ Requires ffmpeg >= 4
186 # Generate HLS playlists and fragmented MP4 files. Better playback than with WebTorrent: 197 # Generate HLS playlists and fragmented MP4 files. Better playback than with WebTorrent:
diff --git a/config/production.yaml.example b/config/production.yaml.example
index f84e15670..cdf6136d8 100644
--- a/config/production.yaml.example
+++ b/config/production.yaml.example
@@ -54,6 +54,12 @@ smtp:
54 ca_file: null # Used for self signed certificates 54 ca_file: null # Used for self signed certificates
55 from_address: 'admin@example.com' 55 from_address: 'admin@example.com'
56 56
57email:
58 body:
59 signature: "PeerTube"
60 object:
61 prefix: "[PeerTube]"
62
57# From the project root directory 63# From the project root directory
58storage: 64storage:
59 tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... 65 tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
@@ -70,6 +76,8 @@ storage:
70 76
71log: 77log:
72 level: 'info' # debug/info/warning/error 78 level: 'info' # debug/info/warning/error
79 rotation:
80 enabled : true
73 81
74search: 82search:
75 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance 83 # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance
@@ -188,6 +196,8 @@ transcoding:
188 enabled: true 196 enabled: true
189 # Allow your users to upload .mkv, .mov, .avi, .flv videos 197 # Allow your users to upload .mkv, .mov, .avi, .flv videos
190 allow_additional_extensions: true 198 allow_additional_extensions: true
199 # If a user uploads an audio file, PeerTube will create a video by merging the preview file and the audio file
200 allow_audio_files: true
191 threads: 1 201 threads: 1
192 resolutions: # Only created if the original video has a higher resolution, uses more storage! 202 resolutions: # Only created if the original video has a higher resolution, uses more storage!
193 240p: false 203 240p: false
@@ -195,6 +205,7 @@ transcoding:
195 480p: false 205 480p: false
196 720p: false 206 720p: false
197 1080p: false 207 1080p: false
208 2160p: false
198 # /!\ EXPERIMENTAL /!\ 209 # /!\ EXPERIMENTAL /!\
199 # /!\ Requires ffmpeg >= 4 210 # /!\ Requires ffmpeg >= 4
200 # Generate HLS playlists and fragmented MP4 files. Better playback than with WebTorrent: 211 # Generate HLS playlists and fragmented MP4 files. Better playback than with WebTorrent:
@@ -245,8 +256,6 @@ instance:
245 "# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:" 256 "# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:"
246 257
247services: 258services:
248 # You can provide a reporting endpoint for Content Security Policy violations
249 csp-logger:
250 # Cards configuration to format video in Twitter 259 # Cards configuration to format video in Twitter
251 twitter: 260 twitter:
252 username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published 261 username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
diff --git a/config/test-2.yaml b/config/test-2.yaml
index a5515afa4..de7300366 100644
--- a/config/test-2.yaml
+++ b/config/test-2.yaml
@@ -31,3 +31,4 @@ signup:
31transcoding: 31transcoding:
32 enabled: true 32 enabled: true
33 allow_additional_extensions: true 33 allow_additional_extensions: true
34 allow_audio_files: true
diff --git a/config/test.yaml b/config/test.yaml
index 682530840..8d3921614 100644
--- a/config/test.yaml
+++ b/config/test.yaml
@@ -55,6 +55,7 @@ signup:
55transcoding: 55transcoding:
56 enabled: true 56 enabled: true
57 allow_additional_extensions: false 57 allow_additional_extensions: false
58 allow_audio_files: false
58 threads: 2 59 threads: 2
59 resolutions: 60 resolutions:
60 240p: true 61 240p: true
@@ -62,6 +63,7 @@ transcoding:
62 480p: true 63 480p: true
63 720p: true 64 720p: true
64 1080p: true 65 1080p: true
66 2160p: true
65 hls: 67 hls:
66 enabled: true 68 enabled: true
67 69
diff --git a/package.json b/package.json
index 3846b45fa..ee12718c7 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
23 "scripts": { 23 "scripts": {
24 "e2e": "scripty", 24 "e2e": "scripty",
25 "e2e:local": "scripty", 25 "e2e:local": "scripty",
26 "setup:cli": "scripty",
26 "build": "SCRIPTY_PARALLEL=true scripty", 27 "build": "SCRIPTY_PARALLEL=true scripty",
27 "build:server": "scripty", 28 "build:server": "scripty",
28 "build:client": "scripty", 29 "build:client": "scripty",
@@ -56,6 +57,7 @@
56 "postinstall": "test -n \"$NOCLIENT\" || (cd client && yarn install --pure-lockfile)", 57 "postinstall": "test -n \"$NOCLIENT\" || (cd client && yarn install --pure-lockfile)",
57 "tsc": "tsc", 58 "tsc": "tsc",
58 "commander": "commander", 59 "commander": "commander",
60 "lint": "npm run travis -- lint",
59 "ng": "ng", 61 "ng": "ng",
60 "nodemon": "nodemon", 62 "nodemon": "nodemon",
61 "ts-node": "ts-node", 63 "ts-node": "ts-node",
@@ -92,18 +94,15 @@
92 }, 94 },
93 "dependencies": { 95 "dependencies": {
94 "apicache": "^1.4.0", 96 "apicache": "^1.4.0",
95 "application-config": "^1.0.1", 97 "async": "^3.0.1",
96 "async": "^2.0.0",
97 "async-lru": "^1.1.1", 98 "async-lru": "^1.1.1",
98 "bcrypt": "3.0.5", 99 "bcrypt": "3.0.6",
99 "bittorrent-tracker": "^9.0.0", 100 "bittorrent-tracker": "^9.0.0",
100 "bluebird": "^3.5.0", 101 "bluebird": "^3.5.0",
101 "body-parser": "^1.12.4", 102 "body-parser": "^1.12.4",
102 "bull": "^3.4.2", 103 "bull": "^3.4.2",
103 "bytes": "^3.0.0", 104 "bytes": "^3.0.0",
104 "cli-table": "^0.3.1",
105 "commander": "^2.13.0", 105 "commander": "^2.13.0",
106 "concurrently": "^4.0.1",
107 "config": "^3.0.0", 106 "config": "^3.0.0",
108 "cookie-parser": "^1.4.3", 107 "cookie-parser": "^1.4.3",
109 "cors": "^2.8.1", 108 "cors": "^2.8.1",
@@ -111,14 +110,14 @@
111 "deep-object-diff": "^1.1.0", 110 "deep-object-diff": "^1.1.0",
112 "express": "^4.12.4", 111 "express": "^4.12.4",
113 "express-oauth-server": "^2.0.0", 112 "express-oauth-server": "^2.0.0",
114 "express-rate-limit": "^3.1.0", 113 "express-rate-limit": "^4.0.4",
115 "express-validator": "^5.0.0", 114 "express-validator": "^5.0.0",
116 "flat": "^4.1.0", 115 "flat": "^4.1.0",
117 "fluent-ffmpeg": "^2.1.0", 116 "fluent-ffmpeg": "^2.1.0",
118 "fs-extra": "^7.0.0", 117 "fs-extra": "^8.0.1",
119 "helmet": "^3.12.1", 118 "helmet": "^3.12.1",
120 "http-signature": "^1.2.0", 119 "http-signature": "^1.2.0",
121 "ip-anonymize": "^0.0.6", 120 "ip-anonymize": "^0.1.0",
122 "ipaddr.js": "1.9.0", 121 "ipaddr.js": "1.9.0",
123 "is-cidr": "^3.0.0", 122 "is-cidr": "^3.0.0",
124 "iso-639-3": "^1.0.1", 123 "iso-639-3": "^1.0.1",
@@ -130,7 +129,6 @@
130 "memoizee": "^0.4.14", 129 "memoizee": "^0.4.14",
131 "morgan": "^1.5.3", 130 "morgan": "^1.5.3",
132 "multer": "^1.1.0", 131 "multer": "^1.1.0",
133 "netrc-parser": "^3.1.6",
134 "nodemailer": "^6.0.0", 132 "nodemailer": "^6.0.0",
135 "parse-torrent": "^6.0.0", 133 "parse-torrent": "^6.0.0",
136 "password-generator": "^2.0.2", 134 "password-generator": "^2.0.2",
@@ -142,25 +140,24 @@
142 "reflect-metadata": "^0.1.12", 140 "reflect-metadata": "^0.1.12",
143 "request": "^2.81.0", 141 "request": "^2.81.0",
144 "scripty": "^1.5.0", 142 "scripty": "^1.5.0",
145 "sequelize": "5.7.4", 143 "sequelize": "5.8.7",
146 "sequelize-typescript": "1.0.0-beta.2", 144 "sequelize-typescript": "1.0.0-beta.2",
147 "sharp": "^0.22.0", 145 "sharp": "^0.22.0",
148 "sitemap": "^2.1.0", 146 "sitemap": "^2.1.0",
149 "socket.io": "^2.2.0", 147 "socket.io": "^2.2.0",
150 "srt-to-vtt": "^1.1.2", 148 "srt-to-vtt": "^1.1.2",
151 "summon-install": "^0.4.3",
152 "useragent": "^2.3.0", 149 "useragent": "^2.3.0",
153 "uuid": "^3.1.0", 150 "uuid": "^3.1.0",
154 "validator": "^10.2.0", 151 "validator": "^11.0.0",
155 "webfinger.js": "^2.6.6", 152 "webfinger.js": "^2.6.6",
156 "webtorrent": "^0.103.0", 153 "webtorrent": "^0.103.0",
157 "winston": "3.2.1", 154 "winston": "3.2.1",
158 "ws": "^6.0.0", 155 "ws": "^7.0.0",
159 "youtube-dl": "^1.12.2" 156 "youtube-dl": "^2.0.0"
160 }, 157 },
161 "devDependencies": { 158 "devDependencies": {
162 "@types/apicache": "^1.2.0", 159 "@types/apicache": "^1.2.0",
163 "@types/async": "^2.0.40", 160 "@types/async": "^3.0.0",
164 "@types/async-lock": "^1.1.0", 161 "@types/async-lock": "^1.1.0",
165 "@types/bcrypt": "^3.0.0", 162 "@types/bcrypt": "^3.0.0",
166 "@types/bluebird": "3.5.21", 163 "@types/bluebird": "3.5.21",
@@ -174,7 +171,7 @@
174 "@types/express": "^4.0.35", 171 "@types/express": "^4.0.35",
175 "@types/express-rate-limit": "^3.3.0", 172 "@types/express-rate-limit": "^3.3.0",
176 "@types/fluent-ffmpeg": "^2.1.8", 173 "@types/fluent-ffmpeg": "^2.1.8",
177 "@types/fs-extra": "^5.0.4", 174 "@types/fs-extra": "^7.0.0",
178 "@types/libxmljs": "^0.18.0", 175 "@types/libxmljs": "^0.18.0",
179 "@types/lodash": "^4.14.64", 176 "@types/lodash": "^4.14.64",
180 "@types/magnet-uri": "^5.1.1", 177 "@types/magnet-uri": "^5.1.1",
@@ -185,7 +182,7 @@
185 "@types/morgan": "^1.7.32", 182 "@types/morgan": "^1.7.32",
186 "@types/multer": "^1.3.3", 183 "@types/multer": "^1.3.3",
187 "@types/node": "^10.0.8", 184 "@types/node": "^10.0.8",
188 "@types/nodemailer": "^4.3.1", 185 "@types/nodemailer": "^6.2.0",
189 "@types/oauth2-server": "^3.0.8", 186 "@types/oauth2-server": "^3.0.8",
190 "@types/pem": "^1.9.3", 187 "@types/pem": "^1.9.3",
191 "@types/redis": "^2.8.5", 188 "@types/redis": "^2.8.5",
@@ -199,18 +196,20 @@
199 "chai": "^4.1.1", 196 "chai": "^4.1.1",
200 "chai-json-schema": "^1.5.0", 197 "chai-json-schema": "^1.5.0",
201 "chai-xml": "^0.3.2", 198 "chai-xml": "^0.3.2",
202 "husky": "^1.0.0-rc.4", 199 "concurrently": "^4.1.0",
200 "husky": "^2.4.0",
203 "libxmljs": "0.19.5", 201 "libxmljs": "0.19.5",
204 "lint-staged": "^8.0.4", 202 "lint-staged": "^8.0.4",
205 "maildev": "^1.0.0-rc3", 203 "maildev": "^1.0.0-rc3",
206 "marked-man": "^0.4.2", 204 "marked-man": "^0.6.0",
207 "mocha": "^6.0.0", 205 "mocha": "^6.0.0",
206 "mocha-parallel-tests": "^2.1.0",
208 "nodemon": "^1.18.6", 207 "nodemon": "^1.18.6",
209 "sass-lint": "^1.12.1", 208 "sass-lint": "^1.12.1",
210 "source-map-support": "^0.5.0", 209 "source-map-support": "^0.5.0",
211 "supertest": "^4.0.2", 210 "supertest": "^4.0.2",
212 "swagger-cli": "^2.2.0", 211 "swagger-cli": "^2.2.0",
213 "ts-node": "8.0.3", 212 "ts-node": "8.2.0",
214 "tslint": "^5.7.0", 213 "tslint": "^5.7.0",
215 "tslint-config-standard": "^8.0.1", 214 "tslint-config-standard": "^8.0.1",
216 "typescript": "^3.4.3", 215 "typescript": "^3.4.3",
@@ -219,8 +218,5 @@
219 "scripty": { 218 "scripty": {
220 "silent": true 219 "silent": true
221 }, 220 },
222 "summon": {
223 "silent": true
224 },
225 "sasslintConfig": "client/.sass-lint.yml" 221 "sasslintConfig": "client/.sass-lint.yml"
226} 222}
diff --git a/scripts/clean/server/test.sh b/scripts/clean/server/test.sh
index 5694ac922..20745170d 100755
--- a/scripts/clean/server/test.sh
+++ b/scripts/clean/server/test.sh
@@ -13,12 +13,15 @@ recreateDB () {
13} 13}
14 14
15removeFiles () { 15removeFiles () {
16 rm -rf "./test$1" "./config/local-test.json" "./config/local-test-$1.json" 16 rm -rf "./test$1" "./config/local-test.json" "./config/local-test-$1.json" ~/.config/PeerTube/CLI-$1
17} 17}
18 18
19dropRedis () { 19dropRedis () {
20 redis-cli KEYS "bull-localhost:900$1*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL 20 port=$((9000+$1))
21 redis-cli KEYS "redis-localhost:900$1*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL 21
22 redis-cli KEYS "bull-localhost:$port*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
23 redis-cli KEYS "redis-localhost:$port*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
24 redis-cli KEYS "*redis-localhost:$port-" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
22} 25}
23 26
24seq=$(seq 1 6) 27seq=$(seq 1 6)
diff --git a/scripts/create-transcoding-job.ts b/scripts/create-transcoding-job.ts
index 4a677eacb..2b7cb5177 100755
--- a/scripts/create-transcoding-job.ts
+++ b/scripts/create-transcoding-job.ts
@@ -2,6 +2,7 @@ import * as program from 'commander'
2import { VideoModel } from '../server/models/video/video' 2import { VideoModel } from '../server/models/video/video'
3import { initDatabaseModels } from '../server/initializers' 3import { initDatabaseModels } from '../server/initializers'
4import { JobQueue } from '../server/lib/job-queue' 4import { JobQueue } from '../server/lib/job-queue'
5import { VideoTranscodingPayload } from '../server/lib/job-queue/handlers/video-transcoding'
5 6
6program 7program
7 .option('-v, --video [videoUUID]', 'Video UUID') 8 .option('-v, --video [videoUUID]', 'Video UUID')
@@ -31,15 +32,9 @@ async function run () {
31 const video = await VideoModel.loadByUUIDWithFile(program['video']) 32 const video = await VideoModel.loadByUUIDWithFile(program['video'])
32 if (!video) throw new Error('Video not found.') 33 if (!video) throw new Error('Video not found.')
33 34
34 const dataInput = { 35 const dataInput: VideoTranscodingPayload = program.resolution !== undefined
35 videoUUID: video.uuid, 36 ? { type: 'new-resolution' as 'new-resolution', videoUUID: video.uuid, isNewVideo: false, resolution: program.resolution }
36 isNewVideo: false, 37 : { type: 'optimize' as 'optimize', videoUUID: video.uuid, isNewVideo: false }
37 resolution: undefined
38 }
39
40 if (program.resolution !== undefined) {
41 dataInput.resolution = program.resolution
42 }
43 38
44 await JobQueue.Instance.init() 39 await JobQueue.Instance.init()
45 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 40 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
diff --git a/scripts/setup/cli.sh b/scripts/setup/cli.sh
new file mode 100755
index 000000000..f761d7f8a
--- /dev/null
+++ b/scripts/setup/cli.sh
@@ -0,0 +1,15 @@
1#!/bin/sh
2
3set -eu
4
5NOCLIENT=1 yarn install --pure-lockfile
6
7rm -rf ./dist/server/tools/
8
9(
10 cd ./server/tools
11 yarn install --pure-lockfile
12)
13
14npm run tsc -- --build ./server/tools/tsconfig.json
15cp -r "./server/tools/node_modules" "./dist/server/tools"
diff --git a/scripts/test.sh b/scripts/test.sh
index cf8895365..5ec7a5920 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -3,6 +3,8 @@
3set -eu 3set -eu
4 4
5npm run build:server 5npm run build:server
6npm run setup:cli
7
6npm run travis -- lint 8npm run travis -- lint
7 9
8mocha --exit --require ts-node/register/type-check --bail server/tests/index.ts 10mocha --exit --require ts-node/register/type-check --bail server/tests/index.ts
diff --git a/scripts/travis.sh b/scripts/travis.sh
index 3557816c8..664d9fd6c 100755
--- a/scripts/travis.sh
+++ b/scripts/travis.sh
@@ -17,19 +17,20 @@ if [ "$1" = "misc" ]; then
17 server/tests/helpers/index.ts 17 server/tests/helpers/index.ts
18elif [ "$1" = "cli" ]; then 18elif [ "$1" = "cli" ]; then
19 npm run build:server 19 npm run build:server
20 CC=gcc-4.9 CXX=g++-4.9 npm run setup:cli
20 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/cli/index.ts 21 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/cli/index.ts
21elif [ "$1" = "api-1" ]; then 22elif [ "$1" = "api-1" ]; then
22 npm run build:server 23 npm run build:server
23 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-1.ts 24 sh ./server/tests/api/travis-1.sh 2
24elif [ "$1" = "api-2" ]; then 25elif [ "$1" = "api-2" ]; then
25 npm run build:server 26 npm run build:server
26 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-2.ts 27 sh ./server/tests/api/travis-2.sh 2
27elif [ "$1" = "api-3" ]; then 28elif [ "$1" = "api-3" ]; then
28 npm run build:server 29 npm run build:server
29 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-3.ts 30 sh ./server/tests/api/travis-3.sh 2
30elif [ "$1" = "api-4" ]; then 31elif [ "$1" = "api-4" ]; then
31 npm run build:server 32 npm run build:server
32 mocha --timeout 5000 --exit --require ts-node/register --bail server/tests/api/index-4.ts 33 sh ./server/tests/api/travis-4.sh 2
33elif [ "$1" = "lint" ]; then 34elif [ "$1" = "lint" ]; then
34 npm run tslint -- --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" "shared/**/*.ts" 35 npm run tslint -- --project ./tsconfig.json -c ./tslint.json server.ts "server/**/*.ts" "shared/**/*.ts"
35 36
diff --git a/server/assets/default-audio-background.jpg b/server/assets/default-audio-background.jpg
new file mode 100644
index 000000000..a19173eac
--- /dev/null
+++ b/server/assets/default-audio-background.jpg
Binary files differ
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 8d4db1e75..5a1d652f2 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -16,7 +16,8 @@ import {
16 accountNameWithHostGetValidator, 16 accountNameWithHostGetValidator,
17 accountsSortValidator, 17 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator, 18 ensureAuthUserOwnsAccountValidator,
19 videosSortValidator 19 videosSortValidator,
20 videoChannelsSortValidator
20} from '../../middlewares/validators' 21} from '../../middlewares/validators'
21import { AccountModel } from '../../models/account/account' 22import { AccountModel } from '../../models/account/account'
22import { AccountVideoRateModel } from '../../models/account/account-video-rate' 23import { AccountVideoRateModel } from '../../models/account/account-video-rate'
@@ -56,6 +57,10 @@ accountsRouter.get('/:accountName/videos',
56 57
57accountsRouter.get('/:accountName/video-channels', 58accountsRouter.get('/:accountName/video-channels',
58 asyncMiddleware(accountNameWithHostGetValidator), 59 asyncMiddleware(accountNameWithHostGetValidator),
60 paginationValidator,
61 videoChannelsSortValidator,
62 setDefaultSort,
63 setDefaultPagination,
59 asyncMiddleware(listAccountChannels) 64 asyncMiddleware(listAccountChannels)
60) 65)
61 66
@@ -108,7 +113,14 @@ async function listAccounts (req: express.Request, res: express.Response) {
108} 113}
109 114
110async function listAccountChannels (req: express.Request, res: express.Response) { 115async function listAccountChannels (req: express.Request, res: express.Response) {
111 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) 116 const options = {
117 accountId: res.locals.account.id,
118 start: req.query.start,
119 count: req.query.count,
120 sort: req.query.sort
121 }
122
123 const resultList = await VideoChannelModel.listByAccount(options)
112 124
113 return res.json(getFormattedObjects(resultList.data, resultList.total)) 125 return res.json(getFormattedObjects(resultList.data, resultList.total))
114} 126}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 40012c03b..1d12f701b 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -51,7 +51,7 @@ async function getConfig (req: express.Request, res: express.Response) {
51 if (serverCommit === undefined) serverCommit = await getServerCommit() 51 if (serverCommit === undefined) serverCommit = await getServerCommit()
52 52
53 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) 53 const enabledResolutions = Object.keys(CONFIG.TRANSCODING.RESOLUTIONS)
54 .filter(key => CONFIG.TRANSCODING.ENABLED === CONFIG.TRANSCODING.RESOLUTIONS[key] === true) 54 .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true)
55 .map(r => parseInt(r, 10)) 55 .map(r => parseInt(r, 10))
56 56
57 const json: ServerConfig = { 57 const json: ServerConfig = {
@@ -255,13 +255,15 @@ function customConfig (): CustomConfig {
255 transcoding: { 255 transcoding: {
256 enabled: CONFIG.TRANSCODING.ENABLED, 256 enabled: CONFIG.TRANSCODING.ENABLED,
257 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS, 257 allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
258 allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES,
258 threads: CONFIG.TRANSCODING.THREADS, 259 threads: CONFIG.TRANSCODING.THREADS,
259 resolutions: { 260 resolutions: {
260 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], 261 '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
261 '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ], 262 '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ],
262 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 263 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
263 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 264 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
264 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] 265 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ],
266 '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ]
265 }, 267 },
266 hls: { 268 hls: {
267 enabled: CONFIG.TRANSCODING.HLS.ENABLED 269 enabled: CONFIG.TRANSCODING.HLS.ENABLED
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index 0aafba66e..c1d72087c 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -6,7 +6,7 @@ import { getFormattedObjects } from '../../../helpers/utils'
6import { RATES_LIMIT, WEBSERVER } from '../../../initializers/constants' 6import { RATES_LIMIT, WEBSERVER } from '../../../initializers/constants'
7import { Emailer } from '../../../lib/emailer' 7import { Emailer } from '../../../lib/emailer'
8import { Redis } from '../../../lib/redis' 8import { Redis } from '../../../lib/redis'
9import { createUserAccountAndChannelAndPlaylist } from '../../../lib/user' 9import { createUserAccountAndChannelAndPlaylist, sendVerifyUserEmail } from '../../../lib/user'
10import { 10import {
11 asyncMiddleware, 11 asyncMiddleware,
12 asyncRetryTransactionMiddleware, 12 asyncRetryTransactionMiddleware,
@@ -46,14 +46,18 @@ import { mySubscriptionsRouter } from './my-subscriptions'
46import { CONFIG } from '../../../initializers/config' 46import { CONFIG } from '../../../initializers/config'
47import { sequelizeTypescript } from '../../../initializers/database' 47import { sequelizeTypescript } from '../../../initializers/database'
48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' 48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
49import { UserRegister } from '../../../../shared/models/users/user-register.model'
49 50
50const auditLogger = auditLoggerFactory('users') 51const auditLogger = auditLoggerFactory('users')
51 52
52const loginRateLimiter = new RateLimit({ 53// FIXME: https://github.com/nfriedly/express-rate-limit/issues/138
54// @ts-ignore
55const loginRateLimiter = RateLimit({
53 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS, 56 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
54 max: RATES_LIMIT.LOGIN.MAX 57 max: RATES_LIMIT.LOGIN.MAX
55}) 58})
56 59
60// @ts-ignore
57const askSendEmailLimiter = new RateLimit({ 61const askSendEmailLimiter = new RateLimit({
58 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, 62 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
59 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX 63 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX
@@ -143,7 +147,7 @@ usersRouter.post('/:id/reset-password',
143usersRouter.post('/ask-send-verify-email', 147usersRouter.post('/ask-send-verify-email',
144 askSendEmailLimiter, 148 askSendEmailLimiter,
145 asyncMiddleware(usersAskSendVerifyEmailValidator), 149 asyncMiddleware(usersAskSendVerifyEmailValidator),
146 asyncMiddleware(askSendVerifyUserEmail) 150 asyncMiddleware(reSendVerifyUserEmail)
147) 151)
148 152
149usersRouter.post('/:id/verify-email', 153usersRouter.post('/:id/verify-email',
@@ -180,7 +184,7 @@ async function createUser (req: express.Request, res: express.Response) {
180 adminFlags: body.adminFlags || UserAdminFlag.NONE 184 adminFlags: body.adminFlags || UserAdminFlag.NONE
181 }) 185 })
182 186
183 const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate) 187 const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate })
184 188
185 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) 189 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
186 logger.info('User %s with its channel and account created.', body.username) 190 logger.info('User %s with its channel and account created.', body.username)
@@ -189,15 +193,14 @@ async function createUser (req: express.Request, res: express.Response) {
189 user: { 193 user: {
190 id: user.id, 194 id: user.id,
191 account: { 195 account: {
192 id: account.id, 196 id: account.id
193 uuid: account.Actor.uuid
194 } 197 }
195 } 198 }
196 }).end() 199 }).end()
197} 200}
198 201
199async function registerUser (req: express.Request, res: express.Response) { 202async function registerUser (req: express.Request, res: express.Response) {
200 const body: UserCreate = req.body 203 const body: UserRegister = req.body
201 204
202 const userToCreate = new UserModel({ 205 const userToCreate = new UserModel({
203 username: body.username, 206 username: body.username,
@@ -211,7 +214,11 @@ async function registerUser (req: express.Request, res: express.Response) {
211 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null 214 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
212 }) 215 })
213 216
214 const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate) 217 const { user } = await createUserAccountAndChannelAndPlaylist({
218 userToCreate: userToCreate,
219 userDisplayName: body.displayName || undefined,
220 channelNames: body.channel
221 })
215 222
216 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) 223 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
217 logger.info('User %s with its channel and account registered.', body.username) 224 logger.info('User %s with its channel and account registered.', body.username)
@@ -313,14 +320,7 @@ async function resetUserPassword (req: express.Request, res: express.Response) {
313 return res.status(204).end() 320 return res.status(204).end()
314} 321}
315 322
316async function sendVerifyUserEmail (user: UserModel) { 323async function reSendVerifyUserEmail (req: express.Request, res: express.Response) {
317 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
318 const url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
319 await Emailer.Instance.addVerifyEmailJob(user.email, url)
320 return
321}
322
323async function askSendVerifyUserEmail (req: express.Request, res: express.Response) {
324 const user = res.locals.user 324 const user = res.locals.user
325 325
326 await sendVerifyUserEmail(user) 326 await sendVerifyUserEmail(user)
@@ -332,6 +332,11 @@ async function verifyUserEmail (req: express.Request, res: express.Response) {
332 const user = res.locals.user 332 const user = res.locals.user
333 user.emailVerified = true 333 user.emailVerified = true
334 334
335 if (req.body.isPendingEmail === true) {
336 user.email = user.pendingEmail
337 user.pendingEmail = null
338 }
339
335 await user.save() 340 await user.save()
336 341
337 return res.status(204).end() 342 return res.status(204).end()
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index ddb239e7b..1750a02e9 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -28,6 +28,7 @@ import { VideoImportModel } from '../../../models/video/video-import'
28import { AccountModel } from '../../../models/account/account' 28import { AccountModel } from '../../../models/account/account'
29import { CONFIG } from '../../../initializers/config' 29import { CONFIG } from '../../../initializers/config'
30import { sequelizeTypescript } from '../../../initializers/database' 30import { sequelizeTypescript } from '../../../initializers/database'
31import { sendVerifyUserEmail } from '../../../lib/user'
31 32
32const auditLogger = auditLoggerFactory('users-me') 33const auditLogger = auditLoggerFactory('users-me')
33 34
@@ -171,17 +172,26 @@ async function deleteMe (req: express.Request, res: express.Response) {
171 172
172async function updateMe (req: express.Request, res: express.Response) { 173async function updateMe (req: express.Request, res: express.Response) {
173 const body: UserUpdateMe = req.body 174 const body: UserUpdateMe = req.body
175 let sendVerificationEmail = false
174 176
175 const user = res.locals.oauth.token.user 177 const user = res.locals.oauth.token.user
176 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({})) 178 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
177 179
178 if (body.password !== undefined) user.password = body.password 180 if (body.password !== undefined) user.password = body.password
179 if (body.email !== undefined) user.email = body.email
180 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy 181 if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
181 if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled 182 if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
182 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo 183 if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
183 if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled 184 if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
184 185
186 if (body.email !== undefined) {
187 if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
188 user.pendingEmail = body.email
189 sendVerificationEmail = true
190 } else {
191 user.email = body.email
192 }
193 }
194
185 await sequelizeTypescript.transaction(async t => { 195 await sequelizeTypescript.transaction(async t => {
186 const userAccount = await AccountModel.load(user.Account.id) 196 const userAccount = await AccountModel.load(user.Account.id)
187 197
@@ -196,6 +206,10 @@ async function updateMe (req: express.Request, res: express.Response) {
196 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) 206 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
197 }) 207 })
198 208
209 if (sendVerificationEmail === true) {
210 await sendVerifyUserEmail(user, true)
211 }
212
199 return res.sendStatus(204) 213 return res.sendStatus(204)
200} 214}
201 215
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index 3d6dbfe70..81a03a62b 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel'
19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' 19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
20import { sendUpdateActor } from '../../lib/activitypub/send' 20import { sendUpdateActor } from '../../lib/activitypub/send'
21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22import { createVideoChannel } from '../../lib/video-channel' 22import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
24import { setAsyncActorKeys } from '../../lib/activitypub' 24import { setAsyncActorKeys } from '../../lib/activitypub'
25import { AccountModel } from '../../models/account/account' 25import { AccountModel } from '../../models/account/account'
@@ -143,15 +143,14 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
143 }) 143 })
144 144
145 setAsyncActorKeys(videoChannelCreated.Actor) 145 setAsyncActorKeys(videoChannelCreated.Actor)
146 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.uuid, { err })) 146 .catch(err => logger.error('Cannot set async actor keys for account %s.', videoChannelCreated.Actor.url, { err }))
147 147
148 auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON())) 148 auditLogger.create(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelCreated.toFormattedJSON()))
149 logger.info('Video channel with uuid %s created.', videoChannelCreated.Actor.uuid) 149 logger.info('Video channel %s created.', videoChannelCreated.Actor.url)
150 150
151 return res.json({ 151 return res.json({
152 videoChannel: { 152 videoChannel: {
153 id: videoChannelCreated.id, 153 id: videoChannelCreated.id
154 uuid: videoChannelCreated.Actor.uuid
155 } 154 }
156 }).end() 155 }).end()
157} 156}
@@ -161,6 +160,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
161 const videoChannelFieldsSave = videoChannelInstance.toJSON() 160 const videoChannelFieldsSave = videoChannelInstance.toJSON()
162 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) 161 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
163 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate 162 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
163 let doBulkVideoUpdate = false
164 164
165 try { 165 try {
166 await sequelizeTypescript.transaction(async t => { 166 await sequelizeTypescript.transaction(async t => {
@@ -168,9 +168,18 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
168 transaction: t 168 transaction: t
169 } 169 }
170 170
171 if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.set('name', videoChannelInfoToUpdate.displayName) 171 if (videoChannelInfoToUpdate.displayName !== undefined) videoChannelInstance.name = videoChannelInfoToUpdate.displayName
172 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.set('description', videoChannelInfoToUpdate.description) 172 if (videoChannelInfoToUpdate.description !== undefined) videoChannelInstance.description = videoChannelInfoToUpdate.description
173 if (videoChannelInfoToUpdate.support !== undefined) videoChannelInstance.set('support', videoChannelInfoToUpdate.support) 173
174 if (videoChannelInfoToUpdate.support !== undefined) {
175 const oldSupportField = videoChannelInstance.support
176 videoChannelInstance.support = videoChannelInfoToUpdate.support
177
178 if (videoChannelInfoToUpdate.bulkVideosSupportUpdate === true && oldSupportField !== videoChannelInfoToUpdate.support) {
179 doBulkVideoUpdate = true
180 await VideoModel.bulkUpdateSupportField(videoChannelInstance, t)
181 }
182 }
174 183
175 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) 184 const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions)
176 await sendUpdateActor(videoChannelInstanceUpdated, t) 185 await sendUpdateActor(videoChannelInstanceUpdated, t)
@@ -180,7 +189,8 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
180 new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()), 189 new VideoChannelAuditView(videoChannelInstanceUpdated.toFormattedJSON()),
181 oldVideoChannelAuditKeys 190 oldVideoChannelAuditKeys
182 ) 191 )
183 logger.info('Video channel with name %s and uuid %s updated.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) 192
193 logger.info('Video channel %s updated.', videoChannelInstance.Actor.url)
184 }) 194 })
185 } catch (err) { 195 } catch (err) {
186 logger.debug('Cannot update the video channel.', { err }) 196 logger.debug('Cannot update the video channel.', { err })
@@ -193,7 +203,12 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
193 throw err 203 throw err
194 } 204 }
195 205
196 return res.type('json').status(204).end() 206 res.type('json').status(204).end()
207
208 // Don't process in a transaction, and after the response because it could be long
209 if (doBulkVideoUpdate) {
210 await federateAllVideosOfChannel(videoChannelInstance)
211 }
197} 212}
198 213
199async function removeVideoChannel (req: express.Request, res: express.Response) { 214async function removeVideoChannel (req: express.Request, res: express.Response) {
@@ -205,7 +220,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
205 await videoChannelInstance.destroy({ transaction: t }) 220 await videoChannelInstance.destroy({ transaction: t })
206 221
207 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) 222 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
208 logger.info('Video channel with name %s and uuid %s deleted.', videoChannelInstance.name, videoChannelInstance.Actor.uuid) 223 logger.info('Video channel %s deleted.', videoChannelInstance.Actor.url)
209 }) 224 })
210 225
211 return res.type('json').status(204).end() 226 return res.type('json').status(204).end()
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
index a17136401..62490e63b 100644
--- a/server/controllers/api/video-playlist.ts
+++ b/server/controllers/api/video-playlist.ts
@@ -203,7 +203,9 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
203 const videoPlaylistInstance = res.locals.videoPlaylist 203 const videoPlaylistInstance = res.locals.videoPlaylist
204 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() 204 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
205 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate 205 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
206
206 const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE 207 const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE
208 const wasNotPrivatePlaylist = videoPlaylistInstance.privacy !== VideoPlaylistPrivacy.PRIVATE
207 209
208 const thumbnailField = req.files['thumbnailfile'] 210 const thumbnailField = req.files['thumbnailfile']
209 const thumbnailModel = thumbnailField 211 const thumbnailModel = thumbnailField
@@ -232,6 +234,10 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
232 234
233 if (videoPlaylistInfoToUpdate.privacy !== undefined) { 235 if (videoPlaylistInfoToUpdate.privacy !== undefined) {
234 videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10) 236 videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10)
237
238 if (wasNotPrivatePlaylist === true && videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE) {
239 await sendDeleteVideoPlaylist(videoPlaylistInstance, t)
240 }
235 } 241 }
236 242
237 const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions) 243 const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 1a18a8ae8..5ebd8fbc4 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -6,7 +6,14 @@ import { logger } from '../../../helpers/logger'
6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
7import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 7import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' 8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
9import { MIMETYPES, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants' 9import {
10 DEFAULT_AUDIO_RESOLUTION,
11 MIMETYPES,
12 VIDEO_CATEGORIES,
13 VIDEO_LANGUAGES,
14 VIDEO_LICENCES,
15 VIDEO_PRIVACIES
16} from '../../../initializers/constants'
10import { 17import {
11 changeVideoChannelShare, 18 changeVideoChannelShare,
12 federateVideoIfNeeded, 19 federateVideoIfNeeded,
@@ -54,6 +61,7 @@ import { CONFIG } from '../../../initializers/config'
54import { sequelizeTypescript } from '../../../initializers/database' 61import { sequelizeTypescript } from '../../../initializers/database'
55import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' 62import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
56import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 63import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
64import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
57 65
58const auditLogger = auditLoggerFactory('videos') 66const auditLogger = auditLoggerFactory('videos')
59const videosRouter = express.Router() 67const videosRouter = express.Router()
@@ -191,17 +199,17 @@ async function addVideo (req: express.Request, res: express.Response) {
191 const video = new VideoModel(videoData) 199 const video = new VideoModel(videoData)
192 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object 200 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
193 201
194 // Build the file object 202 const videoFile = new VideoFileModel({
195 const { videoFileResolution } = await getVideoFileResolution(videoPhysicalFile.path)
196 const fps = await getVideoFileFPS(videoPhysicalFile.path)
197
198 const videoFileData = {
199 extname: extname(videoPhysicalFile.filename), 203 extname: extname(videoPhysicalFile.filename),
200 resolution: videoFileResolution, 204 size: videoPhysicalFile.size
201 size: videoPhysicalFile.size, 205 })
202 fps 206
207 if (videoFile.isAudio()) {
208 videoFile.resolution = DEFAULT_AUDIO_RESOLUTION
209 } else {
210 videoFile.fps = await getVideoFileFPS(videoPhysicalFile.path)
211 videoFile.resolution = (await getVideoFileResolution(videoPhysicalFile.path)).videoFileResolution
203 } 212 }
204 const videoFile = new VideoFileModel(videoFileData)
205 213
206 // Move physical file 214 // Move physical file
207 const videoDir = CONFIG.STORAGE.VIDEOS_DIR 215 const videoDir = CONFIG.STORAGE.VIDEOS_DIR
@@ -279,9 +287,21 @@ async function addVideo (req: express.Request, res: express.Response) {
279 287
280 if (video.state === VideoState.TO_TRANSCODE) { 288 if (video.state === VideoState.TO_TRANSCODE) {
281 // Put uuid because we don't have id auto incremented for now 289 // Put uuid because we don't have id auto incremented for now
282 const dataInput = { 290 let dataInput: VideoTranscodingPayload
283 videoUUID: videoCreated.uuid, 291
284 isNewVideo: true 292 if (videoFile.isAudio()) {
293 dataInput = {
294 type: 'merge-audio' as 'merge-audio',
295 resolution: DEFAULT_AUDIO_RESOLUTION,
296 videoUUID: videoCreated.uuid,
297 isNewVideo: true
298 }
299 } else {
300 dataInput = {
301 type: 'optimize' as 'optimize',
302 videoUUID: videoCreated.uuid,
303 isNewVideo: true
304 }
285 } 305 }
286 306
287 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) 307 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
@@ -300,7 +320,9 @@ async function updateVideo (req: express.Request, res: express.Response) {
300 const videoFieldsSave = videoInstance.toJSON() 320 const videoFieldsSave = videoInstance.toJSON()
301 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) 321 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
302 const videoInfoToUpdate: VideoUpdate = req.body 322 const videoInfoToUpdate: VideoUpdate = req.body
323
303 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE 324 const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
325 const wasNotPrivateVideo = videoInstance.privacy !== VideoPrivacy.PRIVATE
304 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED 326 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED
305 327
306 // Process thumbnail or create it from the video 328 // Process thumbnail or create it from the video
@@ -336,9 +358,15 @@ async function updateVideo (req: express.Request, res: express.Response) {
336 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) 358 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
337 videoInstance.privacy = newPrivacy 359 videoInstance.privacy = newPrivacy
338 360
361 // The video was private, and is not anymore -> publish it
339 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) { 362 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) {
340 videoInstance.publishedAt = new Date() 363 videoInstance.publishedAt = new Date()
341 } 364 }
365
366 // The video was not private, but now it is -> we need to unfederate it
367 if (wasNotPrivateVideo === true && newPrivacy === VideoPrivacy.PRIVATE) {
368 await VideoModel.sendDelete(videoInstance, { transaction: t })
369 }
342 } 370 }
343 371
344 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 372 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index fb2e7742a..a6b462443 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -194,7 +194,7 @@ async function getVideoCaption (req: express.Request, res: express.Response) {
194 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE }) 194 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
195} 195}
196 196
197async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) { 197async function generateNodeinfo (req: express.Request, res: express.Response) {
198 const { totalVideos } = await VideoModel.getStats() 198 const { totalVideos } = await VideoModel.getStats()
199 const { totalLocalVideoComments } = await VideoCommentModel.getStats() 199 const { totalLocalVideoComments } = await VideoCommentModel.getStats()
200 const { totalUsers } = await UserModel.getStats() 200 const { totalUsers } = await UserModel.getStats()
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 305d3b71e..b1e9af0a1 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -134,6 +134,10 @@ function isProdInstance () {
134 return process.env.NODE_ENV === 'production' 134 return process.env.NODE_ENV === 'production'
135} 135}
136 136
137function getAppNumber () {
138 return process.env.NODE_APP_INSTANCE
139}
140
137function root () { 141function root () {
138 // We are in /helpers/utils.js 142 // We are in /helpers/utils.js
139 const paths = [ __dirname, '..', '..' ] 143 const paths = [ __dirname, '..', '..' ]
@@ -256,6 +260,7 @@ const execPromise = promisify1<string, string>(exec)
256export { 260export {
257 isTestInstance, 261 isTestInstance,
258 isProdInstance, 262 isProdInstance,
263 getAppNumber,
259 264
260 objectConverter, 265 objectConverter,
261 root, 266 root,
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts
index 146c7708e..31a2de5ca 100644
--- a/server/helpers/custom-validators/accounts.ts
+++ b/server/helpers/custom-validators/accounts.ts
@@ -1,7 +1,6 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { Response } from 'express' 2import { Response } from 'express'
3import 'express-validator' 3import 'express-validator'
4import * as validator from 'validator'
5import { AccountModel } from '../../models/account/account' 4import { AccountModel } from '../../models/account/account'
6import { isUserDescriptionValid, isUserUsernameValid } from './users' 5import { isUserDescriptionValid, isUserUsernameValid } from './users'
7import { exists } from './misc' 6import { exists } from './misc'
@@ -18,14 +17,8 @@ function isAccountDescriptionValid (value: string) {
18 return isUserDescriptionValid(value) 17 return isUserDescriptionValid(value)
19} 18}
20 19
21function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { 20function doesAccountIdExist (id: number, res: Response, sendNotFound = true) {
22 let promise: Bluebird<AccountModel> 21 const promise = AccountModel.load(id)
23
24 if (validator.isInt('' + id)) {
25 promise = AccountModel.load(+id)
26 } else { // UUID
27 promise = AccountModel.loadByUUID('' + id)
28 }
29 22
30 return doesAccountExist(promise, res, sendNotFound) 23 return doesAccountExist(promise, res, sendNotFound)
31} 24}
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts
index 26c8c4cc6..e04c5388f 100644
--- a/server/helpers/custom-validators/activitypub/video-comments.ts
+++ b/server/helpers/custom-validators/activitypub/video-comments.ts
@@ -36,7 +36,8 @@ function normalizeComment (comment: any) {
36 if (!comment) return 36 if (!comment) return
37 37
38 if (typeof comment.url !== 'string') { 38 if (typeof comment.url !== 'string') {
39 comment.url = comment.url.href || comment.url.url 39 if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url
40 else comment.url = comment.id
40 } 41 }
41 42
42 return 43 return
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts
index fd56b9a70..f818ce8f1 100644
--- a/server/helpers/custom-validators/video-channels.ts
+++ b/server/helpers/custom-validators/video-channels.ts
@@ -26,13 +26,8 @@ async function doesLocalVideoChannelNameExist (name: string, res: express.Respon
26 return processVideoChannelExist(videoChannel, res) 26 return processVideoChannelExist(videoChannel, res)
27} 27}
28 28
29async function doesVideoChannelIdExist (id: number | string, res: express.Response) { 29async function doesVideoChannelIdExist (id: number, res: express.Response) {
30 let videoChannel: VideoChannelModel 30 const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
31 if (validator.isInt('' + id)) {
32 videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id)
33 } else { // UUID
34 videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount('' + id)
35 }
36 31
37 return processVideoChannelExist(videoChannel, res) 32 return processVideoChannelExist(videoChannel, res)
38} 33}
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts
index e0a1d56a5..00f3f198b 100644
--- a/server/helpers/express-utils.ts
+++ b/server/helpers/express-utils.ts
@@ -74,7 +74,18 @@ function createReqFiles (
74 }, 74 },
75 75
76 filename: async (req, file, cb) => { 76 filename: async (req, file, cb) => {
77 const extension = mimeTypes[ file.mimetype ] || extname(file.originalname) 77 let extension: string
78 const fileExtension = extname(file.originalname)
79 const extensionFromMimetype = mimeTypes[ file.mimetype ]
80
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
83 if (fileExtension === '.ogg' || fileExtension === '.ogv' || !extensionFromMimetype) {
84 extension = fileExtension
85 } else {
86 extension = extensionFromMimetype
87 }
88
78 let randomString = '' 89 let randomString = ''
79 90
80 try { 91 try {
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 76b744de8..8041e7b3b 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, VideoResolution } from '../../shared/models/videos' 3import { getTargetBitrate, getMaxBitrate, 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'
@@ -18,7 +18,8 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
18 VideoResolution.H_360P, 18 VideoResolution.H_360P,
19 VideoResolution.H_720P, 19 VideoResolution.H_720P,
20 VideoResolution.H_240P, 20 VideoResolution.H_240P,
21 VideoResolution.H_1080P 21 VideoResolution.H_1080P,
22 VideoResolution.H_4K
22 ] 23 ]
23 24
24 for (const resolution of resolutions) { 25 for (const resolution of resolutions) {
@@ -31,7 +32,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) {
31} 32}
32 33
33async function getVideoFileSize (path: string) { 34async function getVideoFileSize (path: string) {
34 const videoStream = await getVideoFileStream(path) 35 const videoStream = await getVideoStreamFromFile(path)
35 36
36 return { 37 return {
37 width: videoStream.width, 38 width: videoStream.width,
@@ -49,7 +50,7 @@ async function getVideoFileResolution (path: string) {
49} 50}
50 51
51async function getVideoFileFPS (path: string) { 52async function getVideoFileFPS (path: string) {
52 const videoStream = await getVideoFileStream(path) 53 const videoStream = await getVideoStreamFromFile(path)
53 54
54 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { 55 for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
55 const valuesText: string = videoStream[key] 56 const valuesText: string = videoStream[key]
@@ -117,25 +118,50 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
117 } 118 }
118} 119}
119 120
120type TranscodeOptions = { 121type TranscodeOptionsType = 'hls' | 'quick-transcode' | 'video' | 'merge-audio'
122
123interface BaseTranscodeOptions {
124 type: TranscodeOptionsType
121 inputPath: string 125 inputPath: string
122 outputPath: string 126 outputPath: string
123 resolution: VideoResolution 127 resolution: VideoResolution
124 isPortraitMode?: boolean 128 isPortraitMode?: boolean
129}
125 130
126 hlsPlaylist?: { 131interface HLSTranscodeOptions extends BaseTranscodeOptions {
132 type: 'hls'
133 hlsPlaylist: {
127 videoFilename: string 134 videoFilename: string
128 } 135 }
129} 136}
130 137
138interface QuickTranscodeOptions extends BaseTranscodeOptions {
139 type: 'quick-transcode'
140}
141
142interface VideoTranscodeOptions extends BaseTranscodeOptions {
143 type: 'video'
144}
145
146interface MergeAudioTranscodeOptions extends BaseTranscodeOptions {
147 type: 'merge-audio'
148 audioPath: string
149}
150
151type TranscodeOptions = HLSTranscodeOptions | VideoTranscodeOptions | MergeAudioTranscodeOptions | QuickTranscodeOptions
152
131function transcode (options: TranscodeOptions) { 153function transcode (options: TranscodeOptions) {
132 return new Promise<void>(async (res, rej) => { 154 return new Promise<void>(async (res, rej) => {
133 try { 155 try {
134 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) 156 let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
135 .output(options.outputPath) 157 .output(options.outputPath)
136 158
137 if (options.hlsPlaylist) { 159 if (options.type === 'quick-transcode') {
160 command = await buildQuickTranscodeCommand(command)
161 } else if (options.type === 'hls') {
138 command = await buildHLSCommand(command, options) 162 command = await buildHLSCommand(command, options)
163 } else if (options.type === 'merge-audio') {
164 command = await buildAudioMergeCommand(command, options)
139 } else { 165 } else {
140 command = await buildx264Command(command, options) 166 command = await buildx264Command(command, options)
141 } 167 }
@@ -151,7 +177,7 @@ function transcode (options: TranscodeOptions) {
151 return rej(err) 177 return rej(err)
152 }) 178 })
153 .on('end', () => { 179 .on('end', () => {
154 return onTranscodingSuccess(options) 180 return fixHLSPlaylistIfNeeded(options)
155 .then(() => res()) 181 .then(() => res())
156 .catch(err => rej(err)) 182 .catch(err => rej(err))
157 }) 183 })
@@ -162,6 +188,30 @@ function transcode (options: TranscodeOptions) {
162 }) 188 })
163} 189}
164 190
191async function canDoQuickTranscode (path: string): Promise<boolean> {
192 // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway)
193 const videoStream = await getVideoStreamFromFile(path)
194 const parsedAudio = await audio.get(path)
195 const fps = await getVideoFileFPS(path)
196 const bitRate = await getVideoFileBitrate(path)
197 const resolution = await getVideoFileResolution(path)
198
199 // check video params
200 if (videoStream[ 'codec_name' ] !== 'h264') return false
201 if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
202 if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
203
204 // check audio params (if audio stream exists)
205 if (parsedAudio.audioStream) {
206 if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false
207
208 const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ])
209 if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false
210 }
211
212 return true
213}
214
165// --------------------------------------------------------------------------- 215// ---------------------------------------------------------------------------
166 216
167export { 217export {
@@ -169,16 +219,19 @@ export {
169 getVideoFileResolution, 219 getVideoFileResolution,
170 getDurationFromVideoFile, 220 getDurationFromVideoFile,
171 generateImageFromVideoFile, 221 generateImageFromVideoFile,
222 TranscodeOptions,
223 TranscodeOptionsType,
172 transcode, 224 transcode,
173 getVideoFileFPS, 225 getVideoFileFPS,
174 computeResolutionsToTranscode, 226 computeResolutionsToTranscode,
175 audio, 227 audio,
176 getVideoFileBitrate 228 getVideoFileBitrate,
229 canDoQuickTranscode
177} 230}
178 231
179// --------------------------------------------------------------------------- 232// ---------------------------------------------------------------------------
180 233
181async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { 234async function buildx264Command (command: ffmpeg.FfmpegCommand, options: VideoTranscodeOptions) {
182 let fps = await getVideoFileFPS(options.inputPath) 235 let fps = await getVideoFileFPS(options.inputPath)
183 // On small/medium resolutions, limit FPS 236 // On small/medium resolutions, limit FPS
184 if ( 237 if (
@@ -189,7 +242,7 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
189 fps = VIDEO_TRANSCODING_FPS.AVERAGE 242 fps = VIDEO_TRANSCODING_FPS.AVERAGE
190 } 243 }
191 244
192 command = await presetH264(command, options.resolution, fps) 245 command = await presetH264(command, options.inputPath, options.resolution, fps)
193 246
194 if (options.resolution !== undefined) { 247 if (options.resolution !== undefined) {
195 // '?x720' or '720x?' for example 248 // '?x720' or '720x?' for example
@@ -208,7 +261,29 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco
208 return command 261 return command
209} 262}
210 263
211async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { 264async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) {
265 command = command.loop(undefined)
266
267 command = await presetH264VeryFast(command, options.audioPath, options.resolution)
268
269 command = command.input(options.audioPath)
270 .videoFilter('scale=trunc(iw/2)*2:trunc(ih/2)*2') // Avoid "height not divisible by 2" error
271 .outputOption('-tune stillimage')
272 .outputOption('-shortest')
273
274 return command
275}
276
277async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
278 command = await presetCopy(command)
279
280 command = command.outputOption('-map_metadata -1') // strip all metadata
281 .outputOption('-movflags faststart')
282
283 return command
284}
285
286async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
212 const videoPath = getHLSVideoPath(options) 287 const videoPath = getHLSVideoPath(options)
213 288
214 command = await presetCopy(command) 289 command = await presetCopy(command)
@@ -224,26 +299,26 @@ async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: Transcod
224 return command 299 return command
225} 300}
226 301
227function getHLSVideoPath (options: TranscodeOptions) { 302function getHLSVideoPath (options: HLSTranscodeOptions) {
228 return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` 303 return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}`
229} 304}
230 305
231async function onTranscodingSuccess (options: TranscodeOptions) { 306async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) {
232 if (!options.hlsPlaylist) return 307 if (options.type !== 'hls') return
233 308
234 // Fix wrong mapping with some ffmpeg versions
235 const fileContent = await readFile(options.outputPath) 309 const fileContent = await readFile(options.outputPath)
236 310
237 const videoFileName = options.hlsPlaylist.videoFilename 311 const videoFileName = options.hlsPlaylist.videoFilename
238 const videoFilePath = getHLSVideoPath(options) 312 const videoFilePath = getHLSVideoPath(options)
239 313
314 // Fix wrong mapping with some ffmpeg versions
240 const newContent = fileContent.toString() 315 const newContent = fileContent.toString()
241 .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`) 316 .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`)
242 317
243 await writeFile(options.outputPath, newContent) 318 await writeFile(options.outputPath, newContent)
244} 319}
245 320
246function getVideoFileStream (path: string) { 321function getVideoStreamFromFile (path: string) {
247 return new Promise<any>((res, rej) => { 322 return new Promise<any>((res, rej) => {
248 ffmpeg.ffprobe(path, (err, metadata) => { 323 ffmpeg.ffprobe(path, (err, metadata) => {
249 if (err) return rej(err) 324 if (err) return rej(err)
@@ -263,44 +338,27 @@ function getVideoFileStream (path: string) {
263 * and quality. Superfast and ultrafast will give you better 338 * and quality. Superfast and ultrafast will give you better
264 * performance, but then quality is noticeably worse. 339 * performance, but then quality is noticeably worse.
265 */ 340 */
266async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> { 341async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string, resolution: VideoResolution, fps?: number) {
267 let localCommand = await presetH264(command, resolution, fps) 342 let localCommand = await presetH264(command, input, resolution, fps)
343
268 localCommand = localCommand.outputOption('-preset:v veryfast') 344 localCommand = localCommand.outputOption('-preset:v veryfast')
269 .outputOption([ '--aq-mode=2', '--aq-strength=1.3' ]) 345
270 /* 346 /*
271 MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html 347 MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html
272 Our target situation is closer to a livestream than a stream, 348 Our target situation is closer to a livestream than a stream,
273 since we want to reduce as much a possible the encoding burden, 349 since we want to reduce as much a possible the encoding burden,
274 altough not to the point of a livestream where there is a hard 350 although not to the point of a livestream where there is a hard
275 constraint on the frames per second to be encoded. 351 constraint on the frames per second to be encoded.
276
277 why '--aq-mode=2 --aq-strength=1.3' instead of '-profile:v main'?
278 Make up for most of the loss of grain and macroblocking
279 with less computing power.
280 */ 352 */
281 353
282 return localCommand 354 return localCommand
283} 355}
284 356
285/** 357/**
286 * A preset optimised for a stillimage audio video
287 */
288async function presetStillImageWithAudio (
289 command: ffmpeg.FfmpegCommand,
290 resolution: VideoResolution,
291 fps: number
292): Promise<ffmpeg.FfmpegCommand> {
293 let localCommand = await presetH264VeryFast(command, resolution, fps)
294 localCommand = localCommand.outputOption('-tune stillimage')
295
296 return localCommand
297}
298
299/**
300 * A toolbox to play with audio 358 * A toolbox to play with audio
301 */ 359 */
302namespace audio { 360namespace audio {
303 export const get = (option: ffmpeg.FfmpegCommand | string) => { 361 export const get = (option: string) => {
304 // without position, ffprobe considers the last input only 362 // without position, ffprobe considers the last input only
305 // we make it consider the first input only 363 // we make it consider the first input only
306 // if you pass a file path to pos, then ffprobe acts on that file directly 364 // if you pass a file path to pos, then ffprobe acts on that file directly
@@ -322,11 +380,7 @@ namespace audio {
322 return res({ absolutePath: data.format.filename }) 380 return res({ absolutePath: data.format.filename })
323 } 381 }
324 382
325 if (typeof option === 'string') { 383 return ffmpeg.ffprobe(option, parseFfprobe)
326 return ffmpeg.ffprobe(option, parseFfprobe)
327 }
328
329 return option.ffprobe(parseFfprobe)
330 }) 384 })
331 } 385 }
332 386
@@ -368,7 +422,7 @@ namespace audio {
368 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel 422 * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel
369 * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr 423 * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr
370 */ 424 */
371async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResolution, fps: number): Promise<ffmpeg.FfmpegCommand> { 425async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolution: VideoResolution, fps?: number) {
372 let localCommand = command 426 let localCommand = command
373 .format('mp4') 427 .format('mp4')
374 .videoCodec('libx264') 428 .videoCodec('libx264')
@@ -379,7 +433,7 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol
379 .outputOption('-map_metadata -1') // strip all metadata 433 .outputOption('-map_metadata -1') // strip all metadata
380 .outputOption('-movflags faststart') 434 .outputOption('-movflags faststart')
381 435
382 const parsedAudio = await audio.get(localCommand) 436 const parsedAudio = await audio.get(input)
383 437
384 if (!parsedAudio.audioStream) { 438 if (!parsedAudio.audioStream) {
385 localCommand = localCommand.noAudio() 439 localCommand = localCommand.noAudio()
@@ -388,28 +442,30 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol
388 .audioCodec('libfdk_aac') 442 .audioCodec('libfdk_aac')
389 .audioQuality(5) 443 .audioQuality(5)
390 } else { 444 } else {
391 // we try to reduce the ceiling bitrate by making rough correspondances of bitrates 445 // we try to reduce the ceiling bitrate by making rough matches of bitrates
392 // of course this is far from perfect, but it might save some space in the end 446 // of course this is far from perfect, but it might save some space in the end
447 localCommand = localCommand.audioCodec('aac')
448
393 const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] 449 const audioCodecName = parsedAudio.audioStream[ 'codec_name' ]
394 let bitrate: number
395 if (audio.bitrate[ audioCodecName ]) {
396 localCommand = localCommand.audioCodec('aac')
397 450
398 bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) 451 if (audio.bitrate[ audioCodecName ]) {
452 const bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
399 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) 453 if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate)
400 } 454 }
401 } 455 }
402 456
403 // Constrained Encoding (VBV) 457 if (fps) {
404 // https://slhck.info/video/2017/03/01/rate-control.html 458 // Constrained Encoding (VBV)
405 // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate 459 // https://slhck.info/video/2017/03/01/rate-control.html
406 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) 460 // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate
407 localCommand = localCommand.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) 461 const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)
408 462 localCommand = localCommand.outputOptions([ `-maxrate ${targetBitrate}`, `-bufsize ${targetBitrate * 2}` ])
409 // Keyframe interval of 2 seconds for faster seeking and resolution switching. 463
410 // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html 464 // Keyframe interval of 2 seconds for faster seeking and resolution switching.
411 // https://superuser.com/a/908325 465 // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
412 localCommand = localCommand.outputOption(`-g ${ fps * 2 }`) 466 // https://superuser.com/a/908325
467 localCommand = localCommand.outputOption(`-g ${fps * 2}`)
468 }
413 469
414 return localCommand 470 return localCommand
415} 471}
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts
index 734523b01..8603dd761 100644
--- a/server/helpers/logger.ts
+++ b/server/helpers/logger.ts
@@ -2,6 +2,7 @@
2import { mkdirpSync } from 'fs-extra' 2import { mkdirpSync } from 'fs-extra'
3import * as path from 'path' 3import * as path from 'path'
4import * as winston from 'winston' 4import * as winston from 'winston'
5import { FileTransportOptions } from 'winston/lib/winston/transports'
5import { CONFIG } from '../initializers/config' 6import { CONFIG } from '../initializers/config'
6import { omit } from 'lodash' 7import { omit } from 'lodash'
7 8
@@ -45,6 +46,21 @@ const labelFormatter = winston.format.label({
45 label 46 label
46}) 47})
47 48
49const fileLoggerOptions: FileTransportOptions = {
50
51 filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'),
52 handleExceptions: true,
53 format: winston.format.combine(
54 winston.format.timestamp(),
55 jsonLoggerFormat
56 )
57}
58
59if (CONFIG.LOG.ROTATION) {
60 fileLoggerOptions.maxsize = 1024 * 1024 * 12
61 fileLoggerOptions.maxFiles = 20
62}
63
48const logger = winston.createLogger({ 64const logger = winston.createLogger({
49 level: CONFIG.LOG.LEVEL, 65 level: CONFIG.LOG.LEVEL,
50 format: winston.format.combine( 66 format: winston.format.combine(
@@ -52,16 +68,7 @@ const logger = winston.createLogger({
52 winston.format.splat() 68 winston.format.splat()
53 ), 69 ),
54 transports: [ 70 transports: [
55 new winston.transports.File({ 71 new winston.transports.File(fileLoggerOptions),
56 filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'),
57 handleExceptions: true,
58 maxsize: 1024 * 1024 * 12,
59 maxFiles: 20,
60 format: winston.format.combine(
61 winston.format.timestamp(),
62 jsonLoggerFormat
63 )
64 }),
65 new winston.transports.Console({ 72 new winston.transports.Console({
66 handleExceptions: true, 73 handleExceptions: true,
67 format: winston.format.combine( 74 format: winston.format.combine(
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index 622ad7d6b..c211d725c 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -10,6 +10,7 @@ function checkMissedConfig () {
10 'trust_proxy', 10 'trust_proxy',
11 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', 11 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
12 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', 12 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
13 'email.body.signature', 'email.object.prefix',
13 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', 14 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
14 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', 15 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists',
15 'log.level', 16 'log.level',
diff --git a/server/initializers/config.ts b/server/initializers/config.ts
index 4f77e144d..bb278ba43 100644
--- a/server/initializers/config.ts
+++ b/server/initializers/config.ts
@@ -44,6 +44,14 @@ const CONFIG = {
44 CA_FILE: config.get<string>('smtp.ca_file'), 44 CA_FILE: config.get<string>('smtp.ca_file'),
45 FROM_ADDRESS: config.get<string>('smtp.from_address') 45 FROM_ADDRESS: config.get<string>('smtp.from_address')
46 }, 46 },
47 EMAIL: {
48 BODY: {
49 SIGNATURE: config.get<string>('email.body.signature')
50 },
51 OBJECT: {
52 PREFIX: config.get<string>('email.object.prefix') + ' '
53 }
54 },
47 STORAGE: { 55 STORAGE: {
48 TMP_DIR: buildPath(config.get<string>('storage.tmp')), 56 TMP_DIR: buildPath(config.get<string>('storage.tmp')),
49 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), 57 AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
@@ -75,7 +83,8 @@ const CONFIG = {
75 }, 83 },
76 TRUST_PROXY: config.get<string[]>('trust_proxy'), 84 TRUST_PROXY: config.get<string[]>('trust_proxy'),
77 LOG: { 85 LOG: {
78 LEVEL: config.get<string>('log.level') 86 LEVEL: config.get<string>('log.level'),
87 ROTATION: config.get<boolean>('log.rotation.enabled')
79 }, 88 },
80 SEARCH: { 89 SEARCH: {
81 REMOTE_URI: { 90 REMOTE_URI: {
@@ -140,13 +149,15 @@ const CONFIG = {
140 TRANSCODING: { 149 TRANSCODING: {
141 get ENABLED () { return config.get<boolean>('transcoding.enabled') }, 150 get ENABLED () { return config.get<boolean>('transcoding.enabled') },
142 get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') }, 151 get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
152 get ALLOW_AUDIO_FILES () { return config.get<boolean>('transcoding.allow_audio_files') },
143 get THREADS () { return config.get<number>('transcoding.threads') }, 153 get THREADS () { return config.get<number>('transcoding.threads') },
144 RESOLUTIONS: { 154 RESOLUTIONS: {
145 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, 155 get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
146 get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') }, 156 get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') },
147 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') }, 157 get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') },
148 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') }, 158 get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') },
149 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') } 159 get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') },
160 get '2160p' () { return config.get<boolean>('transcoding.resolutions.2160p') }
150 }, 161 },
151 HLS: { 162 HLS: {
152 get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') } 163 get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') }
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts
index b5f8fc0bc..c2b8eff95 100644
--- a/server/initializers/constants.ts
+++ b/server/initializers/constants.ts
@@ -1,10 +1,10 @@
1import { join } from 'path' 1import { join } from 'path'
2import { JobType, VideoRateType, VideoState } from '../../shared/models' 2import { JobType, VideoRateType, VideoResolution, VideoState } from '../../shared/models'
3import { ActivityPubActorType } from '../../shared/models/activitypub' 3import { 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 } 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 = 380 17const LAST_MIGRATION_VERSION = 390
18 18
19// --------------------------------------------------------------------------- 19// ---------------------------------------------------------------------------
20 20
@@ -228,7 +228,7 @@ let CONSTRAINTS_FIELDS = {
228 max: 2 * 1024 * 1024 // 2MB 228 max: 2 * 1024 * 1024 // 2MB
229 } 229 }
230 }, 230 },
231 EXTNAME: buildVideosExtname(), 231 EXTNAME: [] as string[],
232 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2 232 INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
233 DURATION: { min: 0 }, // Number 233 DURATION: { min: 0 }, // Number
234 TAGS: { min: 0, max: 5 }, // Number of total tags 234 TAGS: { min: 0, max: 5 }, // Number of total tags
@@ -300,6 +300,8 @@ const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
300 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) 300 KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
301} 301}
302 302
303const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P
304
303const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { 305const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = {
304 LIKE: 'like', 306 LIKE: 'like',
305 DISLIKE: 'dislike' 307 DISLIKE: 'dislike'
@@ -380,8 +382,18 @@ const VIDEO_PLAYLIST_TYPES = {
380} 382}
381 383
382const MIMETYPES = { 384const MIMETYPES = {
385 AUDIO: {
386 MIMETYPE_EXT: {
387 'audio/mpeg': '.mp3',
388 'audio/mp3': '.mp3',
389 'application/ogg': '.ogg',
390 'audio/ogg': '.ogg',
391 'audio/flac': '.flac'
392 },
393 EXT_MIMETYPE: null as { [ id: string ]: string }
394 },
383 VIDEO: { 395 VIDEO: {
384 MIMETYPE_EXT: buildVideoMimetypeExt(), 396 MIMETYPE_EXT: null as { [ id: string ]: string },
385 EXT_MIMETYPE: null as { [ id: string ]: string } 397 EXT_MIMETYPE: null as { [ id: string ]: string }
386 }, 398 },
387 IMAGE: { 399 IMAGE: {
@@ -403,7 +415,7 @@ const MIMETYPES = {
403 } 415 }
404 } 416 }
405} 417}
406MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT) 418MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT)
407 419
408// --------------------------------------------------------------------------- 420// ---------------------------------------------------------------------------
409 421
@@ -429,7 +441,7 @@ const ACTIVITY_PUB = {
429 COLLECTION_ITEMS_PER_PAGE: 10, 441 COLLECTION_ITEMS_PER_PAGE: 10,
430 FETCH_PAGE_LIMIT: 100, 442 FETCH_PAGE_LIMIT: 100,
431 URL_MIME_TYPES: { 443 URL_MIME_TYPES: {
432 VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT), 444 VIDEO: [] as string[],
433 TORRENT: [ 'application/x-bittorrent' ], 445 TORRENT: [ 'application/x-bittorrent' ],
434 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] 446 MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
435 }, 447 },
@@ -497,8 +509,8 @@ const THUMBNAILS_SIZE = {
497 height: 122 509 height: 122
498} 510}
499const PREVIEWS_SIZE = { 511const PREVIEWS_SIZE = {
500 width: 560, 512 width: 850,
501 height: 315 513 height: 480
502} 514}
503const AVATARS_SIZE = { 515const AVATARS_SIZE = {
504 width: 120, 516 width: 120,
@@ -543,6 +555,10 @@ const REDUNDANCY = {
543 555
544const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS) 556const ACCEPT_HEADERS = [ 'html', 'application/json' ].concat(ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS)
545 557
558const ASSETS_PATH = {
559 DEFAULT_AUDIO_BACKGROUND: join(root(), 'server', 'assets', 'default-audio-background.jpg')
560}
561
546// --------------------------------------------------------------------------- 562// ---------------------------------------------------------------------------
547 563
548const CUSTOM_HTML_TAG_COMMENTS = { 564const CUSTOM_HTML_TAG_COMMENTS = {
@@ -612,6 +628,7 @@ if (isTestInstance() === true) {
612} 628}
613 629
614updateWebserverUrls() 630updateWebserverUrls()
631updateWebserverConfig()
615 632
616registerConfigChangedHandler(() => { 633registerConfigChangedHandler(() => {
617 updateWebserverUrls() 634 updateWebserverUrls()
@@ -681,12 +698,14 @@ export {
681 RATES_LIMIT, 698 RATES_LIMIT,
682 MIMETYPES, 699 MIMETYPES,
683 CRAWL_REQUEST_CONCURRENCY, 700 CRAWL_REQUEST_CONCURRENCY,
701 DEFAULT_AUDIO_RESOLUTION,
684 JOB_COMPLETED_LIFETIME, 702 JOB_COMPLETED_LIFETIME,
685 HTTP_SIGNATURE, 703 HTTP_SIGNATURE,
686 VIDEO_IMPORT_STATES, 704 VIDEO_IMPORT_STATES,
687 VIDEO_VIEW_LIFETIME, 705 VIDEO_VIEW_LIFETIME,
688 CONTACT_FORM_LIFETIME, 706 CONTACT_FORM_LIFETIME,
689 VIDEO_PLAYLIST_PRIVACIES, 707 VIDEO_PLAYLIST_PRIVACIES,
708 ASSETS_PATH,
690 loadLanguages, 709 loadLanguages,
691 buildLanguages 710 buildLanguages
692} 711}
@@ -700,15 +719,21 @@ function buildVideoMimetypeExt () {
700 'video/mp4': '.mp4' 719 'video/mp4': '.mp4'
701 } 720 }
702 721
703 if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) { 722 if (CONFIG.TRANSCODING.ENABLED) {
704 Object.assign(data, { 723 if (CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
705 'video/quicktime': '.mov', 724 Object.assign(data, {
706 'video/x-msvideo': '.avi', 725 'video/quicktime': '.mov',
707 'video/x-flv': '.flv', 726 'video/x-msvideo': '.avi',
708 'video/x-matroska': '.mkv', 727 'video/x-flv': '.flv',
709 'application/octet-stream': '.mkv', 728 'video/x-matroska': '.mkv',
710 'video/avi': '.avi' 729 'application/octet-stream': '.mkv',
711 }) 730 'video/avi': '.avi'
731 })
732 }
733
734 if (CONFIG.TRANSCODING.ALLOW_AUDIO_FILES) {
735 Object.assign(data, MIMETYPES.AUDIO.MIMETYPE_EXT)
736 }
712 } 737 }
713 738
714 return data 739 return data
@@ -724,16 +749,15 @@ function updateWebserverUrls () {
724} 749}
725 750
726function updateWebserverConfig () { 751function updateWebserverConfig () {
727 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
728
729 MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt() 752 MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
730 MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT) 753 MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
754 ACTIVITY_PUB.URL_MIME_TYPES.VIDEO = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
755
756 CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
731} 757}
732 758
733function buildVideosExtname () { 759function buildVideosExtname () {
734 return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS 760 return Object.keys(MIMETYPES.VIDEO.EXT_MIMETYPE)
735 ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
736 : [ '.mp4', '.ogv', '.webm' ]
737} 761}
738 762
739function loadLanguages () { 763function loadLanguages () {
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts
index 127449577..cb58454cb 100644
--- a/server/initializers/installer.ts
+++ b/server/initializers/installer.ts
@@ -128,6 +128,8 @@ async function createOAuthAdminIfNotExist () {
128 128
129 // Our password is weak so do not validate it 129 // Our password is weak so do not validate it
130 validatePassword = false 130 validatePassword = false
131 } else if (process.env.PT_INITIAL_ROOT_PASSWORD) {
132 password = process.env.PT_INITIAL_ROOT_PASSWORD
131 } else { 133 } else {
132 password = passwordGenerator(16, true) 134 password = passwordGenerator(16, true)
133 } 135 }
@@ -144,7 +146,7 @@ async function createOAuthAdminIfNotExist () {
144 } 146 }
145 const user = new UserModel(userData) 147 const user = new UserModel(userData)
146 148
147 await createUserAccountAndChannelAndPlaylist(user, validatePassword) 149 await createUserAccountAndChannelAndPlaylist({ userToCreate: user, channelNames: undefined, validateUser: validatePassword })
148 logger.info('Username: ' + username) 150 logger.info('Username: ' + username)
149 logger.info('User password: ' + password) 151 logger.info('User password: ' + password)
150} 152}
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts
index 2880a97d9..96d44a7ce 100644
--- a/server/initializers/migrations/0100-activitypub.ts
+++ b/server/initializers/migrations/0100-activitypub.ts
@@ -65,7 +65,12 @@ async function up (utils: {
65 // Create application account 65 // Create application account
66 { 66 {
67 const applicationInstance = await ApplicationModel.findOne() 67 const applicationInstance = await ApplicationModel.findOne()
68 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined) 68 const accountCreated = await createLocalAccountWithoutKeys({
69 name: SERVER_ACTOR_NAME,
70 userId: null,
71 applicationId: applicationInstance.id,
72 t: undefined
73 })
69 74
70 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 75 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
71 accountCreated.Actor.publicKey = publicKey 76 accountCreated.Actor.publicKey = publicKey
@@ -83,7 +88,7 @@ async function up (utils: {
83 // Recreate accounts for each user 88 // Recreate accounts for each user
84 const users = await db.User.findAll() 89 const users = await db.User.findAll()
85 for (const user of users) { 90 for (const user of users) {
86 const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined) 91 const account = await createLocalAccountWithoutKeys({ name: user.username, userId: user.id, applicationId: null, t: undefined })
87 92
88 const { publicKey, privateKey } = await createPrivateAndPublicKeys() 93 const { publicKey, privateKey } = await createPrivateAndPublicKeys()
89 account.Actor.publicKey = publicKey 94 account.Actor.publicKey = publicKey
diff --git a/server/initializers/migrations/0385-remove-actor-uuid.ts b/server/initializers/migrations/0385-remove-actor-uuid.ts
new file mode 100644
index 000000000..032c0562b
--- /dev/null
+++ b/server/initializers/migrations/0385-remove-actor-uuid.ts
@@ -0,0 +1,19 @@
1import * as Sequelize from 'sequelize'
2
3async function up (utils: {
4 transaction: Sequelize.Transaction,
5 queryInterface: Sequelize.QueryInterface,
6 sequelize: Sequelize.Sequelize,
7 db: any
8}): Promise<void> {
9 await utils.queryInterface.removeColumn('actor', 'uuid')
10}
11
12function down (options) {
13 throw new Error('Not implemented.')
14}
15
16export {
17 up,
18 down
19}
diff --git a/server/initializers/migrations/0390-user-pending-email.ts b/server/initializers/migrations/0390-user-pending-email.ts
new file mode 100644
index 000000000..5ca871746
--- /dev/null
+++ b/server/initializers/migrations/0390-user-pending-email.ts
@@ -0,0 +1,25 @@
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 const data = {
10 type: Sequelize.STRING(400),
11 allowNull: true,
12 defaultValue: null
13 }
14
15 await utils.queryInterface.addColumn('user', 'pendingEmail', data)
16}
17
18function down (options) {
19 throw new Error('Not implemented.')
20}
21
22export {
23 up,
24 down
25}
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts
index 25cd40905..38eb87d1e 100644
--- a/server/lib/activitypub/actor.ts
+++ b/server/lib/activitypub/actor.ts
@@ -5,7 +5,7 @@ import * 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'
7import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' 7import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
8import { isActorObjectValid, normalizeActor } from '../../helpers/custom-validators/activitypub/actor' 8import { sanitizeAndCheckActorObject } from '../../helpers/custom-validators/activitypub/actor'
9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 9import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
10import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils' 10import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
11import { logger } from '../../helpers/logger' 11import { logger } from '../../helpers/logger'
@@ -33,7 +33,7 @@ function setAsyncActorKeys (actor: ActorModel) {
33 return actor.save() 33 return actor.save()
34 }) 34 })
35 .catch(err => { 35 .catch(err => {
36 logger.error('Cannot set public/private keys of actor %d.', actor.uuid, { err }) 36 logger.error('Cannot set public/private keys of actor %d.', actor.url, { err })
37 return actor 37 return actor
38 }) 38 })
39} 39}
@@ -128,18 +128,17 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ
128 const followersCount = await fetchActorTotalItems(attributes.followers) 128 const followersCount = await fetchActorTotalItems(attributes.followers)
129 const followingCount = await fetchActorTotalItems(attributes.following) 129 const followingCount = await fetchActorTotalItems(attributes.following)
130 130
131 actorInstance.set('type', attributes.type) 131 actorInstance.type = attributes.type
132 actorInstance.set('uuid', attributes.uuid) 132 actorInstance.preferredUsername = attributes.preferredUsername
133 actorInstance.set('preferredUsername', attributes.preferredUsername) 133 actorInstance.url = attributes.id
134 actorInstance.set('url', attributes.id) 134 actorInstance.publicKey = attributes.publicKey.publicKeyPem
135 actorInstance.set('publicKey', attributes.publicKey.publicKeyPem) 135 actorInstance.followersCount = followersCount
136 actorInstance.set('followersCount', followersCount) 136 actorInstance.followingCount = followingCount
137 actorInstance.set('followingCount', followingCount) 137 actorInstance.inboxUrl = attributes.inbox
138 actorInstance.set('inboxUrl', attributes.inbox) 138 actorInstance.outboxUrl = attributes.outbox
139 actorInstance.set('outboxUrl', attributes.outbox) 139 actorInstance.sharedInboxUrl = attributes.endpoints.sharedInbox
140 actorInstance.set('sharedInboxUrl', attributes.endpoints.sharedInbox) 140 actorInstance.followersUrl = attributes.followers
141 actorInstance.set('followersUrl', attributes.followers) 141 actorInstance.followingUrl = attributes.following
142 actorInstance.set('followingUrl', attributes.following)
143} 142}
144 143
145async function updateActorAvatarInstance (actorInstance: ActorModel, avatarName: string, t: Transaction) { 144async function updateActorAvatarInstance (actorInstance: ActorModel, avatarName: string, t: Transaction) {
@@ -370,10 +369,9 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
370 logger.info('Fetching remote actor %s.', actorUrl) 369 logger.info('Fetching remote actor %s.', actorUrl)
371 370
372 const requestResult = await doRequest<ActivityPubActor>(options) 371 const requestResult = await doRequest<ActivityPubActor>(options)
373 normalizeActor(requestResult.body)
374
375 const actorJSON = requestResult.body 372 const actorJSON = requestResult.body
376 if (isActorObjectValid(actorJSON) === false) { 373
374 if (sanitizeAndCheckActorObject(actorJSON) === false) {
377 logger.debug('Remote actor JSON is not valid.', { actorJSON }) 375 logger.debug('Remote actor JSON is not valid.', { actorJSON })
378 return { result: undefined, statusCode: requestResult.response.statusCode } 376 return { result: undefined, statusCode: requestResult.response.statusCode }
379 } 377 }
@@ -388,7 +386,6 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
388 386
389 const actor = new ActorModel({ 387 const actor = new ActorModel({
390 type: actorJSON.type, 388 type: actorJSON.type,
391 uuid: actorJSON.uuid,
392 preferredUsername: actorJSON.preferredUsername, 389 preferredUsername: actorJSON.preferredUsername,
393 url: actorJSON.id, 390 url: actorJSON.id,
394 publicKey: actorJSON.publicKey.publicKeyPem, 391 publicKey: actorJSON.publicKey.publicKeyPem,
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts
index 686eef04d..9e469e3e6 100644
--- a/server/lib/activitypub/crawl.ts
+++ b/server/lib/activitypub/crawl.ts
@@ -28,13 +28,22 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>
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) {
31 // Don't crawl ourselves 31 let body: any
32 const remoteHost = parse(nextLink).host
33 if (remoteHost === WEBSERVER.HOST) continue
34 32
35 options.uri = nextLink 33 if (typeof nextLink === 'string') {
34 // Don't crawl ourselves
35 const remoteHost = parse(nextLink).host
36 if (remoteHost === WEBSERVER.HOST) continue
37
38 options.uri = nextLink
39
40 const res = await doRequest<ActivityPubOrderedCollection<T>>(options)
41 body = res.body
42 } else {
43 // nextLink is already the object we want
44 body = nextLink
45 }
36 46
37 const { body } = await doRequest<ActivityPubOrderedCollection<T>>(options)
38 nextLink = body.next 47 nextLink = body.next
39 i++ 48 i++
40 49
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index 23310b41e..bbf1bd3a8 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -5,8 +5,9 @@ import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
6import { forwardVideoRelatedActivity } from '../send/utils' 6import { forwardVideoRelatedActivity } from '../send/utils'
7import { getOrCreateVideoAndAccountAndChannel } from '../videos' 7import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { VideoPrivacy } from '../../../../shared/models/videos'
9import { Notifier } from '../../notifier' 8import { Notifier } from '../../notifier'
9import { VideoModel } from '../../../models/video/video'
10import { logger } from '../../../helpers/logger'
10 11
11async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { 12async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) {
12 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) 13 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity)
@@ -23,7 +24,17 @@ export {
23async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { 24async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
24 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id 25 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
25 26
26 const { video, created: videoCreated } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) 27 let video: VideoModel
28 let videoCreated: boolean
29
30 try {
31 const result = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri })
32 video = result.video
33 videoCreated = result.created
34 } catch (err) {
35 logger.debug('Cannot process share of %s. Maybe this is not a video object, so just skipping.', objectUri, { err })
36 return
37 }
27 38
28 await sequelizeTypescript.transaction(async t => { 39 await sequelizeTypescript.transaction(async t => {
29 // Add share entry 40 // Add share entry
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index e882669ce..daf846513 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -9,28 +9,14 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos'
9import { forwardVideoRelatedActivity } from '../send/utils' 9import { forwardVideoRelatedActivity } from '../send/utils'
10import { createOrUpdateCacheFile } from '../cache-file' 10import { createOrUpdateCacheFile } from '../cache-file'
11import { Notifier } from '../../notifier' 11import { Notifier } from '../../notifier'
12import { processViewActivity } from './process-view'
13import { processDislikeActivity } from './process-dislike'
14import { processFlagActivity } from './process-flag'
15import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' 12import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
16import { createOrUpdateVideoPlaylist } from '../playlist' 13import { createOrUpdateVideoPlaylist } from '../playlist'
14import { VideoModel } from '../../../models/video/video'
17 15
18async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { 16async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
19 const activityObject = activity.object 17 const activityObject = activity.object
20 const activityType = activityObject.type 18 const activityType = activityObject.type
21 19
22 if (activityType === 'View') {
23 return processViewActivity(activity, byActor)
24 }
25
26 if (activityType === 'Dislike') {
27 return retryTransactionWrapper(processDislikeActivity, activity, byActor)
28 }
29
30 if (activityType === 'Flag') {
31 return retryTransactionWrapper(processFlagActivity, activity, byActor)
32 }
33
34 if (activityType === 'Video') { 20 if (activityType === 'Video') {
35 return processCreateVideo(activity) 21 return processCreateVideo(activity)
36 } 22 }
@@ -91,7 +77,18 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act
91 77
92 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) 78 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
93 79
94 const { video } = await resolveThread(commentObject.inReplyTo) 80 let video: VideoModel
81 try {
82 const resolveThreadResult = await resolveThread(commentObject.inReplyTo)
83 video = resolveThreadResult.video
84 } catch (err) {
85 logger.debug(
86 'Cannot process video comment because we could not resolve thread %s. Maybe it was not a video thread, so skip it.',
87 commentObject.inReplyTo,
88 { err }
89 )
90 return
91 }
95 92
96 const { comment, created } = await addVideoComment(video, commentObject.id) 93 const { comment, created } = await addVideoComment(video, commentObject.id)
97 94
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 76f07fd8a..6f10a50bd 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -95,23 +95,23 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete:
95} 95}
96 96
97async function processDeleteAccount (accountToRemove: AccountModel) { 97async function processDeleteAccount (accountToRemove: AccountModel) {
98 logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid) 98 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
99 99
100 await sequelizeTypescript.transaction(async t => { 100 await sequelizeTypescript.transaction(async t => {
101 await accountToRemove.destroy({ transaction: t }) 101 await accountToRemove.destroy({ transaction: t })
102 }) 102 })
103 103
104 logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid) 104 logger.info('Remote account %s removed.', accountToRemove.Actor.url)
105} 105}
106 106
107async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { 107async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
108 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid) 108 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
109 109
110 await sequelizeTypescript.transaction(async t => { 110 await sequelizeTypescript.transaction(async t => {
111 await videoChannelToRemove.destroy({ transaction: t }) 111 await videoChannelToRemove.destroy({ transaction: t })
112 }) 112 })
113 113
114 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid) 114 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
115} 115}
116 116
117function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { 117function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 54a9234bb..71a16dacc 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -95,7 +95,7 @@ async function processUpdateCacheFile (byActor: ActorModel, activity: ActivityUp
95async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) { 95async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) {
96 const actorAttributesToUpdate = activity.object as ActivityPubActor 96 const actorAttributesToUpdate = activity.object as ActivityPubActor
97 97
98 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.uuid) 98 logger.debug('Updating remote account "%s".', actorAttributesToUpdate.url)
99 let accountOrChannelInstance: AccountModel | VideoChannelModel 99 let accountOrChannelInstance: AccountModel | VideoChannelModel
100 let actorFieldsSave: object 100 let actorFieldsSave: object
101 let accountOrChannelFieldsSave: object 101 let accountOrChannelFieldsSave: object
@@ -128,7 +128,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate)
128 await accountOrChannelInstance.save({ transaction: t }) 128 await accountOrChannelInstance.save({ transaction: t })
129 }) 129 })
130 130
131 logger.info('Remote account with uuid %s updated', actorAttributesToUpdate.uuid) 131 logger.info('Remote account %s updated', actorAttributesToUpdate.url)
132 } catch (err) { 132 } catch (err) {
133 if (actor !== undefined && actorFieldsSave !== undefined) { 133 if (actor !== undefined && actorFieldsSave !== undefined) {
134 resetSequelizeInstance(actor, actorFieldsSave) 134 resetSequelizeInstance(actor, actorFieldsSave)
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts
index 18f44d50e..c3fc6b462 100644
--- a/server/lib/activitypub/video-comments.ts
+++ b/server/lib/activitypub/video-comments.ts
@@ -80,7 +80,8 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
80 return { comment, created } 80 return { comment, created }
81} 81}
82 82
83async function resolveThread (url: string, comments: VideoCommentModel[] = []) { 83type ResolveThreadResult = Promise<{ video: VideoModel, parents: VideoCommentModel[] }>
84async function resolveThread (url: string, comments: VideoCommentModel[] = []): ResolveThreadResult {
84 // Already have this comment? 85 // Already have this comment?
85 const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url) 86 const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url)
86 if (commentFromDatabase) { 87 if (commentFromDatabase) {
@@ -161,7 +162,6 @@ async function resolveThread (url: string, comments: VideoCommentModel[] = []) {
161 162
162 return resolveThread(body.inReplyTo, comments.concat([ comment ])) 163 return resolveThread(body.inReplyTo, comments.concat([ comment ]))
163 } 164 }
164
165} 165}
166 166
167export { 167export {
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 8c06e9751..c4a5a5853 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -100,11 +100,11 @@ class Emailer {
100 `You can view it on ${videoUrl} ` + 100 `You can view it on ${videoUrl} ` +
101 `\n\n` + 101 `\n\n` +
102 `Cheers,\n` + 102 `Cheers,\n` +
103 `PeerTube.` 103 `${CONFIG.EMAIL.BODY.SIGNATURE}`
104 104
105 const emailPayload: EmailPayload = { 105 const emailPayload: EmailPayload = {
106 to, 106 to,
107 subject: channelName + ' just published a new video', 107 subject: CONFIG.EMAIL.OBJECT.PREFIX + channelName + ' just published a new video',
108 text 108 text
109 } 109 }
110 110
@@ -119,11 +119,11 @@ class Emailer {
119 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + 119 `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
120 `\n\n` + 120 `\n\n` +
121 `Cheers,\n` + 121 `Cheers,\n` +
122 `PeerTube.` 122 `${CONFIG.EMAIL.BODY.SIGNATURE}`
123 123
124 const emailPayload: EmailPayload = { 124 const emailPayload: EmailPayload = {
125 to, 125 to,
126 subject: 'New follower on your channel ' + followingName, 126 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New follower on your channel ' + followingName,
127 text 127 text
128 } 128 }
129 129
@@ -137,11 +137,11 @@ class Emailer {
137 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + 137 `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` +
138 `\n\n` + 138 `\n\n` +
139 `Cheers,\n` + 139 `Cheers,\n` +
140 `PeerTube.` 140 `${CONFIG.EMAIL.BODY.SIGNATURE}`
141 141
142 const emailPayload: EmailPayload = { 142 const emailPayload: EmailPayload = {
143 to, 143 to,
144 subject: 'New instance follower', 144 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New instance follower',
145 text 145 text
146 } 146 }
147 147
@@ -157,11 +157,11 @@ class Emailer {
157 `You can view it on ${videoUrl} ` + 157 `You can view it on ${videoUrl} ` +
158 `\n\n` + 158 `\n\n` +
159 `Cheers,\n` + 159 `Cheers,\n` +
160 `PeerTube.` 160 `${CONFIG.EMAIL.BODY.SIGNATURE}`
161 161
162 const emailPayload: EmailPayload = { 162 const emailPayload: EmailPayload = {
163 to, 163 to,
164 subject: `Your video ${video.name} is published`, 164 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video ${video.name} is published`,
165 text 165 text
166 } 166 }
167 167
@@ -177,11 +177,11 @@ class Emailer {
177 `You can view the imported video on ${videoUrl} ` + 177 `You can view the imported video on ${videoUrl} ` +
178 `\n\n` + 178 `\n\n` +
179 `Cheers,\n` + 179 `Cheers,\n` +
180 `PeerTube.` 180 `${CONFIG.EMAIL.BODY.SIGNATURE}`
181 181
182 const emailPayload: EmailPayload = { 182 const emailPayload: EmailPayload = {
183 to, 183 to,
184 subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`, 184 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`,
185 text 185 text
186 } 186 }
187 187
@@ -197,11 +197,11 @@ class Emailer {
197 `See your videos import dashboard for more information: ${importUrl}` + 197 `See your videos import dashboard for more information: ${importUrl}` +
198 `\n\n` + 198 `\n\n` +
199 `Cheers,\n` + 199 `Cheers,\n` +
200 `PeerTube.` 200 `${CONFIG.EMAIL.BODY.SIGNATURE}`
201 201
202 const emailPayload: EmailPayload = { 202 const emailPayload: EmailPayload = {
203 to, 203 to,
204 subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, 204 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
205 text 205 text
206 } 206 }
207 207
@@ -219,11 +219,11 @@ class Emailer {
219 `You can view it on ${commentUrl} ` + 219 `You can view it on ${commentUrl} ` +
220 `\n\n` + 220 `\n\n` +
221 `Cheers,\n` + 221 `Cheers,\n` +
222 `PeerTube.` 222 `${CONFIG.EMAIL.BODY.SIGNATURE}`
223 223
224 const emailPayload: EmailPayload = { 224 const emailPayload: EmailPayload = {
225 to, 225 to,
226 subject: 'New comment on your video ' + video.name, 226 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New comment on your video ' + video.name,
227 text 227 text
228 } 228 }
229 229
@@ -241,11 +241,11 @@ class Emailer {
241 `You can view the comment on ${commentUrl} ` + 241 `You can view the comment on ${commentUrl} ` +
242 `\n\n` + 242 `\n\n` +
243 `Cheers,\n` + 243 `Cheers,\n` +
244 `PeerTube.` 244 `${CONFIG.EMAIL.BODY.SIGNATURE}`
245 245
246 const emailPayload: EmailPayload = { 246 const emailPayload: EmailPayload = {
247 to, 247 to,
248 subject: 'Mention on video ' + video.name, 248 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Mention on video ' + video.name,
249 text 249 text
250 } 250 }
251 251
@@ -258,11 +258,11 @@ class Emailer {
258 const text = `Hi,\n\n` + 258 const text = `Hi,\n\n` +
259 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + 259 `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
260 `Cheers,\n` + 260 `Cheers,\n` +
261 `PeerTube.` 261 `${CONFIG.EMAIL.BODY.SIGNATURE}`
262 262
263 const emailPayload: EmailPayload = { 263 const emailPayload: EmailPayload = {
264 to, 264 to,
265 subject: '[PeerTube] Received a video abuse', 265 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Received a video abuse',
266 text 266 text
267 } 267 }
268 268
@@ -281,11 +281,11 @@ class Emailer {
281 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + 281 `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` +
282 `\n\n` + 282 `\n\n` +
283 `Cheers,\n` + 283 `Cheers,\n` +
284 `PeerTube.` 284 `${CONFIG.EMAIL.BODY.SIGNATURE}`
285 285
286 const emailPayload: EmailPayload = { 286 const emailPayload: EmailPayload = {
287 to, 287 to,
288 subject: '[PeerTube] An auto-blacklisted video is awaiting review', 288 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'An auto-blacklisted video is awaiting review',
289 text 289 text
290 } 290 }
291 291
@@ -296,11 +296,11 @@ class Emailer {
296 const text = `Hi,\n\n` + 296 const text = `Hi,\n\n` +
297 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + 297 `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` +
298 `Cheers,\n` + 298 `Cheers,\n` +
299 `PeerTube.` 299 `${CONFIG.EMAIL.BODY.SIGNATURE}`
300 300
301 const emailPayload: EmailPayload = { 301 const emailPayload: EmailPayload = {
302 to, 302 to,
303 subject: '[PeerTube] New user registration on ' + WEBSERVER.HOST, 303 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST,
304 text 304 text
305 } 305 }
306 306
@@ -318,11 +318,11 @@ class Emailer {
318 blockedString + 318 blockedString +
319 '\n\n' + 319 '\n\n' +
320 'Cheers,\n' + 320 'Cheers,\n' +
321 `PeerTube.` 321 `${CONFIG.EMAIL.BODY.SIGNATURE}`
322 322
323 const emailPayload: EmailPayload = { 323 const emailPayload: EmailPayload = {
324 to, 324 to,
325 subject: `[PeerTube] Video ${videoName} blacklisted`, 325 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${videoName} blacklisted`,
326 text 326 text
327 } 327 }
328 328
@@ -336,11 +336,11 @@ class Emailer {
336 `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + 336 `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` +
337 '\n\n' + 337 '\n\n' +
338 'Cheers,\n' + 338 'Cheers,\n' +
339 `PeerTube.` 339 `${CONFIG.EMAIL.BODY.SIGNATURE}`
340 340
341 const emailPayload: EmailPayload = { 341 const emailPayload: EmailPayload = {
342 to, 342 to,
343 subject: `[PeerTube] Video ${video.name} unblacklisted`, 343 subject: CONFIG.EMAIL.OBJECT.PREFIX + `Video ${video.name} unblacklisted`,
344 text 344 text
345 } 345 }
346 346
@@ -353,11 +353,11 @@ class Emailer {
353 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + 353 `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
354 `If you are not the person who initiated this request, please ignore this email.\n\n` + 354 `If you are not the person who initiated this request, please ignore this email.\n\n` +
355 `Cheers,\n` + 355 `Cheers,\n` +
356 `PeerTube.` 356 `${CONFIG.EMAIL.BODY.SIGNATURE}`
357 357
358 const emailPayload: EmailPayload = { 358 const emailPayload: EmailPayload = {
359 to: [ to ], 359 to: [ to ],
360 subject: 'Reset your PeerTube password', 360 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Reset your password',
361 text 361 text
362 } 362 }
363 363
@@ -370,11 +370,11 @@ class Emailer {
370 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + 370 `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
371 `If you are not the person who initiated this request, please ignore this email.\n\n` + 371 `If you are not the person who initiated this request, please ignore this email.\n\n` +
372 `Cheers,\n` + 372 `Cheers,\n` +
373 `PeerTube.` 373 `${CONFIG.EMAIL.BODY.SIGNATURE}`
374 374
375 const emailPayload: EmailPayload = { 375 const emailPayload: EmailPayload = {
376 to: [ to ], 376 to: [ to ],
377 subject: 'Verify your PeerTube email', 377 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Verify your email',
378 text 378 text
379 } 379 }
380 380
@@ -390,12 +390,12 @@ class Emailer {
390 blockedString + 390 blockedString +
391 '\n\n' + 391 '\n\n' +
392 'Cheers,\n' + 392 'Cheers,\n' +
393 `PeerTube.` 393 `${CONFIG.EMAIL.BODY.SIGNATURE}`
394 394
395 const to = user.email 395 const to = user.email
396 const emailPayload: EmailPayload = { 396 const emailPayload: EmailPayload = {
397 to: [ to ], 397 to: [ to ],
398 subject: '[PeerTube] Account ' + blockedWord, 398 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Account ' + blockedWord,
399 text 399 text
400 } 400 }
401 401
@@ -415,7 +415,7 @@ class Emailer {
415 fromDisplayName: fromEmail, 415 fromDisplayName: fromEmail,
416 replyTo: fromEmail, 416 replyTo: fromEmail,
417 to: [ CONFIG.ADMIN.EMAIL ], 417 to: [ CONFIG.ADMIN.EMAIL ],
418 subject: '[PeerTube] Contact form submitted', 418 subject: CONFIG.EMAIL.OBJECT.PREFIX + 'Contact form submitted',
419 text 419 text
420 } 420 }
421 421
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts
index 14be7f24a..a68619d07 100644
--- a/server/lib/files-cache/videos-preview-cache.ts
+++ b/server/lib/files-cache/videos-preview-cache.ts
@@ -21,7 +21,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
21 const video = await VideoModel.loadByUUIDWithFile(videoUUID) 21 const video = await VideoModel.loadByUUIDWithFile(videoUUID)
22 if (!video) return undefined 22 if (!video) return undefined
23 23
24 if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } 24 if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() }
25 25
26 return this.loadRemoteFile(videoUUID) 26 return this.loadRemoteFile(videoUUID)
27 } 27 }
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts
index 921d9a083..8cacb0ef3 100644
--- a/server/lib/job-queue/handlers/video-file-import.ts
+++ b/server/lib/job-queue/handlers/video-file-import.ts
@@ -1,7 +1,7 @@
1import * as Bull from 'bull' 1import * as Bull from 'bull'
2import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
3import { VideoModel } from '../../../models/video/video' 3import { VideoModel } from '../../../models/video/video'
4import { publishVideoIfNeeded } from './video-transcoding' 4import { publishNewResolutionIfNeeded } from './video-transcoding'
5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 5import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
6import { copy, stat } from 'fs-extra' 6import { copy, stat } from 'fs-extra'
7import { VideoFileModel } from '../../../models/video/video-file' 7import { VideoFileModel } from '../../../models/video/video-file'
@@ -25,7 +25,7 @@ async function processVideoFileImport (job: Bull.Job) {
25 25
26 await updateVideoFile(video, payload.filePath) 26 await updateVideoFile(video, payload.filePath)
27 27
28 await publishVideoIfNeeded(video) 28 await publishNewResolutionIfNeeded(video)
29 return video 29 return video
30} 30}
31 31
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts
index 1650916a6..50e159245 100644
--- a/server/lib/job-queue/handlers/video-import.ts
+++ b/server/lib/job-queue/handlers/video-import.ts
@@ -209,6 +209,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { 209 if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
210 // Put uuid because we don't have id auto incremented for now 210 // Put uuid because we don't have id auto incremented for now
211 const dataInput = { 211 const dataInput = {
212 type: 'optimize' as 'optimize',
212 videoUUID: videoImportUpdated.Video.uuid, 213 videoUUID: videoImportUpdated.Video.uuid,
213 isNewVideo: true 214 isNewVideo: true
214 } 215 }
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts
index 48cac517e..e9b84ecd6 100644
--- a/server/lib/job-queue/handlers/video-transcoding.ts
+++ b/server/lib/job-queue/handlers/video-transcoding.ts
@@ -8,18 +8,39 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { sequelizeTypescript } from '../../../initializers' 8import { sequelizeTypescript } from '../../../initializers'
9import * as Bluebird from 'bluebird' 9import * as Bluebird from 'bluebird'
10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' 10import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' 11import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding'
12import { Notifier } from '../../notifier' 12import { Notifier } from '../../notifier'
13import { CONFIG } from '../../../initializers/config' 13import { CONFIG } from '../../../initializers/config'
14 14
15export type VideoTranscodingPayload = { 15interface BaseTranscodingPayload {
16 videoUUID: string 16 videoUUID: string
17 resolution?: VideoResolution
18 isNewVideo?: boolean 17 isNewVideo?: boolean
18}
19
20interface HLSTranscodingPayload extends BaseTranscodingPayload {
21 type: 'hls'
22 isPortraitMode?: boolean
23 resolution: VideoResolution
24}
25
26interface NewResolutionTranscodingPayload extends BaseTranscodingPayload {
27 type: 'new-resolution'
19 isPortraitMode?: boolean 28 isPortraitMode?: boolean
20 generateHlsPlaylist?: boolean 29 resolution: VideoResolution
30}
31
32interface MergeAudioTranscodingPayload extends BaseTranscodingPayload {
33 type: 'merge-audio'
34 resolution: VideoResolution
35}
36
37interface OptimizeTranscodingPayload extends BaseTranscodingPayload {
38 type: 'optimize'
21} 39}
22 40
41export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload
42 | OptimizeTranscodingPayload | MergeAudioTranscodingPayload
43
23async function processVideoTranscoding (job: Bull.Job) { 44async function processVideoTranscoding (job: Bull.Job) {
24 const payload = job.data as VideoTranscodingPayload 45 const payload = job.data as VideoTranscodingPayload
25 logger.info('Processing video file in job %d.', job.id) 46 logger.info('Processing video file in job %d.', job.id)
@@ -31,14 +52,18 @@ async function processVideoTranscoding (job: Bull.Job) {
31 return undefined 52 return undefined
32 } 53 }
33 54
34 if (payload.generateHlsPlaylist) { 55 if (payload.type === 'hls') {
35 await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) 56 await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false)
36 57
37 await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) 58 await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video)
38 } else if (payload.resolution) { // Transcoding in other resolution 59 } else if (payload.type === 'new-resolution') {
39 await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) 60 await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false)
40 61
41 await retryTransactionWrapper(publishVideoIfNeeded, video, payload) 62 await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
63 } else if (payload.type === 'merge-audio') {
64 await mergeAudioVideofile(video, payload.resolution)
65
66 await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload)
42 } else { 67 } else {
43 await optimizeVideofile(video) 68 await optimizeVideofile(video)
44 69
@@ -62,7 +87,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) {
62 }) 87 })
63} 88}
64 89
65async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) { 90async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) {
66 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { 91 const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
67 // Maybe the video changed in database, refresh it 92 // Maybe the video changed in database, refresh it
68 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) 93 let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -94,7 +119,7 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi
94 await createHlsJobIfEnabled(payload) 119 await createHlsJobIfEnabled(payload)
95} 120}
96 121
97async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) { 122async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) {
98 if (videoArg === undefined) return undefined 123 if (videoArg === undefined) return undefined
99 124
100 // Outside the transaction (IO on disk) 125 // Outside the transaction (IO on disk)
@@ -120,6 +145,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
120 145
121 for (const resolution of resolutionsEnabled) { 146 for (const resolution of resolutionsEnabled) {
122 const dataInput = { 147 const dataInput = {
148 type: 'new-resolution' as 'new-resolution',
123 videoUUID: videoDatabase.uuid, 149 videoUUID: videoDatabase.uuid,
124 resolution 150 resolution
125 } 151 }
@@ -149,27 +175,27 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video
149 if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) 175 if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
150 if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) 176 if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase)
151 177
152 await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) 178 const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })
179 await createHlsJobIfEnabled(hlsPayload)
153} 180}
154 181
155// --------------------------------------------------------------------------- 182// ---------------------------------------------------------------------------
156 183
157export { 184export {
158 processVideoTranscoding, 185 processVideoTranscoding,
159 publishVideoIfNeeded 186 publishNewResolutionIfNeeded
160} 187}
161 188
162// --------------------------------------------------------------------------- 189// ---------------------------------------------------------------------------
163 190
164function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) { 191function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: number, isPortraitMode?: boolean }) {
165 // Generate HLS playlist? 192 // Generate HLS playlist?
166 if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { 193 if (payload && CONFIG.TRANSCODING.HLS.ENABLED) {
167 const hlsTranscodingPayload = { 194 const hlsTranscodingPayload = {
195 type: 'hls' as 'hls',
168 videoUUID: payload.videoUUID, 196 videoUUID: payload.videoUUID,
169 resolution: payload.resolution, 197 resolution: payload.resolution,
170 isPortraitMode: payload.isPortraitMode, 198 isPortraitMode: payload.isPortraitMode
171
172 generateHlsPlaylist: true
173 } 199 }
174 200
175 return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) 201 return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload })
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts
index 950b14c3b..18bdcded4 100644
--- a/server/lib/thumbnail.ts
+++ b/server/lib/thumbnail.ts
@@ -1,7 +1,7 @@
1import { VideoFileModel } from '../models/video/video-file' 1import { VideoFileModel } from '../models/video/video-file'
2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' 2import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
3import { CONFIG } from '../initializers/config' 3import { CONFIG } from '../initializers/config'
4import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' 4import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants'
5import { VideoModel } from '../models/video/video' 5import { VideoModel } from '../models/video/video'
6import { ThumbnailModel } from '../models/video/thumbnail' 6import { ThumbnailModel } from '../models/video/thumbnail'
7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' 7import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
@@ -45,8 +45,10 @@ function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel,
45function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { 45function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
46 const input = video.getVideoFilePath(videoFile) 46 const input = video.getVideoFilePath(videoFile)
47 47
48 const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) 48 const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
49 const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width }) 49 const thumbnailCreator = videoFile.isAudio()
50 ? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
51 : () => generateImageFromVideoFile(input, basePath, filename, { height, width })
50 52
51 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) 53 return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
52} 54}
diff --git a/server/lib/user.ts b/server/lib/user.ts
index 7badb3e72..0e4007770 100644
--- a/server/lib/user.ts
+++ b/server/lib/user.ts
@@ -1,7 +1,6 @@
1import * as Sequelize from 'sequelize'
2import * as uuidv4 from 'uuid/v4' 1import * as uuidv4 from 'uuid/v4'
3import { ActivityPubActorType } from '../../shared/models/activitypub' 2import { ActivityPubActorType } from '../../shared/models/activitypub'
4import { SERVER_ACTOR_NAME } from '../initializers/constants' 3import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants'
5import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
6import { UserModel } from '../models/account/user' 5import { UserModel } from '../models/account/user'
7import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' 6import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub'
@@ -12,8 +11,19 @@ import { UserNotificationSettingModel } from '../models/account/user-notificatio
12import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' 11import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
13import { createWatchLaterPlaylist } from './video-playlist' 12import { createWatchLaterPlaylist } from './video-playlist'
14import { sequelizeTypescript } from '../initializers/database' 13import { sequelizeTypescript } from '../initializers/database'
14import { Transaction } from 'sequelize/types'
15import { Redis } from './redis'
16import { Emailer } from './emailer'
17
18type ChannelNames = { name: string, displayName: string }
19async function createUserAccountAndChannelAndPlaylist (parameters: {
20 userToCreate: UserModel,
21 userDisplayName?: string,
22 channelNames?: ChannelNames,
23 validateUser?: boolean
24}) {
25 const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters
15 26
16async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) {
17 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { 27 const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
18 const userOptions = { 28 const userOptions = {
19 transaction: t, 29 transaction: t,
@@ -23,21 +33,17 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
23 const userCreated = await userToCreate.save(userOptions) 33 const userCreated = await userToCreate.save(userOptions)
24 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) 34 userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
25 35
26 const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t) 36 const accountCreated = await createLocalAccountWithoutKeys({
37 name: userCreated.username,
38 displayName: userDisplayName,
39 userId: userCreated.id,
40 applicationId: null,
41 t: t
42 })
27 userCreated.Account = accountCreated 43 userCreated.Account = accountCreated
28 44
29 let channelName = userCreated.username + '_channel' 45 const channelAttributes = await buildChannelAttributes(userCreated, channelNames)
30 46 const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t)
31 // Conflict, generate uuid instead
32 const actor = await ActorModel.loadLocalByName(channelName)
33 if (actor) channelName = uuidv4()
34
35 const videoChannelDisplayName = `Main ${userCreated.username} channel`
36 const videoChannelInfo = {
37 name: channelName,
38 displayName: videoChannelDisplayName
39 }
40 const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t)
41 47
42 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) 48 const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t)
43 49
@@ -55,20 +61,22 @@ async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel,
55 return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } 61 return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel }
56} 62}
57 63
58async function createLocalAccountWithoutKeys ( 64async function createLocalAccountWithoutKeys (parameters: {
59 name: string, 65 name: string,
66 displayName?: string,
60 userId: number | null, 67 userId: number | null,
61 applicationId: number | null, 68 applicationId: number | null,
62 t: Sequelize.Transaction | undefined, 69 t: Transaction | undefined,
63 type: ActivityPubActorType= 'Person' 70 type?: ActivityPubActorType
64) { 71}) {
72 const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters
65 const url = getAccountActivityPubUrl(name) 73 const url = getAccountActivityPubUrl(name)
66 74
67 const actorInstance = buildActorInstance(type, url, name) 75 const actorInstance = buildActorInstance(type, url, name)
68 const actorInstanceCreated = await actorInstance.save({ transaction: t }) 76 const actorInstanceCreated = await actorInstance.save({ transaction: t })
69 77
70 const accountInstance = new AccountModel({ 78 const accountInstance = new AccountModel({
71 name, 79 name: displayName || name,
72 userId, 80 userId,
73 applicationId, 81 applicationId,
74 actorId: actorInstanceCreated.id 82 actorId: actorInstanceCreated.id
@@ -81,24 +89,42 @@ async function createLocalAccountWithoutKeys (
81} 89}
82 90
83async function createApplicationActor (applicationId: number) { 91async function createApplicationActor (applicationId: number) {
84 const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationId, undefined, 'Application') 92 const accountCreated = await createLocalAccountWithoutKeys({
93 name: SERVER_ACTOR_NAME,
94 userId: null,
95 applicationId: applicationId,
96 t: undefined,
97 type: 'Application'
98 })
85 99
86 accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor) 100 accountCreated.Actor = await setAsyncActorKeys(accountCreated.Actor)
87 101
88 return accountCreated 102 return accountCreated
89} 103}
90 104
105async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) {
106 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
107 let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
108
109 if (isPendingEmail) url += '&isPendingEmail=true'
110
111 const email = isPendingEmail ? user.pendingEmail : user.email
112
113 await Emailer.Instance.addVerifyEmailJob(email, url)
114}
115
91// --------------------------------------------------------------------------- 116// ---------------------------------------------------------------------------
92 117
93export { 118export {
94 createApplicationActor, 119 createApplicationActor,
95 createUserAccountAndChannelAndPlaylist, 120 createUserAccountAndChannelAndPlaylist,
96 createLocalAccountWithoutKeys 121 createLocalAccountWithoutKeys,
122 sendVerifyUserEmail
97} 123}
98 124
99// --------------------------------------------------------------------------- 125// ---------------------------------------------------------------------------
100 126
101function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Transaction | undefined) { 127function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) {
102 const values: UserNotificationSetting & { userId: number } = { 128 const values: UserNotificationSetting & { userId: number } = {
103 userId: user.id, 129 userId: user.id,
104 newVideoFromSubscription: UserNotificationSettingValue.WEB, 130 newVideoFromSubscription: UserNotificationSettingValue.WEB,
@@ -116,3 +142,20 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr
116 142
117 return UserNotificationSettingModel.create(values, { transaction: t }) 143 return UserNotificationSettingModel.create(values, { transaction: t })
118} 144}
145
146async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) {
147 if (channelNames) return channelNames
148
149 let channelName = user.username + '_channel'
150
151 // Conflict, generate uuid instead
152 const actor = await ActorModel.loadLocalByName(channelName)
153 if (actor) channelName = uuidv4()
154
155 const videoChannelDisplayName = `Main ${user.username} channel`
156
157 return {
158 name: channelName,
159 displayName: videoChannelDisplayName
160 }
161}
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts
index 0fe95ca09..ee0482c36 100644
--- a/server/lib/video-channel.ts
+++ b/server/lib/video-channel.ts
@@ -3,7 +3,8 @@ import * as uuidv4 from 'uuid/v4'
3import { VideoChannelCreate } from '../../shared/models' 3import { VideoChannelCreate } from '../../shared/models'
4import { AccountModel } from '../models/account/account' 4import { AccountModel } from '../models/account/account'
5import { VideoChannelModel } from '../models/video/video-channel' 5import { VideoChannelModel } from '../models/video/video-channel'
6import { buildActorInstance, getVideoChannelActivityPubUrl } from './activitypub' 6import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub'
7import { VideoModel } from '../models/video/video'
7 8
8async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { 9async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) {
9 const uuid = uuidv4() 10 const uuid = uuidv4()
@@ -33,8 +34,19 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account
33 return videoChannelCreated 34 return videoChannelCreated
34} 35}
35 36
37async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) {
38 const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel)
39
40 for (const videoId of videoIds) {
41 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
42
43 await federateVideoIfNeeded(video, false)
44 }
45}
46
36// --------------------------------------------------------------------------- 47// ---------------------------------------------------------------------------
37 48
38export { 49export {
39 createVideoChannel 50 createVideoChannel,
51 federateAllVideosOfChannel
40} 52}
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index 0fe0ff12a..8d786e0ef 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -1,6 +1,6 @@
1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' 1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
2import { join } from 'path' 2import { join } from 'path'
3import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils' 3import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils'
4import { ensureDir, move, remove, stat } from 'fs-extra' 4import { ensureDir, move, remove, stat } from 'fs-extra'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
@@ -11,15 +11,24 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla
11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' 11import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
12import { CONFIG } from '../initializers/config' 12import { CONFIG } from '../initializers/config'
13 13
14/**
15 * Optimize the original video file and replace it. The resolution is not changed.
16 */
14async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { 17async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
15 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 18 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
19 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
16 const newExtname = '.mp4' 20 const newExtname = '.mp4'
17 21
18 const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() 22 const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile()
19 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) 23 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
20 const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) 24 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
21 25
22 const transcodeOptions = { 26 const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath)
27 ? 'quick-transcode'
28 : 'video'
29
30 const transcodeOptions: TranscodeOptions = {
31 type: transcodeType as any, // FIXME: typing issue
23 inputPath: videoInputPath, 32 inputPath: videoInputPath,
24 outputPath: videoTranscodedPath, 33 outputPath: videoTranscodedPath,
25 resolution: inputVideoFile.resolution 34 resolution: inputVideoFile.resolution
@@ -32,18 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
32 await remove(videoInputPath) 41 await remove(videoInputPath)
33 42
34 // Important to do this before getVideoFilename() to take in account the new file extension 43 // Important to do this before getVideoFilename() to take in account the new file extension
35 inputVideoFile.set('extname', newExtname) 44 inputVideoFile.extname = newExtname
36 45
37 const videoOutputPath = video.getVideoFilePath(inputVideoFile) 46 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
38 await move(videoTranscodedPath, videoOutputPath)
39 const stats = await stat(videoOutputPath)
40 const fps = await getVideoFileFPS(videoOutputPath)
41 47
42 inputVideoFile.set('size', stats.size) 48 await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
43 inputVideoFile.set('fps', fps)
44
45 await video.createTorrentAndSetInfoHash(inputVideoFile)
46 await inputVideoFile.save()
47 } catch (err) { 49 } catch (err) {
48 // Auto destruction... 50 // Auto destruction...
49 video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) 51 video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err }))
@@ -52,8 +54,12 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
52 } 54 }
53} 55}
54 56
57/**
58 * Transcode the original video file to a lower resolution.
59 */
55async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { 60async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
56 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR 61 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
62 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
57 const extname = '.mp4' 63 const extname = '.mp4'
58 64
59 // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed 65 // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
@@ -66,27 +72,49 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR
66 videoId: video.id 72 videoId: video.id
67 }) 73 })
68 const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) 74 const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile))
75 const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))
69 76
70 const transcodeOptions = { 77 const transcodeOptions = {
78 type: 'video' as 'video',
71 inputPath: videoInputPath, 79 inputPath: videoInputPath,
72 outputPath: videoOutputPath, 80 outputPath: videoTranscodedPath,
73 resolution, 81 resolution,
74 isPortraitMode: isPortrait 82 isPortraitMode: isPortrait
75 } 83 }
76 84
77 await transcode(transcodeOptions) 85 await transcode(transcodeOptions)
78 86
79 const stats = await stat(videoOutputPath) 87 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
80 const fps = await getVideoFileFPS(videoOutputPath) 88}
89
90async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
91 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
92 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
93 const newExtname = '.mp4'
94
95 const inputVideoFile = video.getOriginalFile()
81 96
82 newVideoFile.set('size', stats.size) 97 const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
83 newVideoFile.set('fps', fps) 98 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
84 99
85 await video.createTorrentAndSetInfoHash(newVideoFile) 100 const transcodeOptions = {
101 type: 'merge-audio' as 'merge-audio',
102 inputPath: video.getPreview().getPath(),
103 outputPath: videoTranscodedPath,
104 audioPath: audioInputPath,
105 resolution
106 }
107
108 await transcode(transcodeOptions)
86 109
87 await newVideoFile.save() 110 await remove(audioInputPath)
88 111
89 video.VideoFiles.push(newVideoFile) 112 // Important to do this before getVideoFilename() to take in account the new file extension
113 inputVideoFile.extname = newExtname
114
115 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
116
117 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
90} 118}
91 119
92async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { 120async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
@@ -97,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
97 const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) 125 const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
98 126
99 const transcodeOptions = { 127 const transcodeOptions = {
128 type: 'hls' as 'hls',
100 inputPath: videoInputPath, 129 inputPath: videoInputPath,
101 outputPath, 130 outputPath,
102 resolution, 131 resolution,
@@ -125,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti
125 }) 154 })
126} 155}
127 156
157// ---------------------------------------------------------------------------
158
128export { 159export {
129 generateHlsPlaylist, 160 generateHlsPlaylist,
130 optimizeVideofile, 161 optimizeVideofile,
131 transcodeOriginalVideofile 162 transcodeOriginalVideofile,
163 mergeAudioVideofile
164}
165
166// ---------------------------------------------------------------------------
167
168async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
169 const stats = await stat(transcodingPath)
170 const fps = await getVideoFileFPS(transcodingPath)
171
172 await move(transcodingPath, outputPath)
173
174 videoFile.set('size', stats.size)
175 videoFile.set('fps', fps)
176
177 await video.createTorrentAndSetInfoHash(videoFile)
178
179 const updatedVideoFile = await videoFile.save()
180
181 // Add it if this is a new created file
182 if (video.VideoFiles.some(f => f.id === videoFile.id) === false) {
183 video.VideoFiles.push(updatedVideoFile)
184 }
185
186 return video
132} 187}
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts
index e4f5c98fe..dd362619d 100644
--- a/server/middlewares/validators/feeds.ts
+++ b/server/middlewares/validators/feeds.ts
@@ -1,21 +1,20 @@
1import * as express from 'express' 1import * as express from 'express'
2import { param, query } from 'express-validator/check' 2import { param, query } from 'express-validator/check'
3import { doesAccountIdExist, isAccountNameValid, doesAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' 3import { doesAccountIdExist, doesAccountNameWithHostExist } from '../../helpers/custom-validators/accounts'
4import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' 4import { isIdOrUUIDValid, isIdValid } from '../../helpers/custom-validators/misc'
5import { logger } from '../../helpers/logger' 5import { logger } from '../../helpers/logger'
6import { areValidationErrors } from './utils' 6import { areValidationErrors } from './utils'
7import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' 7import { isValidRSSFeed } from '../../helpers/custom-validators/feeds'
8import { doesVideoChannelIdExist, doesVideoChannelNameWithHostExist } from '../../helpers/custom-validators/video-channels' 8import { doesVideoChannelIdExist, doesVideoChannelNameWithHostExist } from '../../helpers/custom-validators/video-channels'
9import { doesVideoExist } from '../../helpers/custom-validators/videos' 9import { doesVideoExist } from '../../helpers/custom-validators/videos'
10import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
11 10
12const videoFeedsValidator = [ 11const videoFeedsValidator = [
13 param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), 12 param('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
14 query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'), 13 query('format').optional().custom(isValidRSSFeed).withMessage('Should have a valid format (rss, atom, json)'),
15 query('accountId').optional().custom(isIdOrUUIDValid), 14 query('accountId').optional().custom(isIdValid),
16 query('accountName').optional().custom(isAccountNameValid), 15 query('accountName').optional(),
17 query('videoChannelId').optional().custom(isIdOrUUIDValid), 16 query('videoChannelId').optional().custom(isIdValid),
18 query('videoChannelName').optional().custom(isActorPreferredUsernameValid), 17 query('videoChannelName').optional(),
19 18
20 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 19 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
21 logger.debug('Checking feeds parameters', { parameters: req.query }) 20 logger.debug('Checking feeds parameters', { parameters: req.query })
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts
index 6d8cd7894..ec70fa0fd 100644
--- a/server/middlewares/validators/users.ts
+++ b/server/middlewares/validators/users.ts
@@ -25,6 +25,9 @@ import { Redis } from '../../lib/redis'
25import { UserModel } from '../../models/account/user' 25import { UserModel } from '../../models/account/user'
26import { areValidationErrors } from './utils' 26import { areValidationErrors } from './utils'
27import { ActorModel } from '../../models/activitypub/actor' 27import { ActorModel } from '../../models/activitypub/actor'
28import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor'
29import { isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
30import { UserRegister } from '../../../shared/models/users/user-register.model'
28 31
29const usersAddValidator = [ 32const usersAddValidator = [
30 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), 33 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -49,6 +52,16 @@ const usersRegisterValidator = [
49 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'), 52 body('username').custom(isUserUsernameValid).withMessage('Should have a valid username'),
50 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), 53 body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'),
51 body('email').isEmail().withMessage('Should have a valid email'), 54 body('email').isEmail().withMessage('Should have a valid email'),
55 body('displayName')
56 .optional()
57 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
58
59 body('channel.name')
60 .optional()
61 .custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
62 body('channel.displayName')
63 .optional()
64 .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
52 65
53 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 66 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
54 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') }) 67 logger.debug('Checking usersRegister parameters', { parameters: omit(req.body, 'password') })
@@ -56,6 +69,28 @@ const usersRegisterValidator = [
56 if (areValidationErrors(req, res)) return 69 if (areValidationErrors(req, res)) return
57 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return 70 if (!await checkUserNameOrEmailDoesNotAlreadyExist(req.body.username, req.body.email, res)) return
58 71
72 const body: UserRegister = req.body
73 if (body.channel) {
74 if (!body.channel.name || !body.channel.displayName) {
75 return res.status(400)
76 .send({ error: 'Channel is optional but if you specify it, channel.name and channel.displayName are required.' })
77 .end()
78 }
79
80 if (body.channel.name === body.username) {
81 return res.status(400)
82 .send({ error: 'Channel name cannot be the same than user username.' })
83 .end()
84 }
85
86 const existing = await ActorModel.loadLocalByName(body.channel.name)
87 if (existing) {
88 return res.status(409)
89 .send({ error: `Channel with name ${body.channel.name} already exists.` })
90 .end()
91 }
92 }
93
59 return next() 94 return next()
60 } 95 }
61] 96]
@@ -142,13 +177,27 @@ const usersUpdateValidator = [
142] 177]
143 178
144const usersUpdateMeValidator = [ 179const usersUpdateMeValidator = [
145 body('displayName').optional().custom(isUserDisplayNameValid).withMessage('Should have a valid display name'), 180 body('displayName')
146 body('description').optional().custom(isUserDescriptionValid).withMessage('Should have a valid description'), 181 .optional()
147 body('currentPassword').optional().custom(isUserPasswordValid).withMessage('Should have a valid current password'), 182 .custom(isUserDisplayNameValid).withMessage('Should have a valid display name'),
148 body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), 183 body('description')
149 body('email').optional().isEmail().withMessage('Should have a valid email attribute'), 184 .optional()
150 body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'), 185 .custom(isUserDescriptionValid).withMessage('Should have a valid description'),
151 body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'), 186 body('currentPassword')
187 .optional()
188 .custom(isUserPasswordValid).withMessage('Should have a valid current password'),
189 body('password')
190 .optional()
191 .custom(isUserPasswordValid).withMessage('Should have a valid password'),
192 body('email')
193 .optional()
194 .isEmail().withMessage('Should have a valid email attribute'),
195 body('nsfwPolicy')
196 .optional()
197 .custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
198 body('autoPlayVideo')
199 .optional()
200 .custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
152 body('videosHistoryEnabled') 201 body('videosHistoryEnabled')
153 .optional() 202 .optional()
154 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'), 203 .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
@@ -156,7 +205,7 @@ const usersUpdateMeValidator = [
156 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 205 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
157 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) 206 logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
158 207
159 if (req.body.password) { 208 if (req.body.password || req.body.email) {
160 if (!req.body.currentPassword) { 209 if (!req.body.currentPassword) {
161 return res.status(400) 210 return res.status(400)
162 .send({ error: 'currentPassword parameter is missing.' }) 211 .send({ error: 'currentPassword parameter is missing.' })
@@ -293,8 +342,14 @@ const usersAskSendVerifyEmailValidator = [
293] 342]
294 343
295const usersVerifyEmailValidator = [ 344const usersVerifyEmailValidator = [
296 param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), 345 param('id')
297 body('verificationString').not().isEmpty().withMessage('Should have a valid verification string'), 346 .isInt().not().isEmpty().withMessage('Should have a valid id'),
347
348 body('verificationString')
349 .not().isEmpty().withMessage('Should have a valid verification string'),
350 body('isPendingEmail')
351 .optional()
352 .toBoolean(),
298 353
299 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 354 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
300 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params }) 355 logger.debug('Checking usersVerifyEmail parameters', { parameters: req.params })
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts
index 4b26f0bc4..f5a59cacb 100644
--- a/server/middlewares/validators/videos/video-channels.ts
+++ b/server/middlewares/validators/videos/video-channels.ts
@@ -14,6 +14,7 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
14import { areValidationErrors } from '../utils' 14import { areValidationErrors } from '../utils'
15import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' 15import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor'
16import { ActorModel } from '../../../models/activitypub/actor' 16import { ActorModel } from '../../../models/activitypub/actor'
17import { isBooleanValid } from '../../../helpers/custom-validators/misc'
17 18
18const videoChannelsAddValidator = [ 19const videoChannelsAddValidator = [
19 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), 20 body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'),
@@ -40,9 +41,18 @@ const videoChannelsAddValidator = [
40 41
41const videoChannelsUpdateValidator = [ 42const videoChannelsUpdateValidator = [
42 param('nameWithHost').exists().withMessage('Should have an video channel name with host'), 43 param('nameWithHost').exists().withMessage('Should have an video channel name with host'),
43 body('displayName').optional().custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), 44 body('displayName')
44 body('description').optional().custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'), 45 .optional()
45 body('support').optional().custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'), 46 .custom(isVideoChannelNameValid).withMessage('Should have a valid display name'),
47 body('description')
48 .optional()
49 .custom(isVideoChannelDescriptionValid).withMessage('Should have a valid description'),
50 body('support')
51 .optional()
52 .custom(isVideoChannelSupportValid).withMessage('Should have a valid support text'),
53 body('bulkVideosSupportUpdate')
54 .optional()
55 .custom(isBooleanValid).withMessage('Should have a valid bulkVideosSupportUpdate boolean field'),
46 56
47 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 57 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
48 logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) 58 logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body })
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts
index f68eeeeb3..9c88dd291 100644
--- a/server/middlewares/validators/videos/video-playlists.ts
+++ b/server/middlewares/validators/videos/video-playlists.ts
@@ -24,6 +24,9 @@ import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/
24import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' 24import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model'
25 25
26const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ 26const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([
27 body('displayName')
28 .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
29
27 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 30 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
28 logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body }) 31 logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body })
29 32
@@ -46,6 +49,10 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
46 param('playlistId') 49 param('playlistId')
47 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), 50 .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'),
48 51
52 body('displayName')
53 .optional()
54 .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
55
49 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 56 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
50 logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body }) 57 logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body })
51 58
@@ -61,12 +68,6 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([
61 68
62 const body: VideoPlaylistUpdate = req.body 69 const body: VideoPlaylistUpdate = req.body
63 70
64 if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PRIVATE && body.privacy === VideoPlaylistPrivacy.PRIVATE) {
65 cleanUpReqFiles(req)
66 return res.status(400)
67 .json({ error: 'Cannot set "private" a video playlist that was not private.' })
68 }
69
70 const newPrivacy = body.privacy || videoPlaylist.privacy 71 const newPrivacy = body.privacy || videoPlaylist.privacy
71 if (newPrivacy === VideoPlaylistPrivacy.PUBLIC && 72 if (newPrivacy === VideoPlaylistPrivacy.PUBLIC &&
72 ( 73 (
@@ -368,8 +369,6 @@ function getCommonPlaylistEditAttributes () {
368 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') 369 + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ')
369 ), 370 ),
370 371
371 body('displayName')
372 .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'),
373 body('description') 372 body('description')
374 .optional() 373 .optional()
375 .customSanitizer(toValueOrNull) 374 .customSanitizer(toValueOrNull)
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts
index 2b01f108d..b1c05ab2d 100644
--- a/server/middlewares/validators/videos/videos.ts
+++ b/server/middlewares/validators/videos/videos.ts
@@ -111,18 +111,10 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([
111 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) 111 if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req)
112 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) 112 if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req)
113 113
114 const video = res.locals.video
115
116 // Check if the user who did the request is able to update the video 114 // Check if the user who did the request is able to update the video
117 const user = res.locals.oauth.token.User 115 const user = res.locals.oauth.token.User
118 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) 116 if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req)
119 117
120 if (video.privacy !== VideoPrivacy.PRIVATE && req.body.privacy === VideoPrivacy.PRIVATE) {
121 cleanUpReqFiles(req)
122 return res.status(409)
123 .json({ error: 'Cannot set "private" a video that was not private.' })
124 }
125
126 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) 118 if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req)
127 119
128 return next() 120 return next()
diff --git a/server/models/account/account.ts b/server/models/account/account.ts
index 2b04acd86..09cada096 100644
--- a/server/models/account/account.ts
+++ b/server/models/account/account.ts
@@ -47,7 +47,7 @@ export enum ScopeNames {
47 attributes: [ 'id', 'name' ], 47 attributes: [ 'id', 'name' ],
48 include: [ 48 include: [
49 { 49 {
50 attributes: [ 'id', 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], 50 attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
51 model: ActorModel.unscoped(), 51 model: ActorModel.unscoped(),
52 required: true, 52 required: true,
53 where: whereActor, 53 where: whereActor,
@@ -180,22 +180,6 @@ export class AccountModel extends Model<AccountModel> {
180 return AccountModel.findByPk(id, { transaction }) 180 return AccountModel.findByPk(id, { transaction })
181 } 181 }
182 182
183 static loadByUUID (uuid: string) {
184 const query = {
185 include: [
186 {
187 model: ActorModel,
188 required: true,
189 where: {
190 uuid
191 }
192 }
193 ]
194 }
195
196 return AccountModel.findOne(query)
197 }
198
199 static loadByNameWithHost (nameWithHost: string) { 183 static loadByNameWithHost (nameWithHost: string) {
200 const [ accountName, host ] = nameWithHost.split('@') 184 const [ accountName, host ] = nameWithHost.split('@')
201 185
@@ -332,7 +316,6 @@ export class AccountModel extends Model<AccountModel> {
332 316
333 return { 317 return {
334 id: this.id, 318 id: this.id,
335 uuid: actor.uuid,
336 name: actor.name, 319 name: actor.name,
337 displayName: this.getDisplayName(), 320 displayName: this.getDisplayName(),
338 url: actor.url, 321 url: actor.url,
diff --git a/server/models/account/user.ts b/server/models/account/user.ts
index 4a9acd703..e75039521 100644
--- a/server/models/account/user.ts
+++ b/server/models/account/user.ts
@@ -114,6 +114,11 @@ export class UserModel extends Model<UserModel> {
114 email: string 114 email: string
115 115
116 @AllowNull(true) 116 @AllowNull(true)
117 @IsEmail
118 @Column(DataType.STRING(400))
119 pendingEmail: string
120
121 @AllowNull(true)
117 @Default(null) 122 @Default(null)
118 @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true)) 123 @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true))
119 @Column 124 @Column
@@ -540,6 +545,7 @@ export class UserModel extends Model<UserModel> {
540 id: this.id, 545 id: this.id,
541 username: this.username, 546 username: this.username,
542 email: this.email, 547 email: this.email,
548 pendingEmail: this.pendingEmail,
543 emailVerified: this.emailVerified, 549 emailVerified: this.emailVerified,
544 nsfwPolicy: this.nsfwPolicy, 550 nsfwPolicy: this.nsfwPolicy,
545 webTorrentEnabled: this.webTorrentEnabled, 551 webTorrentEnabled: this.webTorrentEnabled,
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts
index 4a466441c..bd6a2c8fd 100644
--- a/server/models/activitypub/actor.ts
+++ b/server/models/activitypub/actor.ts
@@ -7,13 +7,11 @@ import {
7 Column, 7 Column,
8 CreatedAt, 8 CreatedAt,
9 DataType, 9 DataType,
10 Default,
11 DefaultScope, 10 DefaultScope,
12 ForeignKey, 11 ForeignKey,
13 HasMany, 12 HasMany,
14 HasOne, 13 HasOne,
15 Is, 14 Is,
16 IsUUID,
17 Model, 15 Model,
18 Scopes, 16 Scopes,
19 Table, 17 Table,
@@ -120,10 +118,6 @@ export const unusedActorAttributesForAPI = [
120 fields: [ 'avatarId' ] 118 fields: [ 'avatarId' ]
121 }, 119 },
122 { 120 {
123 fields: [ 'uuid' ],
124 unique: true
125 },
126 {
127 fields: [ 'followersUrl' ] 121 fields: [ 'followersUrl' ]
128 } 122 }
129 ] 123 ]
@@ -135,12 +129,6 @@ export class ActorModel extends Model<ActorModel> {
135 type: ActivityPubActorType 129 type: ActivityPubActorType
136 130
137 @AllowNull(false) 131 @AllowNull(false)
138 @Default(DataType.UUIDV4)
139 @IsUUID(4)
140 @Column(DataType.UUID)
141 uuid: string
142
143 @AllowNull(false)
144 @Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username')) 132 @Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username'))
145 @Column 133 @Column
146 preferredUsername: string 134 preferredUsername: string
@@ -408,7 +396,6 @@ export class ActorModel extends Model<ActorModel> {
408 return { 396 return {
409 id: this.id, 397 id: this.id,
410 url: this.url, 398 url: this.url,
411 uuid: this.uuid,
412 name: this.preferredUsername, 399 name: this.preferredUsername,
413 host: this.getHost(), 400 host: this.getHost(),
414 hostRedundancyAllowed: this.getRedundancyAllowed(), 401 hostRedundancyAllowed: this.getRedundancyAllowed(),
@@ -454,7 +441,6 @@ export class ActorModel extends Model<ActorModel> {
454 endpoints: { 441 endpoints: {
455 sharedInbox: this.sharedInboxUrl 442 sharedInbox: this.sharedInboxUrl
456 }, 443 },
457 uuid: this.uuid,
458 publicKey: { 444 publicKey: {
459 id: this.getPublicKeyUrl(), 445 id: this.getPublicKeyUrl(),
460 owner: this.url, 446 owner: this.url,
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts
index 206e9a3d6..8faf0adba 100644
--- a/server/models/video/thumbnail.ts
+++ b/server/models/video/thumbnail.ts
@@ -107,10 +107,12 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
107 return WEBSERVER.URL + staticPath + this.filename 107 return WEBSERVER.URL + staticPath + this.filename
108 } 108 }
109 109
110 removeThumbnail () { 110 getPath () {
111 const directory = ThumbnailModel.types[this.type].directory 111 const directory = ThumbnailModel.types[this.type].directory
112 const thumbnailPath = join(directory, this.filename) 112 return join(directory, this.filename)
113 }
113 114
114 return remove(thumbnailPath) 115 removeThumbnail () {
116 return remove(this.getPath())
115 } 117 }
116} 118}
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index fb70e6625..b0b261c88 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -72,7 +72,7 @@ type AvailableForListOptions = {
72 attributes: [ 'name', 'description', 'id', 'actorId' ], 72 attributes: [ 'name', 'description', 'id', 'actorId' ],
73 include: [ 73 include: [
74 { 74 {
75 attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], 75 attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ],
76 model: ActorModel.unscoped(), 76 model: ActorModel.unscoped(),
77 required: true, 77 required: true,
78 include: [ 78 include: [
@@ -334,14 +334,21 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
334 }) 334 })
335 } 335 }
336 336
337 static listByAccount (accountId: number) { 337 static listByAccount (options: {
338 accountId: number,
339 start: number,
340 count: number,
341 sort: string
342 }) {
338 const query = { 343 const query = {
339 order: getSort('createdAt'), 344 offset: options.start,
345 limit: options.count,
346 order: getSort(options.sort),
340 include: [ 347 include: [
341 { 348 {
342 model: AccountModel, 349 model: AccountModel,
343 where: { 350 where: {
344 id: accountId 351 id: options.accountId
345 }, 352 },
346 required: true 353 required: true
347 } 354 }
@@ -380,24 +387,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
380 .findByPk(id) 387 .findByPk(id)
381 } 388 }
382 389
383 static loadByUUIDAndPopulateAccount (uuid: string) {
384 const query = {
385 include: [
386 {
387 model: ActorModel,
388 required: true,
389 where: {
390 uuid
391 }
392 }
393 ]
394 }
395
396 return VideoChannelModel
397 .scope([ ScopeNames.WITH_ACCOUNT ])
398 .findOne(query)
399 }
400
401 static loadByUrlAndPopulateAccount (url: string) { 390 static loadByUrlAndPopulateAccount (url: string) {
402 const query = { 391 const query = {
403 include: [ 392 include: [
@@ -503,7 +492,6 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
503 492
504 return { 493 return {
505 id: this.id, 494 id: this.id,
506 uuid: actor.uuid,
507 name: actor.name, 495 name: actor.name,
508 displayName: this.getDisplayName(), 496 displayName: this.getDisplayName(),
509 url: actor.url, 497 url: actor.url,
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 2203a7aba..05c490759 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -24,6 +24,7 @@ import { VideoModel } from './video'
24import { VideoRedundancyModel } from '../redundancy/video-redundancy' 24import { VideoRedundancyModel } from '../redundancy/video-redundancy'
25import { VideoStreamingPlaylistModel } from './video-streaming-playlist' 25import { VideoStreamingPlaylistModel } from './video-streaming-playlist'
26import { FindOptions, QueryTypes, Transaction } from 'sequelize' 26import { FindOptions, QueryTypes, Transaction } from 'sequelize'
27import { MIMETYPES } from '../../initializers/constants'
27 28
28@Table({ 29@Table({
29 tableName: 'videoFile', 30 tableName: 'videoFile',
@@ -161,6 +162,10 @@ export class VideoFileModel extends Model<VideoFileModel> {
161 })) 162 }))
162 } 163 }
163 164
165 isAudio () {
166 return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname]
167 }
168
164 hasSameUniqueKeysThan (other: VideoFileModel) { 169 hasSameUniqueKeysThan (other: VideoFileModel) {
165 return this.fps === other.fps && 170 return this.fps === other.fps &&
166 this.resolution === other.resolution && 171 this.resolution === other.resolution &&
diff --git a/server/models/video/video.ts b/server/models/video/video.ts
index c0a7892a4..eccf0a4fa 100644
--- a/server/models/video/video.ts
+++ b/server/models/video/video.ts
@@ -1515,6 +1515,29 @@ export class VideoModel extends Model<VideoModel> {
1515 .then(results => results.length === 1) 1515 .then(results => results.length === 1)
1516 } 1516 }
1517 1517
1518 static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) {
1519 const options = {
1520 where: {
1521 channelId: videoChannel.id
1522 },
1523 transaction: t
1524 }
1525
1526 return VideoModel.update({ support: videoChannel.support }, options)
1527 }
1528
1529 static getAllIdsFromChannel (videoChannel: VideoChannelModel) {
1530 const query = {
1531 attributes: [ 'id' ],
1532 where: {
1533 channelId: videoChannel.id
1534 }
1535 }
1536
1537 return VideoModel.findAll(query)
1538 .then(videos => videos.map(v => v.id))
1539 }
1540
1518 // threshold corresponds to how many video the field should have to be returned 1541 // threshold corresponds to how many video the field should have to be returned
1519 static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) { 1542 static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
1520 const serverActor = await getServerActor() 1543 const serverActor = await getServerActor()
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts
index edf588c16..34c6be49b 100644
--- a/server/tests/api/activitypub/client.ts
+++ b/server/tests/api/activitypub/client.ts
@@ -3,6 +3,7 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 cleanupTests,
6 doubleFollow, 7 doubleFollow,
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 flushTests, 9 flushTests,
@@ -39,7 +40,7 @@ describe('Test activitypub', function () {
39 const object = res.body 40 const object = res.body
40 41
41 expect(object.type).to.equal('Person') 42 expect(object.type).to.equal('Person')
42 expect(object.id).to.equal('http://localhost:9001/accounts/root') 43 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/accounts/root')
43 expect(object.name).to.equal('root') 44 expect(object.name).to.equal('root')
44 expect(object.preferredUsername).to.equal('root') 45 expect(object.preferredUsername).to.equal('root')
45 }) 46 })
@@ -49,17 +50,17 @@ describe('Test activitypub', function () {
49 const object = res.body 50 const object = res.body
50 51
51 expect(object.type).to.equal('Video') 52 expect(object.type).to.equal('Video')
52 expect(object.id).to.equal('http://localhost:9001/videos/watch/' + videoUUID) 53 expect(object.id).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID)
53 expect(object.name).to.equal('video') 54 expect(object.name).to.equal('video')
54 }) 55 })
55 56
56 it('Should redirect to the origin video object', async function () { 57 it('Should redirect to the origin video object', async function () {
57 const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + videoUUID, 302) 58 const res = await makeActivityPubGetRequest(servers[1].url, '/videos/watch/' + videoUUID, 302)
58 59
59 expect(res.header.location).to.equal('http://localhost:9001/videos/watch/' + videoUUID) 60 expect(res.header.location).to.equal('http://localhost:' + servers[0].port + '/videos/watch/' + videoUUID)
60 }) 61 })
61 62
62 after(function () { 63 after(async function () {
63 killallServers(servers) 64 await cleanupTests(servers)
64 }) 65 })
65}) 66})
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts
index 7240bb0fb..3a1c0d321 100644
--- a/server/tests/api/activitypub/fetch.ts
+++ b/server/tests/api/activitypub/fetch.ts
@@ -3,6 +3,7 @@
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 closeAllSequelize, 7 closeAllSequelize,
7 createUser, 8 createUser,
8 doubleFollow, 9 doubleFollow,
@@ -48,8 +49,16 @@ describe('Test ActivityPub fetcher', function () {
48 const badVideoUUID = res.body.video.uuid 49 const badVideoUUID = res.body.video.uuid
49 await uploadVideo(servers[0].url, userAccessToken, { name: 'video user' }) 50 await uploadVideo(servers[0].url, userAccessToken, { name: 'video user' })
50 51
51 await setActorField(1, 'http://localhost:9001/accounts/user1', 'url', 'http://localhost:9002/accounts/user1') 52 {
52 await setVideoField(1, badVideoUUID, 'url', 'http://localhost:9003/videos/watch/' + badVideoUUID) 53 const to = 'http://localhost:' + servers[0].port + '/accounts/user1'
54 const value = 'http://localhost:' + servers[1].port + '/accounts/user1'
55 await setActorField(servers[0].internalServerNumber, to, 'url', value)
56 }
57
58 {
59 const value = 'http://localhost:' + servers[2].port + '/videos/watch/' + badVideoUUID
60 await setVideoField(servers[0].internalServerNumber, badVideoUUID, 'url', value)
61 }
53 }) 62 })
54 63
55 it('Should add only the video with a valid actor URL', async function () { 64 it('Should add only the video with a valid actor URL', async function () {
@@ -78,7 +87,9 @@ describe('Test ActivityPub fetcher', function () {
78 }) 87 })
79 88
80 after(async function () { 89 after(async function () {
81 killallServers(servers) 90 this.timeout(10000)
91
92 await cleanupTests(servers)
82 93
83 await closeAllSequelize(servers) 94 await closeAllSequelize(servers)
84 }) 95 })
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index 9be9aa495..921ee874c 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -2,13 +2,14 @@
2 2
3import 'mocha' 3import 'mocha'
4import { 4import {
5 cleanupTests, closeAllSequelize,
5 createVideoPlaylist, 6 createVideoPlaylist,
6 doubleFollow, 7 doubleFollow,
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 generateUserAccessToken, 9 generateUserAccessToken,
9 getVideo, 10 getVideo,
10 getVideoPlaylist, 11 getVideoPlaylist,
11 killallServers, rateVideo, 12 killallServers,
12 reRunServer, 13 reRunServer,
13 ServerInfo, 14 ServerInfo,
14 setAccessTokensToServers, 15 setAccessTokensToServers,
@@ -48,26 +49,26 @@ describe('Test AP refresher', function () {
48 } 49 }
49 50
50 { 51 {
51 const a1 = await generateUserAccessToken(servers[1], 'user1') 52 const a1 = await generateUserAccessToken(servers[ 1 ], 'user1')
52 await uploadVideo(servers[1].url, a1, { name: 'video4' }) 53 await uploadVideo(servers[ 1 ].url, a1, { name: 'video4' })
53 54
54 const a2 = await generateUserAccessToken(servers[1], 'user2') 55 const a2 = await generateUserAccessToken(servers[ 1 ], 'user2')
55 await uploadVideo(servers[1].url, a2, { name: 'video5' }) 56 await uploadVideo(servers[ 1 ].url, a2, { name: 'video5' })
56 } 57 }
57 58
58 { 59 {
59 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 }
60 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 })
61 playlistUUID1 = res.body.videoPlaylist.uuid 62 playlistUUID1 = res.body.videoPlaylist.uuid
62 } 63 }
63 64
64 { 65 {
65 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 }
66 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 })
67 playlistUUID2 = res.body.videoPlaylist.uuid 68 playlistUUID2 = res.body.videoPlaylist.uuid
68 } 69 }
69 70
70 await doubleFollow(servers[0], servers[1]) 71 await doubleFollow(servers[ 0 ], servers[ 1 ])
71 }) 72 })
72 73
73 describe('Videos refresher', function () { 74 describe('Videos refresher', function () {
@@ -78,7 +79,7 @@ describe('Test AP refresher', function () {
78 await wait(10000) 79 await wait(10000)
79 80
80 // Change UUID so the remote server returns a 404 81 // Change UUID so the remote server returns a 404
81 await setVideoField(2, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') 82 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f')
82 83
83 await getVideo(servers[ 0 ].url, videoUUID1) 84 await getVideo(servers[ 0 ].url, videoUUID1)
84 await getVideo(servers[ 0 ].url, videoUUID2) 85 await getVideo(servers[ 0 ].url, videoUUID2)
@@ -94,7 +95,7 @@ describe('Test AP refresher', function () {
94 95
95 killallServers([ servers[ 1 ] ]) 96 killallServers([ servers[ 1 ] ])
96 97
97 await setVideoField(2, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') 98 await setVideoField(servers[ 1 ].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e')
98 99
99 // Video will need a refresh 100 // Video will need a refresh
100 await wait(10000) 101 await wait(10000)
@@ -121,15 +122,16 @@ describe('Test AP refresher', function () {
121 await wait(10000) 122 await wait(10000)
122 123
123 // Change actor name so the remote server returns a 404 124 // Change actor name so the remote server returns a 404
124 await setActorField(2, 'http://localhost:9002/accounts/user2', 'preferredUsername', 'toto') 125 const to = 'http://localhost:' + servers[ 1 ].port + '/accounts/user2'
126 await setActorField(servers[ 1 ].internalServerNumber, to, 'preferredUsername', 'toto')
125 127
126 await getAccount(servers[ 0 ].url, 'user1@localhost:9002') 128 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port)
127 await getAccount(servers[ 0 ].url, 'user2@localhost:9002') 129 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port)
128 130
129 await waitJobs(servers) 131 await waitJobs(servers)
130 132
131 await getAccount(servers[ 0 ].url, 'user1@localhost:9002', 200) 133 await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port, 200)
132 await getAccount(servers[ 0 ].url, 'user2@localhost:9002', 404) 134 await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port, 404)
133 }) 135 })
134 }) 136 })
135 137
@@ -141,7 +143,7 @@ describe('Test AP refresher', function () {
141 await wait(10000) 143 await wait(10000)
142 144
143 // Change UUID so the remote server returns a 404 145 // Change UUID so the remote server returns a 404
144 await setPlaylistField(2, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') 146 await setPlaylistField(servers[ 1 ].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e')
145 147
146 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1) 148 await getVideoPlaylist(servers[ 0 ].url, playlistUUID1)
147 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2) 149 await getVideoPlaylist(servers[ 0 ].url, playlistUUID2)
@@ -153,7 +155,11 @@ describe('Test AP refresher', function () {
153 }) 155 })
154 }) 156 })
155 157
156 after(function () { 158 after(async function () {
157 killallServers(servers) 159 this.timeout(10000)
160
161 await cleanupTests(servers)
162
163 await closeAllSequelize(servers)
158 }) 164 })
159}) 165})
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts
index 11e6859bf..dc960c5c3 100644
--- a/server/tests/api/activitypub/security.ts
+++ b/server/tests/api/activitypub/security.ts
@@ -3,9 +3,9 @@
3import 'mocha' 3import 'mocha'
4 4
5import { 5import {
6 cleanupTests,
6 closeAllSequelize, 7 closeAllSequelize,
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 flushTests,
9 killallServers, 9 killallServers,
10 ServerInfo, 10 ServerInfo,
11 setActorField 11 setActorField
@@ -18,18 +18,26 @@ import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-u
18 18
19const expect = chai.expect 19const expect = chai.expect
20 20
21function setKeysOfServer2 (serverNumber: number, publicKey: string, privateKey: string) { 21function setKeysOfServer (onServer: ServerInfo, ofServer: ServerInfo, publicKey: string, privateKey: string) {
22 return Promise.all([ 22 return Promise.all([
23 setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'publicKey', publicKey), 23 setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'publicKey', publicKey),
24 setActorField(serverNumber, 'http://localhost:9002/accounts/peertube', 'privateKey', privateKey) 24 setActorField(onServer.internalServerNumber, 'http://localhost:' + ofServer.port + '/accounts/peertube', 'privateKey', privateKey)
25 ]) 25 ])
26} 26}
27 27
28function setKeysOfServer3 (serverNumber: number, publicKey: string, privateKey: string) { 28function getAnnounceWithoutContext (server2: ServerInfo) {
29 return Promise.all([ 29 const json = require('./json/peertube/announce-without-context.json')
30 setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'publicKey', publicKey), 30 const result: typeof json = {}
31 setActorField(serverNumber, 'http://localhost:9003/accounts/peertube', 'privateKey', privateKey) 31
32 ]) 32 for (const key of Object.keys(json)) {
33 if (Array.isArray(json[key])) {
34 result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`))
35 } else {
36 result[ key ] = json[ key ].replace(':9002', `:${server2.port}`)
37 }
38 }
39
40 return result
33} 41}
34 42
35describe('Test ActivityPub security', function () { 43describe('Test ActivityPub security', function () {
@@ -38,13 +46,13 @@ describe('Test ActivityPub security', function () {
38 46
39 const keys = require('./json/peertube/keys.json') 47 const keys = require('./json/peertube/keys.json')
40 const invalidKeys = require('./json/peertube/invalid-keys.json') 48 const invalidKeys = require('./json/peertube/invalid-keys.json')
41 const baseHttpSignature = { 49 const baseHttpSignature = () => ({
42 algorithm: HTTP_SIGNATURE.ALGORITHM, 50 algorithm: HTTP_SIGNATURE.ALGORITHM,
43 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, 51 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
44 keyId: 'acct:peertube@localhost:9002', 52 keyId: 'acct:peertube@localhost:' + servers[1].port,
45 key: keys.privateKey, 53 key: keys.privateKey,
46 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN 54 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
47 } 55 })
48 56
49 // --------------------------------------------------------------- 57 // ---------------------------------------------------------------
50 58
@@ -55,56 +63,56 @@ describe('Test ActivityPub security', function () {
55 63
56 url = servers[0].url + '/inbox' 64 url = servers[0].url + '/inbox'
57 65
58 await setKeysOfServer2(1, keys.publicKey, keys.privateKey) 66 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
59 67
60 const to = { url: 'http://localhost:9001/accounts/peertube' } 68 const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
61 const by = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } 69 const by = { url: 'http://localhost:' + servers[1].port + '/accounts/peertube', privateKey: keys.privateKey }
62 await makeFollowRequest(to, by) 70 await makeFollowRequest(to, by)
63 }) 71 })
64 72
65 describe('When checking HTTP signature', function () { 73 describe('When checking HTTP signature', function () {
66 74
67 it('Should fail with an invalid digest', async function () { 75 it('Should fail with an invalid digest', async function () {
68 const body = activityPubContextify(require('./json/peertube/announce-without-context.json')) 76 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
69 const headers = { 77 const headers = {
70 Digest: buildDigest({ hello: 'coucou' }) 78 Digest: buildDigest({ hello: 'coucou' })
71 } 79 }
72 80
73 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers) 81 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
74 82
75 expect(response.statusCode).to.equal(403) 83 expect(response.statusCode).to.equal(403)
76 }) 84 })
77 85
78 it('Should fail with an invalid date', async function () { 86 it('Should fail with an invalid date', async function () {
79 const body = activityPubContextify(require('./json/peertube/announce-without-context.json')) 87 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
80 const headers = buildGlobalHeaders(body) 88 const headers = buildGlobalHeaders(body)
81 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT' 89 headers['date'] = 'Wed, 21 Oct 2015 07:28:00 GMT'
82 90
83 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers) 91 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
84 92
85 expect(response.statusCode).to.equal(403) 93 expect(response.statusCode).to.equal(403)
86 }) 94 })
87 95
88 it('Should fail with bad keys', async function () { 96 it('Should fail with bad keys', async function () {
89 await setKeysOfServer2(1, invalidKeys.publicKey, invalidKeys.privateKey) 97 await setKeysOfServer(servers[0], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
90 await setKeysOfServer2(2, invalidKeys.publicKey, invalidKeys.privateKey) 98 await setKeysOfServer(servers[1], servers[1], invalidKeys.publicKey, invalidKeys.privateKey)
91 99
92 const body = activityPubContextify(require('./json/peertube/announce-without-context.json')) 100 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
93 const headers = buildGlobalHeaders(body) 101 const headers = buildGlobalHeaders(body)
94 102
95 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers) 103 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
96 104
97 expect(response.statusCode).to.equal(403) 105 expect(response.statusCode).to.equal(403)
98 }) 106 })
99 107
100 it('Should succeed with a valid HTTP signature', async function () { 108 it('Should succeed with a valid HTTP signature', async function () {
101 await setKeysOfServer2(1, keys.publicKey, keys.privateKey) 109 await setKeysOfServer(servers[0], servers[1], keys.publicKey, keys.privateKey)
102 await setKeysOfServer2(2, keys.publicKey, keys.privateKey) 110 await setKeysOfServer(servers[1], servers[1], keys.publicKey, keys.privateKey)
103 111
104 const body = activityPubContextify(require('./json/peertube/announce-without-context.json')) 112 const body = activityPubContextify(getAnnounceWithoutContext(servers[1]))
105 const headers = buildGlobalHeaders(body) 113 const headers = buildGlobalHeaders(body)
106 114
107 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature, headers) 115 const { response } = await makePOSTAPRequest(url, body, baseHttpSignature(), headers)
108 116
109 expect(response.statusCode).to.equal(204) 117 expect(response.statusCode).to.equal(204)
110 }) 118 })
@@ -112,28 +120,28 @@ describe('Test ActivityPub security', function () {
112 120
113 describe('When checking Linked Data Signature', function () { 121 describe('When checking Linked Data Signature', function () {
114 before(async () => { 122 before(async () => {
115 await setKeysOfServer3(3, keys.publicKey, keys.privateKey) 123 await setKeysOfServer(servers[2], servers[2], keys.publicKey, keys.privateKey)
116 124
117 const to = { url: 'http://localhost:9001/accounts/peertube' } 125 const to = { url: 'http://localhost:' + servers[0].port + '/accounts/peertube' }
118 const by = { url: 'http://localhost:9003/accounts/peertube', privateKey: keys.privateKey } 126 const by = { url: 'http://localhost:' + servers[2].port + '/accounts/peertube', privateKey: keys.privateKey }
119 await makeFollowRequest(to, by) 127 await makeFollowRequest(to, by)
120 }) 128 })
121 129
122 it('Should fail with bad keys', async function () { 130 it('Should fail with bad keys', async function () {
123 this.timeout(10000) 131 this.timeout(10000)
124 132
125 await setKeysOfServer3(1, invalidKeys.publicKey, invalidKeys.privateKey) 133 await setKeysOfServer(servers[0], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
126 await setKeysOfServer3(3, invalidKeys.publicKey, invalidKeys.privateKey) 134 await setKeysOfServer(servers[2], servers[2], invalidKeys.publicKey, invalidKeys.privateKey)
127 135
128 const body = require('./json/peertube/announce-without-context.json') 136 const body = getAnnounceWithoutContext(servers[1])
129 body.actor = 'http://localhost:9003/accounts/peertube' 137 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
130 138
131 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:9003/accounts/peertube' } 139 const signer: any = { privateKey: invalidKeys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
132 const signedBody = await buildSignedActivity(signer, body) 140 const signedBody = await buildSignedActivity(signer, body)
133 141
134 const headers = buildGlobalHeaders(signedBody) 142 const headers = buildGlobalHeaders(signedBody)
135 143
136 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers) 144 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
137 145
138 expect(response.statusCode).to.equal(403) 146 expect(response.statusCode).to.equal(403)
139 }) 147 })
@@ -141,20 +149,20 @@ describe('Test ActivityPub security', function () {
141 it('Should fail with an altered body', async function () { 149 it('Should fail with an altered body', async function () {
142 this.timeout(10000) 150 this.timeout(10000)
143 151
144 await setKeysOfServer3(1, keys.publicKey, keys.privateKey) 152 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
145 await setKeysOfServer3(3, keys.publicKey, keys.privateKey) 153 await setKeysOfServer(servers[0], servers[2], keys.publicKey, keys.privateKey)
146 154
147 const body = require('./json/peertube/announce-without-context.json') 155 const body = getAnnounceWithoutContext(servers[1])
148 body.actor = 'http://localhost:9003/accounts/peertube' 156 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
149 157
150 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' } 158 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
151 const signedBody = await buildSignedActivity(signer, body) 159 const signedBody = await buildSignedActivity(signer, body)
152 160
153 signedBody.actor = 'http://localhost:9003/account/peertube' 161 signedBody.actor = 'http://localhost:' + servers[2].port + '/account/peertube'
154 162
155 const headers = buildGlobalHeaders(signedBody) 163 const headers = buildGlobalHeaders(signedBody)
156 164
157 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers) 165 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
158 166
159 expect(response.statusCode).to.equal(403) 167 expect(response.statusCode).to.equal(403)
160 }) 168 })
@@ -162,22 +170,24 @@ describe('Test ActivityPub security', function () {
162 it('Should succeed with a valid signature', async function () { 170 it('Should succeed with a valid signature', async function () {
163 this.timeout(10000) 171 this.timeout(10000)
164 172
165 const body = require('./json/peertube/announce-without-context.json') 173 const body = getAnnounceWithoutContext(servers[1])
166 body.actor = 'http://localhost:9003/accounts/peertube' 174 body.actor = 'http://localhost:' + servers[2].port + '/accounts/peertube'
167 175
168 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:9003/accounts/peertube' } 176 const signer: any = { privateKey: keys.privateKey, url: 'http://localhost:' + servers[2].port + '/accounts/peertube' }
169 const signedBody = await buildSignedActivity(signer, body) 177 const signedBody = await buildSignedActivity(signer, body)
170 178
171 const headers = buildGlobalHeaders(signedBody) 179 const headers = buildGlobalHeaders(signedBody)
172 180
173 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature, headers) 181 const { response } = await makePOSTAPRequest(url, signedBody, baseHttpSignature(), headers)
174 182
175 expect(response.statusCode).to.equal(204) 183 expect(response.statusCode).to.equal(204)
176 }) 184 })
177 }) 185 })
178 186
179 after(async function () { 187 after(async function () {
180 killallServers(servers) 188 this.timeout(10000)
189
190 await cleanupTests(servers)
181 191
182 await closeAllSequelize(servers) 192 await closeAllSequelize(servers)
183 }) 193 })
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index 2a2ec606a..a0d9392dc 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -59,13 +59,15 @@ describe('Test config API validators', function () {
59 transcoding: { 59 transcoding: {
60 enabled: true, 60 enabled: true,
61 allowAdditionalExtensions: true, 61 allowAdditionalExtensions: true,
62 allowAudioFiles: true,
62 threads: 1, 63 threads: 1,
63 resolutions: { 64 resolutions: {
64 '240p': false, 65 '240p': false,
65 '360p': true, 66 '360p': true,
66 '480p': true, 67 '480p': true,
67 '720p': false, 68 '720p': false,
68 '1080p': false 69 '1080p': false,
70 '2160p': false
69 }, 71 },
70 hls: { 72 hls: {
71 enabled: false 73 enabled: false
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts
index 5935104a5..2316033a1 100644
--- a/server/tests/api/check-params/users.ts
+++ b/server/tests/api/check-params/users.ts
@@ -6,6 +6,7 @@ import { join } from 'path'
6import { UserRole, VideoImport, VideoImportState } from '../../../../shared' 6import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
7 7
8import { 8import {
9 addVideoChannel,
9 blockUser, 10 blockUser,
10 cleanupTests, 11 cleanupTests,
11 createUser, 12 createUser,
@@ -378,8 +379,7 @@ describe('Test users API validators', function () {
378 it('Should succeed without password change with the correct params', async function () { 379 it('Should succeed without password change with the correct params', async function () {
379 const fields = { 380 const fields = {
380 nsfwPolicy: 'blur', 381 nsfwPolicy: 'blur',
381 autoPlayVideo: false, 382 autoPlayVideo: false
382 email: 'super_email@example.com'
383 } 383 }
384 384
385 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) 385 await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 })
@@ -638,10 +638,11 @@ describe('Test users API validators', function () {
638 }) 638 })
639 }) 639 })
640 640
641 describe('When register a new user', function () { 641 describe('When registering a new user', function () {
642 const registrationPath = path + '/register' 642 const registrationPath = path + '/register'
643 const baseCorrectParams = { 643 const baseCorrectParams = {
644 username: 'user3', 644 username: 'user3',
645 displayName: 'super user',
645 email: 'test3@example.com', 646 email: 'test3@example.com',
646 password: 'my super password' 647 password: 'my super password'
647 } 648 }
@@ -724,12 +725,48 @@ describe('Test users API validators', function () {
724 }) 725 })
725 }) 726 })
726 727
728 it('Should fail with a bad display name', async function () {
729 const fields = immutableAssign(baseCorrectParams, { displayName: 'a'.repeat(150) })
730
731 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
732 })
733
734 it('Should fail with a bad channel name', async function () {
735 const fields = immutableAssign(baseCorrectParams, { channel: { name: '[]azf', displayName: 'toto' } })
736
737 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
738 })
739
740 it('Should fail with a bad channel display name', async function () {
741 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'toto', displayName: '' } })
742
743 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
744 })
745
746 it('Should fail with a channel name that is the same than user username', async function () {
747 const source = { username: 'super_user', channel: { name: 'super_user', displayName: 'display name' } }
748 const fields = immutableAssign(baseCorrectParams, source)
749
750 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
751 })
752
753 it('Should fail with an existing channel', async function () {
754 const videoChannelAttributesArg = { name: 'existing_channel', displayName: 'hello', description: 'super description' }
755 await addVideoChannel(server.url, server.accessToken, videoChannelAttributesArg)
756
757 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'existing_channel', displayName: 'toto' } })
758
759 await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields, statusCodeExpected: 409 })
760 })
761
727 it('Should succeed with the correct params', async function () { 762 it('Should succeed with the correct params', async function () {
763 const fields = immutableAssign(baseCorrectParams, { channel: { name: 'super_channel', displayName: 'toto' } })
764
728 await makePostBodyRequest({ 765 await makePostBodyRequest({
729 url: server.url, 766 url: server.url,
730 path: registrationPath, 767 path: registrationPath,
731 token: server.accessToken, 768 token: server.accessToken,
732 fields: baseCorrectParams, 769 fields: fields,
733 statusCodeExpected: 204 770 statusCodeExpected: 204
734 }) 771 })
735 }) 772 })
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts
index 65bc20613..de88298d1 100644
--- a/server/tests/api/check-params/video-channels.ts
+++ b/server/tests/api/check-params/video-channels.ts
@@ -24,6 +24,7 @@ import {
24 checkBadStartPagination 24 checkBadStartPagination
25} from '../../../../shared/extra-utils/requests/check-api-params' 25} from '../../../../shared/extra-utils/requests/check-api-params'
26import { join } from 'path' 26import { join } from 'path'
27import { VideoChannelUpdate } from '../../../../shared/models/videos'
27 28
28const expect = chai.expect 29const expect = chai.expect
29 30
@@ -67,8 +68,30 @@ describe('Test video channels API validator', function () {
67 }) 68 })
68 69
69 describe('When listing account video channels', function () { 70 describe('When listing account video channels', function () {
71 const accountChannelPath = '/api/v1/accounts/fake/video-channels'
72
73 it('Should fail with a bad start pagination', async function () {
74 await checkBadStartPagination(server.url, accountChannelPath, server.accessToken)
75 })
76
77 it('Should fail with a bad count pagination', async function () {
78 await checkBadCountPagination(server.url, accountChannelPath, server.accessToken)
79 })
80
81 it('Should fail with an incorrect sort', async function () {
82 await checkBadSortPagination(server.url, accountChannelPath, server.accessToken)
83 })
84
70 it('Should fail with a unknown account', async function () { 85 it('Should fail with a unknown account', async function () {
71 await getAccountVideoChannelsList(server.url, 'unknown', 404) 86 await getAccountVideoChannelsList({ url: server.url, accountName: 'unknown', specialStatus: 404 })
87 })
88
89 it('Should succeed with the correct parameters', async function () {
90 await makeGetRequest({
91 url: server.url,
92 path: accountChannelPath,
93 statusCodeExpected: 200
94 })
72 }) 95 })
73 }) 96 })
74 97
@@ -147,9 +170,11 @@ describe('Test video channels API validator', function () {
147 }) 170 })
148 171
149 describe('When updating a video channel', function () { 172 describe('When updating a video channel', function () {
150 const baseCorrectParams = { 173 const baseCorrectParams: VideoChannelUpdate = {
151 displayName: 'hello', 174 displayName: 'hello',
152 description: 'super description' 175 description: 'super description',
176 support: 'toto',
177 bulkVideosSupportUpdate: false
153 } 178 }
154 let path: string 179 let path: string
155 180
@@ -192,6 +217,11 @@ describe('Test video channels API validator', function () {
192 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields }) 217 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
193 }) 218 })
194 219
220 it('Should fail with a bad bulkVideosSupportUpdate field', async function () {
221 const fields = immutableAssign(baseCorrectParams, { bulkVideosSupportUpdate: 'super' })
222 await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
223 })
224
195 it('Should succeed with the correct parameters', async function () { 225 it('Should succeed with the correct parameters', async function () {
196 await makePutBodyRequest({ 226 await makePutBodyRequest({
197 url: server.url, 227 url: server.url,
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts
index b7b94c035..8c5e44bdd 100644
--- a/server/tests/api/check-params/video-playlists.ts
+++ b/server/tests/api/check-params/video-playlists.ts
@@ -205,7 +205,6 @@ describe('Test video playlists API validator', function () {
205 const params = getBase({ displayName: undefined }) 205 const params = getBase({ displayName: undefined })
206 206
207 await createVideoPlaylist(params) 207 await createVideoPlaylist(params)
208 await updateVideoPlaylist(getUpdate(params, playlistUUID))
209 }) 208 })
210 209
211 it('Should fail with an incorrect display name', async function () { 210 it('Should fail with an incorrect display name', async function () {
@@ -269,17 +268,6 @@ describe('Test video playlists API validator', function () {
269 )) 268 ))
270 }) 269 })
271 270
272 it('Should fail to update to private a public/unlisted playlist', async function () {
273 const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC }, { expectedStatus: 200 })
274
275 const res = await createVideoPlaylist(params)
276 const playlist = res.body.videoPlaylist
277
278 const paramsUpdate = getBase({ privacy: VideoPlaylistPrivacy.PRIVATE }, { expectedStatus: 400 })
279
280 await updateVideoPlaylist(getUpdate(paramsUpdate, playlist.id))
281 })
282
283 it('Should fail to update the watch later playlist', async function () { 271 it('Should fail to update the watch later playlist', async function () {
284 await updateVideoPlaylist(getUpdate( 272 await updateVideoPlaylist(getUpdate(
285 getBase({}, { expectedStatus: 400 }), 273 getBase({}, { expectedStatus: 400 }),
diff --git a/server/tests/api/index-1.ts b/server/tests/api/index-1.ts
deleted file mode 100644
index 75cdd9025..000000000
--- a/server/tests/api/index-1.ts
+++ /dev/null
@@ -1,3 +0,0 @@
1import './check-params'
2import './notifications'
3import './search'
diff --git a/server/tests/api/index-2.ts b/server/tests/api/index-2.ts
deleted file mode 100644
index ed93faa91..000000000
--- a/server/tests/api/index-2.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1import './server'
2import './users'
diff --git a/server/tests/api/index-3.ts b/server/tests/api/index-3.ts
deleted file mode 100644
index 39823b82c..000000000
--- a/server/tests/api/index-3.ts
+++ /dev/null
@@ -1 +0,0 @@
1import './videos'
diff --git a/server/tests/api/index-4.ts b/server/tests/api/index-4.ts
deleted file mode 100644
index 7d8be2b3d..000000000
--- a/server/tests/api/index-4.ts
+++ /dev/null
@@ -1,2 +0,0 @@
1import './redundancy'
2import './activitypub'
diff --git a/server/tests/api/index.ts b/server/tests/api/index.ts
index bc140f860..bac77ab2e 100644
--- a/server/tests/api/index.ts
+++ b/server/tests/api/index.ts
@@ -1,5 +1,9 @@
1// Order of the tests we want to execute 1// Order of the tests we want to execute
2import './index-1' 2import './activitypub'
3import './index-2' 3import './check-params'
4import './index-3' 4import './notifications'
5import './index-4' 5import './redundancy'
6import './search'
7import './server'
8import './users'
9import './videos'
diff --git a/server/tests/api/notifications/index.ts b/server/tests/api/notifications/index.ts
index 95ac8fc51..b573f850e 100644
--- a/server/tests/api/notifications/index.ts
+++ b/server/tests/api/notifications/index.ts
@@ -1 +1 @@
export * from './user-notifications' import './user-notifications'
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts
index f479e1785..662b64e05 100644
--- a/server/tests/api/notifications/user-notifications.ts
+++ b/server/tests/api/notifications/user-notifications.ts
@@ -114,11 +114,12 @@ describe('Test users notifications', function () {
114 before(async function () { 114 before(async function () {
115 this.timeout(120000) 115 this.timeout(120000)
116 116
117 await MockSmtpServer.Instance.collectEmails(emails) 117 const port = await MockSmtpServer.Instance.collectEmails(emails)
118 118
119 const overrideConfig = { 119 const overrideConfig = {
120 smtp: { 120 smtp: {
121 hostname: 'localhost' 121 hostname: 'localhost',
122 port
122 } 123 }
123 } 124 }
124 servers = await flushAndRunMultipleServers(3, overrideConfig) 125 servers = await flushAndRunMultipleServers(3, overrideConfig)
@@ -194,7 +195,7 @@ describe('Test users notifications', function () {
194 it('Should send a new video notification if the user follows the local video publisher', async function () { 195 it('Should send a new video notification if the user follows the local video publisher', async function () {
195 this.timeout(15000) 196 this.timeout(15000)
196 197
197 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001') 198 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[0].port)
198 await waitJobs(servers) 199 await waitJobs(servers)
199 200
200 const { name, uuid } = await uploadVideoByLocalAccount(servers) 201 const { name, uuid } = await uploadVideoByLocalAccount(servers)
@@ -204,7 +205,7 @@ describe('Test users notifications', function () {
204 it('Should send a new video notification from a remote account', async function () { 205 it('Should send a new video notification from a remote account', async function () {
205 this.timeout(50000) // Server 2 has transcoding enabled 206 this.timeout(50000) // Server 2 has transcoding enabled
206 207
207 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002') 208 await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:' + servers[1].port)
208 await waitJobs(servers) 209 await waitJobs(servers)
209 210
210 const { name, uuid } = await uploadVideoByRemoteAccount(servers) 211 const { name, uuid } = await uploadVideoByRemoteAccount(servers)
@@ -578,7 +579,9 @@ describe('Test users notifications', function () {
578 const uuid = resVideo.body.video.uuid 579 const uuid = resVideo.body.video.uuid
579 580
580 await waitJobs(servers) 581 await waitJobs(servers)
581 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'hello @user_1@localhost:9001 1') 582
583 const text1 = `hello @user_1@localhost:${servers[ 0 ].port} 1`
584 const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1)
582 const server2ThreadId = resThread.body.comment.id 585 const server2ThreadId = resThread.body.comment.id
583 586
584 await waitJobs(servers) 587 await waitJobs(servers)
@@ -588,8 +591,8 @@ describe('Test users notifications', function () {
588 const server1ThreadId = resThread2.body.data[0].id 591 const server1ThreadId = resThread2.body.data[0].id
589 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') 592 await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence')
590 593
591 const text = '@user_1@localhost:9001 hello 2 @root@localhost:9001' 594 const text2 = `@user_1@localhost:${servers[ 0 ].port} hello 2 @root@localhost:${servers[ 0 ].port}`
592 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text) 595 await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2)
593 596
594 await waitJobs(servers) 597 await waitJobs(servers)
595 598
@@ -889,10 +892,10 @@ describe('Test users notifications', function () {
889 892
890 await waitJobs(servers) 893 await waitJobs(servers)
891 894
892 await checkNewInstanceFollower(baseParams, 'localhost:9003', 'presence') 895 await checkNewInstanceFollower(baseParams, 'localhost:' + servers[2].port, 'presence')
893 896
894 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } 897 const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
895 await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:9003', 'absence') 898 await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence')
896 }) 899 })
897 }) 900 })
898 901
@@ -933,29 +936,29 @@ describe('Test users notifications', function () {
933 it('Should notify when a local channel is following one of our channel', async function () { 936 it('Should notify when a local channel is following one of our channel', async function () {
934 this.timeout(10000) 937 this.timeout(10000)
935 938
936 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') 939 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
937 await waitJobs(servers) 940 await waitJobs(servers)
938 941
939 await checkNewActorFollow(baseParams, 'channel', 'root', 'super root name', myChannelName, 'presence') 942 await checkNewActorFollow(baseParams, 'channel', 'root', 'super root name', myChannelName, 'presence')
940 943
941 await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') 944 await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
942 }) 945 })
943 946
944 it('Should notify when a remote channel is following one of our channel', async function () { 947 it('Should notify when a remote channel is following one of our channel', async function () {
945 this.timeout(10000) 948 this.timeout(10000)
946 949
947 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') 950 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
948 await waitJobs(servers) 951 await waitJobs(servers)
949 952
950 await checkNewActorFollow(baseParams, 'channel', 'root', 'super root 2 name', myChannelName, 'presence') 953 await checkNewActorFollow(baseParams, 'channel', 'root', 'super root 2 name', myChannelName, 'presence')
951 954
952 await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') 955 await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
953 }) 956 })
954 957
955 it('Should notify when a local account is following one of our channel', async function () { 958 it('Should notify when a local account is following one of our channel', async function () {
956 this.timeout(10000) 959 this.timeout(10000)
957 960
958 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:9001') 961 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port)
959 962
960 await waitJobs(servers) 963 await waitJobs(servers)
961 964
@@ -965,7 +968,7 @@ describe('Test users notifications', function () {
965 it('Should notify when a remote account is following one of our channel', async function () { 968 it('Should notify when a remote account is following one of our channel', async function () {
966 this.timeout(10000) 969 this.timeout(10000)
967 970
968 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:9001') 971 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port)
969 972
970 await waitJobs(servers) 973 await waitJobs(servers)
971 974
@@ -1019,8 +1022,8 @@ describe('Test users notifications', function () {
1019 autoBlacklistTestsCustomConfig.transcoding.enabled = true 1022 autoBlacklistTestsCustomConfig.transcoding.enabled = true
1020 await updateCustomConfig(servers[0].url, servers[0].accessToken, autoBlacklistTestsCustomConfig) 1023 await updateCustomConfig(servers[0].url, servers[0].accessToken, autoBlacklistTestsCustomConfig)
1021 1024
1022 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') 1025 await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
1023 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') 1026 await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
1024 1027
1025 }) 1028 })
1026 1029
@@ -1142,8 +1145,8 @@ describe('Test users notifications', function () {
1142 after(async () => { 1145 after(async () => {
1143 await updateCustomConfig(servers[0].url, servers[0].accessToken, currentCustomConfig) 1146 await updateCustomConfig(servers[0].url, servers[0].accessToken, currentCustomConfig)
1144 1147
1145 await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') 1148 await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:' + servers[0].port)
1146 await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') 1149 await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port)
1147 }) 1150 })
1148 }) 1151 })
1149 1152
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts
index e31329c25..6f2c59076 100644
--- a/server/tests/api/redundancy/redundancy.ts
+++ b/server/tests/api/redundancy/redundancy.ts
@@ -100,7 +100,7 @@ async function check1WebSeed (videoUUID?: string) {
100 if (!videoUUID) videoUUID = video1Server2UUID 100 if (!videoUUID) videoUUID = video1Server2UUID
101 101
102 const webseeds = [ 102 const webseeds = [
103 'http://localhost:9002/static/webseed/' + videoUUID 103 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}`
104 ] 104 ]
105 105
106 for (const server of servers) { 106 for (const server of servers) {
@@ -118,8 +118,8 @@ async function check2Webseeds (videoUUID?: string) {
118 if (!videoUUID) videoUUID = video1Server2UUID 118 if (!videoUUID) videoUUID = video1Server2UUID
119 119
120 const webseeds = [ 120 const webseeds = [
121 'http://localhost:9001/static/redundancy/' + videoUUID, 121 `http://localhost:${servers[ 0 ].port}/static/redundancy/${videoUUID}`,
122 'http://localhost:9002/static/webseed/' + videoUUID 122 `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}`
123 ] 123 ]
124 124
125 for (const server of servers) { 125 for (const server of servers) {
@@ -145,7 +145,12 @@ async function check2Webseeds (videoUUID?: string) {
145 } 145 }
146 } 146 }
147 147
148 for (const directory of [ 'test1/redundancy', 'test2/videos' ]) { 148 const directories = [
149 'test' + servers[0].internalServerNumber + '/redundancy',
150 'test' + servers[1].internalServerNumber + '/videos'
151 ]
152
153 for (const directory of directories) {
149 const files = await readdir(join(root(), directory)) 154 const files = await readdir(join(root(), directory))
150 expect(files).to.have.length.at.least(4) 155 expect(files).to.have.length.at.least(4)
151 156
@@ -194,7 +199,12 @@ async function check1PlaylistRedundancies (videoUUID?: string) {
194 await checkSegmentHash(baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist) 199 await checkSegmentHash(baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist)
195 } 200 }
196 201
197 for (const directory of [ 'test1/redundancy/hls', 'test2/streaming-playlists/hls' ]) { 202 const directories = [
203 'test' + servers[0].internalServerNumber + '/redundancy/hls',
204 'test' + servers[1].internalServerNumber + '/streaming-playlists/hls'
205 ]
206
207 for (const directory of directories) {
198 const files = await readdir(join(root(), directory, videoUUID)) 208 const files = await readdir(join(root(), directory, videoUUID))
199 expect(files).to.have.length.at.least(4) 209 expect(files).to.have.length.at.least(4)
200 210
@@ -239,8 +249,8 @@ async function enableRedundancyOnServer1 () {
239 249
240 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt') 250 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
241 const follows: ActorFollow[] = res.body.data 251 const follows: ActorFollow[] = res.body.data
242 const server2 = follows.find(f => f.following.host === 'localhost:9002') 252 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
243 const server3 = follows.find(f => f.following.host === 'localhost:9003') 253 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
244 254
245 expect(server3).to.not.be.undefined 255 expect(server3).to.not.be.undefined
246 expect(server3.following.hostRedundancyAllowed).to.be.false 256 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -254,8 +264,8 @@ async function disableRedundancyOnServer1 () {
254 264
255 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt') 265 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
256 const follows: ActorFollow[] = res.body.data 266 const follows: ActorFollow[] = res.body.data
257 const server2 = follows.find(f => f.following.host === 'localhost:9002') 267 const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`)
258 const server3 = follows.find(f => f.following.host === 'localhost:9003') 268 const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`)
259 269
260 expect(server3).to.not.be.undefined 270 expect(server3).to.not.be.undefined
261 expect(server3.following.hostRedundancyAllowed).to.be.false 271 expect(server3.following.hostRedundancyAllowed).to.be.false
@@ -475,12 +485,12 @@ describe('Test videos redundancy', function () {
475 await wait(10000) 485 await wait(10000)
476 486
477 try { 487 try {
478 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001') 488 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
479 } catch { 489 } catch {
480 // Maybe a server deleted a redundancy in the scheduler 490 // Maybe a server deleted a redundancy in the scheduler
481 await wait(2000) 491 await wait(2000)
482 492
483 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001') 493 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
484 } 494 }
485 }) 495 })
486 496
@@ -491,7 +501,7 @@ describe('Test videos redundancy', function () {
491 501
492 await wait(15000) 502 await wait(15000)
493 503
494 await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A9001') 504 await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A' + servers[0].port)
495 }) 505 })
496 506
497 after(async function () { 507 after(async function () {
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts
index 4d1ceb767..8a008b8c6 100644
--- a/server/tests/api/search/search-activitypub-video-channels.ts
+++ b/server/tests/api/search/search-activitypub-video-channels.ts
@@ -3,16 +3,17 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 addVideoChannel, cleanupTests, 6 addVideoChannel,
7 cleanupTests,
7 createUser, 8 createUser,
8 deleteVideoChannel, 9 deleteVideoChannel,
9 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
10 flushTests, 11 getVideoChannelsList,
11 getVideoChannelsList, getVideoChannelVideos, 12 getVideoChannelVideos,
12 killallServers,
13 ServerInfo, 13 ServerInfo,
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 updateMyUser, updateVideo, 15 updateMyUser,
16 updateVideo,
16 updateVideoChannel, 17 updateVideoChannel,
17 uploadVideo, 18 uploadVideo,
18 userLogin, 19 userLogin,
@@ -24,7 +25,7 @@ import { searchVideoChannel } from '../../../../shared/extra-utils/search/video-
24 25
25const expect = chai.expect 26const expect = chai.expect
26 27
27describe('Test a ActivityPub video channels search', function () { 28describe('Test ActivityPub video channels search', function () {
28 let servers: ServerInfo[] 29 let servers: ServerInfo[]
29 let userServer2Token: string 30 let userServer2Token: string
30 let videoServer2UUID: string 31 let videoServer2UUID: string
@@ -67,7 +68,7 @@ describe('Test a ActivityPub video channels search', function () {
67 68
68 it('Should not find a remote video channel', async function () { 69 it('Should not find a remote video channel', async function () {
69 { 70 {
70 const search = 'http://localhost:9002/video-channels/channel1_server3' 71 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3'
71 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) 72 const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken)
72 73
73 expect(res.body.total).to.equal(0) 74 expect(res.body.total).to.equal(0)
@@ -77,7 +78,7 @@ describe('Test a ActivityPub video channels search', function () {
77 78
78 { 79 {
79 // Without token 80 // Without token
80 const search = 'http://localhost:9002/video-channels/channel1_server2' 81 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
81 const res = await searchVideoChannel(servers[0].url, search) 82 const res = await searchVideoChannel(servers[0].url, search)
82 83
83 expect(res.body.total).to.equal(0) 84 expect(res.body.total).to.equal(0)
@@ -88,8 +89,8 @@ describe('Test a ActivityPub video channels search', function () {
88 89
89 it('Should search a local video channel', async function () { 90 it('Should search a local video channel', async function () {
90 const searches = [ 91 const searches = [
91 'http://localhost:9001/video-channels/channel1_server1', 92 'http://localhost:' + servers[ 0 ].port + '/video-channels/channel1_server1',
92 'channel1_server1@localhost:9001' 93 'channel1_server1@localhost:' + servers[ 0 ].port
93 ] 94 ]
94 95
95 for (const search of searches) { 96 for (const search of searches) {
@@ -105,8 +106,8 @@ describe('Test a ActivityPub video channels search', function () {
105 106
106 it('Should search a remote video channel with URL or handle', async function () { 107 it('Should search a remote video channel with URL or handle', async function () {
107 const searches = [ 108 const searches = [
108 'http://localhost:9002/video-channels/channel1_server2', 109 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2',
109 'channel1_server2@localhost:9002' 110 'channel1_server2@localhost:' + servers[ 1 ].port
110 ] 111 ]
111 112
112 for (const search of searches) { 113 for (const search of searches) {
@@ -134,13 +135,13 @@ describe('Test a ActivityPub video channels search', function () {
134 135
135 await waitJobs(servers) 136 await waitJobs(servers)
136 137
137 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:9002', 0, 5) 138 const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5)
138 expect(res.body.total).to.equal(0) 139 expect(res.body.total).to.equal(0)
139 expect(res.body.data).to.have.lengthOf(0) 140 expect(res.body.data).to.have.lengthOf(0)
140 }) 141 })
141 142
142 it('Should list video channel videos of server 2 with token', async function () { 143 it('Should list video channel videos of server 2 with token', async function () {
143 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:9002', 0, 5) 144 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5)
144 145
145 expect(res.body.total).to.equal(1) 146 expect(res.body.total).to.equal(1)
146 expect(res.body.data[0].name).to.equal('video 1 server 2') 147 expect(res.body.data[0].name).to.equal('video 1 server 2')
@@ -156,7 +157,7 @@ describe('Test a ActivityPub video channels search', function () {
156 // Expire video channel 157 // Expire video channel
157 await wait(10000) 158 await wait(10000)
158 159
159 const search = 'http://localhost:9002/video-channels/channel1_server2' 160 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
160 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 161 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
161 expect(res.body.total).to.equal(1) 162 expect(res.body.total).to.equal(1)
162 expect(res.body.data).to.have.lengthOf(1) 163 expect(res.body.data).to.have.lengthOf(1)
@@ -179,12 +180,13 @@ describe('Test a ActivityPub video channels search', function () {
179 // Expire video channel 180 // Expire video channel
180 await wait(10000) 181 await wait(10000)
181 182
182 const search = 'http://localhost:9002/video-channels/channel1_server2' 183 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
183 await searchVideoChannel(servers[0].url, search, servers[0].accessToken) 184 await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
184 185
185 await waitJobs(servers) 186 await waitJobs(servers)
186 187
187 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:9002', 0, 5, '-createdAt') 188 const videoChannelName = 'channel1_server2@localhost:' + servers[ 1 ].port
189 const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt')
188 190
189 expect(res.body.total).to.equal(2) 191 expect(res.body.total).to.equal(2)
190 expect(res.body.data[0].name).to.equal('video 2 server 2') 192 expect(res.body.data[0].name).to.equal('video 2 server 2')
@@ -200,7 +202,8 @@ describe('Test a ActivityPub video channels search', function () {
200 // Expire video 202 // Expire video
201 await wait(10000) 203 await wait(10000)
202 204
203 const res = await searchVideoChannel(servers[0].url, 'http://localhost:9002/video-channels/channel1_server2', servers[0].accessToken) 205 const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2'
206 const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken)
204 expect(res.body.total).to.equal(0) 207 expect(res.body.total).to.equal(0)
205 expect(res.body.data).to.have.lengthOf(0) 208 expect(res.body.data).to.have.lengthOf(0)
206 }) 209 })
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts
index e039961cb..dbfefadda 100644
--- a/server/tests/api/search/search-activitypub-videos.ts
+++ b/server/tests/api/search/search-activitypub-videos.ts
@@ -4,25 +4,24 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 addVideoChannel, 6 addVideoChannel,
7 cleanupTests,
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 flushTests,
9 getVideosList, 9 getVideosList,
10 killallServers,
11 removeVideo, 10 removeVideo,
11 searchVideo,
12 searchVideoWithToken, 12 searchVideoWithToken,
13 ServerInfo, 13 ServerInfo,
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 updateVideo, 15 updateVideo,
16 uploadVideo, 16 uploadVideo,
17 wait, 17 wait
18 searchVideo, cleanupTests
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'
21import { Video, VideoPrivacy } from '../../../../shared/models/videos' 20import { Video, VideoPrivacy } from '../../../../shared/models/videos'
22 21
23const expect = chai.expect 22const expect = chai.expect
24 23
25describe('Test a ActivityPub videos search', function () { 24describe('Test ActivityPub videos search', function () {
26 let servers: ServerInfo[] 25 let servers: ServerInfo[]
27 let videoServer1UUID: string 26 let videoServer1UUID: string
28 let videoServer2UUID: string 27 let videoServer2UUID: string
@@ -49,7 +48,8 @@ describe('Test a ActivityPub videos search', function () {
49 48
50 it('Should not find a remote video', async function () { 49 it('Should not find a remote video', async function () {
51 { 50 {
52 const res = await searchVideoWithToken(servers[ 0 ].url, 'http://localhost:9002/videos/watch/43', servers[ 0 ].accessToken) 51 const search = 'http://localhost:' + servers[1].port + '/videos/watch/43'
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')
@@ -58,7 +58,8 @@ describe('Test a ActivityPub videos search', function () {
58 58
59 { 59 {
60 // Without token 60 // Without token
61 const res = await searchVideo(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID) 61 const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
62 const res = await searchVideo(servers[0].url, search)
62 63
63 expect(res.body.total).to.equal(0) 64 expect(res.body.total).to.equal(0)
64 expect(res.body.data).to.be.an('array') 65 expect(res.body.data).to.be.an('array')
@@ -67,7 +68,8 @@ describe('Test a ActivityPub videos search', function () {
67 }) 68 })
68 69
69 it('Should search a local video', async function () { 70 it('Should search a local video', async function () {
70 const res = await searchVideo(servers[0].url, 'http://localhost:9001/videos/watch/' + videoServer1UUID) 71 const search = 'http://localhost:' + servers[0].port + '/videos/watch/' + videoServer1UUID
72 const res = await searchVideo(servers[0].url, search)
71 73
72 expect(res.body.total).to.equal(1) 74 expect(res.body.total).to.equal(1)
73 expect(res.body.data).to.be.an('array') 75 expect(res.body.data).to.be.an('array')
@@ -76,7 +78,8 @@ describe('Test a ActivityPub videos search', function () {
76 }) 78 })
77 79
78 it('Should search a remote video', async function () { 80 it('Should search a remote video', async function () {
79 const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken) 81 const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
82 const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
80 83
81 expect(res.body.total).to.equal(1) 84 expect(res.body.total).to.equal(1)
82 expect(res.body.data).to.be.an('array') 85 expect(res.body.data).to.be.an('array')
@@ -114,12 +117,13 @@ describe('Test a ActivityPub videos search', function () {
114 await wait(10000) 117 await wait(10000)
115 118
116 // Will run refresh async 119 // Will run refresh async
117 await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken) 120 const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
121 await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
118 122
119 // Wait refresh 123 // Wait refresh
120 await wait(5000) 124 await wait(5000)
121 125
122 const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken) 126 const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
123 expect(res.body.total).to.equal(1) 127 expect(res.body.total).to.equal(1)
124 expect(res.body.data).to.have.lengthOf(1) 128 expect(res.body.data).to.have.lengthOf(1)
125 129
@@ -139,12 +143,13 @@ describe('Test a ActivityPub videos search', function () {
139 await wait(10000) 143 await wait(10000)
140 144
141 // Will run refresh async 145 // Will run refresh async
142 await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken) 146 const search = 'http://localhost:' + servers[1].port + '/videos/watch/' + videoServer2UUID
147 await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
143 148
144 // Wait refresh 149 // Wait refresh
145 await wait(5000) 150 await wait(5000)
146 151
147 const res = await searchVideoWithToken(servers[0].url, 'http://localhost:9002/videos/watch/' + videoServer2UUID, servers[0].accessToken) 152 const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken)
148 expect(res.body.total).to.equal(0) 153 expect(res.body.total).to.equal(0)
149 expect(res.body.data).to.have.lengthOf(0) 154 expect(res.body.data).to.have.lengthOf(0)
150 }) 155 })
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts
index 1a086b33a..92cc0dc71 100644
--- a/server/tests/api/search/search-videos.ts
+++ b/server/tests/api/search/search-videos.ts
@@ -4,21 +4,19 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 advancedVideosSearch, 6 advancedVideosSearch,
7 flushTests, 7 cleanupTests,
8 killallServers,
9 flushAndRunServer, 8 flushAndRunServer,
9 immutableAssign,
10 searchVideo, 10 searchVideo,
11 ServerInfo, 11 ServerInfo,
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 uploadVideo, 13 uploadVideo,
14 wait, 14 wait
15 immutableAssign,
16 cleanupTests
17} from '../../../../shared/extra-utils' 15} from '../../../../shared/extra-utils'
18 16
19const expect = chai.expect 17const expect = chai.expect
20 18
21describe('Test a videos search', function () { 19describe('Test videos search', function () {
22 let server: ServerInfo = null 20 let server: ServerInfo = null
23 let startDate: string 21 let startDate: string
24 22
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index c0d11914b..c39516dee 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -11,17 +11,17 @@ import {
11 getAbout, 11 getAbout,
12 getConfig, 12 getConfig,
13 getCustomConfig, 13 getCustomConfig,
14 killallServers, 14 killallServers, parallelTests,
15 registerUser, 15 registerUser,
16 reRunServer, 16 reRunServer, ServerInfo,
17 setAccessTokensToServers, 17 setAccessTokensToServers,
18 updateCustomConfig 18 updateCustomConfig, uploadVideo
19} from '../../../../shared/extra-utils' 19} from '../../../../shared/extra-utils'
20import { ServerConfig } from '../../../../shared/models' 20import { ServerConfig } from '../../../../shared/models'
21 21
22const expect = chai.expect 22const expect = chai.expect
23 23
24function checkInitialConfig (data: CustomConfig) { 24function checkInitialConfig (server: ServerInfo, data: CustomConfig) {
25 expect(data.instance.name).to.equal('PeerTube') 25 expect(data.instance.name).to.equal('PeerTube')
26 expect(data.instance.shortDescription).to.equal( 26 expect(data.instance.shortDescription).to.equal(
27 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser ' + 27 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser ' +
@@ -45,19 +45,21 @@ function checkInitialConfig (data: CustomConfig) {
45 expect(data.signup.limit).to.equal(4) 45 expect(data.signup.limit).to.equal(4)
46 expect(data.signup.requiresEmailVerification).to.be.false 46 expect(data.signup.requiresEmailVerification).to.be.false
47 47
48 expect(data.admin.email).to.equal('admin1@example.com') 48 expect(data.admin.email).to.equal('admin' + server.internalServerNumber + '@example.com')
49 expect(data.contactForm.enabled).to.be.true 49 expect(data.contactForm.enabled).to.be.true
50 50
51 expect(data.user.videoQuota).to.equal(5242880) 51 expect(data.user.videoQuota).to.equal(5242880)
52 expect(data.user.videoQuotaDaily).to.equal(-1) 52 expect(data.user.videoQuotaDaily).to.equal(-1)
53 expect(data.transcoding.enabled).to.be.false 53 expect(data.transcoding.enabled).to.be.false
54 expect(data.transcoding.allowAdditionalExtensions).to.be.false 54 expect(data.transcoding.allowAdditionalExtensions).to.be.false
55 expect(data.transcoding.allowAudioFiles).to.be.false
55 expect(data.transcoding.threads).to.equal(2) 56 expect(data.transcoding.threads).to.equal(2)
56 expect(data.transcoding.resolutions['240p']).to.be.true 57 expect(data.transcoding.resolutions['240p']).to.be.true
57 expect(data.transcoding.resolutions['360p']).to.be.true 58 expect(data.transcoding.resolutions['360p']).to.be.true
58 expect(data.transcoding.resolutions['480p']).to.be.true 59 expect(data.transcoding.resolutions['480p']).to.be.true
59 expect(data.transcoding.resolutions['720p']).to.be.true 60 expect(data.transcoding.resolutions['720p']).to.be.true
60 expect(data.transcoding.resolutions['1080p']).to.be.true 61 expect(data.transcoding.resolutions['1080p']).to.be.true
62 expect(data.transcoding.resolutions['2160p']).to.be.true
61 expect(data.transcoding.hls.enabled).to.be.true 63 expect(data.transcoding.hls.enabled).to.be.true
62 64
63 expect(data.import.videos.http.enabled).to.be.true 65 expect(data.import.videos.http.enabled).to.be.true
@@ -89,7 +91,11 @@ function checkUpdatedConfig (data: CustomConfig) {
89 expect(data.signup.limit).to.equal(5) 91 expect(data.signup.limit).to.equal(5)
90 expect(data.signup.requiresEmailVerification).to.be.false 92 expect(data.signup.requiresEmailVerification).to.be.false
91 93
92 expect(data.admin.email).to.equal('superadmin1@example.com') 94 // We override admin email in parallel tests, so skip this exception
95 if (parallelTests() === false) {
96 expect(data.admin.email).to.equal('superadmin1@example.com')
97 }
98
93 expect(data.contactForm.enabled).to.be.false 99 expect(data.contactForm.enabled).to.be.false
94 100
95 expect(data.user.videoQuota).to.equal(5242881) 101 expect(data.user.videoQuota).to.equal(5242881)
@@ -98,11 +104,13 @@ function checkUpdatedConfig (data: CustomConfig) {
98 expect(data.transcoding.enabled).to.be.true 104 expect(data.transcoding.enabled).to.be.true
99 expect(data.transcoding.threads).to.equal(1) 105 expect(data.transcoding.threads).to.equal(1)
100 expect(data.transcoding.allowAdditionalExtensions).to.be.true 106 expect(data.transcoding.allowAdditionalExtensions).to.be.true
107 expect(data.transcoding.allowAudioFiles).to.be.true
101 expect(data.transcoding.resolutions['240p']).to.be.false 108 expect(data.transcoding.resolutions['240p']).to.be.false
102 expect(data.transcoding.resolutions['360p']).to.be.true 109 expect(data.transcoding.resolutions['360p']).to.be.true
103 expect(data.transcoding.resolutions['480p']).to.be.true 110 expect(data.transcoding.resolutions['480p']).to.be.true
104 expect(data.transcoding.resolutions['720p']).to.be.false 111 expect(data.transcoding.resolutions['720p']).to.be.false
105 expect(data.transcoding.resolutions['1080p']).to.be.false 112 expect(data.transcoding.resolutions['1080p']).to.be.false
113 expect(data.transcoding.resolutions['2160p']).to.be.false
106 expect(data.transcoding.hls.enabled).to.be.false 114 expect(data.transcoding.hls.enabled).to.be.false
107 115
108 expect(data.import.videos.http.enabled).to.be.false 116 expect(data.import.videos.http.enabled).to.be.false
@@ -118,6 +126,7 @@ describe('Test config', function () {
118 126
119 before(async function () { 127 before(async function () {
120 this.timeout(30000) 128 this.timeout(30000)
129
121 server = await flushAndRunServer(1) 130 server = await flushAndRunServer(1)
122 await setAccessTokensToServers([ server ]) 131 await setAccessTokensToServers([ server ])
123 }) 132 })
@@ -153,6 +162,9 @@ describe('Test config', function () {
153 expect(data.video.file.extensions).to.contain('.webm') 162 expect(data.video.file.extensions).to.contain('.webm')
154 expect(data.video.file.extensions).to.contain('.ogv') 163 expect(data.video.file.extensions).to.contain('.ogv')
155 164
165 await uploadVideo(server.url, server.accessToken, { fixture: 'video_short.mkv' }, 400)
166 await uploadVideo(server.url, server.accessToken, { fixture: 'sample.ogg' }, 400)
167
156 expect(data.contactForm.enabled).to.be.true 168 expect(data.contactForm.enabled).to.be.true
157 }) 169 })
158 170
@@ -160,7 +172,7 @@ describe('Test config', function () {
160 const res = await getCustomConfig(server.url, server.accessToken) 172 const res = await getCustomConfig(server.url, server.accessToken)
161 const data = res.body as CustomConfig 173 const data = res.body as CustomConfig
162 174
163 checkInitialConfig(data) 175 checkInitialConfig(server, data)
164 }) 176 })
165 177
166 it('Should update the customized configuration', async function () { 178 it('Should update the customized configuration', async function () {
@@ -210,13 +222,15 @@ describe('Test config', function () {
210 transcoding: { 222 transcoding: {
211 enabled: true, 223 enabled: true,
212 allowAdditionalExtensions: true, 224 allowAdditionalExtensions: true,
225 allowAudioFiles: true,
213 threads: 1, 226 threads: 1,
214 resolutions: { 227 resolutions: {
215 '240p': false, 228 '240p': false,
216 '360p': true, 229 '360p': true,
217 '480p': true, 230 '480p': true,
218 '720p': false, 231 '720p': false,
219 '1080p': false 232 '1080p': false,
233 '2160p': false
220 }, 234 },
221 hls: { 235 hls: {
222 enabled: false 236 enabled: false
@@ -264,6 +278,12 @@ describe('Test config', function () {
264 expect(data.video.file.extensions).to.contain('.ogv') 278 expect(data.video.file.extensions).to.contain('.ogv')
265 expect(data.video.file.extensions).to.contain('.flv') 279 expect(data.video.file.extensions).to.contain('.flv')
266 expect(data.video.file.extensions).to.contain('.mkv') 280 expect(data.video.file.extensions).to.contain('.mkv')
281 expect(data.video.file.extensions).to.contain('.mp3')
282 expect(data.video.file.extensions).to.contain('.ogg')
283 expect(data.video.file.extensions).to.contain('.flac')
284
285 await uploadVideo(server.url, server.accessToken, { fixture: 'video_short.mkv' }, 200)
286 await uploadVideo(server.url, server.accessToken, { fixture: 'sample.ogg' }, 200)
267 }) 287 })
268 288
269 it('Should have the configuration updated after a restart', async function () { 289 it('Should have the configuration updated after a restart', async function () {
@@ -297,7 +317,7 @@ describe('Test config', function () {
297 const res = await getCustomConfig(server.url, server.accessToken) 317 const res = await getCustomConfig(server.url, server.accessToken)
298 const data = res.body 318 const data = res.body
299 319
300 checkInitialConfig(data) 320 checkInitialConfig(server, data)
301 }) 321 })
302 322
303 after(async function () { 323 after(async function () {
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts
index ba51198b3..87e55060c 100644
--- a/server/tests/api/server/contact-form.ts
+++ b/server/tests/api/server/contact-form.ts
@@ -24,11 +24,12 @@ describe('Test contact form', function () {
24 before(async function () { 24 before(async function () {
25 this.timeout(30000) 25 this.timeout(30000)
26 26
27 await MockSmtpServer.Instance.collectEmails(emails) 27 const port = await MockSmtpServer.Instance.collectEmails(emails)
28 28
29 const overrideConfig = { 29 const overrideConfig = {
30 smtp: { 30 smtp: {
31 hostname: 'localhost' 31 hostname: 'localhost',
32 port
32 } 33 }
33 } 34 }
34 server = await flushAndRunServer(1, overrideConfig) 35 server = await flushAndRunServer(1, overrideConfig)
@@ -53,7 +54,7 @@ describe('Test contact form', function () {
53 54
54 expect(email['from'][0]['address']).equal('test-admin@localhost') 55 expect(email['from'][0]['address']).equal('test-admin@localhost')
55 expect(email['from'][0]['name']).equal('toto@example.com') 56 expect(email['from'][0]['name']).equal('toto@example.com')
56 expect(email['to'][0]['address']).equal('admin1@example.com') 57 expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
57 expect(email['subject']).contains('Contact form') 58 expect(email['subject']).contains('Contact form')
58 expect(email['text']).contains('my super message') 59 expect(email['text']).contains('my super message')
59 }) 60 })
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts
index bacdf1b1b..7b7acfd12 100644
--- a/server/tests/api/server/email.ts
+++ b/server/tests/api/server/email.ts
@@ -7,18 +7,18 @@ import {
7 askResetPassword, 7 askResetPassword,
8 askSendVerifyEmail, 8 askSendVerifyEmail,
9 blockUser, 9 blockUser,
10 createUser, removeVideoFromBlacklist, 10 cleanupTests,
11 createUser,
12 flushAndRunServer,
13 removeVideoFromBlacklist,
11 reportVideoAbuse, 14 reportVideoAbuse,
12 resetPassword, 15 resetPassword,
13 flushAndRunServer, 16 ServerInfo,
17 setAccessTokensToServers,
14 unblockUser, 18 unblockUser,
15 uploadVideo, 19 uploadVideo,
16 userLogin, 20 userLogin,
17 verifyEmail, 21 verifyEmail
18 flushTests,
19 killallServers,
20 ServerInfo,
21 setAccessTokensToServers, cleanupTests
22} from '../../../../shared/extra-utils' 22} from '../../../../shared/extra-utils'
23import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 23import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
24import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 24import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
@@ -37,15 +37,17 @@ describe('Test emails', function () {
37 username: 'user_1', 37 username: 'user_1',
38 password: 'super_password' 38 password: 'super_password'
39 } 39 }
40 let emailPort: number
40 41
41 before(async function () { 42 before(async function () {
42 this.timeout(30000) 43 this.timeout(30000)
43 44
44 await MockSmtpServer.Instance.collectEmails(emails) 45 emailPort = await MockSmtpServer.Instance.collectEmails(emails)
45 46
46 const overrideConfig = { 47 const overrideConfig = {
47 smtp: { 48 smtp: {
48 hostname: 'localhost' 49 hostname: 'localhost',
50 port: emailPort
49 } 51 }
50 } 52 }
51 server = await flushAndRunServer(1, overrideConfig) 53 server = await flushAndRunServer(1, overrideConfig)
@@ -87,7 +89,7 @@ describe('Test emails', function () {
87 89
88 const email = emails[0] 90 const email = emails[0]
89 91
90 expect(email['from'][0]['name']).equal('localhost:9001') 92 expect(email['from'][0]['name']).equal('localhost:' + server.port)
91 expect(email['from'][0]['address']).equal('test-admin@localhost') 93 expect(email['from'][0]['address']).equal('test-admin@localhost')
92 expect(email['to'][0]['address']).equal('user_1@example.com') 94 expect(email['to'][0]['address']).equal('user_1@example.com')
93 expect(email['subject']).contains('password') 95 expect(email['subject']).contains('password')
@@ -132,9 +134,9 @@ describe('Test emails', function () {
132 134
133 const email = emails[1] 135 const email = emails[1]
134 136
135 expect(email['from'][0]['name']).equal('localhost:9001') 137 expect(email['from'][0]['name']).equal('localhost:' + server.port)
136 expect(email['from'][0]['address']).equal('test-admin@localhost') 138 expect(email['from'][0]['address']).equal('test-admin@localhost')
137 expect(email['to'][0]['address']).equal('admin1@example.com') 139 expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com')
138 expect(email['subject']).contains('abuse') 140 expect(email['subject']).contains('abuse')
139 expect(email['text']).contains(videoUUID) 141 expect(email['text']).contains(videoUUID)
140 }) 142 })
@@ -153,7 +155,7 @@ describe('Test emails', function () {
153 155
154 const email = emails[2] 156 const email = emails[2]
155 157
156 expect(email['from'][0]['name']).equal('localhost:9001') 158 expect(email['from'][0]['name']).equal('localhost:' + server.port)
157 expect(email['from'][0]['address']).equal('test-admin@localhost') 159 expect(email['from'][0]['address']).equal('test-admin@localhost')
158 expect(email['to'][0]['address']).equal('user_1@example.com') 160 expect(email['to'][0]['address']).equal('user_1@example.com')
159 expect(email['subject']).contains(' blocked') 161 expect(email['subject']).contains(' blocked')
@@ -171,7 +173,7 @@ describe('Test emails', function () {
171 173
172 const email = emails[3] 174 const email = emails[3]
173 175
174 expect(email['from'][0]['name']).equal('localhost:9001') 176 expect(email['from'][0]['name']).equal('localhost:' + server.port)
175 expect(email['from'][0]['address']).equal('test-admin@localhost') 177 expect(email['from'][0]['address']).equal('test-admin@localhost')
176 expect(email['to'][0]['address']).equal('user_1@example.com') 178 expect(email['to'][0]['address']).equal('user_1@example.com')
177 expect(email['subject']).contains(' unblocked') 179 expect(email['subject']).contains(' unblocked')
@@ -191,7 +193,7 @@ describe('Test emails', function () {
191 193
192 const email = emails[4] 194 const email = emails[4]
193 195
194 expect(email['from'][0]['name']).equal('localhost:9001') 196 expect(email['from'][0]['name']).equal('localhost:' + server.port)
195 expect(email['from'][0]['address']).equal('test-admin@localhost') 197 expect(email['from'][0]['address']).equal('test-admin@localhost')
196 expect(email['to'][0]['address']).equal('user_1@example.com') 198 expect(email['to'][0]['address']).equal('user_1@example.com')
197 expect(email['subject']).contains(' blacklisted') 199 expect(email['subject']).contains(' blacklisted')
@@ -209,7 +211,7 @@ describe('Test emails', function () {
209 211
210 const email = emails[5] 212 const email = emails[5]
211 213
212 expect(email['from'][0]['name']).equal('localhost:9001') 214 expect(email['from'][0]['name']).equal('localhost:' + server.port)
213 expect(email['from'][0]['address']).equal('test-admin@localhost') 215 expect(email['from'][0]['address']).equal('test-admin@localhost')
214 expect(email['to'][0]['address']).equal('user_1@example.com') 216 expect(email['to'][0]['address']).equal('user_1@example.com')
215 expect(email['subject']).contains(' unblacklisted') 217 expect(email['subject']).contains(' unblacklisted')
@@ -229,7 +231,7 @@ describe('Test emails', function () {
229 231
230 const email = emails[6] 232 const email = emails[6]
231 233
232 expect(email['from'][0]['name']).equal('localhost:9001') 234 expect(email['from'][0]['name']).equal('localhost:' + server.port)
233 expect(email['from'][0]['address']).equal('test-admin@localhost') 235 expect(email['from'][0]['address']).equal('test-admin@localhost')
234 expect(email['to'][0]['address']).equal('user_1@example.com') 236 expect(email['to'][0]['address']).equal('user_1@example.com')
235 expect(email['subject']).contains('Verify') 237 expect(email['subject']).contains('Verify')
@@ -248,7 +250,7 @@ describe('Test emails', function () {
248 }) 250 })
249 251
250 it('Should not verify the email with an invalid verification string', async function () { 252 it('Should not verify the email with an invalid verification string', async function () {
251 await verifyEmail(server.url, userId, verificationString + 'b', 403) 253 await verifyEmail(server.url, userId, verificationString + 'b', false, 403)
252 }) 254 })
253 255
254 it('Should verify the email', async function () { 256 it('Should verify the email', async function () {
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts
index 4285a9e7a..ac3ff37f0 100644
--- a/server/tests/api/server/follow-constraints.ts
+++ b/server/tests/api/server/follow-constraints.ts
@@ -3,16 +3,16 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 cleanupTests,
6 doubleFollow, 7 doubleFollow,
8 flushAndRunMultipleServers,
7 getAccountVideos, 9 getAccountVideos,
8 getVideo, 10 getVideo,
9 getVideoChannelVideos, 11 getVideoChannelVideos,
10 getVideoWithToken, 12 getVideoWithToken,
11 flushAndRunMultipleServers,
12 killallServers,
13 ServerInfo, 13 ServerInfo,
14 setAccessTokensToServers, 14 setAccessTokensToServers,
15 uploadVideo, cleanupTests 15 uploadVideo
16} from '../../../../shared/extra-utils' 16} from '../../../../shared/extra-utils'
17import { unfollow } from '../../../../shared/extra-utils/server/follows' 17import { unfollow } from '../../../../shared/extra-utils/server/follows'
18import { userLogin } from '../../../../shared/extra-utils/users/login' 18import { userLogin } from '../../../../shared/extra-utils/users/login'
@@ -66,28 +66,30 @@ describe('Test follow constraints', function () {
66 }) 66 })
67 67
68 it('Should list local account videos', async function () { 68 it('Should list local account videos', async function () {
69 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9001', 0, 5) 69 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[0].port, 0, 5)
70 70
71 expect(res.body.total).to.equal(1) 71 expect(res.body.total).to.equal(1)
72 expect(res.body.data).to.have.lengthOf(1) 72 expect(res.body.data).to.have.lengthOf(1)
73 }) 73 })
74 74
75 it('Should list remote account videos', async function () { 75 it('Should list remote account videos', async function () {
76 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9002', 0, 5) 76 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[1].port, 0, 5)
77 77
78 expect(res.body.total).to.equal(1) 78 expect(res.body.total).to.equal(1)
79 expect(res.body.data).to.have.lengthOf(1) 79 expect(res.body.data).to.have.lengthOf(1)
80 }) 80 })
81 81
82 it('Should list local channel videos', async function () { 82 it('Should list local channel videos', async function () {
83 const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9001', 0, 5) 83 const videoChannelName = 'root_channel@localhost:' + servers[0].port
84 const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
84 85
85 expect(res.body.total).to.equal(1) 86 expect(res.body.total).to.equal(1)
86 expect(res.body.data).to.have.lengthOf(1) 87 expect(res.body.data).to.have.lengthOf(1)
87 }) 88 })
88 89
89 it('Should list remote channel videos', async function () { 90 it('Should list remote channel videos', async function () {
90 const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9002', 0, 5) 91 const videoChannelName = 'root_channel@localhost:' + servers[1].port
92 const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
91 93
92 expect(res.body.total).to.equal(1) 94 expect(res.body.total).to.equal(1)
93 expect(res.body.data).to.have.lengthOf(1) 95 expect(res.body.data).to.have.lengthOf(1)
@@ -104,28 +106,30 @@ describe('Test follow constraints', function () {
104 }) 106 })
105 107
106 it('Should list local account videos', async function () { 108 it('Should list local account videos', async function () {
107 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9001', 0, 5) 109 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[0].port, 0, 5)
108 110
109 expect(res.body.total).to.equal(1) 111 expect(res.body.total).to.equal(1)
110 expect(res.body.data).to.have.lengthOf(1) 112 expect(res.body.data).to.have.lengthOf(1)
111 }) 113 })
112 114
113 it('Should list remote account videos', async function () { 115 it('Should list remote account videos', async function () {
114 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9002', 0, 5) 116 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[1].port, 0, 5)
115 117
116 expect(res.body.total).to.equal(1) 118 expect(res.body.total).to.equal(1)
117 expect(res.body.data).to.have.lengthOf(1) 119 expect(res.body.data).to.have.lengthOf(1)
118 }) 120 })
119 121
120 it('Should list local channel videos', async function () { 122 it('Should list local channel videos', async function () {
121 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9001', 0, 5) 123 const videoChannelName = 'root_channel@localhost:' + servers[0].port
124 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
122 125
123 expect(res.body.total).to.equal(1) 126 expect(res.body.total).to.equal(1)
124 expect(res.body.data).to.have.lengthOf(1) 127 expect(res.body.data).to.have.lengthOf(1)
125 }) 128 })
126 129
127 it('Should list remote channel videos', async function () { 130 it('Should list remote channel videos', async function () {
128 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9002', 0, 5) 131 const videoChannelName = 'root_channel@localhost:' + servers[1].port
132 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
129 133
130 expect(res.body.total).to.equal(1) 134 expect(res.body.total).to.equal(1)
131 expect(res.body.data).to.have.lengthOf(1) 135 expect(res.body.data).to.have.lengthOf(1)
@@ -152,28 +156,30 @@ describe('Test follow constraints', function () {
152 }) 156 })
153 157
154 it('Should list local account videos', async function () { 158 it('Should list local account videos', async function () {
155 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9001', 0, 5) 159 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[0].port, 0, 5)
156 160
157 expect(res.body.total).to.equal(1) 161 expect(res.body.total).to.equal(1)
158 expect(res.body.data).to.have.lengthOf(1) 162 expect(res.body.data).to.have.lengthOf(1)
159 }) 163 })
160 164
161 it('Should not list remote account videos', async function () { 165 it('Should not list remote account videos', async function () {
162 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:9002', 0, 5) 166 const res = await getAccountVideos(servers[0].url, undefined, 'root@localhost:' + servers[1].port, 0, 5)
163 167
164 expect(res.body.total).to.equal(0) 168 expect(res.body.total).to.equal(0)
165 expect(res.body.data).to.have.lengthOf(0) 169 expect(res.body.data).to.have.lengthOf(0)
166 }) 170 })
167 171
168 it('Should list local channel videos', async function () { 172 it('Should list local channel videos', async function () {
169 const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9001', 0, 5) 173 const videoChannelName = 'root_channel@localhost:' + servers[0].port
174 const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
170 175
171 expect(res.body.total).to.equal(1) 176 expect(res.body.total).to.equal(1)
172 expect(res.body.data).to.have.lengthOf(1) 177 expect(res.body.data).to.have.lengthOf(1)
173 }) 178 })
174 179
175 it('Should not list remote channel videos', async function () { 180 it('Should not list remote channel videos', async function () {
176 const res = await getVideoChannelVideos(servers[0].url, undefined, 'root_channel@localhost:9002', 0, 5) 181 const videoChannelName = 'root_channel@localhost:' + servers[1].port
182 const res = await getVideoChannelVideos(servers[0].url, undefined, videoChannelName, 0, 5)
177 183
178 expect(res.body.total).to.equal(0) 184 expect(res.body.total).to.equal(0)
179 expect(res.body.data).to.have.lengthOf(0) 185 expect(res.body.data).to.have.lengthOf(0)
@@ -190,28 +196,30 @@ describe('Test follow constraints', function () {
190 }) 196 })
191 197
192 it('Should list local account videos', async function () { 198 it('Should list local account videos', async function () {
193 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9001', 0, 5) 199 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[0].port, 0, 5)
194 200
195 expect(res.body.total).to.equal(1) 201 expect(res.body.total).to.equal(1)
196 expect(res.body.data).to.have.lengthOf(1) 202 expect(res.body.data).to.have.lengthOf(1)
197 }) 203 })
198 204
199 it('Should list remote account videos', async function () { 205 it('Should list remote account videos', async function () {
200 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:9002', 0, 5) 206 const res = await getAccountVideos(servers[0].url, userAccessToken, 'root@localhost:' + servers[1].port, 0, 5)
201 207
202 expect(res.body.total).to.equal(1) 208 expect(res.body.total).to.equal(1)
203 expect(res.body.data).to.have.lengthOf(1) 209 expect(res.body.data).to.have.lengthOf(1)
204 }) 210 })
205 211
206 it('Should list local channel videos', async function () { 212 it('Should list local channel videos', async function () {
207 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9001', 0, 5) 213 const videoChannelName = 'root_channel@localhost:' + servers[0].port
214 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
208 215
209 expect(res.body.total).to.equal(1) 216 expect(res.body.total).to.equal(1)
210 expect(res.body.data).to.have.lengthOf(1) 217 expect(res.body.data).to.have.lengthOf(1)
211 }) 218 })
212 219
213 it('Should list remote channel videos', async function () { 220 it('Should list remote channel videos', async function () {
214 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, 'root_channel@localhost:9002', 0, 5) 221 const videoChannelName = 'root_channel@localhost:' + servers[1].port
222 const res = await getVideoChannelVideos(servers[0].url, userAccessToken, videoChannelName, 0, 5)
215 223
216 expect(res.body.total).to.equal(1) 224 expect(res.body.total).to.equal(1)
217 expect(res.body.data).to.have.lengthOf(1) 225 expect(res.body.data).to.have.lengthOf(1)
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts
index 2a3a4d5c8..a82acdb34 100644
--- a/server/tests/api/server/follows-moderation.ts
+++ b/server/tests/api/server/follows-moderation.ts
@@ -3,9 +3,9 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 acceptFollower, cleanupTests, 6 acceptFollower,
7 cleanupTests,
7 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
8 killallServers,
9 ServerInfo, 9 ServerInfo,
10 setAccessTokensToServers, 10 setAccessTokensToServers,
11 updateCustomSubConfig 11 updateCustomSubConfig
@@ -14,8 +14,8 @@ import {
14 follow, 14 follow,
15 getFollowersListPaginationAndSort, 15 getFollowersListPaginationAndSort,
16 getFollowingListPaginationAndSort, 16 getFollowingListPaginationAndSort,
17 removeFollower, 17 rejectFollower,
18 rejectFollower 18 removeFollower
19} from '../../../../shared/extra-utils/server/follows' 19} from '../../../../shared/extra-utils/server/follows'
20import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 20import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
21import { ActorFollow } from '../../../../shared/models/actors' 21import { ActorFollow } from '../../../../shared/models/actors'
@@ -29,8 +29,8 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
29 29
30 const follow = res.body.data[0] as ActorFollow 30 const follow = res.body.data[0] as ActorFollow
31 expect(follow.state).to.equal(state) 31 expect(follow.state).to.equal(state)
32 expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') 32 expect(follow.follower.url).to.equal('http://localhost:' + servers[0].port + '/accounts/peertube')
33 expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') 33 expect(follow.following.url).to.equal('http://localhost:' + servers[1].port + '/accounts/peertube')
34 } 34 }
35 35
36 { 36 {
@@ -39,8 +39,8 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc
39 39
40 const follow = res.body.data[0] as ActorFollow 40 const follow = res.body.data[0] as ActorFollow
41 expect(follow.state).to.equal(state) 41 expect(follow.state).to.equal(state)
42 expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') 42 expect(follow.follower.url).to.equal('http://localhost:' + servers[0].port + '/accounts/peertube')
43 expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') 43 expect(follow.following.url).to.equal('http://localhost:' + servers[1].port + '/accounts/peertube')
44 } 44 }
45} 45}
46 46
@@ -151,7 +151,7 @@ describe('Test follows moderation', function () {
151 }) 151 })
152 152
153 it('Should accept a follower', async function () { 153 it('Should accept a follower', async function () {
154 await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:9001') 154 await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:' + servers[0].port)
155 await waitJobs(servers) 155 await waitJobs(servers)
156 156
157 await checkServer1And2HasFollowers(servers) 157 await checkServer1And2HasFollowers(servers)
@@ -178,7 +178,7 @@ describe('Test follows moderation', function () {
178 expect(res.body.total).to.equal(1) 178 expect(res.body.total).to.equal(1)
179 } 179 }
180 180
181 await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:9001') 181 await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:' + servers[0].port)
182 await waitJobs(servers) 182 await waitJobs(servers)
183 183
184 await checkServer1And2HasFollowers(servers) 184 await checkServer1And2HasFollowers(servers)
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts
index 397093cdb..e8d6f5138 100644
--- a/server/tests/api/server/follows.ts
+++ b/server/tests/api/server/follows.ts
@@ -8,7 +8,6 @@ import { cleanupTests, completeVideoCheck } from '../../../../shared/extra-utils
8import { 8import {
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 getVideosList, 10 getVideosList,
11 killallServers,
12 ServerInfo, 11 ServerInfo,
13 setAccessTokensToServers, 12 setAccessTokensToServers,
14 uploadVideo 13 uploadVideo
@@ -89,8 +88,8 @@ describe('Test follows', function () {
89 res = await getFollowingListPaginationAndSort(servers[0].url, 1, 1, 'createdAt') 88 res = await getFollowingListPaginationAndSort(servers[0].url, 1, 1, 'createdAt')
90 follows = follows.concat(res.body.data) 89 follows = follows.concat(res.body.data)
91 90
92 const server2Follow = follows.find(f => f.following.host === 'localhost:9002') 91 const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port)
93 const server3Follow = follows.find(f => f.following.host === 'localhost:9003') 92 const server3Follow = follows.find(f => f.following.host === 'localhost:' + servers[2].port)
94 93
95 expect(server2Follow).to.not.be.undefined 94 expect(server2Follow).to.not.be.undefined
96 expect(server3Follow).to.not.be.undefined 95 expect(server3Follow).to.not.be.undefined
@@ -100,12 +99,12 @@ describe('Test follows', function () {
100 99
101 it('Should search followings on server 1', async function () { 100 it('Should search followings on server 1', async function () {
102 { 101 {
103 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':9002') 102 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 1, 'createdAt', ':' + servers[1].port)
104 const follows = res.body.data 103 const follows = res.body.data
105 104
106 expect(res.body.total).to.equal(1) 105 expect(res.body.total).to.equal(1)
107 expect(follows.length).to.equal(1) 106 expect(follows.length).to.equal(1)
108 expect(follows[ 0 ].following.host).to.equal('localhost:9002') 107 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[1].port)
109 } 108 }
110 109
111 { 110 {
@@ -136,18 +135,18 @@ describe('Test follows', function () {
136 expect(res.body.total).to.equal(1) 135 expect(res.body.total).to.equal(1)
137 expect(follows).to.be.an('array') 136 expect(follows).to.be.an('array')
138 expect(follows.length).to.equal(1) 137 expect(follows.length).to.equal(1)
139 expect(follows[0].follower.host).to.equal('localhost:9001') 138 expect(follows[0].follower.host).to.equal('localhost:' + servers[0].port)
140 } 139 }
141 }) 140 })
142 141
143 it('Should search followers on server 2', async function () { 142 it('Should search followers on server 2', async function () {
144 { 143 {
145 const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', '9001') 144 const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt', servers[0].port + '')
146 const follows = res.body.data 145 const follows = res.body.data
147 146
148 expect(res.body.total).to.equal(1) 147 expect(res.body.total).to.equal(1)
149 expect(follows.length).to.equal(1) 148 expect(follows.length).to.equal(1)
150 expect(follows[ 0 ].following.host).to.equal('localhost:9003') 149 expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[2].port)
151 } 150 }
152 151
153 { 152 {
@@ -169,16 +168,16 @@ describe('Test follows', function () {
169 }) 168 })
170 169
171 it('Should have the correct follows counts', async function () { 170 it('Should have the correct follows counts', async function () {
172 await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2) 171 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
173 await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0) 172 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
174 await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0) 173 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
175 174
176 // Server 2 and 3 does not know server 1 follow another server (there was not a refresh) 175 // Server 2 and 3 does not know server 1 follow another server (there was not a refresh)
177 await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1) 176 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
178 await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0) 177 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
179 178
180 await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 1) 179 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 1)
181 await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0) 180 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
182 }) 181 })
183 182
184 it('Should unfollow server 3 on server 1', async function () { 183 it('Should unfollow server 3 on server 1', async function () {
@@ -197,7 +196,7 @@ describe('Test follows', function () {
197 expect(follows).to.be.an('array') 196 expect(follows).to.be.an('array')
198 expect(follows.length).to.equal(1) 197 expect(follows.length).to.equal(1)
199 198
200 expect(follows[0].following.host).to.equal('localhost:9002') 199 expect(follows[0].following.host).to.equal('localhost:' + servers[1].port)
201 }) 200 })
202 201
203 it('Should not have server 1 as follower on server 3 anymore', async function () { 202 it('Should not have server 1 as follower on server 3 anymore', async function () {
@@ -210,14 +209,14 @@ describe('Test follows', function () {
210 }) 209 })
211 210
212 it('Should have the correct follows counts 2', async function () { 211 it('Should have the correct follows counts 2', async function () {
213 await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 1) 212 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 1)
214 await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0) 213 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
215 214
216 await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1) 215 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
217 await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0) 216 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
218 217
219 await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 0) 218 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 0)
220 await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 0, 0) 219 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 0, 0)
221 }) 220 })
222 221
223 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () { 222 it('Should upload a video on server 2 and 3 and propagate only the video of server 2', async function () {
@@ -310,15 +309,15 @@ describe('Test follows', function () {
310 }) 309 })
311 310
312 it('Should have the correct follows counts 3', async function () { 311 it('Should have the correct follows counts 3', async function () {
313 await expectAccountFollows(servers[0].url, 'peertube@localhost:9001', 0, 2) 312 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[0].port, 0, 2)
314 await expectAccountFollows(servers[0].url, 'peertube@localhost:9002', 1, 0) 313 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[1].port, 1, 0)
315 await expectAccountFollows(servers[0].url, 'peertube@localhost:9003', 1, 0) 314 await expectAccountFollows(servers[0].url, 'peertube@localhost:' + servers[2].port, 1, 0)
316 315
317 await expectAccountFollows(servers[1].url, 'peertube@localhost:9001', 0, 1) 316 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[0].port, 0, 1)
318 await expectAccountFollows(servers[1].url, 'peertube@localhost:9002', 1, 0) 317 await expectAccountFollows(servers[1].url, 'peertube@localhost:' + servers[1].port, 1, 0)
319 318
320 await expectAccountFollows(servers[2].url, 'peertube@localhost:9001', 0, 2) 319 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[0].port, 0, 2)
321 await expectAccountFollows(servers[2].url, 'peertube@localhost:9003', 1, 0) 320 await expectAccountFollows(servers[2].url, 'peertube@localhost:' + servers[2].port, 1, 0)
322 }) 321 })
323 322
324 it('Should have propagated videos', async function () { 323 it('Should have propagated videos', async function () {
@@ -344,7 +343,7 @@ describe('Test follows', function () {
344 support: 'my super support text', 343 support: 'my super support text',
345 account: { 344 account: {
346 name: 'root', 345 name: 'root',
347 host: 'localhost:9003' 346 host: 'localhost:' + servers[2].port
348 }, 347 },
349 isLocal, 348 isLocal,
350 commentsEnabled: true, 349 commentsEnabled: true,
@@ -384,7 +383,7 @@ describe('Test follows', function () {
384 expect(comment.videoId).to.equal(video4.id) 383 expect(comment.videoId).to.equal(video4.id)
385 expect(comment.id).to.equal(comment.threadId) 384 expect(comment.id).to.equal(comment.threadId)
386 expect(comment.account.name).to.equal('root') 385 expect(comment.account.name).to.equal('root')
387 expect(comment.account.host).to.equal('localhost:9003') 386 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
388 expect(comment.totalReplies).to.equal(3) 387 expect(comment.totalReplies).to.equal(3)
389 expect(dateIsValid(comment.createdAt as string)).to.be.true 388 expect(dateIsValid(comment.createdAt as string)).to.be.true
390 expect(dateIsValid(comment.updatedAt as string)).to.be.true 389 expect(dateIsValid(comment.updatedAt as string)).to.be.true
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts
index 19010dbc1..068654d8c 100644
--- a/server/tests/api/server/handle-down.ts
+++ b/server/tests/api/server/handle-down.ts
@@ -60,48 +60,50 @@ describe('Test handle downs', function () {
60 privacy: VideoPrivacy.UNLISTED 60 privacy: VideoPrivacy.UNLISTED
61 }) 61 })
62 62
63 const checkAttributes = { 63 let checkAttributes: any
64 name: 'my super name for server 1', 64 let unlistedCheckAttributes: any
65 category: 5,
66 licence: 4,
67 language: 'ja',
68 nsfw: true,
69 description: 'my super description for server 1',
70 support: 'my super support text for server 1',
71 account: {
72 name: 'root',
73 host: 'localhost:9001'
74 },
75 isLocal: false,
76 duration: 10,
77 tags: [ 'tag1p1', 'tag2p1' ],
78 privacy: VideoPrivacy.PUBLIC,
79 commentsEnabled: true,
80 downloadEnabled: true,
81 channel: {
82 name: 'root_channel',
83 displayName: 'Main root channel',
84 description: '',
85 isLocal: false
86 },
87 fixture: 'video_short1.webm',
88 files: [
89 {
90 resolution: 720,
91 size: 572456
92 }
93 ]
94 }
95
96 const unlistedCheckAttributes = immutableAssign(checkAttributes, {
97 privacy: VideoPrivacy.UNLISTED
98 })
99 65
100 before(async function () { 66 before(async function () {
101 this.timeout(30000) 67 this.timeout(30000)
102 68
103 servers = await flushAndRunMultipleServers(3) 69 servers = await flushAndRunMultipleServers(3)
104 70
71 checkAttributes = {
72 name: 'my super name for server 1',
73 category: 5,
74 licence: 4,
75 language: 'ja',
76 nsfw: true,
77 description: 'my super description for server 1',
78 support: 'my super support text for server 1',
79 account: {
80 name: 'root',
81 host: 'localhost:' + servers[0].port
82 },
83 isLocal: false,
84 duration: 10,
85 tags: [ 'tag1p1', 'tag2p1' ],
86 privacy: VideoPrivacy.PUBLIC,
87 commentsEnabled: true,
88 downloadEnabled: true,
89 channel: {
90 name: 'root_channel',
91 displayName: 'Main root channel',
92 description: '',
93 isLocal: false
94 },
95 fixture: 'video_short1.webm',
96 files: [
97 {
98 resolution: 720,
99 size: 572456
100 }
101 ]
102 }
103 unlistedCheckAttributes = immutableAssign(checkAttributes, {
104 privacy: VideoPrivacy.UNLISTED
105 })
106
105 // Get the access tokens 107 // Get the access tokens
106 await setAccessTokensToServers(servers) 108 await setAccessTokensToServers(servers)
107 }) 109 })
@@ -172,7 +174,7 @@ describe('Test handle downs', function () {
172 const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 2, 'createdAt') 174 const res = await getFollowersListPaginationAndSort(servers[0].url, 0, 2, 'createdAt')
173 expect(res.body.data).to.be.an('array') 175 expect(res.body.data).to.be.an('array')
174 expect(res.body.data).to.have.lengthOf(1) 176 expect(res.body.data).to.have.lengthOf(1)
175 expect(res.body.data[0].follower.host).to.equal('localhost:9003') 177 expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port)
176 }) 178 })
177 179
178 it('Should not have pending/processing jobs anymore', async function () { 180 it('Should not have pending/processing jobs anymore', async function () {
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts
index 634654626..3ab2fe120 100644
--- a/server/tests/api/server/jobs.ts
+++ b/server/tests/api/server/jobs.ts
@@ -26,7 +26,7 @@ describe('Test jobs', function () {
26 }) 26 })
27 27
28 it('Should create some jobs', async function () { 28 it('Should create some jobs', async function () {
29 this.timeout(30000) 29 this.timeout(60000)
30 30
31 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' }) 31 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' })
32 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' }) 32 await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' })
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts
index 3644fa0d3..68f442199 100644
--- a/server/tests/api/server/logs.ts
+++ b/server/tests/api/server/logs.ts
@@ -45,7 +45,7 @@ describe('Test logs', function () {
45 }) 45 })
46 46
47 it('Should get logs with an end date', async function () { 47 it('Should get logs with an end date', async function () {
48 this.timeout(10000) 48 this.timeout(20000)
49 49
50 await uploadVideo(server.url, server.accessToken, { name: 'video 3' }) 50 await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
51 await waitJobs([ server ]) 51 await waitJobs([ server ])
diff --git a/server/tests/api/travis-1.sh b/server/tests/api/travis-1.sh
new file mode 100644
index 000000000..db4021b25
--- /dev/null
+++ b/server/tests/api/travis-1.sh
@@ -0,0 +1,10 @@
1#!/usr/bin/env sh
2
3set -eu
4
5checkParamFiles=$(find server/tests/api/check-params -type f | grep -v index.ts | xargs echo)
6notificationsFiles=$(find server/tests/api/notifications -type f | grep -v index.ts | xargs echo)
7searchFiles=$(find server/tests/api/search -type f | grep -v index.ts | xargs echo)
8
9MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
10 $notificationsFiles $searchFiles $checkParamFiles
diff --git a/server/tests/api/travis-2.sh b/server/tests/api/travis-2.sh
new file mode 100644
index 000000000..ba7a061b0
--- /dev/null
+++ b/server/tests/api/travis-2.sh
@@ -0,0 +1,9 @@
1#!/usr/bin/env sh
2
3set -eu
4
5serverFiles=$(find server/tests/api/server -type f | grep -v index.ts | xargs echo)
6usersFiles=$(find server/tests/api/users -type f | grep -v index.ts | xargs echo)
7
8MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
9 $serverFiles $usersFiles
diff --git a/server/tests/api/travis-3.sh b/server/tests/api/travis-3.sh
new file mode 100644
index 000000000..82457222c
--- /dev/null
+++ b/server/tests/api/travis-3.sh
@@ -0,0 +1,8 @@
1#!/usr/bin/env sh
2
3set -eu
4
5videosFiles=$(find server/tests/api/videos -type f | grep -v index.ts | xargs echo)
6
7MOCHA_PARALLEL=true mocha --timeout 5000 --exit --require ts-node/register --bail \
8 $videosFiles
diff --git a/server/tests/api/travis-4.sh b/server/tests/api/travis-4.sh
new file mode 100644
index 000000000..875986182
--- /dev/null
+++ b/server/tests/api/travis-4.sh
@@ -0,0 +1,9 @@
1#!/usr/bin/env sh
2
3set -eu
4
5redundancyFiles=$(find server/tests/api/redundancy -type f | grep -v index.ts | xargs echo)
6activitypubFiles=$(find server/tests/api/activitypub -type f | grep -v index.ts | xargs echo)
7
8MOCHA_PARALLEL=true mocha-parallel-tests --max-parallel $1 --timeout 5000 --exit --require ts-node/register --bail \
9 $redundancyFiles $activitypubFiles
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts
index fbc57e0ef..c25e85ada 100644
--- a/server/tests/api/users/blocklist.ts
+++ b/server/tests/api/users/blocklist.ts
@@ -144,7 +144,7 @@ describe('Test blocklist', function () {
144 }) 144 })
145 145
146 it('Should block a remote account', async function () { 146 it('Should block a remote account', async function () {
147 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') 147 await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
148 }) 148 })
149 149
150 it('Should hide its videos', async function () { 150 it('Should hide its videos', async function () {
@@ -209,7 +209,7 @@ describe('Test blocklist', function () {
209 expect(block.byAccount.name).to.equal('root') 209 expect(block.byAccount.name).to.equal('root')
210 expect(block.blockedAccount.displayName).to.equal('user2') 210 expect(block.blockedAccount.displayName).to.equal('user2')
211 expect(block.blockedAccount.name).to.equal('user2') 211 expect(block.blockedAccount.name).to.equal('user2')
212 expect(block.blockedAccount.host).to.equal('localhost:9002') 212 expect(block.blockedAccount.host).to.equal('localhost:' + servers[1].port)
213 } 213 }
214 214
215 { 215 {
@@ -223,12 +223,12 @@ describe('Test blocklist', function () {
223 expect(block.byAccount.name).to.equal('root') 223 expect(block.byAccount.name).to.equal('root')
224 expect(block.blockedAccount.displayName).to.equal('user1') 224 expect(block.blockedAccount.displayName).to.equal('user1')
225 expect(block.blockedAccount.name).to.equal('user1') 225 expect(block.blockedAccount.name).to.equal('user1')
226 expect(block.blockedAccount.host).to.equal('localhost:9001') 226 expect(block.blockedAccount.host).to.equal('localhost:' + servers[0].port)
227 } 227 }
228 }) 228 })
229 229
230 it('Should unblock the remote account', async function () { 230 it('Should unblock the remote account', async function () {
231 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') 231 await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
232 }) 232 })
233 233
234 it('Should display its videos', async function () { 234 it('Should display its videos', async function () {
@@ -260,7 +260,7 @@ describe('Test blocklist', function () {
260 }) 260 })
261 261
262 it('Should block a remote server', async function () { 262 it('Should block a remote server', async function () {
263 await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') 263 await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
264 }) 264 })
265 265
266 it('Should hide its videos', async function () { 266 it('Should hide its videos', async function () {
@@ -291,11 +291,11 @@ describe('Test blocklist', function () {
291 const block = blocks[ 0 ] 291 const block = blocks[ 0 ]
292 expect(block.byAccount.displayName).to.equal('root') 292 expect(block.byAccount.displayName).to.equal('root')
293 expect(block.byAccount.name).to.equal('root') 293 expect(block.byAccount.name).to.equal('root')
294 expect(block.blockedServer.host).to.equal('localhost:9002') 294 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
295 }) 295 })
296 296
297 it('Should unblock the remote server', async function () { 297 it('Should unblock the remote server', async function () {
298 await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') 298 await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
299 }) 299 })
300 300
301 it('Should display its videos', function () { 301 it('Should display its videos', function () {
@@ -324,7 +324,7 @@ describe('Test blocklist', function () {
324 }) 324 })
325 325
326 it('Should block a remote account', async function () { 326 it('Should block a remote account', async function () {
327 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') 327 await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
328 }) 328 })
329 329
330 it('Should hide its videos', async function () { 330 it('Should hide its videos', async function () {
@@ -387,7 +387,7 @@ describe('Test blocklist', function () {
387 expect(block.byAccount.name).to.equal('peertube') 387 expect(block.byAccount.name).to.equal('peertube')
388 expect(block.blockedAccount.displayName).to.equal('user2') 388 expect(block.blockedAccount.displayName).to.equal('user2')
389 expect(block.blockedAccount.name).to.equal('user2') 389 expect(block.blockedAccount.name).to.equal('user2')
390 expect(block.blockedAccount.host).to.equal('localhost:9002') 390 expect(block.blockedAccount.host).to.equal('localhost:' + servers[1].port)
391 } 391 }
392 392
393 { 393 {
@@ -401,12 +401,12 @@ describe('Test blocklist', function () {
401 expect(block.byAccount.name).to.equal('peertube') 401 expect(block.byAccount.name).to.equal('peertube')
402 expect(block.blockedAccount.displayName).to.equal('user1') 402 expect(block.blockedAccount.displayName).to.equal('user1')
403 expect(block.blockedAccount.name).to.equal('user1') 403 expect(block.blockedAccount.name).to.equal('user1')
404 expect(block.blockedAccount.host).to.equal('localhost:9001') 404 expect(block.blockedAccount.host).to.equal('localhost:' + servers[0].port)
405 } 405 }
406 }) 406 })
407 407
408 it('Should unblock the remote account', async function () { 408 it('Should unblock the remote account', async function () {
409 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:9002') 409 await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port)
410 }) 410 })
411 411
412 it('Should display its videos', async function () { 412 it('Should display its videos', async function () {
@@ -446,7 +446,7 @@ describe('Test blocklist', function () {
446 }) 446 })
447 447
448 it('Should block a remote server', async function () { 448 it('Should block a remote server', async function () {
449 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') 449 await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
450 }) 450 })
451 451
452 it('Should hide its videos', async function () { 452 it('Should hide its videos', async function () {
@@ -478,11 +478,11 @@ describe('Test blocklist', function () {
478 const block = blocks[ 0 ] 478 const block = blocks[ 0 ]
479 expect(block.byAccount.displayName).to.equal('peertube') 479 expect(block.byAccount.displayName).to.equal('peertube')
480 expect(block.byAccount.name).to.equal('peertube') 480 expect(block.byAccount.name).to.equal('peertube')
481 expect(block.blockedServer.host).to.equal('localhost:9002') 481 expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port)
482 }) 482 })
483 483
484 it('Should unblock the remote server', async function () { 484 it('Should unblock the remote server', async function () {
485 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:9002') 485 await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port)
486 }) 486 })
487 487
488 it('Should list all videos', async function () { 488 it('Should list all videos', async function () {
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts
index 48811e647..c8a89d6be 100644
--- a/server/tests/api/users/user-subscriptions.ts
+++ b/server/tests/api/users/user-subscriptions.ts
@@ -71,8 +71,8 @@ describe('Test users subscriptions', function () {
71 it('User of server 1 should follow user of server 3 and root of server 1', async function () { 71 it('User of server 1 should follow user of server 3 and root of server 1', async function () {
72 this.timeout(60000) 72 this.timeout(60000)
73 73
74 await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003') 74 await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
75 await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001') 75 await addUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
76 76
77 await waitJobs(servers) 77 await waitJobs(servers)
78 78
@@ -116,22 +116,22 @@ describe('Test users subscriptions', function () {
116 116
117 it('Should get subscription', async function () { 117 it('Should get subscription', async function () {
118 { 118 {
119 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:9003') 119 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:' + servers[2].port)
120 const videoChannel: VideoChannel = res.body 120 const videoChannel: VideoChannel = res.body
121 121
122 expect(videoChannel.name).to.equal('user3_channel') 122 expect(videoChannel.name).to.equal('user3_channel')
123 expect(videoChannel.host).to.equal('localhost:9003') 123 expect(videoChannel.host).to.equal('localhost:' + servers[2].port)
124 expect(videoChannel.displayName).to.equal('Main user3 channel') 124 expect(videoChannel.displayName).to.equal('Main user3 channel')
125 expect(videoChannel.followingCount).to.equal(0) 125 expect(videoChannel.followingCount).to.equal(0)
126 expect(videoChannel.followersCount).to.equal(1) 126 expect(videoChannel.followersCount).to.equal(1)
127 } 127 }
128 128
129 { 129 {
130 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:9001') 130 const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:' + servers[0].port)
131 const videoChannel: VideoChannel = res.body 131 const videoChannel: VideoChannel = res.body
132 132
133 expect(videoChannel.name).to.equal('root_channel') 133 expect(videoChannel.name).to.equal('root_channel')
134 expect(videoChannel.host).to.equal('localhost:9001') 134 expect(videoChannel.host).to.equal('localhost:' + servers[0].port)
135 expect(videoChannel.displayName).to.equal('Main root channel') 135 expect(videoChannel.displayName).to.equal('Main root channel')
136 expect(videoChannel.followingCount).to.equal(0) 136 expect(videoChannel.followingCount).to.equal(0)
137 expect(videoChannel.followersCount).to.equal(1) 137 expect(videoChannel.followersCount).to.equal(1)
@@ -140,19 +140,19 @@ describe('Test users subscriptions', function () {
140 140
141 it('Should return the existing subscriptions', async function () { 141 it('Should return the existing subscriptions', async function () {
142 const uris = [ 142 const uris = [
143 'user3_channel@localhost:9003', 143 'user3_channel@localhost:' + servers[2].port,
144 'root2_channel@localhost:9001', 144 'root2_channel@localhost:' + servers[0].port,
145 'root_channel@localhost:9001', 145 'root_channel@localhost:' + servers[0].port,
146 'user3_channel@localhost:9001' 146 'user3_channel@localhost:' + servers[0].port
147 ] 147 ]
148 148
149 const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris) 149 const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris)
150 const body = res.body 150 const body = res.body
151 151
152 expect(body['user3_channel@localhost:9003']).to.be.true 152 expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true
153 expect(body['root2_channel@localhost:9001']).to.be.false 153 expect(body['root2_channel@localhost:' + servers[0].port]).to.be.false
154 expect(body['root_channel@localhost:9001']).to.be.true 154 expect(body['root_channel@localhost:' + servers[0].port]).to.be.true
155 expect(body['user3_channel@localhost:9001']).to.be.false 155 expect(body['user3_channel@localhost:' + servers[0].port]).to.be.false
156 }) 156 })
157 157
158 it('Should list subscription videos', async function () { 158 it('Should list subscription videos', async function () {
@@ -291,7 +291,7 @@ describe('Test users subscriptions', function () {
291 it('Should remove user of server 3 subscription', async function () { 291 it('Should remove user of server 3 subscription', async function () {
292 this.timeout(30000) 292 this.timeout(30000)
293 293
294 await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003') 294 await removeUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
295 295
296 await waitJobs(servers) 296 await waitJobs(servers)
297 }) 297 })
@@ -312,7 +312,7 @@ describe('Test users subscriptions', function () {
312 it('Should remove the root subscription and not display the videos anymore', async function () { 312 it('Should remove the root subscription and not display the videos anymore', async function () {
313 this.timeout(30000) 313 this.timeout(30000)
314 314
315 await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:9001') 315 await removeUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port)
316 316
317 await waitJobs(servers) 317 await waitJobs(servers)
318 318
@@ -340,7 +340,7 @@ describe('Test users subscriptions', function () {
340 it('Should follow user of server 3 again', async function () { 340 it('Should follow user of server 3 again', async function () {
341 this.timeout(60000) 341 this.timeout(60000)
342 342
343 await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:9003') 343 await addUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port)
344 344
345 await waitJobs(servers) 345 await waitJobs(servers)
346 346
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts
index 9a971adb3..791418318 100644
--- a/server/tests/api/users/users-multiple-servers.ts
+++ b/server/tests/api/users/users-multiple-servers.ts
@@ -5,7 +5,8 @@ import 'mocha'
5import { Account } from '../../../../shared/models/actors' 5import { Account } from '../../../../shared/models/actors'
6import { 6import {
7 checkTmpIsEmpty, 7 checkTmpIsEmpty,
8 checkVideoFilesWereRemoved, cleanupTests, 8 checkVideoFilesWereRemoved,
9 cleanupTests,
9 createUser, 10 createUser,
10 doubleFollow, 11 doubleFollow,
11 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
@@ -15,14 +16,7 @@ import {
15 updateMyUser, 16 updateMyUser,
16 userLogin 17 userLogin
17} from '../../../../shared/extra-utils' 18} from '../../../../shared/extra-utils'
18import { 19import { getMyUserInformation, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../../../shared/extra-utils/index'
19 getMyUserInformation,
20 killallServers,
21 ServerInfo,
22 testImage,
23 updateMyAvatar,
24 uploadVideo
25} from '../../../../shared/extra-utils/index'
26import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/extra-utils/users/accounts' 20import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/extra-utils/users/accounts'
27import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 21import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
28import { User } from '../../../../shared/models/users' 22import { User } from '../../../../shared/models/users'
@@ -34,12 +28,10 @@ const expect = chai.expect
34describe('Test users with multiple servers', function () { 28describe('Test users with multiple servers', function () {
35 let servers: ServerInfo[] = [] 29 let servers: ServerInfo[] = []
36 let user: User 30 let user: User
37 let userAccountName: string
38 let userAccountUUID: string
39 let userVideoChannelUUID: string
40 let userId: number 31 let userId: number
41 let videoUUID: string 32 let videoUUID: string
42 let userAccessToken: string 33 let userAccessToken: string
34 let userAvatarFilename: string
43 35
44 before(async function () { 36 before(async function () {
45 this.timeout(120000) 37 this.timeout(120000)
@@ -75,19 +67,6 @@ describe('Test users with multiple servers', function () {
75 } 67 }
76 68
77 { 69 {
78 const res = await getMyUserInformation(servers[0].url, userAccessToken)
79 const account: Account = res.body.account
80 userAccountName = account.name + '@' + account.host
81 userAccountUUID = account.uuid
82 }
83
84 {
85 const res = await getMyUserInformation(servers[ 0 ].url, servers[ 0 ].accessToken)
86 const user: User = res.body
87 userVideoChannelUUID = user.videoChannels[0].uuid
88 }
89
90 {
91 const resVideo = await uploadVideo(servers[ 0 ].url, userAccessToken, {}) 70 const resVideo = await uploadVideo(servers[ 0 ].url, userAccessToken, {})
92 videoUUID = resVideo.body.video.uuid 71 videoUUID = resVideo.body.video.uuid
93 } 72 }
@@ -106,6 +85,8 @@ describe('Test users with multiple servers', function () {
106 85
107 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 86 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
108 user = res.body 87 user = res.body
88
89 const account: Account = user.account
109 expect(user.account.displayName).to.equal('my super display name') 90 expect(user.account.displayName).to.equal('my super display name')
110 91
111 await waitJobs(servers) 92 await waitJobs(servers)
@@ -142,7 +123,9 @@ describe('Test users with multiple servers', function () {
142 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 123 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
143 user = res.body 124 user = res.body
144 125
145 await testImage(servers[0].url, 'avatar2-resized', user.account.avatar.path, '.png') 126 userAvatarFilename = user.account.avatar.path
127
128 await testImage(servers[0].url, 'avatar2-resized', userAvatarFilename, '.png')
146 129
147 await waitJobs(servers) 130 await waitJobs(servers)
148 }) 131 })
@@ -151,13 +134,13 @@ describe('Test users with multiple servers', function () {
151 for (const server of servers) { 134 for (const server of servers) {
152 const resAccounts = await getAccountsList(server.url, '-createdAt') 135 const resAccounts = await getAccountsList(server.url, '-createdAt')
153 136
154 const rootServer1List = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:9001') as Account 137 const rootServer1List = resAccounts.body.data.find(a => a.name === 'root' && a.host === 'localhost:' + servers[0].port) as Account
155 expect(rootServer1List).not.to.be.undefined 138 expect(rootServer1List).not.to.be.undefined
156 139
157 const resAccount = await getAccount(server.url, rootServer1List.name + '@' + rootServer1List.host) 140 const resAccount = await getAccount(server.url, rootServer1List.name + '@' + rootServer1List.host)
158 const rootServer1Get = resAccount.body as Account 141 const rootServer1Get = resAccount.body as Account
159 expect(rootServer1Get.name).to.equal('root') 142 expect(rootServer1Get.name).to.equal('root')
160 expect(rootServer1Get.host).to.equal('localhost:9001') 143 expect(rootServer1Get.host).to.equal('localhost:' + servers[0].port)
161 expect(rootServer1Get.displayName).to.equal('my super display name') 144 expect(rootServer1Get.displayName).to.equal('my super display name')
162 expect(rootServer1Get.description).to.equal('my super description updated') 145 expect(rootServer1Get.description).to.equal('my super description updated')
163 146
@@ -173,7 +156,7 @@ describe('Test users with multiple servers', function () {
173 156
174 it('Should list account videos', async function () { 157 it('Should list account videos', async function () {
175 for (const server of servers) { 158 for (const server of servers) {
176 const res = await getAccountVideos(server.url, server.accessToken, userAccountName, 0, 5) 159 const res = await getAccountVideos(server.url, server.accessToken, 'user1@localhost:' + servers[0].port, 0, 5)
177 160
178 expect(res.body.total).to.equal(1) 161 expect(res.body.total).to.equal(1)
179 expect(res.body.data).to.be.an('array') 162 expect(res.body.data).to.be.an('array')
@@ -188,12 +171,12 @@ describe('Test users with multiple servers', function () {
188 for (const server of servers) { 171 for (const server of servers) {
189 const resAccounts = await getAccountsList(server.url, '-createdAt') 172 const resAccounts = await getAccountsList(server.url, '-createdAt')
190 173
191 const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:9001') as Account 174 const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account
192 expect(accountDeleted).not.to.be.undefined 175 expect(accountDeleted).not.to.be.undefined
193 176
194 const resVideoChannels = await getVideoChannelsList(server.url, 0, 10) 177 const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
195 const videoChannelDeleted = resVideoChannels.body.data.find(a => { 178 const videoChannelDeleted = resVideoChannels.body.data.find(a => {
196 return a.displayName === 'Main user1 channel' && a.host === 'localhost:9001' 179 return a.displayName === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port
197 }) as VideoChannel 180 }) as VideoChannel
198 expect(videoChannelDeleted).not.to.be.undefined 181 expect(videoChannelDeleted).not.to.be.undefined
199 } 182 }
@@ -205,12 +188,12 @@ describe('Test users with multiple servers', function () {
205 for (const server of servers) { 188 for (const server of servers) {
206 const resAccounts = await getAccountsList(server.url, '-createdAt') 189 const resAccounts = await getAccountsList(server.url, '-createdAt')
207 190
208 const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:9001') as Account 191 const accountDeleted = resAccounts.body.data.find(a => a.name === 'user1' && a.host === 'localhost:' + servers[0].port) as Account
209 expect(accountDeleted).to.be.undefined 192 expect(accountDeleted).to.be.undefined
210 193
211 const resVideoChannels = await getVideoChannelsList(server.url, 0, 10) 194 const resVideoChannels = await getVideoChannelsList(server.url, 0, 10)
212 const videoChannelDeleted = resVideoChannels.body.data.find(a => { 195 const videoChannelDeleted = resVideoChannels.body.data.find(a => {
213 return a.name === 'Main user1 channel' && a.host === 'localhost:9001' 196 return a.name === 'Main user1 channel' && a.host === 'localhost:' + servers[0].port
214 }) as VideoChannel 197 }) as VideoChannel
215 expect(videoChannelDeleted).to.be.undefined 198 expect(videoChannelDeleted).to.be.undefined
216 } 199 }
@@ -218,14 +201,13 @@ describe('Test users with multiple servers', function () {
218 201
219 it('Should not have actor files', async () => { 202 it('Should not have actor files', async () => {
220 for (const server of servers) { 203 for (const server of servers) {
221 await checkActorFilesWereRemoved(userAccountUUID, server.serverNumber) 204 await checkActorFilesWereRemoved(userAvatarFilename, server.internalServerNumber)
222 await checkActorFilesWereRemoved(userVideoChannelUUID, server.serverNumber)
223 } 205 }
224 }) 206 })
225 207
226 it('Should not have video files', async () => { 208 it('Should not have video files', async () => {
227 for (const server of servers) { 209 for (const server of servers) {
228 await checkVideoFilesWereRemoved(videoUUID, server.serverNumber) 210 await checkVideoFilesWereRemoved(videoUUID, server.internalServerNumber)
229 } 211 }
230 }) 212 })
231 213
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts
index 514acf2e7..7cd61f539 100644
--- a/server/tests/api/users/users-verification.ts
+++ b/server/tests/api/users/users-verification.ts
@@ -3,18 +3,29 @@
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 registerUser, flushTests, getUserInformation, getMyUserInformation, killallServers, 6 cleanupTests,
7 userLogin, login, flushAndRunServer, ServerInfo, verifyEmail, updateCustomSubConfig, wait, cleanupTests 7 flushAndRunServer,
8 getMyUserInformation,
9 getUserInformation,
10 login,
11 registerUser,
12 ServerInfo,
13 updateCustomSubConfig,
14 updateMyUser,
15 userLogin,
16 verifyEmail
8} from '../../../../shared/extra-utils' 17} from '../../../../shared/extra-utils'
9import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' 18import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login'
10import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' 19import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email'
11import { waitJobs } from '../../../../shared/extra-utils/server/jobs' 20import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
21import { User } from '../../../../shared/models/users'
12 22
13const expect = chai.expect 23const expect = chai.expect
14 24
15describe('Test users account verification', function () { 25describe('Test users account verification', function () {
16 let server: ServerInfo 26 let server: ServerInfo
17 let userId: number 27 let userId: number
28 let userAccessToken: string
18 let verificationString: string 29 let verificationString: string
19 let expectedEmailsLength = 0 30 let expectedEmailsLength = 0
20 const user1 = { 31 const user1 = {
@@ -30,11 +41,12 @@ describe('Test users account verification', function () {
30 before(async function () { 41 before(async function () {
31 this.timeout(30000) 42 this.timeout(30000)
32 43
33 await MockSmtpServer.Instance.collectEmails(emails) 44 const port = await MockSmtpServer.Instance.collectEmails(emails)
34 45
35 const overrideConfig = { 46 const overrideConfig = {
36 smtp: { 47 smtp: {
37 hostname: 'localhost' 48 hostname: 'localhost',
49 port
38 } 50 }
39 } 51 }
40 server = await flushAndRunServer(1, overrideConfig) 52 server = await flushAndRunServer(1, overrideConfig)
@@ -82,11 +94,54 @@ describe('Test users account verification', function () {
82 94
83 it('Should verify the user via email and allow login', async function () { 95 it('Should verify the user via email and allow login', async function () {
84 await verifyEmail(server.url, userId, verificationString) 96 await verifyEmail(server.url, userId, verificationString)
85 await login(server.url, server.client, user1) 97
98 const res = await login(server.url, server.client, user1)
99 userAccessToken = res.body.access_token
100
86 const resUserVerified = await getUserInformation(server.url, server.accessToken, userId) 101 const resUserVerified = await getUserInformation(server.url, server.accessToken, userId)
87 expect(resUserVerified.body.emailVerified).to.be.true 102 expect(resUserVerified.body.emailVerified).to.be.true
88 }) 103 })
89 104
105 it('Should be able to change the user email', async function () {
106 let updateVerificationString: string
107
108 {
109 await updateMyUser({
110 url: server.url,
111 accessToken: userAccessToken,
112 email: 'updated@example.com',
113 currentPassword: user1.password
114 })
115
116 await waitJobs(server)
117 expectedEmailsLength++
118 expect(emails).to.have.lengthOf(expectedEmailsLength)
119
120 const email = emails[expectedEmailsLength - 1]
121
122 const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text'])
123 updateVerificationString = verificationStringMatches[1]
124 }
125
126 {
127 const res = await getMyUserInformation(server.url, userAccessToken)
128 const me: User = res.body
129
130 expect(me.email).to.equal('user_1@example.com')
131 expect(me.pendingEmail).to.equal('updated@example.com')
132 }
133
134 {
135 await verifyEmail(server.url, userId, updateVerificationString, true)
136
137 const res = await getMyUserInformation(server.url, userAccessToken)
138 const me: User = res.body
139
140 expect(me.email).to.equal('updated@example.com')
141 expect(me.pendingEmail).to.be.null
142 }
143 })
144
90 it('Should register user not requiring email verification if setting not enabled', async function () { 145 it('Should register user not requiring email verification if setting not enabled', async function () {
91 this.timeout(5000) 146 this.timeout(5000)
92 await updateCustomSubConfig(server.url, server.accessToken, { 147 await updateCustomSubConfig(server.url, server.accessToken, {
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts
index c8e32f3f5..403d1a089 100644
--- a/server/tests/api/users/users.ts
+++ b/server/tests/api/users/users.ts
@@ -17,11 +17,12 @@ import {
17 getUserInformation, 17 getUserInformation,
18 getUsersList, 18 getUsersList,
19 getUsersListPaginationAndSort, 19 getUsersListPaginationAndSort,
20 getVideoChannel,
20 getVideosList, 21 getVideosList,
21 login, 22 login,
22 makePutBodyRequest, 23 makePutBodyRequest,
23 rateVideo, 24 rateVideo,
24 registerUser, 25 registerUserWithChannel,
25 removeUser, 26 removeUser,
26 removeVideo, 27 removeVideo,
27 ServerInfo, 28 ServerInfo,
@@ -316,7 +317,7 @@ describe('Test users', function () {
316 317
317 const rootUser = users[ 1 ] 318 const rootUser = users[ 1 ]
318 expect(rootUser.username).to.equal('root') 319 expect(rootUser.username).to.equal('root')
319 expect(rootUser.email).to.equal('admin1@example.com') 320 expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com')
320 expect(user.nsfwPolicy).to.equal('display') 321 expect(user.nsfwPolicy).to.equal('display')
321 322
322 userId = user.id 323 userId = user.id
@@ -334,7 +335,7 @@ describe('Test users', function () {
334 335
335 const user = users[ 0 ] 336 const user = users[ 0 ]
336 expect(user.username).to.equal('root') 337 expect(user.username).to.equal('root')
337 expect(user.email).to.equal('admin1@example.com') 338 expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com')
338 expect(user.roleLabel).to.equal('Administrator') 339 expect(user.roleLabel).to.equal('Administrator')
339 expect(user.nsfwPolicy).to.equal('display') 340 expect(user.nsfwPolicy).to.equal('display')
340 }) 341 })
@@ -379,7 +380,7 @@ describe('Test users', function () {
379 expect(users.length).to.equal(2) 380 expect(users.length).to.equal(2)
380 381
381 expect(users[ 0 ].username).to.equal('root') 382 expect(users[ 0 ].username).to.equal('root')
382 expect(users[ 0 ].email).to.equal('admin1@example.com') 383 expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com')
383 expect(users[ 0 ].nsfwPolicy).to.equal('display') 384 expect(users[ 0 ].nsfwPolicy).to.equal('display')
384 385
385 expect(users[ 1 ].username).to.equal('user_1') 386 expect(users[ 1 ].username).to.equal('user_1')
@@ -467,10 +468,11 @@ describe('Test users', function () {
467 expect(user.autoPlayVideo).to.be.false 468 expect(user.autoPlayVideo).to.be.false
468 }) 469 })
469 470
470 it('Should be able to change the email display attribute', async function () { 471 it('Should be able to change the email attribute', async function () {
471 await updateMyUser({ 472 await updateMyUser({
472 url: server.url, 473 url: server.url,
473 accessToken: accessTokenUser, 474 accessToken: accessTokenUser,
475 currentPassword: 'new password',
474 email: 'updated@example.com' 476 email: 'updated@example.com'
475 }) 477 })
476 478
@@ -617,7 +619,10 @@ describe('Test users', function () {
617 619
618 describe('Registering a new user', function () { 620 describe('Registering a new user', function () {
619 it('Should register a new user', async function () { 621 it('Should register a new user', async function () {
620 await registerUser(server.url, 'user_15', 'my super password') 622 const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' }
623 const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' }
624
625 await registerUserWithChannel({ url: server.url, user, channel })
621 }) 626 })
622 627
623 it('Should be able to login with this registered user', async function () { 628 it('Should be able to login with this registered user', async function () {
@@ -629,6 +634,13 @@ describe('Test users', function () {
629 accessToken = await userLogin(server, user15) 634 accessToken = await userLogin(server, user15)
630 }) 635 })
631 636
637 it('Should have the correct display name', async function () {
638 const res = await getMyUserInformation(server.url, accessToken)
639 const user: User = res.body
640
641 expect(user.account.displayName).to.equal('super user 15')
642 })
643
632 it('Should have the correct video quota', async function () { 644 it('Should have the correct video quota', async function () {
633 const res = await getMyUserInformation(server.url, accessToken) 645 const res = await getMyUserInformation(server.url, accessToken)
634 const user = res.body 646 const user = res.body
@@ -636,6 +648,12 @@ describe('Test users', function () {
636 expect(user.videoQuota).to.equal(5 * 1024 * 1024) 648 expect(user.videoQuota).to.equal(5 * 1024 * 1024)
637 }) 649 })
638 650
651 it('Should have created the channel', async function () {
652 const res = await getVideoChannel(server.url, 'my_user_15_channel')
653
654 expect(res.body.displayName).to.equal('my channel rocks')
655 })
656
639 it('Should remove me', async function () { 657 it('Should remove me', async function () {
640 { 658 {
641 const res = await getUsersList(server.url, server.accessToken) 659 const res = await getUsersList(server.url, server.accessToken)
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts
index 68c1e9a8d..e9625e5f7 100644
--- a/server/tests/api/videos/multiple-servers.ts
+++ b/server/tests/api/videos/multiple-servers.ts
@@ -9,18 +9,17 @@ import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/
9import { 9import {
10 addVideoChannel, 10 addVideoChannel,
11 checkTmpIsEmpty, 11 checkTmpIsEmpty,
12 checkVideoFilesWereRemoved, cleanupTests, 12 checkVideoFilesWereRemoved,
13 cleanupTests,
13 completeVideoCheck, 14 completeVideoCheck,
14 createUser, 15 createUser,
15 dateIsValid, 16 dateIsValid,
16 doubleFollow, 17 doubleFollow,
17 flushAndRunMultipleServers, 18 flushAndRunMultipleServers,
18 flushTests,
19 getLocalVideos, 19 getLocalVideos,
20 getVideo, 20 getVideo,
21 getVideoChannelsList, 21 getVideoChannelsList,
22 getVideosList, 22 getVideosList,
23 killallServers,
24 rateVideo, 23 rateVideo,
25 removeVideo, 24 removeVideo,
26 ServerInfo, 25 ServerInfo,
@@ -110,7 +109,7 @@ describe('Test multiple servers', function () {
110 // All servers should have this video 109 // All servers should have this video
111 let publishedAt: string = null 110 let publishedAt: string = null
112 for (const server of servers) { 111 for (const server of servers) {
113 const isLocal = server.url === 'http://localhost:9001' 112 const isLocal = server.port === servers[0].port
114 const checkAttributes = { 113 const checkAttributes = {
115 name: 'my super name for server 1', 114 name: 'my super name for server 1',
116 category: 5, 115 category: 5,
@@ -122,7 +121,7 @@ describe('Test multiple servers', function () {
122 originallyPublishedAt: '2019-02-10T13:38:14.449Z', 121 originallyPublishedAt: '2019-02-10T13:38:14.449Z',
123 account: { 122 account: {
124 name: 'root', 123 name: 'root',
125 host: 'localhost:9001' 124 host: 'localhost:' + servers[0].port
126 }, 125 },
127 isLocal, 126 isLocal,
128 publishedAt, 127 publishedAt,
@@ -187,7 +186,7 @@ describe('Test multiple servers', function () {
187 186
188 // All servers should have this video 187 // All servers should have this video
189 for (const server of servers) { 188 for (const server of servers) {
190 const isLocal = server.url === 'http://localhost:9002' 189 const isLocal = server.url === 'http://localhost:' + servers[1].port
191 const checkAttributes = { 190 const checkAttributes = {
192 name: 'my super name for server 2', 191 name: 'my super name for server 2',
193 category: 4, 192 category: 4,
@@ -198,7 +197,7 @@ describe('Test multiple servers', function () {
198 support: 'my super support text for server 2', 197 support: 'my super support text for server 2',
199 account: { 198 account: {
200 name: 'user1', 199 name: 'user1',
201 host: 'localhost:9002' 200 host: 'localhost:' + servers[1].port
202 }, 201 },
203 isLocal, 202 isLocal,
204 commentsEnabled: true, 203 commentsEnabled: true,
@@ -216,7 +215,7 @@ describe('Test multiple servers', function () {
216 files: [ 215 files: [
217 { 216 {
218 resolution: 240, 217 resolution: 240,
219 size: 187000 218 size: 189000
220 }, 219 },
221 { 220 {
222 resolution: 360, 221 resolution: 360,
@@ -224,7 +223,7 @@ describe('Test multiple servers', function () {
224 }, 223 },
225 { 224 {
226 resolution: 480, 225 resolution: 480,
227 size: 383000 226 size: 384000
228 }, 227 },
229 { 228 {
230 resolution: 720, 229 resolution: 720,
@@ -278,7 +277,7 @@ describe('Test multiple servers', function () {
278 277
279 // All servers should have this video 278 // All servers should have this video
280 for (const server of servers) { 279 for (const server of servers) {
281 const isLocal = server.url === 'http://localhost:9003' 280 const isLocal = server.url === 'http://localhost:' + servers[2].port
282 const res = await getVideosList(server.url) 281 const res = await getVideosList(server.url)
283 282
284 const videos = res.body.data 283 const videos = res.body.data
@@ -306,7 +305,7 @@ describe('Test multiple servers', function () {
306 support: 'my super support text for server 3', 305 support: 'my super support text for server 3',
307 account: { 306 account: {
308 name: 'root', 307 name: 'root',
309 host: 'localhost:9003' 308 host: 'localhost:' + servers[2].port
310 }, 309 },
311 isLocal, 310 isLocal,
312 duration: 5, 311 duration: 5,
@@ -340,7 +339,7 @@ describe('Test multiple servers', function () {
340 support: 'my super support text for server 3-2', 339 support: 'my super support text for server 3-2',
341 account: { 340 account: {
342 name: 'root', 341 name: 'root',
343 host: 'localhost:9003' 342 host: 'localhost:' + servers[2].port
344 }, 343 },
345 commentsEnabled: true, 344 commentsEnabled: true,
346 downloadEnabled: true, 345 downloadEnabled: true,
@@ -646,7 +645,7 @@ describe('Test multiple servers', function () {
646 const videoUpdated = videos.find(video => video.name === 'my super video updated') 645 const videoUpdated = videos.find(video => video.name === 'my super video updated')
647 expect(!!videoUpdated).to.be.true 646 expect(!!videoUpdated).to.be.true
648 647
649 const isLocal = server.url === 'http://localhost:9003' 648 const isLocal = server.url === 'http://localhost:' + servers[2].port
650 const checkAttributes = { 649 const checkAttributes = {
651 name: 'my super video updated', 650 name: 'my super video updated',
652 category: 10, 651 category: 10,
@@ -658,7 +657,7 @@ describe('Test multiple servers', function () {
658 originallyPublishedAt: '2019-02-11T13:38:14.449Z', 657 originallyPublishedAt: '2019-02-11T13:38:14.449Z',
659 account: { 658 account: {
660 name: 'root', 659 name: 'root',
661 host: 'localhost:9003' 660 host: 'localhost:' + servers[2].port
662 }, 661 },
663 isLocal, 662 isLocal,
664 duration: 5, 663 duration: 5,
@@ -813,7 +812,7 @@ describe('Test multiple servers', function () {
813 expect(comment).to.not.be.undefined 812 expect(comment).to.not.be.undefined
814 expect(comment.inReplyToCommentId).to.be.null 813 expect(comment.inReplyToCommentId).to.be.null
815 expect(comment.account.name).to.equal('root') 814 expect(comment.account.name).to.equal('root')
816 expect(comment.account.host).to.equal('localhost:9001') 815 expect(comment.account.host).to.equal('localhost:' + servers[0].port)
817 expect(comment.totalReplies).to.equal(3) 816 expect(comment.totalReplies).to.equal(3)
818 expect(dateIsValid(comment.createdAt as string)).to.be.true 817 expect(dateIsValid(comment.createdAt as string)).to.be.true
819 expect(dateIsValid(comment.updatedAt as string)).to.be.true 818 expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -824,7 +823,7 @@ describe('Test multiple servers', function () {
824 expect(comment).to.not.be.undefined 823 expect(comment).to.not.be.undefined
825 expect(comment.inReplyToCommentId).to.be.null 824 expect(comment.inReplyToCommentId).to.be.null
826 expect(comment.account.name).to.equal('root') 825 expect(comment.account.name).to.equal('root')
827 expect(comment.account.host).to.equal('localhost:9003') 826 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
828 expect(comment.totalReplies).to.equal(0) 827 expect(comment.totalReplies).to.equal(0)
829 expect(dateIsValid(comment.createdAt as string)).to.be.true 828 expect(dateIsValid(comment.createdAt as string)).to.be.true
830 expect(dateIsValid(comment.updatedAt as string)).to.be.true 829 expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -842,25 +841,25 @@ describe('Test multiple servers', function () {
842 const tree: VideoCommentThreadTree = res2.body 841 const tree: VideoCommentThreadTree = res2.body
843 expect(tree.comment.text).equal('my super first comment') 842 expect(tree.comment.text).equal('my super first comment')
844 expect(tree.comment.account.name).equal('root') 843 expect(tree.comment.account.name).equal('root')
845 expect(tree.comment.account.host).equal('localhost:9001') 844 expect(tree.comment.account.host).equal('localhost:' + servers[0].port)
846 expect(tree.children).to.have.lengthOf(2) 845 expect(tree.children).to.have.lengthOf(2)
847 846
848 const firstChild = tree.children[0] 847 const firstChild = tree.children[0]
849 expect(firstChild.comment.text).to.equal('my super answer to thread 1') 848 expect(firstChild.comment.text).to.equal('my super answer to thread 1')
850 expect(firstChild.comment.account.name).equal('root') 849 expect(firstChild.comment.account.name).equal('root')
851 expect(firstChild.comment.account.host).equal('localhost:9002') 850 expect(firstChild.comment.account.host).equal('localhost:' + servers[1].port)
852 expect(firstChild.children).to.have.lengthOf(1) 851 expect(firstChild.children).to.have.lengthOf(1)
853 852
854 childOfFirstChild = firstChild.children[0] 853 childOfFirstChild = firstChild.children[0]
855 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') 854 expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
856 expect(childOfFirstChild.comment.account.name).equal('root') 855 expect(childOfFirstChild.comment.account.name).equal('root')
857 expect(childOfFirstChild.comment.account.host).equal('localhost:9003') 856 expect(childOfFirstChild.comment.account.host).equal('localhost:' + servers[2].port)
858 expect(childOfFirstChild.children).to.have.lengthOf(0) 857 expect(childOfFirstChild.children).to.have.lengthOf(0)
859 858
860 const secondChild = tree.children[1] 859 const secondChild = tree.children[1]
861 expect(secondChild.comment.text).to.equal('my second answer to thread 1') 860 expect(secondChild.comment.text).to.equal('my second answer to thread 1')
862 expect(secondChild.comment.account.name).equal('root') 861 expect(secondChild.comment.account.name).equal('root')
863 expect(secondChild.comment.account.host).equal('localhost:9003') 862 expect(secondChild.comment.account.host).equal('localhost:' + servers[2].port)
864 expect(secondChild.children).to.have.lengthOf(0) 863 expect(secondChild.children).to.have.lengthOf(0)
865 } 864 }
866 }) 865 })
@@ -915,7 +914,7 @@ describe('Test multiple servers', function () {
915 expect(comment).to.not.be.undefined 914 expect(comment).to.not.be.undefined
916 expect(comment.inReplyToCommentId).to.be.null 915 expect(comment.inReplyToCommentId).to.be.null
917 expect(comment.account.name).to.equal('root') 916 expect(comment.account.name).to.equal('root')
918 expect(comment.account.host).to.equal('localhost:9003') 917 expect(comment.account.host).to.equal('localhost:' + servers[2].port)
919 expect(comment.totalReplies).to.equal(0) 918 expect(comment.totalReplies).to.equal(0)
920 expect(dateIsValid(comment.createdAt as string)).to.be.true 919 expect(dateIsValid(comment.createdAt as string)).to.be.true
921 expect(dateIsValid(comment.updatedAt as string)).to.be.true 920 expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -971,7 +970,7 @@ describe('Test multiple servers', function () {
971 const res = await getVideosList(server.url) 970 const res = await getVideosList(server.url)
972 const video = res.body.data.find(v => v.name === 'minimum parameters') 971 const video = res.body.data.find(v => v.name === 'minimum parameters')
973 972
974 const isLocal = server.url === 'http://localhost:9002' 973 const isLocal = server.url === 'http://localhost:' + servers[1].port
975 const checkAttributes = { 974 const checkAttributes = {
976 name: 'minimum parameters', 975 name: 'minimum parameters',
977 category: null, 976 category: null,
@@ -982,7 +981,7 @@ describe('Test multiple servers', function () {
982 support: null, 981 support: null,
983 account: { 982 account: {
984 name: 'root', 983 name: 'root',
985 host: 'localhost:9002' 984 host: 'localhost:' + servers[1].port
986 }, 985 },
987 isLocal, 986 isLocal,
988 duration: 5, 987 duration: 5,
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts
index e9ad947b2..17172331f 100644
--- a/server/tests/api/videos/services.ts
+++ b/server/tests/api/videos/services.ts
@@ -27,13 +27,13 @@ describe('Test services', function () {
27 }) 27 })
28 28
29 it('Should have a valid oEmbed response', async function () { 29 it('Should have a valid oEmbed response', async function () {
30 const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid 30 const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
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:9001/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:9001/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)
39 expect(res.body.title).to.equal(server.video.name) 39 expect(res.body.title).to.equal(server.video.name)
@@ -41,19 +41,19 @@ describe('Test services', function () {
41 expect(res.body.width).to.equal(560) 41 expect(res.body.width).to.equal(560)
42 expect(res.body.height).to.equal(315) 42 expect(res.body.height).to.equal(315)
43 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl) 43 expect(res.body.thumbnail_url).to.equal(expectedThumbnailUrl)
44 expect(res.body.thumbnail_width).to.equal(560) 44 expect(res.body.thumbnail_width).to.equal(850)
45 expect(res.body.thumbnail_height).to.equal(315) 45 expect(res.body.thumbnail_height).to.equal(480)
46 }) 46 })
47 47
48 it('Should have a valid oEmbed response with small max height query', async function () { 48 it('Should have a valid oEmbed response with small max height query', async function () {
49 const oembedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid 49 const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
50 const format = 'json' 50 const format = 'json'
51 const maxHeight = 50 51 const maxHeight = 50
52 const maxWidth = 50 52 const maxWidth = 50
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:9001/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)
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts
index 1f366b642..d8f394ac7 100644
--- a/server/tests/api/videos/single-server.ts
+++ b/server/tests/api/videos/single-server.ts
@@ -37,7 +37,7 @@ describe('Test a single server', function () {
37 let videoUUID = '' 37 let videoUUID = ''
38 let videosListBase: any[] = null 38 let videosListBase: any[] = null
39 39
40 const getCheckAttributes = { 40 const getCheckAttributes = () => ({
41 name: 'my super name', 41 name: 'my super name',
42 category: 2, 42 category: 2,
43 licence: 6, 43 licence: 6,
@@ -47,7 +47,7 @@ describe('Test a single server', function () {
47 support: 'my super support text', 47 support: 'my super support text',
48 account: { 48 account: {
49 name: 'root', 49 name: 'root',
50 host: 'localhost:9001' 50 host: 'localhost:' + server.port
51 }, 51 },
52 isLocal: true, 52 isLocal: true,
53 duration: 5, 53 duration: 5,
@@ -68,9 +68,9 @@ describe('Test a single server', function () {
68 size: 218910 68 size: 218910
69 } 69 }
70 ] 70 ]
71 } 71 })
72 72
73 const updateCheckAttributes = { 73 const updateCheckAttributes = () => ({
74 name: 'my super video updated', 74 name: 'my super video updated',
75 category: 4, 75 category: 4,
76 licence: 2, 76 licence: 2,
@@ -80,7 +80,7 @@ describe('Test a single server', function () {
80 support: 'my super support text updated', 80 support: 'my super support text updated',
81 account: { 81 account: {
82 name: 'root', 82 name: 'root',
83 host: 'localhost:9001' 83 host: 'localhost:' + server.port
84 }, 84 },
85 isLocal: true, 85 isLocal: true,
86 tags: [ 'tagup1', 'tagup2' ], 86 tags: [ 'tagup1', 'tagup2' ],
@@ -101,7 +101,7 @@ describe('Test a single server', function () {
101 size: 292677 101 size: 292677
102 } 102 }
103 ] 103 ]
104 } 104 })
105 105
106 before(async function () { 106 before(async function () {
107 this.timeout(30000) 107 this.timeout(30000)
@@ -182,7 +182,7 @@ describe('Test a single server', function () {
182 expect(res.body.data.length).to.equal(1) 182 expect(res.body.data.length).to.equal(1)
183 183
184 const video = res.body.data[0] 184 const video = res.body.data[0]
185 await completeVideoCheck(server.url, video, getCheckAttributes) 185 await completeVideoCheck(server.url, video, getCheckAttributes())
186 }) 186 })
187 187
188 it('Should get the video by UUID', async function () { 188 it('Should get the video by UUID', async function () {
@@ -191,7 +191,7 @@ describe('Test a single server', function () {
191 const res = await getVideo(server.url, videoUUID) 191 const res = await getVideo(server.url, videoUUID)
192 192
193 const video = res.body 193 const video = res.body
194 await completeVideoCheck(server.url, video, getCheckAttributes) 194 await completeVideoCheck(server.url, video, getCheckAttributes())
195 }) 195 })
196 196
197 it('Should have the views updated', async function () { 197 it('Should have the views updated', async function () {
@@ -376,7 +376,7 @@ describe('Test a single server', function () {
376 const res = await getVideo(server.url, videoId) 376 const res = await getVideo(server.url, videoId)
377 const video = res.body 377 const video = res.body
378 378
379 await completeVideoCheck(server.url, video, updateCheckAttributes) 379 await completeVideoCheck(server.url, video, updateCheckAttributes())
380 }) 380 })
381 381
382 it('Should update only the tags of a video', async function () { 382 it('Should update only the tags of a video', async function () {
@@ -388,7 +388,7 @@ describe('Test a single server', function () {
388 const res = await getVideo(server.url, videoId) 388 const res = await getVideo(server.url, videoId)
389 const video = res.body 389 const video = res.body
390 390
391 await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes, attributes)) 391 await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes(), attributes))
392 }) 392 })
393 393
394 it('Should update only the description of a video', async function () { 394 it('Should update only the description of a video', async function () {
@@ -400,7 +400,8 @@ describe('Test a single server', function () {
400 const res = await getVideo(server.url, videoId) 400 const res = await getVideo(server.url, videoId)
401 const video = res.body 401 const video = res.body
402 402
403 await completeVideoCheck(server.url, video, Object.assign(updateCheckAttributes, attributes)) 403 const expectedAttributes = Object.assign(updateCheckAttributes(), { tags: [ 'supertag', 'tag1', 'tag2' ] }, attributes)
404 await completeVideoCheck(server.url, video, expectedAttributes)
404 }) 405 })
405 406
406 it('Should like a video', async function () { 407 it('Should like a video', async function () {
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts
index 7318497d5..a2f3ee161 100644
--- a/server/tests/api/videos/video-abuse.ts
+++ b/server/tests/api/videos/video-abuse.ts
@@ -9,7 +9,6 @@ import {
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 getVideoAbusesList, 10 getVideoAbusesList,
11 getVideosList, 11 getVideosList,
12 killallServers,
13 reportVideoAbuse, 12 reportVideoAbuse,
14 ServerInfo, 13 ServerInfo,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
@@ -90,7 +89,7 @@ describe('Test video abuses', function () {
90 const abuse: VideoAbuse = res1.body.data[0] 89 const abuse: VideoAbuse = res1.body.data[0]
91 expect(abuse.reason).to.equal('my super bad reason') 90 expect(abuse.reason).to.equal('my super bad reason')
92 expect(abuse.reporterAccount.name).to.equal('root') 91 expect(abuse.reporterAccount.name).to.equal('root')
93 expect(abuse.reporterAccount.host).to.equal('localhost:9001') 92 expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port)
94 expect(abuse.video.id).to.equal(servers[0].video.id) 93 expect(abuse.video.id).to.equal(servers[0].video.id)
95 94
96 const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) 95 const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken)
@@ -118,7 +117,7 @@ describe('Test video abuses', function () {
118 const abuse1: VideoAbuse = res1.body.data[0] 117 const abuse1: VideoAbuse = res1.body.data[0]
119 expect(abuse1.reason).to.equal('my super bad reason') 118 expect(abuse1.reason).to.equal('my super bad reason')
120 expect(abuse1.reporterAccount.name).to.equal('root') 119 expect(abuse1.reporterAccount.name).to.equal('root')
121 expect(abuse1.reporterAccount.host).to.equal('localhost:9001') 120 expect(abuse1.reporterAccount.host).to.equal('localhost:' + servers[0].port)
122 expect(abuse1.video.id).to.equal(servers[0].video.id) 121 expect(abuse1.video.id).to.equal(servers[0].video.id)
123 expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) 122 expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING)
124 expect(abuse1.state.label).to.equal('Pending') 123 expect(abuse1.state.label).to.equal('Pending')
@@ -127,7 +126,7 @@ describe('Test video abuses', function () {
127 const abuse2: VideoAbuse = res1.body.data[1] 126 const abuse2: VideoAbuse = res1.body.data[1]
128 expect(abuse2.reason).to.equal('my super bad reason 2') 127 expect(abuse2.reason).to.equal('my super bad reason 2')
129 expect(abuse2.reporterAccount.name).to.equal('root') 128 expect(abuse2.reporterAccount.name).to.equal('root')
130 expect(abuse2.reporterAccount.host).to.equal('localhost:9001') 129 expect(abuse2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
131 expect(abuse2.video.id).to.equal(servers[1].video.id) 130 expect(abuse2.video.id).to.equal(servers[1].video.id)
132 expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING) 131 expect(abuse2.state.id).to.equal(VideoAbuseState.PENDING)
133 expect(abuse2.state.label).to.equal('Pending') 132 expect(abuse2.state.label).to.equal('Pending')
@@ -141,7 +140,7 @@ describe('Test video abuses', function () {
141 abuseServer2 = res2.body.data[0] 140 abuseServer2 = res2.body.data[0]
142 expect(abuseServer2.reason).to.equal('my super bad reason 2') 141 expect(abuseServer2.reason).to.equal('my super bad reason 2')
143 expect(abuseServer2.reporterAccount.name).to.equal('root') 142 expect(abuseServer2.reporterAccount.name).to.equal('root')
144 expect(abuseServer2.reporterAccount.host).to.equal('localhost:9001') 143 expect(abuseServer2.reporterAccount.host).to.equal('localhost:' + servers[0].port)
145 expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING) 144 expect(abuseServer2.state.id).to.equal(VideoAbuseState.PENDING)
146 expect(abuseServer2.state.label).to.equal('Pending') 145 expect(abuseServer2.state.label).to.equal('Pending')
147 expect(abuseServer2.moderationComment).to.be.null 146 expect(abuseServer2.moderationComment).to.be.null
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts
index 1c0327d40..3a3add71b 100644
--- a/server/tests/api/videos/video-change-ownership.ts
+++ b/server/tests/api/videos/video-change-ownership.ts
@@ -4,7 +4,8 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { 5import {
6 acceptChangeOwnership, 6 acceptChangeOwnership,
7 changeVideoOwnership, cleanupTests, 7 changeVideoOwnership,
8 cleanupTests,
8 createUser, 9 createUser,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
@@ -13,7 +14,6 @@ import {
13 getVideo, 14 getVideo,
14 getVideoChangeOwnershipList, 15 getVideoChangeOwnershipList,
15 getVideosList, 16 getVideosList,
16 killallServers,
17 refuseChangeOwnership, 17 refuseChangeOwnership,
18 ServerInfo, 18 ServerInfo,
19 setAccessTokensToServers, 19 setAccessTokensToServers,
@@ -203,8 +203,8 @@ describe('Test video change ownership - nominal', function () {
203 } 203 }
204 }) 204 })
205 205
206 after(function () { 206 after(async function () {
207 killallServers(servers) 207 await cleanupTests(servers)
208 }) 208 })
209}) 209})
210 210
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index 345e96f43..4f600cae8 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -2,12 +2,12 @@
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { User, Video } from '../../../../shared/index' 5import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/index'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createUser, 8 createUser,
9 doubleFollow, 9 doubleFollow,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers, getVideo,
11 getVideoChannelVideos, 11 getVideoChannelVideos,
12 testImage, 12 testImage,
13 updateVideo, 13 updateVideo,
@@ -18,12 +18,10 @@ import {
18import { 18import {
19 addVideoChannel, 19 addVideoChannel,
20 deleteVideoChannel, 20 deleteVideoChannel,
21 flushTests,
22 getAccountVideoChannelsList, 21 getAccountVideoChannelsList,
23 getMyUserInformation, 22 getMyUserInformation,
24 getVideoChannel, 23 getVideoChannel,
25 getVideoChannelsList, 24 getVideoChannelsList,
26 killallServers,
27 ServerInfo, 25 ServerInfo,
28 setAccessTokensToServers, 26 setAccessTokensToServers,
29 updateVideoChannel 27 updateVideoChannel
@@ -35,13 +33,12 @@ const expect = chai.expect
35describe('Test video channels', function () { 33describe('Test video channels', function () {
36 let servers: ServerInfo[] 34 let servers: ServerInfo[]
37 let userInfo: User 35 let userInfo: User
38 let accountUUID: string
39 let firstVideoChannelId: number 36 let firstVideoChannelId: number
40 let secondVideoChannelId: number 37 let secondVideoChannelId: number
41 let videoUUID: string 38 let videoUUID: string
42 39
43 before(async function () { 40 before(async function () {
44 this.timeout(30000) 41 this.timeout(60000)
45 42
46 servers = await flushAndRunMultipleServers(2) 43 servers = await flushAndRunMultipleServers(2)
47 44
@@ -51,7 +48,6 @@ describe('Test video channels', function () {
51 { 48 {
52 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 49 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
53 const user: User = res.body 50 const user: User = res.body
54 accountUUID = user.account.uuid
55 51
56 firstVideoChannelId = user.videoChannels[0].id 52 firstVideoChannelId = user.videoChannels[0].id
57 } 53 }
@@ -83,7 +79,8 @@ describe('Test video channels', function () {
83 79
84 // The channel is 1 is propagated to servers 2 80 // The channel is 1 is propagated to servers 2
85 { 81 {
86 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name', channelId: secondVideoChannelId }) 82 const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' }
83 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributesArg)
87 videoUUID = res.body.video.uuid 84 videoUUID = res.body.video.uuid
88 } 85 }
89 86
@@ -108,7 +105,11 @@ describe('Test video channels', function () {
108 }) 105 })
109 106
110 it('Should have two video channels when getting account channels on server 1', async function () { 107 it('Should have two video channels when getting account channels on server 1', async function () {
111 const res = await getAccountVideoChannelsList(servers[0].url, userInfo.account.name + '@' + userInfo.account.host) 108 const res = await getAccountVideoChannelsList({
109 url: servers[ 0 ].url,
110 accountName: userInfo.account.name + '@' + userInfo.account.host
111 })
112
112 expect(res.body.total).to.equal(2) 113 expect(res.body.total).to.equal(2)
113 expect(res.body.data).to.be.an('array') 114 expect(res.body.data).to.be.an('array')
114 expect(res.body.data).to.have.lengthOf(2) 115 expect(res.body.data).to.have.lengthOf(2)
@@ -123,8 +124,62 @@ describe('Test video channels', function () {
123 expect(videoChannels[1].support).to.equal('super video channel support text') 124 expect(videoChannels[1].support).to.equal('super video channel support text')
124 }) 125 })
125 126
127 it('Should paginate and sort account channels', async function () {
128 {
129 const res = await getAccountVideoChannelsList({
130 url: servers[ 0 ].url,
131 accountName: userInfo.account.name + '@' + userInfo.account.host,
132 start: 0,
133 count: 1,
134 sort: 'createdAt'
135 })
136
137 expect(res.body.total).to.equal(2)
138 expect(res.body.data).to.have.lengthOf(1)
139
140 const videoChannel: VideoChannel = res.body.data[ 0 ]
141 expect(videoChannel.name).to.equal('root_channel')
142 }
143
144 {
145 const res = await getAccountVideoChannelsList({
146 url: servers[ 0 ].url,
147 accountName: userInfo.account.name + '@' + userInfo.account.host,
148 start: 0,
149 count: 1,
150 sort: '-createdAt'
151 })
152
153 expect(res.body.total).to.equal(2)
154 expect(res.body.data).to.have.lengthOf(1)
155
156 const videoChannel: VideoChannel = res.body.data[ 0 ]
157 expect(videoChannel.name).to.equal('second_video_channel')
158 }
159
160 {
161 const res = await getAccountVideoChannelsList({
162 url: servers[ 0 ].url,
163 accountName: userInfo.account.name + '@' + userInfo.account.host,
164 start: 1,
165 count: 1,
166 sort: '-createdAt'
167 })
168
169 expect(res.body.total).to.equal(2)
170 expect(res.body.data).to.have.lengthOf(1)
171
172 const videoChannel: VideoChannel = res.body.data[ 0 ]
173 expect(videoChannel.name).to.equal('root_channel')
174 }
175 })
176
126 it('Should have one video channel when getting account channels on server 2', async function () { 177 it('Should have one video channel when getting account channels on server 2', async function () {
127 const res = await getAccountVideoChannelsList(servers[1].url, userInfo.account.name + '@' + userInfo.account.host) 178 const res = await getAccountVideoChannelsList({
179 url: servers[ 1 ].url,
180 accountName: userInfo.account.name + '@' + userInfo.account.host
181 })
182
128 expect(res.body.total).to.equal(1) 183 expect(res.body.total).to.equal(1)
129 expect(res.body.data).to.be.an('array') 184 expect(res.body.data).to.be.an('array')
130 expect(res.body.data).to.have.lengthOf(1) 185 expect(res.body.data).to.have.lengthOf(1)
@@ -147,12 +202,12 @@ describe('Test video channels', function () {
147 }) 202 })
148 203
149 it('Should update video channel', async function () { 204 it('Should update video channel', async function () {
150 this.timeout(5000) 205 this.timeout(15000)
151 206
152 const videoChannelAttributes = { 207 const videoChannelAttributes = {
153 displayName: 'video channel updated', 208 displayName: 'video channel updated',
154 description: 'video channel description updated', 209 description: 'video channel description updated',
155 support: 'video channel support text updated' 210 support: 'support updated'
156 } 211 }
157 212
158 await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes) 213 await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
@@ -170,7 +225,36 @@ describe('Test video channels', function () {
170 expect(res.body.data[0].name).to.equal('second_video_channel') 225 expect(res.body.data[0].name).to.equal('second_video_channel')
171 expect(res.body.data[0].displayName).to.equal('video channel updated') 226 expect(res.body.data[0].displayName).to.equal('video channel updated')
172 expect(res.body.data[0].description).to.equal('video channel description updated') 227 expect(res.body.data[0].description).to.equal('video channel description updated')
173 expect(res.body.data[0].support).to.equal('video channel support text updated') 228 expect(res.body.data[0].support).to.equal('support updated')
229 }
230 })
231
232 it('Should not have updated the video support field', async function () {
233 for (const server of servers) {
234 const res = await getVideo(server.url, videoUUID)
235 const video: VideoDetails = res.body
236
237 expect(video.support).to.equal('video support field')
238 }
239 })
240
241 it('Should update the channel support field and update videos too', async function () {
242 this.timeout(35000)
243
244 const videoChannelAttributes = {
245 support: 'video channel support text updated',
246 bulkVideosSupportUpdate: true
247 }
248
249 await updateVideoChannel(servers[0].url, servers[0].accessToken, 'second_video_channel', videoChannelAttributes)
250
251 await waitJobs(servers)
252
253 for (const server of servers) {
254 const res = await getVideo(server.url, videoUUID)
255 const video: VideoDetails = res.body
256
257 expect(video.support).to.equal(videoChannelAttributes.support)
174 } 258 }
175 }) 259 })
176 260
@@ -213,7 +297,7 @@ describe('Test video channels', function () {
213 this.timeout(10000) 297 this.timeout(10000)
214 298
215 for (const server of servers) { 299 for (const server of servers) {
216 const channelURI = 'second_video_channel@localhost:9001' 300 const channelURI = 'second_video_channel@localhost:' + servers[0].port
217 const res1 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5) 301 const res1 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
218 expect(res1.body.total).to.equal(1) 302 expect(res1.body.total).to.equal(1)
219 expect(res1.body.data).to.be.an('array') 303 expect(res1.body.data).to.be.an('array')
@@ -234,11 +318,11 @@ describe('Test video channels', function () {
234 this.timeout(10000) 318 this.timeout(10000)
235 319
236 for (const server of servers) { 320 for (const server of servers) {
237 const secondChannelURI = 'second_video_channel@localhost:9001' 321 const secondChannelURI = 'second_video_channel@localhost:' + servers[0].port
238 const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondChannelURI, 0, 5) 322 const res1 = await getVideoChannelVideos(server.url, server.accessToken, secondChannelURI, 0, 5)
239 expect(res1.body.total).to.equal(0) 323 expect(res1.body.total).to.equal(0)
240 324
241 const channelURI = 'root_channel@localhost:9001' 325 const channelURI = 'root_channel@localhost:' + servers[0].port
242 const res2 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5) 326 const res2 = await getVideoChannelVideos(server.url, server.accessToken, channelURI, 0, 5)
243 expect(res2.body.total).to.equal(1) 327 expect(res2.body.total).to.equal(1)
244 328
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts
index 22fd8c058..82182cc7c 100644
--- a/server/tests/api/videos/video-comments.ts
+++ b/server/tests/api/videos/video-comments.ts
@@ -66,8 +66,8 @@ describe('Test video comments', function () {
66 expect(comment.videoId).to.equal(videoId) 66 expect(comment.videoId).to.equal(videoId)
67 expect(comment.id).to.equal(comment.threadId) 67 expect(comment.id).to.equal(comment.threadId)
68 expect(comment.account.name).to.equal('root') 68 expect(comment.account.name).to.equal('root')
69 expect(comment.account.host).to.equal('localhost:9001') 69 expect(comment.account.host).to.equal('localhost:' + server.port)
70 expect(comment.account.url).to.equal('http://localhost:9001/accounts/root') 70 expect(comment.account.url).to.equal('http://localhost:' + server.port + '/accounts/root')
71 expect(comment.totalReplies).to.equal(0) 71 expect(comment.totalReplies).to.equal(0)
72 expect(dateIsValid(comment.createdAt as string)).to.be.true 72 expect(dateIsValid(comment.createdAt as string)).to.be.true
73 expect(dateIsValid(comment.updatedAt as string)).to.be.true 73 expect(dateIsValid(comment.updatedAt as string)).to.be.true
@@ -86,7 +86,7 @@ describe('Test video comments', function () {
86 expect(comment.videoId).to.equal(videoId) 86 expect(comment.videoId).to.equal(videoId)
87 expect(comment.id).to.equal(comment.threadId) 87 expect(comment.id).to.equal(comment.threadId)
88 expect(comment.account.name).to.equal('root') 88 expect(comment.account.name).to.equal('root')
89 expect(comment.account.host).to.equal('localhost:9001') 89 expect(comment.account.host).to.equal('localhost:' + server.port)
90 90
91 await testImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png') 91 await testImage(server.url, 'avatar-resized', comment.account.avatar.path, '.png')
92 92
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts
index 22031c18b..39178bb1a 100644
--- a/server/tests/api/videos/video-hls.ts
+++ b/server/tests/api/videos/video-hls.ts
@@ -5,13 +5,12 @@ import 'mocha'
5import { 5import {
6 checkDirectoryIsEmpty, 6 checkDirectoryIsEmpty,
7 checkSegmentHash, 7 checkSegmentHash,
8 checkTmpIsEmpty, cleanupTests, 8 checkTmpIsEmpty,
9 cleanupTests,
9 doubleFollow, 10 doubleFollow,
10 flushAndRunMultipleServers, 11 flushAndRunMultipleServers,
11 flushTests,
12 getPlaylist, 12 getPlaylist,
13 getVideo, 13 getVideo,
14 killallServers,
15 removeVideo, 14 removeVideo,
16 ServerInfo, 15 ServerInfo,
17 setAccessTokensToServers, 16 setAccessTokensToServers,
@@ -22,12 +21,11 @@ import {
22import { VideoDetails } from '../../../../shared/models/videos' 21import { VideoDetails } from '../../../../shared/models/videos'
23import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' 22import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type'
24import { join } from 'path' 23import { join } from 'path'
24import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants'
25 25
26const expect = chai.expect 26const expect = chai.expect
27 27
28async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) { 28async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, resolutions = [ 240, 360, 480, 720 ]) {
29 const resolutions = [ 240, 360, 480, 720 ]
30
31 for (const server of servers) { 29 for (const server of servers) {
32 const res = await getVideo(server.url, videoUUID) 30 const res = await getVideo(server.url, videoUUID)
33 const videoDetails: VideoDetails = res.body 31 const videoDetails: VideoDetails = res.body
@@ -42,16 +40,15 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
42 40
43 const masterPlaylist = res2.text 41 const masterPlaylist = res2.text
44 42
45 expect(masterPlaylist).to.contain('#EXT-X-STREAM-INF:BANDWIDTH=55472,RESOLUTION=640x360,FRAME-RATE=25')
46
47 for (const resolution of resolutions) { 43 for (const resolution of resolutions) {
44 expect(masterPlaylist).to.match(new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+'))
48 expect(masterPlaylist).to.contain(`${resolution}.m3u8`) 45 expect(masterPlaylist).to.contain(`${resolution}.m3u8`)
49 } 46 }
50 } 47 }
51 48
52 { 49 {
53 for (const resolution of resolutions) { 50 for (const resolution of resolutions) {
54 const res2 = await getPlaylist(`http://localhost:9001/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`) 51 const res2 = await getPlaylist(`http://localhost:${servers[0].port}/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`)
55 52
56 const subPlaylist = res2.text 53 const subPlaylist = res2.text
57 expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`) 54 expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`)
@@ -59,7 +56,7 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
59 } 56 }
60 57
61 { 58 {
62 const baseUrl = 'http://localhost:9001/static/streaming-playlists/hls' 59 const baseUrl = 'http://localhost:' + servers[0].port + '/static/streaming-playlists/hls'
63 60
64 for (const resolution of resolutions) { 61 for (const resolution of resolutions) {
65 await checkSegmentHash(baseUrl, baseUrl, videoUUID, resolution, hlsPlaylist) 62 await checkSegmentHash(baseUrl, baseUrl, videoUUID, resolution, hlsPlaylist)
@@ -71,11 +68,21 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) {
71describe('Test HLS videos', function () { 68describe('Test HLS videos', function () {
72 let servers: ServerInfo[] = [] 69 let servers: ServerInfo[] = []
73 let videoUUID = '' 70 let videoUUID = ''
71 let videoAudioUUID = ''
74 72
75 before(async function () { 73 before(async function () {
76 this.timeout(120000) 74 this.timeout(120000)
77 75
78 servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: true, hls: { enabled: true } } }) 76 const configOverride = {
77 transcoding: {
78 enabled: true,
79 allow_audio_files: true,
80 hls: {
81 enabled: true
82 }
83 }
84 }
85 servers = await flushAndRunMultipleServers(2, configOverride)
79 86
80 // Get the access tokens 87 // Get the access tokens
81 await setAccessTokensToServers(servers) 88 await setAccessTokensToServers(servers)
@@ -87,17 +94,28 @@ describe('Test HLS videos', function () {
87 it('Should upload a video and transcode it to HLS', async function () { 94 it('Should upload a video and transcode it to HLS', async function () {
88 this.timeout(120000) 95 this.timeout(120000)
89 96
90 { 97 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' })
91 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) 98 videoUUID = res.body.video.uuid
92 videoUUID = res.body.video.uuid
93 }
94 99
95 await waitJobs(servers) 100 await waitJobs(servers)
96 101
97 await checkHlsPlaylist(servers, videoUUID) 102 await checkHlsPlaylist(servers, videoUUID)
98 }) 103 })
99 104
105 it('Should upload an audio file and transcode it to HLS', async function () {
106 this.timeout(120000)
107
108 const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video audio', fixture: 'sample.ogg' })
109 videoAudioUUID = res.body.video.uuid
110
111 await waitJobs(servers)
112
113 await checkHlsPlaylist(servers, videoAudioUUID, [ DEFAULT_AUDIO_RESOLUTION ])
114 })
115
100 it('Should update the video', async function () { 116 it('Should update the video', async function () {
117 this.timeout(10000)
118
101 await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' }) 119 await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' })
102 120
103 await waitJobs(servers) 121 await waitJobs(servers)
@@ -105,13 +123,17 @@ describe('Test HLS videos', function () {
105 await checkHlsPlaylist(servers, videoUUID) 123 await checkHlsPlaylist(servers, videoUUID)
106 }) 124 })
107 125
108 it('Should delete the video', async function () { 126 it('Should delete videos', async function () {
127 this.timeout(10000)
128
109 await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) 129 await removeVideo(servers[0].url, servers[0].accessToken, videoUUID)
130 await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID)
110 131
111 await waitJobs(servers) 132 await waitJobs(servers)
112 133
113 for (const server of servers) { 134 for (const server of servers) {
114 await getVideo(server.url, videoUUID, 404) 135 await getVideo(server.url, videoUUID, 404)
136 await getVideo(server.url, videoAudioUUID, 404)
115 } 137 }
116 }) 138 })
117 139
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index fd5e4c4be..f82c8cbce 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -371,7 +371,7 @@ describe('Test video playlists', function () {
371 371
372 for (const server of servers) { 372 for (const server of servers) {
373 const results = [ 373 const results = [
374 await getAccountPlaylistsList(server.url, 'root@localhost:9002', 0, 5, '-createdAt'), 374 await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'),
375 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') 375 await getVideoPlaylistsList(server.url, 0, 2, '-createdAt')
376 ] 376 ]
377 377
@@ -754,6 +754,39 @@ describe('Test video playlists', function () {
754 } 754 }
755 }) 755 })
756 756
757 it('Should be able to create a public playlist, and set it to private', async function () {
758 this.timeout(30000)
759
760 const res = await createVideoPlaylist({
761 url: servers[0].url,
762 token: servers[0].accessToken,
763 playlistAttrs: {
764 displayName: 'my super public playlist',
765 privacy: VideoPlaylistPrivacy.PUBLIC,
766 videoChannelId: servers[0].videoChannel.id
767 }
768 })
769 const videoPlaylistIds = res.body.videoPlaylist
770
771 await waitJobs(servers)
772
773 for (const server of servers) {
774 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 200)
775 }
776
777 const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE }
778 await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs })
779
780 await waitJobs(servers)
781
782 for (const server of [ servers[1], servers[2] ]) {
783 await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404)
784 }
785 await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401)
786
787 await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200)
788 })
789
757 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { 790 it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () {
758 this.timeout(30000) 791 this.timeout(30000)
759 792
@@ -770,7 +803,7 @@ describe('Test video playlists', function () {
770 this.timeout(30000) 803 this.timeout(30000)
771 804
772 for (const server of servers) { 805 for (const server of servers) {
773 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.serverNumber) 806 await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.internalServerNumber)
774 } 807 }
775 }) 808 })
776 809
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts
index ef1cf0f07..40b539106 100644
--- a/server/tests/api/videos/video-privacy.ts
+++ b/server/tests/api/videos/video-privacy.ts
@@ -6,8 +6,7 @@ import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enu
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 flushAndRunMultipleServers, 8 flushAndRunMultipleServers,
9 getVideosList, 9 getVideosList, getVideosListWithToken,
10 killallServers,
11 ServerInfo, 10 ServerInfo,
12 setAccessTokensToServers, 11 setAccessTokensToServers,
13 uploadVideo 12 uploadVideo
@@ -153,6 +152,29 @@ describe('Test video privacy', function () {
153 } 152 }
154 }) 153 })
155 154
155 it('Should set this new video as private', async function () {
156 this.timeout(10000)
157
158 await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, { privacy: VideoPrivacy.PRIVATE })
159
160 await waitJobs(servers)
161
162 for (const server of servers) {
163 const res = await getVideosList(server.url)
164
165 expect(res.body.total).to.equal(0)
166 expect(res.body.data).to.have.lengthOf(0)
167 }
168
169 {
170 const res = await getMyVideos(servers[0].url, servers[0].accessToken, 0, 5)
171
172 expect(res.body.total).to.equal(1)
173 expect(res.body.data).to.have.lengthOf(1)
174 expect(res.body.data[0].name).to.equal('super video public')
175 }
176 })
177
156 after(async function () { 178 after(async function () {
157 await cleanupTests(servers) 179 await cleanupTests(servers)
158 }) 180 })
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index 3cd43e99b..90ade1652 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -4,24 +4,25 @@ import * as chai from 'chai'
4import 'mocha' 4import 'mocha'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' 6import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
7import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 7import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
8import { 8import {
9 buildAbsoluteFixturePath, cleanupTests, 9 buildAbsoluteFixturePath,
10 cleanupTests,
10 doubleFollow, 11 doubleFollow,
11 flushAndRunMultipleServers, 12 flushAndRunMultipleServers,
12 generateHighBitrateVideo, 13 generateHighBitrateVideo,
13 getMyVideos, 14 getMyVideos,
14 getVideo, 15 getVideo,
15 getVideosList, 16 getVideosList,
16 killallServers, 17 makeGetRequest,
17 root, 18 root,
18 ServerInfo, 19 ServerInfo,
19 setAccessTokensToServers, 20 setAccessTokensToServers,
20 uploadVideo, 21 uploadVideo,
22 waitJobs,
21 webtorrentAdd 23 webtorrentAdd
22} from '../../../../shared/extra-utils' 24} from '../../../../shared/extra-utils'
23import { extname, join } from 'path' 25import { join } from 'path'
24import { waitJobs } from '../../../../shared/extra-utils/server/jobs'
25import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' 26import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
26 27
27const expect = chai.expect 28const expect = chai.expect
@@ -121,7 +122,7 @@ describe('Test video transcoding', function () {
121 122
122 expect(videoDetails.files).to.have.lengthOf(4) 123 expect(videoDetails.files).to.have.lengthOf(4)
123 124
124 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4') 125 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
125 const probe = await audio.get(path) 126 const probe = await audio.get(path)
126 127
127 if (probe.audioStream) { 128 if (probe.audioStream) {
@@ -152,7 +153,7 @@ describe('Test video transcoding', function () {
152 const videoDetails: VideoDetails = res2.body 153 const videoDetails: VideoDetails = res2.body
153 154
154 expect(videoDetails.files).to.have.lengthOf(4) 155 expect(videoDetails.files).to.have.lengthOf(4)
155 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4') 156 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
156 const probe = await audio.get(path) 157 const probe = await audio.get(path)
157 expect(probe).to.not.have.property('audioStream') 158 expect(probe).to.not.have.property('audioStream')
158 } 159 }
@@ -179,7 +180,7 @@ describe('Test video transcoding', function () {
179 expect(videoDetails.files).to.have.lengthOf(4) 180 expect(videoDetails.files).to.have.lengthOf(4)
180 const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture) 181 const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture)
181 const fixtureVideoProbe = await audio.get(fixturePath) 182 const fixtureVideoProbe = await audio.get(fixturePath)
182 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4') 183 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
183 const videoProbe = await audio.get(path) 184 const videoProbe = await audio.get(path)
184 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) { 185 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
185 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ] 186 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
@@ -216,13 +217,13 @@ describe('Test video transcoding', function () {
216 expect(videoDetails.files[ 3 ].fps).to.be.below(31) 217 expect(videoDetails.files[ 3 ].fps).to.be.below(31)
217 218
218 for (const resolution of [ '240', '360', '480' ]) { 219 for (const resolution of [ '240', '360', '480' ]) {
219 const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4') 220 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
220 const fps = await getVideoFileFPS(path) 221 const fps = await getVideoFileFPS(path)
221 222
222 expect(fps).to.be.below(31) 223 expect(fps).to.be.below(31)
223 } 224 }
224 225
225 const path = join(root(), 'test2', 'videos', video.uuid + '-720.mp4') 226 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4')
226 const fps = await getVideoFileFPS(path) 227 const fps = await getVideoFileFPS(path)
227 228
228 expect(fps).to.be.above(58).and.below(62) 229 expect(fps).to.be.above(58).and.below(62)
@@ -310,7 +311,7 @@ describe('Test video transcoding', function () {
310 const video = res.body.data.find(v => v.name === videoAttributes.name) 311 const video = res.body.data.find(v => v.name === videoAttributes.name)
311 312
312 for (const resolution of ['240', '360', '480', '720', '1080']) { 313 for (const resolution of ['240', '360', '480', '720', '1080']) {
313 const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4') 314 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
314 const bitrate = await getVideoFileBitrate(path) 315 const bitrate = await getVideoFileBitrate(path)
315 const fps = await getVideoFileFPS(path) 316 const fps = await getVideoFileFPS(path)
316 const resolution2 = await getVideoFileResolution(path) 317 const resolution2 = await getVideoFileResolution(path)
@@ -324,6 +325,15 @@ describe('Test video transcoding', function () {
324 it('Should accept and transcode additional extensions', async function () { 325 it('Should accept and transcode additional extensions', async function () {
325 this.timeout(300000) 326 this.timeout(300000)
326 327
328 let tempFixturePath: string
329
330 {
331 tempFixturePath = await generateHighBitrateVideo()
332
333 const bitrate = await getVideoFileBitrate(tempFixturePath)
334 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
335 }
336
327 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) { 337 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
328 const videoAttributes = { 338 const videoAttributes = {
329 name: fixture, 339 name: fixture,
@@ -349,6 +359,63 @@ describe('Test video transcoding', function () {
349 } 359 }
350 }) 360 })
351 361
362 it('Should correctly detect if quick transcode is possible', async function () {
363 this.timeout(10000)
364
365 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
366 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
367 })
368
369 it('Should merge an audio file with the preview file', async function () {
370 this.timeout(60000)
371
372 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
373 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
374
375 await waitJobs(servers)
376
377 for (const server of servers) {
378 const res = await getVideosList(server.url)
379
380 const video = res.body.data.find(v => v.name === 'audio_with_preview')
381 const res2 = await getVideo(server.url, video.id)
382 const videoDetails: VideoDetails = res2.body
383
384 expect(videoDetails.files).to.have.lengthOf(1)
385
386 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
387 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
388
389 const magnetUri = videoDetails.files[ 0 ].magnetUri
390 expect(magnetUri).to.contain('.mp4')
391 }
392 })
393
394 it('Should upload an audio file and choose a default background image', async function () {
395 this.timeout(60000)
396
397 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
398 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
399
400 await waitJobs(servers)
401
402 for (const server of servers) {
403 const res = await getVideosList(server.url)
404
405 const video = res.body.data.find(v => v.name === 'audio_without_preview')
406 const res2 = await getVideo(server.url, video.id)
407 const videoDetails = res2.body
408
409 expect(videoDetails.files).to.have.lengthOf(1)
410
411 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
412 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
413
414 const magnetUri = videoDetails.files[ 0 ].magnetUri
415 expect(magnetUri).to.contain('.mp4')
416 }
417 })
418
352 after(async function () { 419 after(async function () {
353 await cleanupTests(servers) 420 await cleanupTests(servers)
354 }) 421 })
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts
index c21d46d56..fbddd40f4 100644
--- a/server/tests/api/videos/videos-views-cleaner.ts
+++ b/server/tests/api/videos/videos-views-cleaner.ts
@@ -10,7 +10,7 @@ import {
10 flushAndRunServer, 10 flushAndRunServer,
11 ServerInfo, 11 ServerInfo,
12 setAccessTokensToServers, 12 setAccessTokensToServers,
13 uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests 13 uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests, closeAllSequelize
14} from '../../../../shared/extra-utils' 14} from '../../../../shared/extra-utils'
15import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' 15import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews'
16import { VideosOverview } from '../../../../shared/models/overviews' 16import { VideosOverview } from '../../../../shared/models/overviews'
@@ -58,14 +58,14 @@ describe('Test video views cleaner', function () {
58 58
59 { 59 {
60 for (const server of servers) { 60 for (const server of servers) {
61 const total = await countVideoViewsOf(server.serverNumber, videoIdServer1) 61 const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1)
62 expect(total).to.equal(2) 62 expect(total).to.equal(2)
63 } 63 }
64 } 64 }
65 65
66 { 66 {
67 for (const server of servers) { 67 for (const server of servers) {
68 const total = await countVideoViewsOf(server.serverNumber, videoIdServer2) 68 const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer2)
69 expect(total).to.equal(2) 69 expect(total).to.equal(2)
70 } 70 }
71 } 71 }
@@ -74,8 +74,6 @@ describe('Test video views cleaner', function () {
74 it('Should clean old video views', async function () { 74 it('Should clean old video views', async function () {
75 this.timeout(50000) 75 this.timeout(50000)
76 76
77 this.timeout(50000)
78
79 killallServers([ servers[0] ]) 77 killallServers([ servers[0] ])
80 78
81 await reRunServer(servers[0], { views: { videos: { remote: { max_age: '5 seconds' } } } }) 79 await reRunServer(servers[0], { views: { videos: { remote: { max_age: '5 seconds' } } } })
@@ -86,21 +84,23 @@ describe('Test video views cleaner', function () {
86 84
87 { 85 {
88 for (const server of servers) { 86 for (const server of servers) {
89 const total = await countVideoViewsOf(server.serverNumber, videoIdServer1) 87 const total = await countVideoViewsOf(server.internalServerNumber, videoIdServer1)
90 expect(total).to.equal(2) 88 expect(total).to.equal(2)
91 } 89 }
92 } 90 }
93 91
94 { 92 {
95 const totalServer1 = await countVideoViewsOf(servers[0].serverNumber, videoIdServer2) 93 const totalServer1 = await countVideoViewsOf(servers[0].internalServerNumber, videoIdServer2)
96 expect(totalServer1).to.equal(0) 94 expect(totalServer1).to.equal(0)
97 95
98 const totalServer2 = await countVideoViewsOf(servers[1].serverNumber, videoIdServer2) 96 const totalServer2 = await countVideoViewsOf(servers[1].internalServerNumber, videoIdServer2)
99 expect(totalServer2).to.equal(2) 97 expect(totalServer2).to.equal(2)
100 } 98 }
101 }) 99 })
102 100
103 after(async function () { 101 after(async function () {
102 await closeAllSequelize(servers)
103
104 await cleanupTests(servers) 104 await cleanupTests(servers)
105 }) 105 })
106}) 106})
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts
index 5e12c0089..3822fca42 100644
--- a/server/tests/cli/optimize-old-videos.ts
+++ b/server/tests/cli/optimize-old-videos.ts
@@ -8,14 +8,16 @@ import {
8 doubleFollow, 8 doubleFollow,
9 execCLI, 9 execCLI,
10 flushAndRunMultipleServers, 10 flushAndRunMultipleServers,
11 flushTests, generateHighBitrateVideo, 11 generateHighBitrateVideo,
12 getEnvCli, 12 getEnvCli,
13 getVideo, 13 getVideo,
14 getVideosList, 14 getVideosList,
15 killallServers, root, 15 root,
16 ServerInfo, 16 ServerInfo,
17 setAccessTokensToServers, 17 setAccessTokensToServers,
18 uploadVideo, viewVideo, wait 18 uploadVideo,
19 viewVideo,
20 wait
19} from '../../../shared/extra-utils' 21} from '../../../shared/extra-utils'
20import { waitJobs } from '../../../shared/extra-utils/server/jobs' 22import { waitJobs } from '../../../shared/extra-utils/server/jobs'
21import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils' 23import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils'
@@ -102,7 +104,7 @@ describe('Test optimize old videos', function () {
102 104
103 expect(file.size).to.be.below(5000000) 105 expect(file.size).to.be.below(5000000)
104 106
105 const path = join(root(), 'test1', 'videos', video.uuid + '-' + file.resolution.id + '.mp4') 107 const path = join(root(), 'test' + servers[0].internalServerNumber, 'videos', video.uuid + '-' + file.resolution.id + '.mp4')
106 const bitrate = await getVideoFileBitrate(path) 108 const bitrate = await getVideoFileBitrate(path)
107 const fps = await getVideoFileFPS(path) 109 const fps = await getVideoFileFPS(path)
108 const resolution = await getVideoFileResolution(path) 110 const resolution = await getVideoFileResolution(path)
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts
index 80bbc98d5..d73e27564 100644
--- a/server/tests/cli/peertube.ts
+++ b/server/tests/cli/peertube.ts
@@ -1,28 +1,46 @@
1/* tslint:disable:no-unused-expression */
2
1import 'mocha' 3import 'mocha'
4import { expect } from 'chai'
2import { 5import {
3 expect 6 addVideoChannel,
4} from 'chai' 7 buildAbsoluteFixturePath,
5import { 8 cleanupTests,
6 createUser, 9 createUser,
7 execCLI, 10 execCLI,
8 flushTests,
9 getEnvCli,
10 killallServers,
11 flushAndRunServer, 11 flushAndRunServer,
12 getEnvCli,
13 getVideo,
14 getVideosList,
15 getVideosListWithToken, removeVideo,
12 ServerInfo, 16 ServerInfo,
13 setAccessTokensToServers, cleanupTests 17 setAccessTokensToServers,
18 userLogin,
19 waitJobs
14} from '../../../shared/extra-utils' 20} from '../../../shared/extra-utils'
21import { Video, VideoDetails } from '../../../shared'
22import { getYoutubeVideoUrl } from '../../../shared/extra-utils/videos/video-imports'
15 23
16describe('Test CLI wrapper', function () { 24describe('Test CLI wrapper', function () {
17 let server: ServerInfo 25 let server: ServerInfo
26 let userAccessToken: string
27
18 const cmd = 'node ./dist/server/tools/peertube.js' 28 const cmd = 'node ./dist/server/tools/peertube.js'
19 29
20 before(async function () { 30 before(async function () {
21 this.timeout(30000) 31 this.timeout(30000)
32
22 server = await flushAndRunServer(1) 33 server = await flushAndRunServer(1)
23 await setAccessTokensToServers([ server ]) 34 await setAccessTokensToServers([ server ])
24 35
25 await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' }) 36 await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super_password' })
37
38 userAccessToken = await userLogin(server, { username: 'user_1', password: 'super_password' })
39
40 {
41 const args = { name: 'user_channel', displayName: 'User channel', support: 'super support text' }
42 await addVideoChannel(server.url, userAccessToken, args)
43 }
26 }) 44 })
27 45
28 it('Should display no selected instance', async function () { 46 it('Should display no selected instance', async function () {
@@ -31,21 +49,132 @@ describe('Test CLI wrapper', function () {
31 const env = getEnvCli(server) 49 const env = getEnvCli(server)
32 const stdout = await execCLI(`${env} ${cmd} --help`) 50 const stdout = await execCLI(`${env} ${cmd} --help`)
33 51
34 expect(stdout).to.contain('selected') 52 expect(stdout).to.contain('no instance selected')
53 })
54
55 it('Should add a user', async function () {
56 this.timeout(60000)
57
58 const env = getEnvCli(server)
59 await execCLI(`${env} ${cmd} auth add -u ${server.url} -U user_1 -p super_password`)
60 })
61
62 it('Should default to this user', async function () {
63 this.timeout(60000)
64
65 const env = getEnvCli(server)
66 const stdout = await execCLI(`${env} ${cmd} --help`)
67
68 expect(stdout).to.contain(`instance ${server.url} selected`)
69 })
70
71 it('Should remember the user', async function () {
72 this.timeout(60000)
73
74 const env = getEnvCli(server)
75 const stdout = await execCLI(`${env} ${cmd} auth list`)
76
77 expect(stdout).to.contain(server.url)
78 })
79
80 it('Should upload a video', async function () {
81 this.timeout(60000)
82
83 const env = getEnvCli(server)
84
85 const fixture = buildAbsoluteFixturePath('60fps_720p_small.mp4')
86
87 const params = `-f ${fixture} --video-name 'test upload' --channel-name user_channel --support 'support_text'`
88
89 await execCLI(`${env} ${cmd} upload ${params}`)
90 })
91
92 it('Should have the video uploaded', async function () {
93 const res = await getVideosList(server.url)
94
95 expect(res.body.total).to.equal(1)
96
97 const videos: Video[] = res.body.data
98
99 const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body
100
101 expect(video.name).to.equal('test upload')
102 expect(video.support).to.equal('support_text')
103 expect(video.channel.name).to.equal('user_channel')
35 }) 104 })
36 105
37 it('Should remember the authentifying material of the user', async function () { 106 it('Should import a video', async function () {
38 this.timeout(60000) 107 this.timeout(60000)
39 108
40 const env = getEnvCli(server) 109 const env = getEnvCli(server)
41 await execCLI(`${env} ` + cmd + ` auth add --url ${server.url} -U user_1 -p "super password"`) 110
111 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel`
112
113 await execCLI(`${env} ${cmd} import ${params}`)
114 })
115
116 it('Should have imported the video', async function () {
117 this.timeout(60000)
118
119 await waitJobs([ server ])
120
121 const res = await getVideosList(server.url)
122
123 expect(res.body.total).to.equal(2)
124
125 const videos: Video[] = res.body.data
126 const video = videos.find(v => v.name === 'small video - youtube')
127 expect(video).to.not.be.undefined
128
129 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
130 expect(videoDetails.channel.name).to.equal('user_channel')
131 expect(videoDetails.support).to.equal('super support text')
132 expect(videoDetails.nsfw).to.be.false
133
134 // So we can reimport it
135 await removeVideo(server.url, userAccessToken, video.id)
136 })
137
138 it('Should import and override some imported attributes', async function () {
139 this.timeout(60000)
140
141 const env = getEnvCli(server)
142
143 const params = `--target-url ${getYoutubeVideoUrl()} --channel-name user_channel --video-name toto --nsfw --support support`
144
145 await execCLI(`${env} ${cmd} import ${params}`)
146
147 await waitJobs([ server ])
148
149 {
150 const res = await getVideosList(server.url)
151 expect(res.body.total).to.equal(2)
152
153 const videos: Video[] = res.body.data
154 const video = videos.find(v => v.name === 'toto')
155 expect(video).to.not.be.undefined
156
157 const videoDetails: VideoDetails = (await getVideo(server.url, video.id)).body
158 expect(videoDetails.channel.name).to.equal('user_channel')
159 expect(videoDetails.support).to.equal('support')
160 expect(videoDetails.nsfw).to.be.true
161 expect(videoDetails.commentsEnabled).to.be.true
162 }
163 })
164
165 it('Should remove the auth user', async function () {
166 const env = getEnvCli(server)
167
168 await execCLI(`${env} ${cmd} auth del ${server.url}`)
169
170 const stdout = await execCLI(`${env} ${cmd} --help`)
171
172 expect(stdout).to.contain('no instance selected')
42 }) 173 })
43 174
44 after(async function () { 175 after(async function () {
45 this.timeout(10000) 176 this.timeout(10000)
46 177
47 await execCLI(cmd + ` auth del ${server.url}`)
48
49 await cleanupTests([ server ]) 178 await cleanupTests([ server ])
50 }) 179 })
51}) 180})
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts
index 0dcdf09cf..437470327 100644
--- a/server/tests/feeds/feeds.ts
+++ b/server/tests/feeds/feeds.ts
@@ -7,13 +7,13 @@ import {
7 createUser, 7 createUser,
8 doubleFollow, 8 doubleFollow,
9 flushAndRunMultipleServers, 9 flushAndRunMultipleServers,
10 flushTests, 10 getJSONfeed,
11 getJSONfeed, getMyUserInformation, 11 getMyUserInformation,
12 getXMLfeed, 12 getXMLfeed,
13 killallServers,
14 ServerInfo, 13 ServerInfo,
15 setAccessTokensToServers, 14 setAccessTokensToServers,
16 uploadVideo, userLogin 15 uploadVideo,
16 userLogin
17} from '../../../shared/extra-utils' 17} from '../../../shared/extra-utils'
18import * as libxmljs from 'libxmljs' 18import * as libxmljs from 'libxmljs'
19import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' 19import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments'
@@ -28,10 +28,10 @@ const expect = chai.expect
28describe('Test syndication feeds', () => { 28describe('Test syndication feeds', () => {
29 let servers: ServerInfo[] = [] 29 let servers: ServerInfo[] = []
30 let userAccessToken: string 30 let userAccessToken: string
31 let rootAccountUUID: string 31 let rootAccountId: number
32 let rootChannelUUID: string 32 let rootChannelId: number
33 let userAccountUUID: string 33 let userAccountId: number
34 let userChannelUUID: string 34 let userChannelId: number
35 35
36 before(async function () { 36 before(async function () {
37 this.timeout(120000) 37 this.timeout(120000)
@@ -45,8 +45,8 @@ describe('Test syndication feeds', () => {
45 { 45 {
46 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) 46 const res = await getMyUserInformation(servers[0].url, servers[0].accessToken)
47 const user: User = res.body 47 const user: User = res.body
48 rootAccountUUID = user.account.uuid 48 rootAccountId = user.account.id
49 rootChannelUUID = user.videoChannels[0].uuid 49 rootChannelId = user.videoChannels[0].id
50 } 50 }
51 51
52 { 52 {
@@ -56,8 +56,8 @@ describe('Test syndication feeds', () => {
56 56
57 const res = await getMyUserInformation(servers[0].url, userAccessToken) 57 const res = await getMyUserInformation(servers[0].url, userAccessToken)
58 const user: User = res.body 58 const user: User = res.body
59 userAccountUUID = user.account.uuid 59 userAccountId = user.account.id
60 userChannelUUID = user.videoChannels[0].uuid 60 userChannelId = user.videoChannels[0].id
61 } 61 }
62 62
63 { 63 {
@@ -127,71 +127,71 @@ describe('Test syndication feeds', () => {
127 }) 127 })
128 128
129 it('Should filter by account', async function () { 129 it('Should filter by account', async function () {
130 {
131 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId })
132 const jsonObj = JSON.parse(json.text)
133 expect(jsonObj.items.length).to.be.equal(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')
136 }
137
138 {
139 const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId })
140 const jsonObj = JSON.parse(json.text)
141 expect(jsonObj.items.length).to.be.equal(1)
142 expect(jsonObj.items[ 0 ].title).to.equal('user video')
143 expect(jsonObj.items[ 0 ].author.name).to.equal('john')
144 }
145
130 for (const server of servers) { 146 for (const server of servers) {
131 { 147 {
132 const json = await getJSONfeed(server.url, 'videos', { accountId: rootAccountUUID }) 148 const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port })
133 const jsonObj = JSON.parse(json.text) 149 const jsonObj = JSON.parse(json.text)
134 expect(jsonObj.items.length).to.be.equal(1) 150 expect(jsonObj.items.length).to.be.equal(1)
135 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')
136 expect(jsonObj.items[ 0 ].author.name).to.equal('root')
137 } 152 }
138 153
139 { 154 {
140 const json = await getJSONfeed(server.url, 'videos', { accountId: userAccountUUID }) 155 const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port })
141 const jsonObj = JSON.parse(json.text) 156 const jsonObj = JSON.parse(json.text)
142 expect(jsonObj.items.length).to.be.equal(1) 157 expect(jsonObj.items.length).to.be.equal(1)
143 expect(jsonObj.items[ 0 ].title).to.equal('user video') 158 expect(jsonObj.items[ 0 ].title).to.equal('user video')
144 expect(jsonObj.items[ 0 ].author.name).to.equal('john')
145 } 159 }
146 } 160 }
161 })
147 162
163 it('Should filter by video channel', async function () {
148 { 164 {
149 const json = await getJSONfeed(servers[0].url, 'videos', { accountName: 'root' }) 165 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId })
150 const jsonObj = JSON.parse(json.text) 166 const jsonObj = JSON.parse(json.text)
151 expect(jsonObj.items.length).to.be.equal(1) 167 expect(jsonObj.items.length).to.be.equal(1)
152 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')
153 } 170 }
154 171
155 { 172 {
156 const json = await getJSONfeed(servers[0].url, 'videos', { accountName: 'john' }) 173 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId })
157 const jsonObj = JSON.parse(json.text) 174 const jsonObj = JSON.parse(json.text)
158 expect(jsonObj.items.length).to.be.equal(1) 175 expect(jsonObj.items.length).to.be.equal(1)
159 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')
160 } 178 }
161 })
162 179
163 it('Should filter by video channel', async function () {
164 for (const server of servers) { 180 for (const server of servers) {
165 { 181 {
166 const json = await getJSONfeed(server.url, 'videos', { videoChannelId: rootChannelUUID }) 182 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port })
167 const jsonObj = JSON.parse(json.text) 183 const jsonObj = JSON.parse(json.text)
168 expect(jsonObj.items.length).to.be.equal(1) 184 expect(jsonObj.items.length).to.be.equal(1)
169 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')
170 expect(jsonObj.items[ 0 ].author.name).to.equal('root')
171 } 186 }
172 187
173 { 188 {
174 const json = await getJSONfeed(server.url, 'videos', { videoChannelId: userChannelUUID }) 189 const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port })
175 const jsonObj = JSON.parse(json.text) 190 const jsonObj = JSON.parse(json.text)
176 expect(jsonObj.items.length).to.be.equal(1) 191 expect(jsonObj.items.length).to.be.equal(1)
177 expect(jsonObj.items[ 0 ].title).to.equal('user video') 192 expect(jsonObj.items[ 0 ].title).to.equal('user video')
178 expect(jsonObj.items[ 0 ].author.name).to.equal('john')
179 } 193 }
180 } 194 }
181
182 {
183 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelName: 'root_channel' })
184 const jsonObj = JSON.parse(json.text)
185 expect(jsonObj.items.length).to.be.equal(1)
186 expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1')
187 }
188
189 {
190 const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelName: 'john_channel' })
191 const jsonObj = JSON.parse(json.text)
192 expect(jsonObj.items.length).to.be.equal(1)
193 expect(jsonObj.items[ 0 ].title).to.equal('user video')
194 }
195 }) 195 })
196 }) 196 })
197 197
diff --git a/server/tests/fixtures/preview.jpg b/server/tests/fixtures/preview.jpg
index c40ece838..cb5692281 100644
--- a/server/tests/fixtures/preview.jpg
+++ b/server/tests/fixtures/preview.jpg
Binary files differ
diff --git a/server/tests/fixtures/sample.ogg b/server/tests/fixtures/sample.ogg
new file mode 100644
index 000000000..0d7f43eb7
--- /dev/null
+++ b/server/tests/fixtures/sample.ogg
Binary files differ
diff --git a/server/tests/fixtures/video_short1-preview.webm.jpg b/server/tests/fixtures/video_short1-preview.webm.jpg
index d2a068b78..157d3ca9a 100644
--- a/server/tests/fixtures/video_short1-preview.webm.jpg
+++ b/server/tests/fixtures/video_short1-preview.webm.jpg
Binary files differ
diff --git a/server/tools/cli.ts b/server/tools/cli.ts
index 59e9fcfc4..2eec51aa4 100644
--- a/server/tools/cli.ts
+++ b/server/tools/cli.ts
@@ -1,5 +1,14 @@
1const config = require('application-config')('PeerTube/CLI') 1import { Netrc } from 'netrc-parser'
2const netrc = require('netrc-parser').default 2import { getAppNumber, isTestInstance } from '../helpers/core-utils'
3import { join } from 'path'
4import { getVideoChannel, root } from '../../shared/extra-utils'
5import { Command } from 'commander'
6import { VideoChannel, VideoPrivacy } from '../../shared/models/videos'
7
8let configName = 'PeerTube/CLI'
9if (isTestInstance()) configName += `-${getAppNumber()}`
10
11const config = require('application-config')(configName)
3 12
4const version = require('../../../package.json').version 13const version = require('../../../package.json').version
5 14
@@ -12,7 +21,7 @@ function getSettings () {
12 return new Promise<Settings>((res, rej) => { 21 return new Promise<Settings>((res, rej) => {
13 const defaultSettings = { 22 const defaultSettings = {
14 remotes: [], 23 remotes: [],
15 default: 0 24 default: -1
16 } 25 }
17 26
18 config.read((err, data) => { 27 config.read((err, data) => {
@@ -24,6 +33,12 @@ function getSettings () {
24} 33}
25 34
26async function getNetrc () { 35async function getNetrc () {
36 const Netrc = require('netrc-parser').Netrc
37
38 const netrc = isTestInstance()
39 ? new Netrc(join(root(), 'test' + getAppNumber(), 'netrc'))
40 : new Netrc()
41
27 await netrc.load() 42 await netrc.load()
28 43
29 return netrc 44 return netrc
@@ -31,7 +46,17 @@ async function getNetrc () {
31 46
32function writeSettings (settings) { 47function writeSettings (settings) {
33 return new Promise((res, rej) => { 48 return new Promise((res, rej) => {
34 config.write(settings, function (err) { 49 config.write(settings, err => {
50 if (err) return rej(err)
51
52 return res()
53 })
54 })
55}
56
57function deleteSettings () {
58 return new Promise((res, rej) => {
59 config.trash((err) => {
35 if (err) return rej(err) 60 if (err) return rej(err)
36 61
37 return res() 62 return res()
@@ -39,10 +64,10 @@ function writeSettings (settings) {
39 }) 64 })
40} 65}
41 66
42function getRemoteObjectOrDie (program: any, settings: Settings) { 67function getRemoteObjectOrDie (program: any, settings: Settings, netrc: Netrc) {
43 if (!program['url'] || !program['username'] || !program['password']) { 68 if (!program['url'] || !program['username'] || !program['password']) {
44 // No remote and we don't have program parameters: throw 69 // No remote and we don't have program parameters: quit
45 if (settings.remotes.length === 0) { 70 if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) {
46 if (!program[ 'url' ]) console.error('--url field is required.') 71 if (!program[ 'url' ]) console.error('--url field is required.')
47 if (!program[ 'username' ]) console.error('--username field is required.') 72 if (!program[ 'username' ]) console.error('--username field is required.')
48 if (!program[ 'password' ]) console.error('--password field is required.') 73 if (!program[ 'password' ]) console.error('--password field is required.')
@@ -54,14 +79,12 @@ function getRemoteObjectOrDie (program: any, settings: Settings) {
54 let username: string = program['username'] 79 let username: string = program['username']
55 let password: string = program['password'] 80 let password: string = program['password']
56 81
57 if (!url) { 82 if (!url && settings.default !== -1) url = settings.remotes[settings.default]
58 url = settings.default !== -1 83
59 ? settings.remotes[settings.default] 84 const machine = netrc.machines[url]
60 : settings.remotes[0]
61 }
62 85
63 if (!username) username = netrc.machines[url].login 86 if (!username && machine) username = machine.login
64 if (!password) password = netrc.machines[url].password 87 if (!password && machine) password = machine.password
65 88
66 return { url, username, password } 89 return { url, username, password }
67 } 90 }
@@ -73,6 +96,71 @@ function getRemoteObjectOrDie (program: any, settings: Settings) {
73 } 96 }
74} 97}
75 98
99function buildCommonVideoOptions (command: Command) {
100 function list (val) {
101 return val.split(',')
102 }
103
104 return command
105 .option('-n, --video-name <name>', 'Video name')
106 .option('-c, --category <category_number>', 'Category number')
107 .option('-l, --licence <licence_number>', 'Licence number')
108 .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
109 .option('-t, --tags <tags>', 'Video tags', list)
110 .option('-N, --nsfw', 'Video is Not Safe For Work')
111 .option('-d, --video-description <description>', 'Video description')
112 .option('-P, --privacy <privacy_number>', 'Privacy')
113 .option('-C, --channel-name <channel_name>', 'Channel name')
114 .option('-m, --comments-enabled', 'Enable comments')
115 .option('-s, --support <support>', 'Video support text')
116 .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video')
117}
118
119async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any) {
120 const defaultBooleanAttributes = {
121 nsfw: false,
122 commentsEnabled: true,
123 downloadEnabled: true,
124 waitTranscoding: true
125 }
126
127 const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
128
129 for (const key of Object.keys(defaultBooleanAttributes)) {
130 if (command[ key ] !== undefined) {
131 booleanAttributes[key] = command[ key ]
132 } else if (defaultAttributes[key] !== undefined) {
133 booleanAttributes[key] = defaultAttributes[key]
134 } else {
135 booleanAttributes[key] = defaultBooleanAttributes[key]
136 }
137 }
138
139 const videoAttributes = {
140 name: command[ 'videoName' ] || defaultAttributes.name,
141 category: command[ 'category' ] || defaultAttributes.category || undefined,
142 licence: command[ 'licence' ] || defaultAttributes.licence || undefined,
143 language: command[ 'language' ] || defaultAttributes.language || undefined,
144 privacy: command[ 'privacy' ] || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
145 support: command[ 'support' ] || defaultAttributes.support || undefined
146 }
147
148 Object.assign(videoAttributes, booleanAttributes)
149
150 if (command[ 'channelName' ]) {
151 const res = await getVideoChannel(url, command['channelName'])
152 const videoChannel: VideoChannel = res.body
153
154 Object.assign(videoAttributes, { channelId: videoChannel.id })
155
156 if (!videoAttributes.support && videoChannel.support) {
157 Object.assign(videoAttributes, { support: videoChannel.support })
158 }
159 }
160
161 return videoAttributes
162}
163
76// --------------------------------------------------------------------------- 164// ---------------------------------------------------------------------------
77 165
78export { 166export {
@@ -81,5 +169,9 @@ export {
81 getSettings, 169 getSettings,
82 getNetrc, 170 getNetrc,
83 getRemoteObjectOrDie, 171 getRemoteObjectOrDie,
84 writeSettings 172 writeSettings,
173 deleteSettings,
174
175 buildCommonVideoOptions,
176 buildVideoAttributesFromCommander
85} 177}
diff --git a/server/tools/package.json b/server/tools/package.json
new file mode 100644
index 000000000..22fb8d24c
--- /dev/null
+++ b/server/tools/package.json
@@ -0,0 +1,14 @@
1{
2 "name": "@peertube/cli",
3 "version": "1.0.0",
4 "private": true,
5 "dependencies": {
6 "application-config": "^1.0.1",
7 "cli-table": "^0.3.1",
8 "netrc-parser": "^3.1.6",
9 "webtorrent-hybrid": "^2.1.0"
10 },
11 "summon": {
12 "silent": true
13 }
14}
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts
index 8bc3d332c..ff5ffb60e 100644
--- a/server/tools/peertube-auth.ts
+++ b/server/tools/peertube-auth.ts
@@ -9,7 +9,11 @@ const Table = require('cli-table')
9async function delInstance (url: string) { 9async function delInstance (url: string) {
10 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 10 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
11 11
12 settings.remotes.splice(settings.remotes.indexOf(url)) 12 const index = settings.remotes.indexOf(url)
13 settings.remotes.splice(index)
14
15 if (settings.default === index) settings.default = -1
16
13 await writeSettings(settings) 17 await writeSettings(settings)
14 18
15 delete netrc.machines[url] 19 delete netrc.machines[url]
@@ -17,12 +21,17 @@ async function delInstance (url: string) {
17 await netrc.save() 21 await netrc.save()
18} 22}
19 23
20async function setInstance (url: string, username: string, password: string) { 24async function setInstance (url: string, username: string, password: string, isDefault: boolean) {
21 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) 25 const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ])
22 26
23 if (settings.remotes.indexOf(url) === -1) { 27 if (settings.remotes.indexOf(url) === -1) {
24 settings.remotes.push(url) 28 settings.remotes.push(url)
25 } 29 }
30
31 if (isDefault || settings.remotes.length === 1) {
32 settings.default = settings.remotes.length - 1
33 }
34
26 await writeSettings(settings) 35 await writeSettings(settings)
27 36
28 netrc.machines[url] = { login: username, password } 37 netrc.machines[url] = { login: username, password }
@@ -66,7 +75,7 @@ program
66 } 75 }
67 } 76 }
68 }, async (_, result) => { 77 }, async (_, result) => {
69 await setInstance(result.url, result.username, result.password) 78 await setInstance(result.url, result.username, result.password, program['default'])
70 79
71 process.exit(0) 80 process.exit(0)
72 }) 81 })
@@ -93,6 +102,8 @@ program
93 }) 102 })
94 103
95 settings.remotes.forEach(element => { 104 settings.remotes.forEach(element => {
105 if (!netrc.machines[element]) return
106
96 table.push([ 107 table.push([
97 element, 108 element,
98 netrc.machines[element].login 109 netrc.machines[element].login
diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts
index 85660de2c..103495347 100644
--- a/server/tools/peertube-get-access-token.ts
+++ b/server/tools/peertube-get-access-token.ts
@@ -1,12 +1,5 @@
1import * as program from 'commander' 1import * as program from 'commander'
2 2import { getClient, Server, serverLogin } from '../../shared/extra-utils'
3import {
4 getClient,
5 serverLogin,
6 Server,
7 Client,
8 User
9} from '../../shared/extra-utils'
10 3
11program 4program
12 .option('-u, --url <url>', 'Server url') 5 .option('-u, --url <url>', 'Server url')
@@ -22,6 +15,7 @@ if (
22 if (!program['url']) console.error('--url field is required.') 15 if (!program['url']) console.error('--url field is required.')
23 if (!program['username']) console.error('--username field is required.') 16 if (!program['username']) console.error('--username field is required.')
24 if (!program['password']) console.error('--password field is required.') 17 if (!program['password']) console.error('--password field is required.')
18
25 process.exit(-1) 19 process.exit(-1)
26} 20}
27 21
@@ -32,11 +26,11 @@ getClient(program.url)
32 user: { 26 user: {
33 username: program['username'], 27 username: program['username'],
34 password: program['password'] 28 password: program['password']
35 } as User, 29 },
36 client: { 30 client: {
37 id: res.body.client_id as string, 31 id: res.body.client_id,
38 secret: res.body.client_secret as string 32 secret: res.body.client_secret
39 } as Client 33 }
40 } as Server 34 } as Server
41 35
42 return serverLogin(server) 36 return serverLogin(server)
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts
index 9a366dbbd..d7bb00e02 100644
--- a/server/tools/peertube-import-videos.ts
+++ b/server/tools/peertube-import-videos.ts
@@ -3,7 +3,6 @@ require('tls').DEFAULT_ECDH_CURVE = 'auto'
3 3
4import * as program from 'commander' 4import * as program from 'commander'
5import { join } from 'path' 5import { join } from 'path'
6import { VideoPrivacy } from '../../shared/models/videos'
7import { doRequestAndSaveToFile } from '../helpers/requests' 6import { doRequestAndSaveToFile } from '../helpers/requests'
8import { CONSTRAINTS_FIELDS } from '../initializers/constants' 7import { CONSTRAINTS_FIELDS } from '../initializers/constants'
9import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' 8import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index'
@@ -12,29 +11,34 @@ import * as prompt from 'prompt'
12import { remove } from 'fs-extra' 11import { remove } from 'fs-extra'
13import { sha256 } from '../helpers/core-utils' 12import { sha256 } from '../helpers/core-utils'
14import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' 13import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl'
15import { getNetrc, getRemoteObjectOrDie, getSettings } from './cli' 14import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
16 15
17let accessToken: string 16type UserInfo = {
18let client: { id: string, secret: string } 17 username: string
18 password: string
19}
19 20
20const processOptions = { 21const processOptions = {
21 cwd: __dirname, 22 cwd: __dirname,
22 maxBuffer: Infinity 23 maxBuffer: Infinity
23} 24}
24 25
25program 26let command = program
26 .name('import-videos') 27 .name('import-videos')
28
29command = buildCommonVideoOptions(command)
30
31command
27 .option('-u, --url <url>', 'Server url') 32 .option('-u, --url <url>', 'Server url')
28 .option('-U, --username <username>', 'Username') 33 .option('-U, --username <username>', 'Username')
29 .option('-p, --password <token>', 'Password') 34 .option('-p, --password <token>', 'Password')
30 .option('-t, --target-url <targetUrl>', 'Video target URL') 35 .option('-t, --target-url <targetUrl>', 'Video target URL')
31 .option('-l, --language <languageCode>', 'Language ISO 639 code (fr or en...)')
32 .option('-v, --verbose', 'Verbose mode') 36 .option('-v, --verbose', 'Verbose mode')
33 .parse(process.argv) 37 .parse(process.argv)
34 38
35Promise.all([ getSettings(), getNetrc() ]) 39Promise.all([ getSettings(), getNetrc() ])
36 .then(([ settings, netrc ]) => { 40 .then(([ settings, netrc ]) => {
37 const { url, username, password } = getRemoteObjectOrDie(program, settings) 41 const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
38 42
39 if (!program[ 'targetUrl' ]) { 43 if (!program[ 'targetUrl' ]) {
40 console.error('--targetUrl field is required.') 44 console.error('--targetUrl field is required.')
@@ -45,56 +49,20 @@ Promise.all([ getSettings(), getNetrc() ])
45 removeEndSlashes(url) 49 removeEndSlashes(url)
46 removeEndSlashes(program[ 'targetUrl' ]) 50 removeEndSlashes(program[ 'targetUrl' ])
47 51
48 const user = { 52 const user = { username, password }
49 username: username,
50 password: password
51 }
52 53
53 run(user, url) 54 run(url, user)
54 .catch(err => { 55 .catch(err => {
55 console.error(err) 56 console.error(err)
56 process.exit(-1) 57 process.exit(-1)
57 }) 58 })
58 }) 59 })
59 60
60async function promptPassword () { 61async function run (url: string, user: UserInfo) {
61 return new Promise((res, rej) => {
62 prompt.start()
63 const schema = {
64 properties: {
65 password: {
66 hidden: true,
67 required: true
68 }
69 }
70 }
71 prompt.get(schema, function (err, result) {
72 if (err) {
73 return rej(err)
74 }
75 return res(result.password)
76 })
77 })
78}
79
80async function run (user, url: string) {
81 if (!user.password) { 62 if (!user.password) {
82 user.password = await promptPassword() 63 user.password = await promptPassword()
83 } 64 }
84 65
85 const res = await getClient(url)
86 client = {
87 id: res.body.client_id,
88 secret: res.body.client_secret
89 }
90
91 try {
92 const res = await login(program[ 'url' ], client, user)
93 accessToken = res.body.access_token
94 } catch (err) {
95 throw new Error('Cannot authenticate. Please check your username/password.')
96 }
97
98 const youtubeDL = await safeGetYoutubeDL() 66 const youtubeDL = await safeGetYoutubeDL()
99 67
100 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] 68 const options = [ '-j', '--flat-playlist', '--playlist-reverse' ]
@@ -115,7 +83,12 @@ async function run (user, url: string) {
115 console.log('Will download and upload %d videos.\n', infoArray.length) 83 console.log('Will download and upload %d videos.\n', infoArray.length)
116 84
117 for (const info of infoArray) { 85 for (const info of infoArray) {
118 await processVideo(info, program[ 'language' ], processOptions.cwd, url, user) 86 await processVideo({
87 cwd: processOptions.cwd,
88 url,
89 user,
90 youtubeInfo: info
91 })
119 } 92 }
120 93
121 console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) 94 console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ])
@@ -123,11 +96,18 @@ async function run (user, url: string) {
123 }) 96 })
124} 97}
125 98
126function processVideo (info: any, languageCode: string, cwd: string, url: string, user) { 99function processVideo (parameters: {
100 cwd: string,
101 url: string,
102 user: { username: string, password: string },
103 youtubeInfo: any
104}) {
105 const { youtubeInfo, cwd, url, user } = parameters
106
127 return new Promise(async res => { 107 return new Promise(async res => {
128 if (program[ 'verbose' ]) console.log('Fetching object.', info) 108 if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo)
129 109
130 const videoInfo = await fetchObject(info) 110 const videoInfo = await fetchObject(youtubeInfo)
131 if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) 111 if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo)
132 112
133 const result = await searchVideoWithSort(url, videoInfo.title, '-match') 113 const result = await searchVideoWithSort(url, videoInfo.title, '-match')
@@ -153,7 +133,13 @@ function processVideo (info: any, languageCode: string, cwd: string, url: string
153 } 133 }
154 134
155 console.log(output.join('\n')) 135 console.log(output.join('\n'))
156 await uploadVideoOnPeerTube(normalizeObject(videoInfo), path, cwd, url, user, languageCode) 136 await uploadVideoOnPeerTube({
137 cwd,
138 url,
139 user,
140 videoInfo: normalizeObject(videoInfo),
141 videoPath: path
142 })
157 return res() 143 return res()
158 }) 144 })
159 } catch (err) { 145 } catch (err) {
@@ -163,7 +149,15 @@ function processVideo (info: any, languageCode: string, cwd: string, url: string
163 }) 149 })
164} 150}
165 151
166async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: string, url: string, user, language?: string) { 152async function uploadVideoOnPeerTube (parameters: {
153 videoInfo: any,
154 videoPath: string,
155 cwd: string,
156 url: string,
157 user: { username: string; password: string }
158}) {
159 const { videoInfo, videoPath, cwd, url, user } = parameters
160
167 const category = await getCategory(videoInfo.categories, url) 161 const category = await getCategory(videoInfo.categories, url)
168 const licence = getLicence(videoInfo.license) 162 const licence = getLicence(videoInfo.license)
169 let tags = [] 163 let tags = []
@@ -186,7 +180,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
186 180
187 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) 181 const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo)
188 182
189 const videoAttributes = { 183 const defaultAttributes = {
190 name: truncate(videoInfo.title, { 184 name: truncate(videoInfo.title, {
191 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, 185 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max,
192 'separator': /,? +/, 186 'separator': /,? +/,
@@ -194,30 +188,31 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
194 }), 188 }),
195 category, 189 category,
196 licence, 190 licence,
197 language,
198 nsfw: isNSFW(videoInfo), 191 nsfw: isNSFW(videoInfo),
199 waitTranscoding: true, 192 description: videoInfo.description,
200 commentsEnabled: true, 193 tags
201 downloadEnabled: true, 194 }
202 description: videoInfo.description || undefined, 195
203 support: undefined, 196 const videoAttributes = await buildVideoAttributesFromCommander(url, program, defaultAttributes)
204 tags, 197
205 privacy: VideoPrivacy.PUBLIC, 198 Object.assign(videoAttributes, {
206 fixture: videoPath, 199 originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null,
207 thumbnailfile, 200 thumbnailfile,
208 previewfile: thumbnailfile, 201 previewfile: thumbnailfile,
209 originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null 202 fixture: videoPath
210 } 203 })
211 204
212 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) 205 console.log('\nUploading on PeerTube video "%s".', videoAttributes.name)
206
207 let accessToken = await getAccessTokenOrDie(url, user)
208
213 try { 209 try {
214 await uploadVideo(url, accessToken, videoAttributes) 210 await uploadVideo(url, accessToken, videoAttributes)
215 } catch (err) { 211 } catch (err) {
216 if (err.message.indexOf('401') !== -1) { 212 if (err.message.indexOf('401') !== -1) {
217 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') 213 console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.')
218 214
219 const res = await login(url, client, user) 215 accessToken = await getAccessTokenOrDie(url, user)
220 accessToken = res.body.access_token
221 216
222 await uploadVideo(url, accessToken, videoAttributes) 217 await uploadVideo(url, accessToken, videoAttributes)
223 } else { 218 } else {
@@ -232,6 +227,8 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st
232 console.log('Uploaded video "%s"!\n', videoAttributes.name) 227 console.log('Uploaded video "%s"!\n', videoAttributes.name)
233} 228}
234 229
230/* ---------------------------------------------------------- */
231
235async function getCategory (categories: string[], url: string) { 232async function getCategory (categories: string[], url: string) {
236 if (!categories) return undefined 233 if (!categories) return undefined
237 234
@@ -250,8 +247,6 @@ async function getCategory (categories: string[], url: string) {
250 return undefined 247 return undefined
251} 248}
252 249
253/* ---------------------------------------------------------- */
254
255function getLicence (licence: string) { 250function getLicence (licence: string) {
256 if (!licence) return undefined 251 if (!licence) return undefined
257 252
@@ -305,9 +300,7 @@ function buildUrl (info: any) {
305} 300}
306 301
307function isNSFW (info: any) { 302function isNSFW (info: any) {
308 if (info.age_limit && info.age_limit >= 16) return true 303 return info.age_limit && info.age_limit >= 16
309
310 return false
311} 304}
312 305
313function removeEndSlashes (url: string) { 306function removeEndSlashes (url: string) {
@@ -315,3 +308,39 @@ function removeEndSlashes (url: string) {
315 url.slice(0, -1) 308 url.slice(0, -1)
316 } 309 }
317} 310}
311
312async function promptPassword () {
313 return new Promise<string>((res, rej) => {
314 prompt.start()
315 const schema = {
316 properties: {
317 password: {
318 hidden: true,
319 required: true
320 }
321 }
322 }
323 prompt.get(schema, function (err, result) {
324 if (err) {
325 return rej(err)
326 }
327 return res(result.password)
328 })
329 })
330}
331
332async function getAccessTokenOrDie (url: string, user: UserInfo) {
333 const resClient = await getClient(url)
334 const client = {
335 id: resClient.body.client_id,
336 secret: resClient.body.client_secret
337 }
338
339 try {
340 const res = await login(url, client, user)
341 return res.body.access_token
342 } catch (err) {
343 console.error('Cannot authenticate. Please check your username/password.')
344 process.exit(-1)
345 }
346}
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts
index 04d8b95a3..fbdec1613 100644
--- a/server/tools/peertube-repl.ts
+++ b/server/tools/peertube-repl.ts
@@ -43,7 +43,7 @@ const start = async () => {
43 Object.defineProperty(context, prop, { 43 Object.defineProperty(context, prop, {
44 configurable: false, 44 configurable: false,
45 enumerable: true, 45 enumerable: true,
46 value: properties[prop] 46 value: properties[ prop ]
47 }) 47 })
48 } 48 }
49 } 49 }
@@ -69,8 +69,7 @@ const start = async () => {
69 69
70} 70}
71 71
72start().then((data) => { 72start()
73 // do nothing 73 .catch((err) => {
74}).catch((err) => { 74 console.error(err)
75 console.error(err) 75 })
76})
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts
index 687f2e60b..c00205e8f 100644
--- a/server/tools/peertube-upload.ts
+++ b/server/tools/peertube-upload.ts
@@ -3,54 +3,47 @@ import { access, constants } from 'fs-extra'
3import { isAbsolute } from 'path' 3import { isAbsolute } from 'path'
4import { getClient, login } from '../../shared/extra-utils' 4import { getClient, login } from '../../shared/extra-utils'
5import { uploadVideo } from '../../shared/extra-utils/' 5import { uploadVideo } from '../../shared/extra-utils/'
6import { VideoPrivacy } from '../../shared/models/videos' 6import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getNetrc, getRemoteObjectOrDie, getSettings } from './cli'
7import { getRemoteObjectOrDie, getSettings } from './cli'
8 7
9program 8let command = program
10 .name('upload') 9 .name('upload')
10
11command = buildCommonVideoOptions(command)
12
13command
14
11 .option('-u, --url <url>', 'Server url') 15 .option('-u, --url <url>', 'Server url')
12 .option('-U, --username <username>', 'Username') 16 .option('-U, --username <username>', 'Username')
13 .option('-p, --password <token>', 'Password') 17 .option('-p, --password <token>', 'Password')
14 .option('-n, --video-name <name>', 'Video name')
15 .option('-P, --privacy <privacy_number>', 'Privacy')
16 .option('-N, --nsfw', 'Video is Not Safe For Work')
17 .option('-c, --category <category_number>', 'Category number')
18 .option('-C, --channel-id <channel_id>', 'Channel ID')
19 .option('-m, --comments-enabled', 'Enable comments')
20 .option('-l, --licence <licence_number>', 'Licence number')
21 .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
22 .option('-d, --video-description <description>', 'Video description')
23 .option('-t, --tags <tags>', 'Video tags', list)
24 .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path') 18 .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
25 .option('-v, --preview <previewPath>', 'Preview path') 19 .option('-v, --preview <previewPath>', 'Preview path')
26 .option('-f, --file <file>', 'Video absolute file path') 20 .option('-f, --file <file>', 'Video absolute file path')
27 .parse(process.argv) 21 .parse(process.argv)
28 22
29getSettings() 23Promise.all([ getSettings(), getNetrc() ])
30 .then(settings => { 24 .then(([ settings, netrc ]) => {
31 const { url, username, password } = getRemoteObjectOrDie(program, settings) 25 const { url, username, password } = getRemoteObjectOrDie(program, settings, netrc)
32 26
33 if (!program['videoName'] || !program['file'] || !program['channelId']) { 27 if (!program[ 'videoName' ] || !program[ 'file' ]) {
34 if (!program['videoName']) console.error('--video-name is required.') 28 if (!program[ 'videoName' ]) console.error('--video-name is required.')
35 if (!program['file']) console.error('--file is required.') 29 if (!program[ 'file' ]) console.error('--file is required.')
36 if (!program['channelId']) console.error('--channel-id is required.')
37 30
38 process.exit(-1) 31 process.exit(-1)
39 } 32 }
40 33
41 if (isAbsolute(program['file']) === false) { 34 if (isAbsolute(program[ 'file' ]) === false) {
42 console.error('File path should be absolute.') 35 console.error('File path should be absolute.')
43 process.exit(-1) 36 process.exit(-1)
44 } 37 }
45 38
46 run(url, username, password).catch(err => { 39 run(url, username, password).catch(err => {
47 console.error(err) 40 console.error(err)
48 process.exit(-1) 41 process.exit(-1)
49 }) 42 })
50 }) 43 })
51 44
52async function run (url: string, username: string, password: string) { 45async function run (url: string, username: string, password: string) {
53 const resClient = await getClient(program[ 'url' ]) 46 const resClient = await getClient(url)
54 const client = { 47 const client = {
55 id: resClient.body.client_id, 48 id: resClient.body.client_id,
56 secret: resClient.body.client_secret 49 secret: resClient.body.client_secret
@@ -70,38 +63,26 @@ async function run (url: string, username: string, password: string) {
70 63
71 console.log('Uploading %s video...', program[ 'videoName' ]) 64 console.log('Uploading %s video...', program[ 'videoName' ])
72 65
73 const videoAttributes = { 66 const defaultAttributes = {
74 name: program['videoName'], 67 tags: command[ 'tags' ],
75 category: program['category'] || undefined, 68 description: command[ 'videoDescription' ]
76 channelId: program['channelId'],
77 licence: program['licence'] || undefined,
78 language: program['language'] || undefined,
79 nsfw: program['nsfw'] !== undefined ? program['nsfw'] : false,
80 description: program['videoDescription'] || '',
81 tags: program['tags'] || [],
82 commentsEnabled: program['commentsEnabled'] !== undefined ? program['commentsEnabled'] : true,
83 downloadEnabled: program['downloadEnabled'] !== undefined ? program['downloadEnabled'] : true,
84 fixture: program['file'],
85 thumbnailfile: program['thumbnail'],
86 previewfile: program['preview'],
87 waitTranscoding: true,
88 privacy: program['privacy'] || VideoPrivacy.PUBLIC,
89 support: undefined
90 } 69 }
70 const videoAttributes = await buildVideoAttributesFromCommander(url, program, defaultAttributes)
71
72 Object.assign(videoAttributes, {
73 fixture: program[ 'file' ],
74 thumbnailfile: program[ 'thumbnail' ],
75 previewfile: program[ 'preview' ]
76 })
91 77
92 try { 78 try {
93 await uploadVideo(url, accessToken, videoAttributes) 79 await uploadVideo(url, accessToken, videoAttributes)
94 console.log(`Video ${program['videoName']} uploaded.`) 80 console.log(`Video ${program[ 'videoName' ]} uploaded.`)
95 process.exit(0) 81 process.exit(0)
96 } catch (err) { 82 } catch (err) {
97 console.log('coucou')
98 console.error(require('util').inspect(err)) 83 console.error(require('util').inspect(err))
99 process.exit(-1) 84 process.exit(-1)
100 } 85 }
101} 86}
102 87
103// ---------------------------------------------------------------------------- 88// ----------------------------------------------------------------------------
104
105function list (val) {
106 return val.split(',')
107}
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts
index bf7274aab..7c27c1364 100644
--- a/server/tools/peertube-watch.ts
+++ b/server/tools/peertube-watch.ts
@@ -1,21 +1,15 @@
1import * as program from 'commander' 1import * as program from 'commander'
2import * as summon from 'summon-install'
3import { join } from 'path' 2import { join } from 'path'
4import { execSync } from 'child_process' 3import { execSync } from 'child_process'
5import { root } from '../helpers/core-utils'
6
7let videoURL
8 4
9program 5program
10 .name('watch') 6 .name('watch')
11 .arguments('<url>') 7 .arguments('<url>')
12 .option('-g, --gui <player>', 'player type', /^(airplay|stdout|chromecast|mpv|vlc|mplayer|ascii|xbmc)$/i, 'ascii') 8 .option('-g, --gui <player>', 'player type', /^(airplay|stdout|chromecast|mpv|vlc|mplayer|xbmc)$/i, 'vlc')
13 .option('-i, --invert', 'invert colors (ascii player only)', true) 9 .option('-r, --resolution <res>', 'video resolution', '480')
14 .option('-r, --resolution <res>', 'video resolution', /^(240|360|720|1080)$/i, '720')
15 .on('--help', function () { 10 .on('--help', function () {
16 console.log(' Available Players:') 11 console.log(' Available Players:')
17 console.log() 12 console.log()
18 console.log(' - ascii')
19 console.log(' - mpv') 13 console.log(' - mpv')
20 console.log(' - mplayer') 14 console.log(' - mplayer')
21 console.log(' - vlc') 15 console.log(' - vlc')
@@ -24,7 +18,6 @@ program
24 console.log(' - airplay') 18 console.log(' - airplay')
25 console.log(' - chromecast') 19 console.log(' - chromecast')
26 console.log() 20 console.log()
27 console.log(' Note: \'ascii\' is the only option not using WebTorrent and not seeding back the video.')
28 console.log() 21 console.log()
29 console.log(' Examples:') 22 console.log(' Examples:')
30 console.log() 23 console.log()
@@ -33,29 +26,25 @@ program
33 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') 26 console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10')
34 console.log() 27 console.log()
35 }) 28 })
36 .action((url) => { 29 .action((url, cmd) => {
37 videoURL = url 30 run(url, cmd)
31 .catch(err => {
32 console.error(err)
33 process.exit(-1)
34 })
38 }) 35 })
39 .parse(process.argv) 36 .parse(process.argv)
40 37
41if (!videoURL) { 38async function run (url: string, program: any) {
42 console.error('<url> positional argument is required.') 39 if (!url) {
43 process.exit(-1) 40 console.error('<url> positional argument is required.')
44} else { program['url'] = videoURL } 41 process.exit(-1)
42 }
45 43
46handler(program) 44 const cmd = 'node ' + join(__dirname, 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
45 const args = ` --${program.gui} ` +
46 url.replace('videos/watch', 'download/torrents') +
47 `-${program.resolution}.torrent`
47 48
48function handler (argv) { 49 execSync(cmd + args)
49 if (argv['gui'] === 'ascii') {
50 summon('peerterminal')
51 const peerterminal = summon('peerterminal')
52 peerterminal([ '--link', videoURL, '--invert', argv['invert'] ])
53 } else {
54 summon('webtorrent-hybrid')
55 const CMD = 'node ' + join(root(), 'node_modules', 'webtorrent-hybrid', 'bin', 'cmd.js')
56 const CMDargs = ` --${argv.gui} ` +
57 argv['url'].replace('videos/watch', 'download/torrents') +
58 `-${argv.resolution}.torrent`
59 execSync(CMD + CMDargs)
60 }
61} 50}
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts
index 5d3ab2815..daa5586c3 100755..100644
--- a/server/tools/peertube.ts
+++ b/server/tools/peertube.ts
@@ -63,9 +63,10 @@ if (!process.argv.slice(2).length) {
63 63
64getSettings() 64getSettings()
65 .then(settings => { 65 .then(settings => {
66 const state = (settings.default === undefined || settings.default === -1) ? 66 const state = (settings.default === undefined || settings.default === -1)
67 'no instance selected, commands will require explicit arguments' : 67 ? 'no instance selected, commands will require explicit arguments'
68 ('instance ' + settings.remotes[settings.default] + ' selected') 68 : 'instance ' + settings.remotes[settings.default] + ' selected'
69
69 program 70 program
70 .on('--help', function () { 71 .on('--help', function () {
71 console.log() 72 console.log()
diff --git a/server/tools/tsconfig.json b/server/tools/tsconfig.json
new file mode 100644
index 000000000..f8a1c705c
--- /dev/null
+++ b/server/tools/tsconfig.json
@@ -0,0 +1,4 @@
1{
2 "extends": "../../tsconfig.json",
3 "exclude": [ ] // Overwrite exclude property
4}
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock
new file mode 100644
index 000000000..7ef68fc71
--- /dev/null
+++ b/server/tools/yarn.lock
@@ -0,0 +1,2063 @@
1# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2# yarn lockfile v1
3
4
5abbrev@1:
6 version "1.1.1"
7 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
8 integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
9
10addr-to-ip-port@^1.0.1, addr-to-ip-port@^1.4.2:
11 version "1.5.1"
12 resolved "https://registry.yarnpkg.com/addr-to-ip-port/-/addr-to-ip-port-1.5.1.tgz#bfada13fd6aeeeac19f1e9f7d84b4bbab45e5208"
13 integrity sha512-bA+dyydTNuQtrEDJ0g9eR7XabNhvrM5yZY0hvTbNK3yvoeC73ZqMES6E1cEqH9WPxs4uMtMsOjfwS4FmluhsAA==
14
15airplay-js@^0.3.0:
16 version "0.3.0"
17 resolved "https://registry.yarnpkg.com/airplay-js/-/airplay-js-0.3.0.tgz#16bac2ef91b31249382924bfdeeabaddc9db7398"
18 integrity sha1-FrrC75GzEkk4KSS/3uq63cnbc5g=
19 dependencies:
20 mdns-js "0.5.0"
21 plist-with-patches "0.5.1"
22
23ansi-regex@^2.0.0:
24 version "2.1.1"
25 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
26 integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
27
28ansi-regex@^3.0.0:
29 version "3.0.0"
30 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
31 integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
32
33application-config-path@^0.1.0:
34 version "0.1.0"
35 resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f"
36 integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8=
37
38application-config@^1.0.1:
39 version "1.0.1"
40 resolved "https://registry.yarnpkg.com/application-config/-/application-config-1.0.1.tgz#5aa2e2a5ed6abd2e5d1d473d3596f574044fe9e7"
41 integrity sha1-WqLipe1qvS5dHUc9NZb1dARP6ec=
42 dependencies:
43 application-config-path "^0.1.0"
44 mkdirp "^0.5.1"
45
46aproba@^1.0.3:
47 version "1.2.0"
48 resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
49 integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
50
51are-we-there-yet@~1.1.2:
52 version "1.1.5"
53 resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
54 integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
55 dependencies:
56 delegates "^1.0.0"
57 readable-stream "^2.0.6"
58
59ascli@~0.3:
60 version "0.3.0"
61 resolved "https://registry.yarnpkg.com/ascli/-/ascli-0.3.0.tgz#5e66230e5219fe3e8952a4efb4f20fae596a813a"
62 integrity sha1-XmYjDlIZ/j6JUqTvtPIPrllqgTo=
63 dependencies:
64 colour latest
65 optjs latest
66
67async-limiter@~1.0.0:
68 version "1.0.0"
69 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
70 integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
71
72balanced-match@^1.0.0:
73 version "1.0.0"
74 resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
75 integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
76
77bencode@^2.0.0:
78 version "2.0.1"
79 resolved "https://registry.yarnpkg.com/bencode/-/bencode-2.0.1.tgz#667a6a31c5e038d558608333da6b7c94e836c85b"
80 integrity sha512-2uhEl8FdjSBUyb69qDTgOEeeqDTa+n3yMQzLW0cOzNf1Ow5bwcg3idf+qsWisIKRH8Bk8oC7UXL8irRcPA8ZEQ==
81 dependencies:
82 safe-buffer "^5.1.1"
83
84binary-search@^1.3.4:
85 version "1.3.5"
86 resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.5.tgz#479ad009589e0273cf54e5d74ab1546c489078ce"
87 integrity sha512-RHFP0AdU6KAB0CCZsRMU2CJTk2EpL8GLURT+4gilpjr1f/7M91FgUMnXuQLmf3OKLet34gjuNFwO7e4agdX5pw==
88
89bitfield@^2.0.0:
90 version "2.0.0"
91 resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837"
92 integrity sha512-4xM4DYejOHQ/qWBfeqBXNA4mJ12PwcOibFYnH1kYh5U9BHciCqEJBqGNVnMJXUhm8mflujNRLSv7IiVQxovgjw==
93
94bittorrent-dht@^9.0.0:
95 version "9.0.0"
96 resolved "https://registry.yarnpkg.com/bittorrent-dht/-/bittorrent-dht-9.0.0.tgz#08d5ebb51ed91d7e3eea5c275554f4323fb523e5"
97 integrity sha512-X5ax4G/PLtEPfqOUjqDZ2nmPENndWRMK4sT2jcQ4sXor904zhR40r4KqTyTvWYAljh5/hPPqM9DCUUtqWzRXoQ==
98 dependencies:
99 bencode "^2.0.0"
100 buffer-equals "^1.0.3"
101 debug "^3.1.0"
102 inherits "^2.0.1"
103 k-bucket "^5.0.0"
104 k-rpc "^5.0.0"
105 last-one-wins "^1.0.4"
106 lru "^3.1.0"
107 randombytes "^2.0.5"
108 record-cache "^1.0.2"
109 safe-buffer "^5.0.1"
110 simple-sha1 "^2.1.0"
111
112bittorrent-peerid@^1.0.2:
113 version "1.3.0"
114 resolved "https://registry.yarnpkg.com/bittorrent-peerid/-/bittorrent-peerid-1.3.0.tgz#a435d3b267c887c586c528b53359845905d7c158"
115 integrity sha512-SYd5H3RbN1ex+TrWAKXkEkASFWxAR7Tk6iLt9tfAT9ehBvZb/Y3AQDVRVJynlrixcWpnmsLYKI7tkRWgp7ORoQ==
116
117bittorrent-protocol@^3.0.0:
118 version "3.0.1"
119 resolved "https://registry.yarnpkg.com/bittorrent-protocol/-/bittorrent-protocol-3.0.1.tgz#d3948f4d2b09d538095f7e5f93f64ba5df6b5c2a"
120 integrity sha512-hnvOzAu9u+2H0OLLL5byoFdz6oz5f3bx5f7R+ItUohTHMq9TgUhEJfcjo7xWtQHSKOVciYWwYTJ4EjczF5RX2A==
121 dependencies:
122 bencode "^2.0.0"
123 bitfield "^2.0.0"
124 debug "^3.1.0"
125 randombytes "^2.0.5"
126 readable-stream "^2.3.2"
127 speedometer "^1.0.0"
128 unordered-array-remove "^1.0.2"
129 xtend "^4.0.0"
130
131bittorrent-tracker@^9.0.0:
132 version "9.11.0"
133 resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.11.0.tgz#9911f9c14e5a29f84990a0c31b3d83dd16eb2876"
134 integrity sha512-T1zvW/kSeEnWT4I3JE+6c7aZbO5jtleZyQe911SyzIxFF9DvtUNWXud3p5ZUkXaoI2xXwfpvlks5VFj5SKEB+A==
135 dependencies:
136 bencode "^2.0.0"
137 bittorrent-peerid "^1.0.2"
138 bn.js "^4.4.0"
139 compact2string "^1.2.0"
140 debug "^4.0.1"
141 ip "^1.0.1"
142 lru "^3.0.0"
143 minimist "^1.1.1"
144 once "^1.3.0"
145 random-iterate "^1.0.1"
146 randombytes "^2.0.3"
147 run-parallel "^1.1.2"
148 run-series "^1.0.2"
149 safe-buffer "^5.0.0"
150 simple-get "^3.0.0"
151 simple-peer "^9.0.0"
152 simple-websocket "^7.0.1"
153 string2compact "^1.1.1"
154 uniq "^1.0.1"
155 unordered-array-remove "^1.0.2"
156 ws "^6.0.0"
157 optionalDependencies:
158 bufferutil "^4.0.0"
159 utf-8-validate "^5.0.1"
160
161blob-to-buffer@^1.2.6:
162 version "1.2.8"
163 resolved "https://registry.yarnpkg.com/blob-to-buffer/-/blob-to-buffer-1.2.8.tgz#78eeeb332f1280ed0ca6fb2b60693a8c6d36903a"
164 integrity sha512-re0AIxakF504MgeMtIyJkVcZ8T5aUxtp/QmTMlmjyb3P44E1BEv5x3LATBGApWAJATyXHtkXRD+gWTmeyYLiQA==
165
166block-stream2@^1.0.0:
167 version "1.1.0"
168 resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-1.1.0.tgz#c738e3a91ba977ebb5e1fef431e13ca11d8639e2"
169 integrity sha1-xzjjqRupd+u14f70MeE8oR2GOeI=
170 dependencies:
171 defined "^1.0.0"
172 inherits "^2.0.1"
173 readable-stream "^2.0.4"
174
175bn.js@^4.4.0:
176 version "4.11.8"
177 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
178 integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
179
180brace-expansion@^1.1.7:
181 version "1.1.11"
182 resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
183 integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
184 dependencies:
185 balanced-match "^1.0.0"
186 concat-map "0.0.1"
187
188browserify-package-json@^1.0.0:
189 version "1.0.1"
190 resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea"
191 integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo=
192
193buffer-alloc-unsafe@^1.1.0:
194 version "1.1.0"
195 resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
196 integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
197
198buffer-alloc@^1.1.0, buffer-alloc@^1.2.0:
199 version "1.2.0"
200 resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
201 integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
202 dependencies:
203 buffer-alloc-unsafe "^1.1.0"
204 buffer-fill "^1.0.0"
205
206buffer-equals@^1.0.3, buffer-equals@^1.0.4:
207 version "1.0.4"
208 resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5"
209 integrity sha1-A1O1T9B/2VZBcGca5vZrnPENJ/U=
210
211buffer-fill@^1.0.0:
212 version "1.0.0"
213 resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
214 integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
215
216buffer-from@^1.0.0, buffer-from@^1.1.0:
217 version "1.1.1"
218 resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
219 integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
220
221buffer-indexof@^1.0.0:
222 version "1.1.1"
223 resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
224 integrity sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==
225
226bufferutil@^4.0.0:
227 version "4.0.1"
228 resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7"
229 integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==
230 dependencies:
231 node-gyp-build "~3.7.0"
232
233bufferview@~1:
234 version "1.0.1"
235 resolved "https://registry.yarnpkg.com/bufferview/-/bufferview-1.0.1.tgz#7afd74a45f937fa422a1d338c08bbfdc76cd725d"
236 integrity sha1-ev10pF+Tf6QiodM4wIu/3HbNcl0=
237
238"bytebuffer@~3 >=3.5":
239 version "3.5.5"
240 resolved "https://registry.yarnpkg.com/bytebuffer/-/bytebuffer-3.5.5.tgz#7a6faf1a13514b083f1fcf9541c4c9bfbe7e7fd3"
241 integrity sha1-em+vGhNRSwg/H8+VQcTJv75+f9M=
242 dependencies:
243 bufferview "~1"
244 long "~2 >=2.2.3"
245
246camelcase@^3.0.0:
247 version "3.0.0"
248 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
249 integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo=
250
251castv2-client@^1.1.0:
252 version "1.2.0"
253 resolved "https://registry.yarnpkg.com/castv2-client/-/castv2-client-1.2.0.tgz#a9193b1a5448b8cb9a0415bd021c8811ed7b0544"
254 integrity sha1-qRk7GlRIuMuaBBW9AhyIEe17BUQ=
255 dependencies:
256 castv2 "~0.1.4"
257 debug "^2.2.0"
258
259castv2@~0.1.4:
260 version "0.1.9"
261 resolved "https://registry.yarnpkg.com/castv2/-/castv2-0.1.9.tgz#d0b0fab1fd06b0d9cca636886716ec1293a5905a"
262 integrity sha1-0LD6sf0GsNnMpjaIZxbsEpOlkFo=
263 dependencies:
264 debug "^2.2.0"
265 protobufjs "^3.2.2"
266
267chownr@^1.1.1:
268 version "1.1.1"
269 resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
270 integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==
271
272chromecasts@^1.5.3:
273 version "1.9.1"
274 resolved "https://registry.yarnpkg.com/chromecasts/-/chromecasts-1.9.1.tgz#67b162e8414d57d6106c49fe4a0e9b08f20bbd12"
275 integrity sha512-nsXv7ufgrpC8s5DUm6FJEa2XJ2VvE9FmbTVi6r4zGreTFTTSRSJjvqVEqLUFX/fGo/zbSre3zdoV+Pu9DGLz0A==
276 dependencies:
277 castv2-client "^1.1.0"
278 debug "^2.1.3"
279 dns-txt "^2.0.2"
280 mime "^1.3.4"
281 multicast-dns "^6.0.1"
282 simple-get "^2.0.0"
283 thunky "^0.1.0"
284 xml2js "^0.4.8"
285 optionalDependencies:
286 node-ssdp "^2.2.0"
287
288chunk-store-stream@^3.0.1:
289 version "3.0.1"
290 resolved "https://registry.yarnpkg.com/chunk-store-stream/-/chunk-store-stream-3.0.1.tgz#8e0d739226dcb386f44447b82a005b597a1d41d9"
291 integrity sha512-GA1NIFDZKElhkjiO6QOyzfK1QbUt6M3gFhUU/aR05JYaDqXbU5d7U92cLvGKdItJEDfojky6NQefy5VL5PpDBA==
292 dependencies:
293 block-stream2 "^1.0.0"
294 readable-stream "^2.0.5"
295
296cli-table@^0.3.1:
297 version "0.3.1"
298 resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
299 integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
300 dependencies:
301 colors "1.0.3"
302
303cliui@^3.2.0:
304 version "3.2.0"
305 resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
306 integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=
307 dependencies:
308 string-width "^1.0.1"
309 strip-ansi "^3.0.1"
310 wrap-ansi "^2.0.0"
311
312clivas@^0.2.0:
313 version "0.2.0"
314 resolved "https://registry.yarnpkg.com/clivas/-/clivas-0.2.0.tgz#b8d19188b3243e390f302410bd0cb1622db82649"
315 integrity sha1-uNGRiLMkPjkPMCQQvQyxYi24Jkk=
316
317closest-to@~2.0.0:
318 version "2.0.0"
319 resolved "https://registry.yarnpkg.com/closest-to/-/closest-to-2.0.0.tgz#bb2a860edb7769b62d04821748ae50da24dbefaa"
320 integrity sha1-uyqGDtt3abYtBIIXSK5Q2iTb76o=
321
322code-point-at@^1.0.0:
323 version "1.1.0"
324 resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
325 integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
326
327colors@1.0.3:
328 version "1.0.3"
329 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
330 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
331
332colour@latest:
333 version "0.7.1"
334 resolved "https://registry.yarnpkg.com/colour/-/colour-0.7.1.tgz#9cb169917ec5d12c0736d3e8685746df1cadf778"
335 integrity sha1-nLFpkX7F0SwHNtPoaFdG3xyt93g=
336
337compact2string@^1.2.0:
338 version "1.4.1"
339 resolved "https://registry.yarnpkg.com/compact2string/-/compact2string-1.4.1.tgz#8d34929055f8300a13cfc030ad1832e2e53c2e25"
340 integrity sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==
341 dependencies:
342 ipaddr.js ">= 0.1.5"
343
344concat-map@0.0.1:
345 version "0.0.1"
346 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
347 integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
348
349concat-stream@^1.4.8:
350 version "1.6.2"
351 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
352 integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
353 dependencies:
354 buffer-from "^1.0.0"
355 inherits "^2.0.3"
356 readable-stream "^2.2.2"
357 typedarray "^0.0.6"
358
359console-control-strings@^1.0.0, console-control-strings@~1.1.0:
360 version "1.1.0"
361 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
362 integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
363
364core-util-is@~1.0.0:
365 version "1.0.2"
366 resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
367 integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
368
369create-torrent@^3.23.1, create-torrent@^3.33.0:
370 version "3.33.0"
371 resolved "https://registry.yarnpkg.com/create-torrent/-/create-torrent-3.33.0.tgz#8a7a2aa2213a799c266c40e4c12f1468ede25105"
372 integrity sha512-KMd0KuvwVUg1grlRd5skG9ZkSbBYDDkAjDUMLnvxdRn0rL7ph3IwoOk7I8u1yLX4HYjGiLVlWYO55YWNNPjJFA==
373 dependencies:
374 bencode "^2.0.0"
375 block-stream2 "^1.0.0"
376 filestream "^4.0.0"
377 flatten "^1.0.2"
378 is-file "^1.0.0"
379 junk "^2.1.0"
380 minimist "^1.1.0"
381 multistream "^2.0.2"
382 once "^1.3.0"
383 piece-length "^1.0.0"
384 readable-stream "^3.0.2"
385 run-parallel "^1.0.0"
386 simple-sha1 "^2.0.0"
387
388cross-spawn@^6.0.0:
389 version "6.0.5"
390 resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
391 integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
392 dependencies:
393 nice-try "^1.0.4"
394 path-key "^2.0.1"
395 semver "^5.5.0"
396 shebang-command "^1.2.0"
397 which "^1.2.9"
398
399debug@^2.1.0, debug@^2.1.1, debug@^2.1.3, debug@^2.2.0:
400 version "2.6.9"
401 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
402 integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
403 dependencies:
404 ms "2.0.0"
405
406debug@^3.1.0, debug@^3.2.6:
407 version "3.2.6"
408 resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
409 integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
410 dependencies:
411 ms "^2.1.1"
412
413debug@^4.0.1, debug@^4.1.0:
414 version "4.1.1"
415 resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
416 integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
417 dependencies:
418 ms "^2.1.1"
419
420decamelize@^1.1.1:
421 version "1.2.0"
422 resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
423 integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
424
425decompress-response@^3.3.0:
426 version "3.3.0"
427 resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
428 integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
429 dependencies:
430 mimic-response "^1.0.0"
431
432deep-extend@^0.6.0:
433 version "0.6.0"
434 resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
435 integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
436
437defined@^1.0.0:
438 version "1.0.0"
439 resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
440 integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
441
442delegates@^1.0.0:
443 version "1.0.0"
444 resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
445 integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
446
447detect-libc@^1.0.2:
448 version "1.0.3"
449 resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
450 integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
451
452dlnacasts@^0.1.0:
453 version "0.1.0"
454 resolved "https://registry.yarnpkg.com/dlnacasts/-/dlnacasts-0.1.0.tgz#f805211dcac74f6bb3a4d5d5541ad783b1b67d22"
455 integrity sha1-+AUhHcrHT2uzpNXVVBrXg7G2fSI=
456 dependencies:
457 debug "^2.1.3"
458 mime "^1.3.4"
459 node-ssdp "^2.7.1"
460 run-parallel "^1.1.6"
461 simple-get "^2.1.0"
462 thunky "^0.1.0"
463 upnp-mediarenderer-client "^1.2.2"
464 xml2js "^0.4.8"
465
466dns-packet@^1.3.1:
467 version "1.3.1"
468 resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
469 integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==
470 dependencies:
471 ip "^1.1.0"
472 safe-buffer "^5.0.1"
473
474dns-txt@^2.0.2:
475 version "2.0.2"
476 resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6"
477 integrity sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=
478 dependencies:
479 buffer-indexof "^1.0.0"
480
481domexception@^1.0.1:
482 version "1.0.1"
483 resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
484 integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==
485 dependencies:
486 webidl-conversions "^4.0.2"
487
488ecstatic@^3.0.0:
489 version "3.3.2"
490 resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-3.3.2.tgz#6d1dd49814d00594682c652adb66076a69d46c48"
491 integrity sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==
492 dependencies:
493 he "^1.1.1"
494 mime "^1.6.0"
495 minimist "^1.1.0"
496 url-join "^2.0.5"
497
498elementtree@^0.1.6, elementtree@~0.1.6:
499 version "0.1.7"
500 resolved "https://registry.yarnpkg.com/elementtree/-/elementtree-0.1.7.tgz#9ac91be6e52fb6e6244c4e54a4ac3ed8ae8e29c0"
501 integrity sha1-mskb5uUvtuYkTE5UpKw+2K6OKcA=
502 dependencies:
503 sax "1.1.4"
504
505end-of-stream@^1.1.0:
506 version "1.4.1"
507 resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
508 integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
509 dependencies:
510 once "^1.4.0"
511
512error-ex@^1.2.0:
513 version "1.3.2"
514 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
515 integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
516 dependencies:
517 is-arrayish "^0.2.1"
518
519execa@^0.10.0:
520 version "0.10.0"
521 resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
522 integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
523 dependencies:
524 cross-spawn "^6.0.0"
525 get-stream "^3.0.0"
526 is-stream "^1.1.0"
527 npm-run-path "^2.0.0"
528 p-finally "^1.0.0"
529 signal-exit "^3.0.0"
530 strip-eof "^1.0.0"
531
532executable@^4.0.0:
533 version "4.1.1"
534 resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
535 integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==
536 dependencies:
537 pify "^2.2.0"
538
539filestream@^4.0.0:
540 version "4.1.3"
541 resolved "https://registry.yarnpkg.com/filestream/-/filestream-4.1.3.tgz#948fcaade8221f715f5ecaddc54862faaacc9325"
542 integrity sha1-lI/KregiH3FfXsrdxUhi+qrMkyU=
543 dependencies:
544 inherits "^2.0.1"
545 readable-stream "^2.0.5"
546 typedarray-to-buffer "^3.0.0"
547 xtend "^4.0.1"
548
549find-up@^1.0.0:
550 version "1.1.2"
551 resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
552 integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=
553 dependencies:
554 path-exists "^2.0.0"
555 pinkie-promise "^2.0.0"
556
557flatten@^1.0.2:
558 version "1.0.2"
559 resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
560 integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=
561
562fs-chunk-store@^1.6.2:
563 version "1.7.0"
564 resolved "https://registry.yarnpkg.com/fs-chunk-store/-/fs-chunk-store-1.7.0.tgz#1c4bcbe93c99af10aa04b65348f2bb27377a4010"
565 integrity sha512-KhjJmZAs2eqfhCb6PdPx4RcZtheGTz86tpTC5JTvqBn/xda+Nb+0C7dCyjOSN7T76H6a56LvH0SVXQMchLXDRw==
566 dependencies:
567 mkdirp "^0.5.1"
568 random-access-file "^2.0.1"
569 randombytes "^2.0.3"
570 rimraf "^2.4.2"
571 run-parallel "^1.1.2"
572 thunky "^1.0.1"
573
574fs-minipass@^1.2.5:
575 version "1.2.6"
576 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
577 integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
578 dependencies:
579 minipass "^2.2.1"
580
581fs.realpath@^1.0.0:
582 version "1.0.0"
583 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
584 integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
585
586gauge@~2.7.3:
587 version "2.7.4"
588 resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
589 integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
590 dependencies:
591 aproba "^1.0.3"
592 console-control-strings "^1.0.0"
593 has-unicode "^2.0.0"
594 object-assign "^4.1.0"
595 signal-exit "^3.0.0"
596 string-width "^1.0.1"
597 strip-ansi "^3.0.1"
598 wide-align "^1.1.0"
599
600get-browser-rtc@^1.0.0:
601 version "1.0.2"
602 resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9"
603 integrity sha1-u81AyEUaftTvXDc7gWmkCd0dEdk=
604
605get-caller-file@^1.0.1:
606 version "1.0.3"
607 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
608 integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
609
610get-stdin@^6.0.0:
611 version "6.0.0"
612 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
613 integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
614
615get-stream@^3.0.0:
616 version "3.0.0"
617 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
618 integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
619
620glob@^7.1.3:
621 version "7.1.4"
622 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
623 integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
624 dependencies:
625 fs.realpath "^1.0.0"
626 inflight "^1.0.4"
627 inherits "2"
628 minimatch "^3.0.4"
629 once "^1.3.0"
630 path-is-absolute "^1.0.0"
631
632graceful-fs@^4.1.2:
633 version "4.1.15"
634 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
635 integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
636
637has-unicode@^2.0.0:
638 version "2.0.1"
639 resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
640 integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
641
642he@^1.1.1:
643 version "1.2.0"
644 resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
645 integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
646
647hosted-git-info@^2.1.4:
648 version "2.7.1"
649 resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
650 integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==
651
652iconv-lite@^0.4.4:
653 version "0.4.24"
654 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
655 integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
656 dependencies:
657 safer-buffer ">= 2.1.2 < 3"
658
659ignore-walk@^3.0.1:
660 version "3.0.1"
661 resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8"
662 integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==
663 dependencies:
664 minimatch "^3.0.4"
665
666immediate-chunk-store@^2.0.0:
667 version "2.0.0"
668 resolved "https://registry.yarnpkg.com/immediate-chunk-store/-/immediate-chunk-store-2.0.0.tgz#f313fd0cc71396d8911ad031179e1cccfda3da18"
669 integrity sha512-5s6NiCGbtWc+OQA60jrre54w12U7tynIyUNjO5LJjNA5lWwvCv6640roq8Wk/wIuaqnd4Pgtp453OyJ7hbONkQ==
670
671inflight@^1.0.4:
672 version "1.0.6"
673 resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
674 integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
675 dependencies:
676 once "^1.3.0"
677 wrappy "1"
678
679inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
680 version "2.0.3"
681 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
682 integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
683
684ini@~1.3.0:
685 version "1.3.5"
686 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
687 integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
688
689invert-kv@^1.0.0:
690 version "1.0.0"
691 resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
692 integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
693
694ip-set@^1.0.0:
695 version "1.0.2"
696 resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.2.tgz#be4f119f82c124836455993dfcd554639c7007de"
697 integrity sha512-Mb6kv78bTi4RNAIIWL8Bbre7hXOR2pNUi3j8FaQkLaitf/ZWxkq3/iIwXNYk2ACO3IMfdVdQrOkUtwZblO7uBA==
698 dependencies:
699 ip "^1.1.3"
700
701ip@^1.0.1, ip@^1.1.0, ip@^1.1.3:
702 version "1.1.5"
703 resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
704 integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
705
706"ipaddr.js@>= 0.1.5", ipaddr.js@^1.0.1:
707 version "1.9.0"
708 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
709 integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
710
711is-arrayish@^0.2.1:
712 version "0.2.1"
713 resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
714 integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
715
716is-ascii@^1.0.0:
717 version "1.0.0"
718 resolved "https://registry.yarnpkg.com/is-ascii/-/is-ascii-1.0.0.tgz#f02ad0259a0921cd199ff21ce1b09e0f6b4e3929"
719 integrity sha1-8CrQJZoJIc0Zn/Ic4bCeD2tOOSk=
720
721is-file@^1.0.0:
722 version "1.0.0"
723 resolved "https://registry.yarnpkg.com/is-file/-/is-file-1.0.0.tgz#28a44cfbd9d3db193045f22b65fce8edf9620596"
724 integrity sha1-KKRM+9nT2xkwRfIrZfzo7fliBZY=
725
726is-fullwidth-code-point@^1.0.0:
727 version "1.0.0"
728 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
729 integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
730 dependencies:
731 number-is-nan "^1.0.0"
732
733is-fullwidth-code-point@^2.0.0:
734 version "2.0.0"
735 resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
736 integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
737
738is-stream@^1.1.0:
739 version "1.1.0"
740 resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
741 integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
742
743is-typedarray@^1.0.0:
744 version "1.0.0"
745 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
746 integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
747
748is-utf8@^0.2.0:
749 version "0.2.1"
750 resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
751 integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
752
753isarray@~1.0.0:
754 version "1.0.0"
755 resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
756 integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
757
758isexe@^2.0.0:
759 version "2.0.0"
760 resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
761 integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
762
763junk@^2.1.0:
764 version "2.1.0"
765 resolved "https://registry.yarnpkg.com/junk/-/junk-2.1.0.tgz#f431b4b7f072dc500a5f10ce7f4ec71930e70134"
766 integrity sha1-9DG0t/By3FAKXxDOf07HGTDnATQ=
767
768k-bucket@^4.0.0:
769 version "4.0.1"
770 resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542"
771 integrity sha512-YvDpmY3waI999h1zZoW1rJ04fZrgZ+5PAlVmvwDHT6YO/Q1AOhdel07xsKy9eAvJjQ9xZV1wz3rXKqEfaWvlcQ==
772 dependencies:
773 inherits "^2.0.1"
774 randombytes "^2.0.3"
775
776k-bucket@^5.0.0:
777 version "5.0.0"
778 resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-5.0.0.tgz#ef7a401fcd4c37cd31dceaa6ae4440ca91055e01"
779 integrity sha512-r/q+wV/Kde62/tk+rqyttEJn6h0jR7x+incdMVSYTqK73zVxVrzJa70kJL49cIKen8XjIgUZKSvk8ktnrQbK4w==
780 dependencies:
781 randombytes "^2.0.3"
782
783k-rpc-socket@^1.7.2:
784 version "1.8.0"
785 resolved "https://registry.yarnpkg.com/k-rpc-socket/-/k-rpc-socket-1.8.0.tgz#9a4dd6a4f3795ed847ffa156579cc389990bd1f2"
786 integrity sha512-f/9TynsO8YYjZ6JjNNtSSH7CJcIHcio1buy3zqByGxb/GX8AWLdL6FZEWTrN8V3/J7W4/E0ZTQQ+Jt2rVq7ELg==
787 dependencies:
788 bencode "^2.0.0"
789 buffer-equals "^1.0.4"
790 safe-buffer "^5.1.1"
791
792k-rpc@^5.0.0:
793 version "5.0.0"
794 resolved "https://registry.yarnpkg.com/k-rpc/-/k-rpc-5.0.0.tgz#a72651860c96db440579e4c9f38dce8a42b481a8"
795 integrity sha512-vCH2rQdfMOS+MlUuTSuar1pS2EMrltURf9LmAR9xR6Jik0XPlMX3vEixgqMn17wKmFVCublJqSJ4hJIP7oKZ3Q==
796 dependencies:
797 buffer-equals "^1.0.3"
798 k-bucket "^4.0.0"
799 k-rpc-socket "^1.7.2"
800 randombytes "^2.0.5"
801 safe-buffer "^5.1.1"
802
803last-one-wins@^1.0.4:
804 version "1.0.4"
805 resolved "https://registry.yarnpkg.com/last-one-wins/-/last-one-wins-1.0.4.tgz#c1bfd0cbcb46790ec9156b8d1aee8fcb86cda22a"
806 integrity sha1-wb/Qy8tGeQ7JFWuNGu6Py4bNoio=
807
808lcid@^1.0.0:
809 version "1.0.0"
810 resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
811 integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
812 dependencies:
813 invert-kv "^1.0.0"
814
815load-ip-set@^2.1.0:
816 version "2.1.0"
817 resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-2.1.0.tgz#2d50b737cae41de4e413d213991d4083a3e1784b"
818 integrity sha512-taz7U6B+F7Zq90dfIKwqsB1CrFKelSEmMGC68OUqem8Cgd1QZygQBYb2Fk9i6muBSfH4xwF/Pjt4KKlAdOyWZw==
819 dependencies:
820 ip-set "^1.0.0"
821 netmask "^1.0.6"
822 once "^1.3.0"
823 simple-get "^3.0.0"
824 split "^1.0.0"
825
826load-json-file@^1.0.0:
827 version "1.1.0"
828 resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
829 integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=
830 dependencies:
831 graceful-fs "^4.1.2"
832 parse-json "^2.2.0"
833 pify "^2.0.0"
834 pinkie-promise "^2.0.0"
835 strip-bom "^2.0.0"
836
837"long@~2 >=2.2.3":
838 version "2.4.0"
839 resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f"
840 integrity sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=
841
842lru@^3.0.0, lru@^3.1.0:
843 version "3.1.0"
844 resolved "https://registry.yarnpkg.com/lru/-/lru-3.1.0.tgz#ea7fb8546d83733396a13091d76cfeb4c06837d5"
845 integrity sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=
846 dependencies:
847 inherits "^2.0.1"
848
849magnet-uri@^5.1.3:
850 version "5.2.4"
851 resolved "https://registry.yarnpkg.com/magnet-uri/-/magnet-uri-5.2.4.tgz#7afe5b736af04445aff744c93a890a3710077688"
852 integrity sha512-VYaJMxhr8B9BrCiNINUsuhaEe40YnG+AQBwcqUKO66lSVaI9I3A1iH/6EmEwRI8OYUg5Gt+4lLE7achg676lrg==
853 dependencies:
854 thirty-two "^1.0.1"
855 uniq "^1.0.1"
856
857mdns-js-packet@~0.2.0:
858 version "0.2.0"
859 resolved "https://registry.yarnpkg.com/mdns-js-packet/-/mdns-js-packet-0.2.0.tgz#642409e8183c7561cc60615bbd1420ec2fad7616"
860 integrity sha1-ZCQJ6Bg8dWHMYGFbvRQg7C+tdhY=
861 dependencies:
862 debug "^2.1.0"
863 qap "^3.1.2"
864
865mdns-js@0.5.0:
866 version "0.5.0"
867 resolved "https://registry.yarnpkg.com/mdns-js/-/mdns-js-0.5.0.tgz#4c8abb6ba7cabdc892d39228c3faa2556e09cf87"
868 integrity sha1-TIq7a6fKvciS05Iow/qiVW4Jz4c=
869 dependencies:
870 debug "^2.1.1"
871 mdns-js-packet "~0.2.0"
872 semver "~5.1.0"
873
874mediasource@^2.1.0, mediasource@^2.2.2:
875 version "2.3.0"
876 resolved "https://registry.yarnpkg.com/mediasource/-/mediasource-2.3.0.tgz#4c7b49e7ea4fb88f1cc181d8fcf0d94649271dc6"
877 integrity sha512-fqm86UwHvAnneIv40Uy1sDQaFtAByq/k0SQ3uCtbnEeSQNT1s5TDHCZOD1VmYCHwfY1jL2NjoZVwzZKYqy3L7A==
878 dependencies:
879 inherits "^2.0.1"
880 readable-stream "^3.0.0"
881 to-arraybuffer "^1.0.1"
882
883memory-chunk-store@^1.2.0:
884 version "1.3.0"
885 resolved "https://registry.yarnpkg.com/memory-chunk-store/-/memory-chunk-store-1.3.0.tgz#ae99e7e3b58b52db43d49d94722930d39459d0c4"
886 integrity sha512-6LsOpHKKhxYrLhHmOJdBCUtSO7op5rUs1pag0fhjHo0QiXRyna0bwYf4EmQuL7InUeF2J7dUMPr6VMogRyf9NA==
887
888mime@^1.3.4, mime@^1.6.0:
889 version "1.6.0"
890 resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
891 integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
892
893mime@^2.1.0, mime@^2.4.0:
894 version "2.4.3"
895 resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.3.tgz#229687331e86f68924e6cb59e1cdd937f18275fe"
896 integrity sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==
897
898mimic-response@^1.0.0:
899 version "1.0.1"
900 resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
901 integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
902
903minimatch@^3.0.4:
904 version "3.0.4"
905 resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
906 integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
907 dependencies:
908 brace-expansion "^1.1.7"
909
910minimist@0.0.8:
911 version "0.0.8"
912 resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
913 integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
914
915minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
916 version "1.2.0"
917 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
918 integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
919
920minipass@^2.2.1, minipass@^2.3.4:
921 version "2.3.5"
922 resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
923 integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
924 dependencies:
925 safe-buffer "^5.1.2"
926 yallist "^3.0.0"
927
928minizlib@^1.1.1:
929 version "1.2.1"
930 resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
931 integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
932 dependencies:
933 minipass "^2.2.1"
934
935mkdirp@^0.5.0, mkdirp@^0.5.1:
936 version "0.5.1"
937 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
938 integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
939 dependencies:
940 minimist "0.0.8"
941
942moment@^2.12.0:
943 version "2.24.0"
944 resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
945 integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
946
947mp4-box-encoding@^1.1.0, mp4-box-encoding@^1.3.0:
948 version "1.3.0"
949 resolved "https://registry.yarnpkg.com/mp4-box-encoding/-/mp4-box-encoding-1.3.0.tgz#2a6f750947ff68c3a498fd76cd6424c53d995d48"
950 integrity sha512-U4pMLpjT/UzB8d36dxj6Mf1bG9xypEvgbuRIa1fztRXNKKTCAtRxsnFZhNOd7YDFOKtjBgssYGvo4H/Q3ZY1MA==
951 dependencies:
952 buffer-alloc "^1.2.0"
953 buffer-from "^1.1.0"
954 uint64be "^2.0.2"
955
956mp4-stream@^2.0.0:
957 version "2.0.3"
958 resolved "https://registry.yarnpkg.com/mp4-stream/-/mp4-stream-2.0.3.tgz#30acee07709d323f8dcd87a07b3ce9c3c4bfb364"
959 integrity sha512-5NzgI0+bGakoZEwnIYINXqB3mnewkt3Y7jcvkXsTubnCNUSdM8cpP0Vemxf6FLg0qUN8fydTgNMVAc3QU8B92g==
960 dependencies:
961 buffer-alloc "^1.1.0"
962 inherits "^2.0.1"
963 mp4-box-encoding "^1.1.0"
964 next-event "^1.0.0"
965 readable-stream "^2.0.3"
966
967ms@2.0.0:
968 version "2.0.0"
969 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
970 integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
971
972ms@^2.1.1:
973 version "2.1.1"
974 resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
975 integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
976
977multicast-dns@^6.0.1:
978 version "6.2.3"
979 resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229"
980 integrity sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==
981 dependencies:
982 dns-packet "^1.3.1"
983 thunky "^1.0.2"
984
985multistream@^2.0.2, multistream@^2.0.5:
986 version "2.1.1"
987 resolved "https://registry.yarnpkg.com/multistream/-/multistream-2.1.1.tgz#629d3a29bd76623489980d04519a2c365948148c"
988 integrity sha512-xasv76hl6nr1dEy3lPvy7Ej7K/Lx3O/FCvwge8PeVJpciPPoNCbaANcNiBug3IpdvTveZUcAV0DJzdnUDMesNQ==
989 dependencies:
990 inherits "^2.0.1"
991 readable-stream "^2.0.5"
992
993nan@*, nan@^2.3.2:
994 version "2.14.0"
995 resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
996 integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
997
998needle@^2.2.1:
999 version "2.4.0"
1000 resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
1001 integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
1002 dependencies:
1003 debug "^3.2.6"
1004 iconv-lite "^0.4.4"
1005 sax "^1.2.4"
1006
1007netmask@^1.0.6:
1008 version "1.0.6"
1009 resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
1010 integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
1011
1012netrc-parser@^3.1.6:
1013 version "3.1.6"
1014 resolved "https://registry.yarnpkg.com/netrc-parser/-/netrc-parser-3.1.6.tgz#7243c9ec850b8e805b9bdc7eae7b1450d4a96e72"
1015 integrity sha512-lY+fmkqSwntAAjfP63jB4z5p5WbuZwyMCD3pInT7dpHU/Gc6Vv90SAC6A0aNiqaRGHiuZFBtiwu+pu8W/Eyotw==
1016 dependencies:
1017 debug "^3.1.0"
1018 execa "^0.10.0"
1019
1020network-address@^1.0.0, network-address@^1.1.0:
1021 version "1.1.2"
1022 resolved "https://registry.yarnpkg.com/network-address/-/network-address-1.1.2.tgz#4aa7bfd43f03f0b81c9702b13d6a858ddb326f3e"
1023 integrity sha1-Sqe/1D8D8LgclwKxPWqFjdsybz4=
1024
1025next-event@^1.0.0:
1026 version "1.0.0"
1027 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8"
1028 integrity sha1-53eKzeLlWALgrRh5w5z2917aYdg=
1029
1030nice-try@^1.0.4:
1031 version "1.0.5"
1032 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
1033 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
1034
1035node-cmake@2.3.2:
1036 version "2.3.2"
1037 resolved "https://registry.yarnpkg.com/node-cmake/-/node-cmake-2.3.2.tgz#e0fbc54b11405b07705e4d6d41865ae95ad289d0"
1038 integrity sha1-4PvFSxFAWwdwXk1tQYZa6VrSidA=
1039 dependencies:
1040 nan "*"
1041 which "^1.2.14"
1042 yargs "^7.0.2"
1043
1044node-gyp-build@~3.7.0:
1045 version "3.7.0"
1046 resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
1047 integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
1048
1049node-pre-gyp@0.11.x:
1050 version "0.11.0"
1051 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
1052 integrity sha512-TwWAOZb0j7e9eGaf9esRx3ZcLaE5tQ2lvYy1pb5IAaG1a2e2Kv5Lms1Y4hpj+ciXJRofIxxlt5haeQ/2ANeE0Q==
1053 dependencies:
1054 detect-libc "^1.0.2"
1055 mkdirp "^0.5.1"
1056 needle "^2.2.1"
1057 nopt "^4.0.1"
1058 npm-packlist "^1.1.6"
1059 npmlog "^4.0.2"
1060 rc "^1.2.7"
1061 rimraf "^2.6.1"
1062 semver "^5.3.0"
1063 tar "^4"
1064
1065node-ssdp@^2.2.0, node-ssdp@^2.7.1:
1066 version "2.9.1"
1067 resolved "https://registry.yarnpkg.com/node-ssdp/-/node-ssdp-2.9.1.tgz#2d6ba8e7eff9bf5b338564f91f7ac5d5cdddc55b"
1068 integrity sha1-LWuo5+/5v1szhWT5H3rF1c3dxVs=
1069 dependencies:
1070 debug "^2.2.0"
1071 ip "^1.0.1"
1072
1073nodebmc@0.0.7:
1074 version "0.0.7"
1075 resolved "https://registry.yarnpkg.com/nodebmc/-/nodebmc-0.0.7.tgz#fae179165265509302cefbebeabd29bd4035184d"
1076 integrity sha1-+uF5FlJlUJMCzvvr6r0pvUA1GE0=
1077 dependencies:
1078 mdns-js "0.5.0"
1079
1080nopt@^4.0.1:
1081 version "4.0.1"
1082 resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
1083 integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
1084 dependencies:
1085 abbrev "1"
1086 osenv "^0.1.4"
1087
1088normalize-package-data@^2.3.2:
1089 version "2.5.0"
1090 resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
1091 integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
1092 dependencies:
1093 hosted-git-info "^2.1.4"
1094 resolve "^1.10.0"
1095 semver "2 || 3 || 4 || 5"
1096 validate-npm-package-license "^3.0.1"
1097
1098npm-bundled@^1.0.1:
1099 version "1.0.6"
1100 resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
1101 integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
1102
1103npm-packlist@^1.1.6:
1104 version "1.4.1"
1105 resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
1106 integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
1107 dependencies:
1108 ignore-walk "^3.0.1"
1109 npm-bundled "^1.0.1"
1110
1111npm-run-path@^2.0.0:
1112 version "2.0.2"
1113 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
1114 integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
1115 dependencies:
1116 path-key "^2.0.0"
1117
1118npmlog@^4.0.2:
1119 version "4.1.2"
1120 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
1121 integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
1122 dependencies:
1123 are-we-there-yet "~1.1.2"
1124 console-control-strings "~1.1.0"
1125 gauge "~2.7.3"
1126 set-blocking "~2.0.0"
1127
1128number-is-nan@^1.0.0:
1129 version "1.0.1"
1130 resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
1131 integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
1132
1133object-assign@^4.1.0:
1134 version "4.1.1"
1135 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
1136 integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
1137
1138once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
1139 version "1.4.0"
1140 resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
1141 integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
1142 dependencies:
1143 wrappy "1"
1144
1145open@0.0.5:
1146 version "0.0.5"
1147 resolved "https://registry.yarnpkg.com/open/-/open-0.0.5.tgz#42c3e18ec95466b6bf0dc42f3a2945c3f0cad8fc"
1148 integrity sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=
1149
1150optjs@latest:
1151 version "3.2.2"
1152 resolved "https://registry.yarnpkg.com/optjs/-/optjs-3.2.2.tgz#69a6ce89c442a44403141ad2f9b370bd5bb6f4ee"
1153 integrity sha1-aabOicRCpEQDFBrS+bNwvVu29O4=
1154
1155os-homedir@^1.0.0:
1156 version "1.0.2"
1157 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
1158 integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
1159
1160os-locale@^1.4.0:
1161 version "1.4.0"
1162 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
1163 integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=
1164 dependencies:
1165 lcid "^1.0.0"
1166
1167os-tmpdir@^1.0.0:
1168 version "1.0.2"
1169 resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
1170 integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
1171
1172osenv@^0.1.4:
1173 version "0.1.5"
1174 resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
1175 integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
1176 dependencies:
1177 os-homedir "^1.0.0"
1178 os-tmpdir "^1.0.0"
1179
1180p-finally@^1.0.0:
1181 version "1.0.0"
1182 resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
1183 integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
1184
1185package-json-versionify@^1.0.2:
1186 version "1.0.4"
1187 resolved "https://registry.yarnpkg.com/package-json-versionify/-/package-json-versionify-1.0.4.tgz#5860587a944873a6b7e6d26e8e51ffb22315bf17"
1188 integrity sha1-WGBYepRIc6a35tJujlH/siMVvxc=
1189 dependencies:
1190 browserify-package-json "^1.0.0"
1191
1192parse-json@^2.2.0:
1193 version "2.2.0"
1194 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
1195 integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=
1196 dependencies:
1197 error-ex "^1.2.0"
1198
1199parse-numeric-range@^0.0.2:
1200 version "0.0.2"
1201 resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4"
1202 integrity sha1-tPCdQTx6282Yf26SM8e0shDJOOQ=
1203
1204parse-torrent@^6.0.0, parse-torrent@^6.1.2:
1205 version "6.1.2"
1206 resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-6.1.2.tgz#99da5bdd23435a1cb7e8e7a63847c4efb21b1956"
1207 integrity sha512-Z/vig84sHwtrTEbOzisT4xnYTFlOgAaLQccPruMPgRahZUppVE/BUXzAos3jZM7c64o0lfukQdQ4ozWa5lN39w==
1208 dependencies:
1209 bencode "^2.0.0"
1210 blob-to-buffer "^1.2.6"
1211 get-stdin "^6.0.0"
1212 magnet-uri "^5.1.3"
1213 simple-get "^3.0.1"
1214 simple-sha1 "^2.0.0"
1215 uniq "^1.0.1"
1216
1217path-exists@^2.0.0:
1218 version "2.1.0"
1219 resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
1220 integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=
1221 dependencies:
1222 pinkie-promise "^2.0.0"
1223
1224path-is-absolute@^1.0.0:
1225 version "1.0.1"
1226 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
1227 integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
1228
1229path-key@^2.0.0, path-key@^2.0.1:
1230 version "2.0.1"
1231 resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
1232 integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
1233
1234path-parse@^1.0.6:
1235 version "1.0.6"
1236 resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
1237 integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
1238
1239path-type@^1.0.0:
1240 version "1.1.0"
1241 resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
1242 integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=
1243 dependencies:
1244 graceful-fs "^4.1.2"
1245 pify "^2.0.0"
1246 pinkie-promise "^2.0.0"
1247
1248piece-length@^1.0.0:
1249 version "1.0.0"
1250 resolved "https://registry.yarnpkg.com/piece-length/-/piece-length-1.0.0.tgz#4db7167157fd69fef14caf7262cd39f189b24508"
1251 integrity sha1-TbcWcVf9af7xTK9yYs058YmyRQg=
1252 dependencies:
1253 closest-to "~2.0.0"
1254
1255pify@^2.0.0, pify@^2.2.0:
1256 version "2.3.0"
1257 resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
1258 integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
1259
1260pinkie-promise@^2.0.0:
1261 version "2.0.1"
1262 resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
1263 integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
1264 dependencies:
1265 pinkie "^2.0.0"
1266
1267pinkie@^2.0.0:
1268 version "2.0.4"
1269 resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
1270 integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
1271
1272plist-with-patches@0.5.1:
1273 version "0.5.1"
1274 resolved "https://registry.yarnpkg.com/plist-with-patches/-/plist-with-patches-0.5.1.tgz#868aae2e0df8989b026562b35cbc19cfd8bb780d"
1275 integrity sha1-hoquLg34mJsCZWKzXLwZz9i7eA0=
1276 dependencies:
1277 xmlbuilder "0.4.x"
1278 xmldom "0.1.x"
1279
1280prettier-bytes@^1.0.3:
1281 version "1.0.4"
1282 resolved "https://registry.yarnpkg.com/prettier-bytes/-/prettier-bytes-1.0.4.tgz#994b02aa46f699c50b6257b5faaa7fe2557e62d6"
1283 integrity sha1-mUsCqkb2mcULYle1+qp/4lV+YtY=
1284
1285process-nextick-args@~2.0.0:
1286 version "2.0.0"
1287 resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
1288 integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
1289
1290protobufjs@^3.2.2:
1291 version "3.8.2"
1292 resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-3.8.2.tgz#bc826e34c3af4697e8d0af7a669e4d612aedcd17"
1293 integrity sha1-vIJuNMOvRpfo0K96Zp5NYSrtzRc=
1294 dependencies:
1295 ascli "~0.3"
1296 bytebuffer "~3 >=3.5"
1297
1298pump@^3.0.0:
1299 version "3.0.0"
1300 resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
1301 integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
1302 dependencies:
1303 end-of-stream "^1.1.0"
1304 once "^1.3.1"
1305
1306qap@^3.1.2:
1307 version "3.3.1"
1308 resolved "https://registry.yarnpkg.com/qap/-/qap-3.3.1.tgz#11f9e8fa8890fe7cb99210c0f44d0613b7372cac"
1309 integrity sha1-Efno+oiQ/ny5khDA9E0GE7c3LKw=
1310
1311random-access-file@^2.0.1:
1312 version "2.1.2"
1313 resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.2.tgz#eeb32e50b9831f32060516862381152ae4e05aa6"
1314 integrity sha512-dZo7HEcEPbZ/6XLXC4GXypiWvFbXVkdeMrJTi0B94pBJwddt/AvJh8GaQhso6KGYROGYCI/VWdHbmRDtkwT9pQ==
1315 dependencies:
1316 mkdirp "^0.5.1"
1317 random-access-storage "^1.1.1"
1318
1319random-access-storage@^1.1.1:
1320 version "1.3.0"
1321 resolved "https://registry.yarnpkg.com/random-access-storage/-/random-access-storage-1.3.0.tgz#d27e4d897b79dc4358afc2bbe553044e5c8cfe35"
1322 integrity sha512-pdS9Mcb9TB7oICypPRALlheaSuszuAKmLVEPKJMuYor7R/zDuHh5ALuQoS+ox31XRwQUL+tDwWH2GPdyspwelA==
1323 dependencies:
1324 inherits "^2.0.3"
1325
1326random-iterate@^1.0.1:
1327 version "1.0.1"
1328 resolved "https://registry.yarnpkg.com/random-iterate/-/random-iterate-1.0.1.tgz#f7d97d92dee6665ec5f6da08c7f963cad4b2ac99"
1329 integrity sha1-99l9kt7mZl7F9toIx/ljytSyrJk=
1330
1331randombytes@^2.0.3, randombytes@^2.0.5:
1332 version "2.1.0"
1333 resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
1334 integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
1335 dependencies:
1336 safe-buffer "^5.1.0"
1337
1338range-parser@^1.2.0:
1339 version "1.2.1"
1340 resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
1341 integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
1342
1343range-slice-stream@^2.0.0:
1344 version "2.0.0"
1345 resolved "https://registry.yarnpkg.com/range-slice-stream/-/range-slice-stream-2.0.0.tgz#1f25fc7a2cacf9ccd140c46f9cf670a1a7fe3ce6"
1346 integrity sha512-PPYLwZ63lXi6Tv2EZ8w3M4FzC0rVqvxivaOVS8pXSp5FMIHFnvi4MWHL3UdFLhwSy50aNtJsgjY0mBC6oFL26Q==
1347 dependencies:
1348 readable-stream "^3.0.2"
1349
1350rc@^1.2.7:
1351 version "1.2.8"
1352 resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
1353 integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
1354 dependencies:
1355 deep-extend "^0.6.0"
1356 ini "~1.3.0"
1357 minimist "^1.2.0"
1358 strip-json-comments "~2.0.1"
1359
1360read-pkg-up@^1.0.1:
1361 version "1.0.1"
1362 resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
1363 integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=
1364 dependencies:
1365 find-up "^1.0.0"
1366 read-pkg "^1.0.0"
1367
1368read-pkg@^1.0.0:
1369 version "1.1.0"
1370 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
1371 integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=
1372 dependencies:
1373 load-json-file "^1.0.0"
1374 normalize-package-data "^2.3.2"
1375 path-type "^1.0.0"
1376
1377readable-stream@^2.0.3, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.2, readable-stream@^2.3.4:
1378 version "2.3.6"
1379 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
1380 integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
1381 dependencies:
1382 core-util-is "~1.0.0"
1383 inherits "~2.0.3"
1384 isarray "~1.0.0"
1385 process-nextick-args "~2.0.0"
1386 safe-buffer "~5.1.1"
1387 string_decoder "~1.1.1"
1388 util-deprecate "~1.0.1"
1389
1390readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6:
1391 version "3.3.0"
1392 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9"
1393 integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==
1394 dependencies:
1395 inherits "^2.0.3"
1396 string_decoder "^1.1.1"
1397 util-deprecate "^1.0.1"
1398
1399record-cache@^1.0.2:
1400 version "1.1.0"
1401 resolved "https://registry.yarnpkg.com/record-cache/-/record-cache-1.1.0.tgz#f8a467a691a469584b26e88d36b18afdb3932037"
1402 integrity sha512-u8rbtLEJV7HRacl/ZYwSBFD8NFyB3PfTTfGLP37IW3hftQCwu6z4Q2RLyxo1YJUNRTEzJfpLpGwVuEYdaIkG9Q==
1403
1404render-media@^3.0.0:
1405 version "3.1.3"
1406 resolved "https://registry.yarnpkg.com/render-media/-/render-media-3.1.3.tgz#aa8c8cd3f720049370067180709b551d3c566254"
1407 integrity sha512-K7ziKKlIcgYpAovRsABDiSaNn7TzDDyyuFGpRwM52cloNcajInB6sCxFPUEzOuTJUeyvKCqT/k5INOjpKLCjhQ==
1408 dependencies:
1409 debug "^3.1.0"
1410 is-ascii "^1.0.0"
1411 mediasource "^2.1.0"
1412 stream-to-blob-url "^2.0.0"
1413 videostream "^2.5.1"
1414
1415require-directory@^2.1.1:
1416 version "2.1.1"
1417 resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
1418 integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
1419
1420require-main-filename@^1.0.1:
1421 version "1.0.1"
1422 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
1423 integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
1424
1425resolve@^1.10.0:
1426 version "1.11.0"
1427 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
1428 integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
1429 dependencies:
1430 path-parse "^1.0.6"
1431
1432rimraf@^2.4.2, rimraf@^2.6.1:
1433 version "2.6.3"
1434 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
1435 integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
1436 dependencies:
1437 glob "^7.1.3"
1438
1439run-parallel-limit@^1.0.3:
1440 version "1.0.5"
1441 resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.5.tgz#c29a4fd17b4df358cb52a8a697811a63c984f1b7"
1442 integrity sha512-NsY+oDngvrvMxKB3G8ijBzIema6aYbQMD2bHOamvN52BysbIGTnEY2xsNyfrcr9GhY995/t/0nQN3R3oZvaDlg==
1443
1444run-parallel@^1.0.0, run-parallel@^1.1.2, run-parallel@^1.1.6:
1445 version "1.1.9"
1446 resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
1447 integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
1448
1449run-series@^1.0.2:
1450 version "1.1.8"
1451 resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36"
1452 integrity sha512-+GztYEPRpIsQoCSraWHDBs9WVy4eVME16zhOtDB4H9J4xN0XRhknnmLOl+4gRgZtu8dpp9N/utSPjKH/xmDzXg==
1453
1454rusha@^0.8.1:
1455 version "0.8.13"
1456 resolved "https://registry.yarnpkg.com/rusha/-/rusha-0.8.13.tgz#9a084e7b860b17bff3015b92c67a6a336191513a"
1457 integrity sha1-mghOe4YLF7/zAVuSxnpqM2GRUTo=
1458
1459safe-buffer@^5.0.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
1460 version "5.1.2"
1461 resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
1462 integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
1463
1464"safer-buffer@>= 2.1.2 < 3":
1465 version "2.1.2"
1466 resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
1467 integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
1468
1469sax@1.1.4:
1470 version "1.1.4"
1471 resolved "https://registry.yarnpkg.com/sax/-/sax-1.1.4.tgz#74b6d33c9ae1e001510f179a91168588f1aedaa9"
1472 integrity sha1-dLbTPJrh4AFRDxeakRaFiPGu2qk=
1473
1474sax@>=0.6.0, sax@^1.2.4:
1475 version "1.2.4"
1476 resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
1477 integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
1478
1479"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0:
1480 version "5.7.0"
1481 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
1482 integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
1483
1484semver@~5.1.0:
1485 version "5.1.1"
1486 resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19"
1487 integrity sha1-oykqNz5vPgeY2gsgZBuanFvEfhk=
1488
1489set-blocking@^2.0.0, set-blocking@~2.0.0:
1490 version "2.0.0"
1491 resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
1492 integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
1493
1494shebang-command@^1.2.0:
1495 version "1.2.0"
1496 resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
1497 integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
1498 dependencies:
1499 shebang-regex "^1.0.0"
1500
1501shebang-regex@^1.0.0:
1502 version "1.0.0"
1503 resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
1504 integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
1505
1506signal-exit@^3.0.0:
1507 version "3.0.2"
1508 resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
1509 integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
1510
1511simple-concat@^1.0.0:
1512 version "1.0.0"
1513 resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6"
1514 integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=
1515
1516simple-get@^2.0.0, simple-get@^2.1.0:
1517 version "2.8.1"
1518 resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d"
1519 integrity sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==
1520 dependencies:
1521 decompress-response "^3.3.0"
1522 once "^1.3.1"
1523 simple-concat "^1.0.0"
1524
1525simple-get@^3.0.0, simple-get@^3.0.1:
1526 version "3.0.3"
1527 resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.0.3.tgz#924528ac3f9d7718ce5e9ec1b1a69c0be4d62efa"
1528 integrity sha512-Wvre/Jq5vgoz31Z9stYWPLn0PqRqmBDpFSdypAnHu5AvRVCYPRYGnvryNLiXu8GOBNDH82J2FRHUGMjjHUpXFw==
1529 dependencies:
1530 decompress-response "^3.3.0"
1531 once "^1.3.1"
1532 simple-concat "^1.0.0"
1533
1534simple-peer@^9.0.0:
1535 version "9.3.0"
1536 resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.3.0.tgz#85ecb126b23d8730f3904f199db65e84141e0f4e"
1537 integrity sha512-5dLDfrRomrS2LuZUuH2aO7yTGtHFEl5Eb+8ZzqM0KC0lHcYUyJudUomP9ZY/lPUKBx2broL/Eee9bQ53yycEgQ==
1538 dependencies:
1539 debug "^4.0.1"
1540 get-browser-rtc "^1.0.0"
1541 inherits "^2.0.1"
1542 randombytes "^2.0.3"
1543 readable-stream "^2.3.4"
1544
1545simple-sha1@^2.0.0, simple-sha1@^2.0.8, simple-sha1@^2.1.0:
1546 version "2.1.2"
1547 resolved "https://registry.yarnpkg.com/simple-sha1/-/simple-sha1-2.1.2.tgz#de40cbd5aae278fde8e3bb3250a35d74c67326b1"
1548 integrity sha512-TQl9rm4rdKAVmhO++sXAb8TNN0D6JAD5iyI1mqEPNpxUzTRrtm4aOG1pDf/5W/qCFihiaoK6uuL9rvQz1x1VKw==
1549 dependencies:
1550 rusha "^0.8.1"
1551
1552simple-websocket@^7.0.1:
1553 version "7.2.0"
1554 resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-7.2.0.tgz#c3190555d74399372b96b51435f2d8c4b04611df"
1555 integrity sha512-wdxFg1fHw1yqFKWDcw+yNb4VIYqtl+vknZMlpLhvZSlR6l7/iVuwozqo+Qtl73mB1IH5QnXzonD1S+hAaLNTvQ==
1556 dependencies:
1557 debug "^3.1.0"
1558 inherits "^2.0.1"
1559 randombytes "^2.0.3"
1560 readable-stream "^2.0.5"
1561 ws "^6.0.0"
1562
1563spdx-correct@^3.0.0:
1564 version "3.1.0"
1565 resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
1566 integrity sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==
1567 dependencies:
1568 spdx-expression-parse "^3.0.0"
1569 spdx-license-ids "^3.0.0"
1570
1571spdx-exceptions@^2.1.0:
1572 version "2.2.0"
1573 resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
1574 integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==
1575
1576spdx-expression-parse@^3.0.0:
1577 version "3.0.0"
1578 resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
1579 integrity sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==
1580 dependencies:
1581 spdx-exceptions "^2.1.0"
1582 spdx-license-ids "^3.0.0"
1583
1584spdx-license-ids@^3.0.0:
1585 version "3.0.4"
1586 resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1"
1587 integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==
1588
1589speedometer@^1.0.0:
1590 version "1.1.0"
1591 resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.1.0.tgz#a30b13abda45687a1a76977012c060f2ac8a7934"
1592 integrity sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==
1593
1594split@^1.0.0:
1595 version "1.0.1"
1596 resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
1597 integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==
1598 dependencies:
1599 through "2"
1600
1601stream-to-blob-url@^2.0.0, stream-to-blob-url@^2.1.0:
1602 version "2.1.1"
1603 resolved "https://registry.yarnpkg.com/stream-to-blob-url/-/stream-to-blob-url-2.1.1.tgz#e1ac97f86ca8e9f512329a48e7830ce9a50beef2"
1604 integrity sha512-DKJPEmCmIZoBfGVle9IhSfERiWaN5cuOtmfPxP2dZbLDRZxkBWZ4QbYxEJOSALk1Kf+WjBgedAMO6qkkf7Lmrg==
1605 dependencies:
1606 stream-to-blob "^1.0.0"
1607
1608stream-to-blob@^1.0.0:
1609 version "1.0.1"
1610 resolved "https://registry.yarnpkg.com/stream-to-blob/-/stream-to-blob-1.0.1.tgz#2dc1e09b71677a234d00445f8eb7ff70c4fe9948"
1611 integrity sha512-aRy4neA4rf+qMtLT9fCRLPGWdrsIKtCx4kUdNTIPgPQ2hkHkdxbViVAvABMx9oRM6yCWfngHx6pwXfbYkVuPuw==
1612 dependencies:
1613 once "^1.3.3"
1614
1615stream-with-known-length-to-buffer@^1.0.0:
1616 version "1.0.2"
1617 resolved "https://registry.yarnpkg.com/stream-with-known-length-to-buffer/-/stream-with-known-length-to-buffer-1.0.2.tgz#b8ea5a92086a1ed5d27fc4c529636682118c945b"
1618 integrity sha512-UxSISjxmguvfYzZdq6d4XAjc3gAocqTIOS1CjgwkDkkGT/LMTsIYiV8agIw42IHFFHf8k4lPOoroCCf4W9oqzg==
1619 dependencies:
1620 once "^1.3.3"
1621
1622string-width@^1.0.1, string-width@^1.0.2:
1623 version "1.0.2"
1624 resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
1625 integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
1626 dependencies:
1627 code-point-at "^1.0.0"
1628 is-fullwidth-code-point "^1.0.0"
1629 strip-ansi "^3.0.0"
1630
1631"string-width@^1.0.2 || 2":
1632 version "2.1.1"
1633 resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
1634 integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
1635 dependencies:
1636 is-fullwidth-code-point "^2.0.0"
1637 strip-ansi "^4.0.0"
1638
1639string2compact@^1.1.1, string2compact@^1.2.5:
1640 version "1.3.0"
1641 resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.0.tgz#22d946127b082d1203c51316af60117a337423c3"
1642 integrity sha512-004ulKKANDuQilQsNxy2lisrpMG0qUJxBU+2YCEF7KziRyNR0Nredm2qk0f1V82nva59H3y9GWeHXE63HzGRFw==
1643 dependencies:
1644 addr-to-ip-port "^1.0.1"
1645 ipaddr.js "^1.0.1"
1646
1647string_decoder@^1.1.1:
1648 version "1.2.0"
1649 resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
1650 integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
1651 dependencies:
1652 safe-buffer "~5.1.0"
1653
1654string_decoder@~1.1.1:
1655 version "1.1.1"
1656 resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
1657 integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
1658 dependencies:
1659 safe-buffer "~5.1.0"
1660
1661strip-ansi@^3.0.0, strip-ansi@^3.0.1:
1662 version "3.0.1"
1663 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
1664 integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
1665 dependencies:
1666 ansi-regex "^2.0.0"
1667
1668strip-ansi@^4.0.0:
1669 version "4.0.0"
1670 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
1671 integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
1672 dependencies:
1673 ansi-regex "^3.0.0"
1674
1675strip-bom@^2.0.0:
1676 version "2.0.0"
1677 resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
1678 integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=
1679 dependencies:
1680 is-utf8 "^0.2.0"
1681
1682strip-eof@^1.0.0:
1683 version "1.0.0"
1684 resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
1685 integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
1686
1687strip-json-comments@~2.0.1:
1688 version "2.0.1"
1689 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
1690 integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
1691
1692tar@^4:
1693 version "4.4.8"
1694 resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
1695 integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
1696 dependencies:
1697 chownr "^1.1.1"
1698 fs-minipass "^1.2.5"
1699 minipass "^2.3.4"
1700 minizlib "^1.1.1"
1701 mkdirp "^0.5.0"
1702 safe-buffer "^5.1.2"
1703 yallist "^3.0.2"
1704
1705thirty-two@^1.0.1:
1706 version "1.0.2"
1707 resolved "https://registry.yarnpkg.com/thirty-two/-/thirty-two-1.0.2.tgz#4ca2fffc02a51290d2744b9e3f557693ca6b627a"
1708 integrity sha1-TKL//AKlEpDSdEueP1V2k8prYno=
1709
1710through@2:
1711 version "2.3.8"
1712 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
1713 integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
1714
1715thunky@^0.1.0:
1716 version "0.1.0"
1717 resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
1718 integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
1719
1720thunky@^1.0.1, thunky@^1.0.2:
1721 version "1.0.3"
1722 resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.3.tgz#f5df732453407b09191dae73e2a8cc73f381a826"
1723 integrity sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==
1724
1725to-arraybuffer@^1.0.1:
1726 version "1.0.1"
1727 resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
1728 integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
1729
1730torrent-discovery@^9.1.1:
1731 version "9.1.1"
1732 resolved "https://registry.yarnpkg.com/torrent-discovery/-/torrent-discovery-9.1.1.tgz#56704e6747b24fe00dbb75b442d202051f78d37d"
1733 integrity sha512-3mHf+bxVCVLrlkPJdAoMbPMY1hpTZVeWw5hNc2pPFm+HCc2DS0HgVFTBTSWtB8vQPWA1hSEZpqJ+3QfdXxDE1g==
1734 dependencies:
1735 bittorrent-dht "^9.0.0"
1736 bittorrent-tracker "^9.0.0"
1737 debug "^3.1.0"
1738 run-parallel "^1.1.2"
1739
1740torrent-piece@^2.0.0:
1741 version "2.0.0"
1742 resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-2.0.0.tgz#6598ae67d93699e887f178db267ba16d89d7ec9b"
1743 integrity sha512-H/Z/yCuvZJj1vl1IQHI8dvF2QrUuXRJoptT5DW5967/dsLpXlCg+uyhFR5lfNj5mNaYePUbKtnL+qKWZGXv4Nw==
1744
1745typedarray-to-buffer@^3.0.0:
1746 version "3.1.5"
1747 resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
1748 integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
1749 dependencies:
1750 is-typedarray "^1.0.0"
1751
1752typedarray@^0.0.6:
1753 version "0.0.6"
1754 resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
1755 integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
1756
1757uint64be@^2.0.2:
1758 version "2.0.2"
1759 resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-2.0.2.tgz#ef4a179752fe8f9ddaa29544ecfc13490031e8e5"
1760 integrity sha512-9QqdvpGQTXgxthP+lY4e/gIBy+RuqcBaC6JVwT5I3bDLgT/btL6twZMR0pI3/Fgah9G/pdwzIprE5gL6v9UvyQ==
1761 dependencies:
1762 buffer-alloc "^1.1.0"
1763
1764uniq@^1.0.1:
1765 version "1.0.1"
1766 resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
1767 integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
1768
1769unordered-array-remove@^1.0.2:
1770 version "1.0.2"
1771 resolved "https://registry.yarnpkg.com/unordered-array-remove/-/unordered-array-remove-1.0.2.tgz#c546e8f88e317a0cf2644c97ecb57dba66d250ef"
1772 integrity sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=
1773
1774upnp-device-client@^1.0.0:
1775 version "1.0.2"
1776 resolved "https://registry.yarnpkg.com/upnp-device-client/-/upnp-device-client-1.0.2.tgz#91f84705f2349bf89082855fff4e3006ac435337"
1777 integrity sha1-kfhHBfI0m/iQgoVf/04wBqxDUzc=
1778 dependencies:
1779 concat-stream "^1.4.8"
1780 debug "^2.1.3"
1781 elementtree "~0.1.6"
1782 network-address "^1.0.0"
1783
1784upnp-mediarenderer-client@^1.2.2:
1785 version "1.2.4"
1786 resolved "https://registry.yarnpkg.com/upnp-mediarenderer-client/-/upnp-mediarenderer-client-1.2.4.tgz#0c63a51802082b6b03b596c475cc64fc1e0877c8"
1787 integrity sha1-DGOlGAIIK2sDtZbEdcxk/B4Id8g=
1788 dependencies:
1789 debug "^2.1.3"
1790 elementtree "^0.1.6"
1791 upnp-device-client "^1.0.0"
1792
1793url-join@^2.0.5:
1794 version "2.0.5"
1795 resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728"
1796 integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=
1797
1798ut_metadata@^3.3.0:
1799 version "3.3.0"
1800 resolved "https://registry.yarnpkg.com/ut_metadata/-/ut_metadata-3.3.0.tgz#a0e0e861ebc39ed96e506601d1463ade3b548a7e"
1801 integrity sha512-IK+ke9yL6a4oPLz/3oSW9TW7m9Wr4RG+5kW5aS2YulzEU1QDGAtago/NnOlno91fo3fSO7mnsqzn3NXNXdv8nA==
1802 dependencies:
1803 bencode "^2.0.0"
1804 bitfield "^2.0.0"
1805 debug "^3.1.0"
1806 simple-sha1 "^2.0.0"
1807
1808ut_pex@^1.1.1:
1809 version "1.2.1"
1810 resolved "https://registry.yarnpkg.com/ut_pex/-/ut_pex-1.2.1.tgz#472ed0ea5e9bbc9148b833339d56d7b17cf3dad0"
1811 integrity sha512-ZrxMCbffYtxQDqvREN9kBXK2CB9tPnd5PylHoqQX9ai+3HV9/S39FnA5JnhLOC82dxIQQg0nTN2wmhtAdGNtOA==
1812 dependencies:
1813 bencode "^2.0.0"
1814 compact2string "^1.2.0"
1815 inherits "^2.0.1"
1816 string2compact "^1.2.5"
1817
1818utf-8-validate@^5.0.1:
1819 version "5.0.2"
1820 resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3"
1821 integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==
1822 dependencies:
1823 node-gyp-build "~3.7.0"
1824
1825util-deprecate@^1.0.1, util-deprecate@~1.0.1:
1826 version "1.0.2"
1827 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
1828 integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
1829
1830validate-npm-package-license@^3.0.1:
1831 version "3.0.4"
1832 resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
1833 integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
1834 dependencies:
1835 spdx-correct "^3.0.0"
1836 spdx-expression-parse "^3.0.0"
1837
1838videostream@^2.5.1:
1839 version "2.6.0"
1840 resolved "https://registry.yarnpkg.com/videostream/-/videostream-2.6.0.tgz#7f0b2b84bc457c12cfe599aa2345f5cc06241ab6"
1841 integrity sha512-nSsullx1BYClJxVSt4Fa+Ulsv0Cf7UwaHq+4LQdLkAUdmqNhY1DlGxXDWVY2gui5XV4FvDiSbXmSbGryMrrUCQ==
1842 dependencies:
1843 binary-search "^1.3.4"
1844 inherits "^2.0.1"
1845 mediasource "^2.2.2"
1846 mp4-box-encoding "^1.3.0"
1847 mp4-stream "^2.0.0"
1848 multistream "^2.0.2"
1849 pump "^3.0.0"
1850 range-slice-stream "^2.0.0"
1851
1852vlc-command@^1.0.0:
1853 version "1.1.2"
1854 resolved "https://registry.yarnpkg.com/vlc-command/-/vlc-command-1.1.2.tgz#61a9b4249a0001c0bcac8cdaf36d3a8e674cffce"
1855 integrity sha512-KZ15RTHz96OEiQDA8oNFn1edYDWyKJIWI4gF74Am9woZo5XmVYryk5RYXSwOMvsaAgL5ejICEGCl0suQyDBu+Q==
1856 dependencies:
1857 run-parallel "^1.1.6"
1858 winreg "^1.2.1"
1859
1860webidl-conversions@^4.0.2:
1861 version "4.0.2"
1862 resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
1863 integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
1864
1865webtorrent-cli@^1.12.3:
1866 version "1.12.3"
1867 resolved "https://registry.yarnpkg.com/webtorrent-cli/-/webtorrent-cli-1.12.3.tgz#e6a1060cd3f104346da91e67763276ca897f238c"
1868 integrity sha512-NnBAGkD64CRsl9edM9q0QU+ku6nCX32nM0U+YC8Gs/36c8y+5m9Tya3mWIux3oZKZ54yGiVtnok4tUpqDE5tMA==
1869 dependencies:
1870 clivas "^0.2.0"
1871 create-torrent "^3.23.1"
1872 dlnacasts "^0.1.0"
1873 ecstatic "^3.0.0"
1874 executable "^4.0.0"
1875 mime "^2.1.0"
1876 minimist "^1.2.0"
1877 moment "^2.12.0"
1878 network-address "^1.1.0"
1879 open "0.0.5"
1880 parse-torrent "^6.0.0"
1881 prettier-bytes "^1.0.3"
1882 vlc-command "^1.0.0"
1883 webtorrent "0.x"
1884 winreg "^1.0.1"
1885 optionalDependencies:
1886 airplay-js "^0.3.0"
1887 chromecasts "^1.5.3"
1888 nodebmc "0.0.7"
1889
1890webtorrent-hybrid@^2.1.0:
1891 version "2.1.0"
1892 resolved "https://registry.yarnpkg.com/webtorrent-hybrid/-/webtorrent-hybrid-2.1.0.tgz#c14d33d6769667d8ae524ca2d9dfdcd18d4cfbf2"
1893 integrity sha512-S8tUgUbPLwGazPrBMTqjsuxlmhaCZaiC+KlgS7ECRGaHVVZTJjKG2kUw8uf558DdZbsEA3jNxOOsMvXiA62sFw==
1894 dependencies:
1895 create-torrent "^3.33.0"
1896 webtorrent "^0.x"
1897 webtorrent-cli "^1.12.3"
1898 wrtc "^0.3.3"
1899
1900webtorrent@0.x, webtorrent@^0.x:
1901 version "0.103.1"
1902 resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-0.103.1.tgz#18ead369bbcaa60dc8ea138784c33451edd34479"
1903 integrity sha512-rqMD8sAaPzrUzEpA6gDEOgcN9Xyz4WGrQiHoVY6HlymLoNSkScMnmnEX1bsdMcIU9iOrnUlghDEW9sJ9jYCmwQ==
1904 dependencies:
1905 addr-to-ip-port "^1.4.2"
1906 bitfield "^2.0.0"
1907 bittorrent-dht "^9.0.0"
1908 bittorrent-protocol "^3.0.0"
1909 chunk-store-stream "^3.0.1"
1910 create-torrent "^3.33.0"
1911 debug "^4.1.0"
1912 end-of-stream "^1.1.0"
1913 fs-chunk-store "^1.6.2"
1914 immediate-chunk-store "^2.0.0"
1915 load-ip-set "^2.1.0"
1916 memory-chunk-store "^1.2.0"
1917 mime "^2.4.0"
1918 multistream "^2.0.5"
1919 package-json-versionify "^1.0.2"
1920 parse-numeric-range "^0.0.2"
1921 parse-torrent "^6.1.2"
1922 pump "^3.0.0"
1923 random-iterate "^1.0.1"
1924 randombytes "^2.0.3"
1925 range-parser "^1.2.0"
1926 readable-stream "^3.0.6"
1927 render-media "^3.0.0"
1928 run-parallel "^1.1.6"
1929 run-parallel-limit "^1.0.3"
1930 safe-buffer "^5.0.1"
1931 simple-concat "^1.0.0"
1932 simple-get "^3.0.1"
1933 simple-peer "^9.0.0"
1934 simple-sha1 "^2.0.8"
1935 speedometer "^1.0.0"
1936 stream-to-blob "^1.0.0"
1937 stream-to-blob-url "^2.1.0"
1938 stream-with-known-length-to-buffer "^1.0.0"
1939 torrent-discovery "^9.1.1"
1940 torrent-piece "^2.0.0"
1941 uniq "^1.0.1"
1942 unordered-array-remove "^1.0.2"
1943 ut_metadata "^3.3.0"
1944 ut_pex "^1.1.1"
1945
1946which-module@^1.0.0:
1947 version "1.0.0"
1948 resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
1949 integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=
1950
1951which@^1.2.14, which@^1.2.9:
1952 version "1.3.1"
1953 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
1954 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
1955 dependencies:
1956 isexe "^2.0.0"
1957
1958wide-align@^1.1.0:
1959 version "1.1.3"
1960 resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
1961 integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
1962 dependencies:
1963 string-width "^1.0.2 || 2"
1964
1965winreg@^1.0.1, winreg@^1.2.1:
1966 version "1.2.4"
1967 resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
1968 integrity sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=
1969
1970wrap-ansi@^2.0.0:
1971 version "2.1.0"
1972 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
1973 integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=
1974 dependencies:
1975 string-width "^1.0.1"
1976 strip-ansi "^3.0.1"
1977
1978wrappy@1:
1979 version "1.0.2"
1980 resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
1981 integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
1982
1983wrtc@^0.3.3:
1984 version "0.3.7"
1985 resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.3.7.tgz#2279f1cb3a83573e77b3d9b7e720071fab2ae4af"
1986 integrity sha512-mDFNFqAB+3IYVKlP15vpGD0EhXjsQlj/GLNje4KLpClLSq8pyTG0xqJFoU+Oq43XvDIUMmIJ/r1aNfrjT7KUQw==
1987 dependencies:
1988 nan "^2.3.2"
1989 node-cmake "2.3.2"
1990 node-pre-gyp "0.11.x"
1991 optionalDependencies:
1992 domexception "^1.0.1"
1993
1994ws@^6.0.0:
1995 version "6.2.1"
1996 resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
1997 integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
1998 dependencies:
1999 async-limiter "~1.0.0"
2000
2001xml2js@^0.4.8:
2002 version "0.4.19"
2003 resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
2004 integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
2005 dependencies:
2006 sax ">=0.6.0"
2007 xmlbuilder "~9.0.1"
2008
2009xmlbuilder@0.4.x:
2010 version "0.4.3"
2011 resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58"
2012 integrity sha1-xGFLp04K0ZbmCcknLNnh3bKKilg=
2013
2014xmlbuilder@~9.0.1:
2015 version "9.0.7"
2016 resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
2017 integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
2018
2019xmldom@0.1.x:
2020 version "0.1.27"
2021 resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
2022 integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
2023
2024xtend@^4.0.0, xtend@^4.0.1:
2025 version "4.0.1"
2026 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
2027 integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
2028
2029y18n@^3.2.1:
2030 version "3.2.1"
2031 resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
2032 integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
2033
2034yallist@^3.0.0, yallist@^3.0.2:
2035 version "3.0.3"
2036 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
2037 integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
2038
2039yargs-parser@^5.0.0:
2040 version "5.0.0"
2041 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
2042 integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=
2043 dependencies:
2044 camelcase "^3.0.0"
2045
2046yargs@^7.0.2:
2047 version "7.1.0"
2048 resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
2049 integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=
2050 dependencies:
2051 camelcase "^3.0.0"
2052 cliui "^3.2.0"
2053 decamelize "^1.1.1"
2054 get-caller-file "^1.0.1"
2055 os-locale "^1.4.0"
2056 read-pkg-up "^1.0.1"
2057 require-directory "^2.1.1"
2058 require-main-filename "^1.0.1"
2059 set-blocking "^2.0.0"
2060 string-width "^1.0.2"
2061 which-module "^1.0.0"
2062 y18n "^3.2.1"
2063 yargs-parser "^5.0.0"
diff --git a/shared/core-utils/miscs/date.ts b/shared/core-utils/miscs/date.ts
new file mode 100644
index 000000000..4f92f758f
--- /dev/null
+++ b/shared/core-utils/miscs/date.ts
@@ -0,0 +1,67 @@
1function isToday (d: Date) {
2 const today = new Date()
3
4 return areDatesEqual(d, today)
5}
6
7function isYesterday (d: Date) {
8 const yesterday = new Date()
9 yesterday.setDate(yesterday.getDate() - 1)
10
11 return areDatesEqual(d, yesterday)
12}
13
14function isThisWeek (d: Date) {
15 const minDateOfThisWeek = new Date()
16 minDateOfThisWeek.setHours(0, 0, 0)
17
18 // getDay() -> Sunday - Saturday : 0 - 6
19 // We want to start our week on Monday
20 let dayOfWeek = minDateOfThisWeek.getDay() - 1
21 if (dayOfWeek < 0) dayOfWeek = 6 // Sunday
22
23 minDateOfThisWeek.setDate(minDateOfThisWeek.getDate() - dayOfWeek)
24
25 return d >= minDateOfThisWeek
26}
27
28function isThisMonth (d: Date) {
29 const thisMonth = new Date().getMonth()
30
31 return d.getMonth() === thisMonth
32}
33
34function isLastMonth (d: Date) {
35 const now = new Date()
36
37 return getDaysDifferences(now, d) <= 30
38}
39
40function isLastWeek (d: Date) {
41 const now = new Date()
42
43 return getDaysDifferences(now, d) <= 7
44}
45
46// ---------------------------------------------------------------------------
47
48export {
49 isYesterday,
50 isThisWeek,
51 isThisMonth,
52 isToday,
53 isLastMonth,
54 isLastWeek
55}
56
57// ---------------------------------------------------------------------------
58
59function areDatesEqual (d1: Date, d2: Date) {
60 return d1.getFullYear() === d2.getFullYear() &&
61 d1.getMonth() === d2.getMonth() &&
62 d1.getDate() === d2.getDate()
63}
64
65function getDaysDifferences (d1: Date, d2: Date) {
66 return (d1.getTime() - d2.getTime()) / (86400000)
67}
diff --git a/shared/extra-utils/miscs/miscs.ts b/shared/extra-utils/miscs/miscs.ts
index d1ffb7be4..fb6430e4f 100644
--- a/shared/extra-utils/miscs/miscs.ts
+++ b/shared/extra-utils/miscs/miscs.ts
@@ -1,7 +1,7 @@
1/* tslint:disable:no-unused-expression */ 1/* tslint:disable:no-unused-expression */
2 2
3import * as chai from 'chai' 3import * as chai from 'chai'
4import { isAbsolute, join } from 'path' 4import { basename, isAbsolute, join, resolve } from 'path'
5import * as request from 'supertest' 5import * as request from 'supertest'
6import * as WebTorrent from 'webtorrent' 6import * as WebTorrent from 'webtorrent'
7import { pathExists, readFile } from 'fs-extra' 7import { pathExists, readFile } from 'fs-extra'
@@ -34,7 +34,11 @@ function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
34 34
35function root () { 35function root () {
36 // We are in /miscs 36 // We are in /miscs
37 return join(__dirname, '..', '..', '..') 37 let root = join(__dirname, '..', '..', '..')
38
39 if (basename(root) === 'dist') root = resolve(root, '..')
40
41 return root
38} 42}
39 43
40async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') { 44async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
diff --git a/shared/extra-utils/miscs/sql.ts b/shared/extra-utils/miscs/sql.ts
index 3cfae5c23..34477cb78 100644
--- a/shared/extra-utils/miscs/sql.ts
+++ b/shared/extra-utils/miscs/sql.ts
@@ -1,11 +1,12 @@
1import { QueryTypes, Sequelize } from 'sequelize' 1import { QueryTypes, Sequelize } from 'sequelize'
2import { ServerInfo } from '../server/servers'
2 3
3let sequelizes: { [ id: number ]: Sequelize } = {} 4let sequelizes: { [ id: number ]: Sequelize } = {}
4 5
5function getSequelize (serverNumber: number) { 6function getSequelize (internalServerNumber: number) {
6 if (sequelizes[serverNumber]) return sequelizes[serverNumber] 7 if (sequelizes[internalServerNumber]) return sequelizes[internalServerNumber]
7 8
8 const dbname = 'peertube_test' + serverNumber 9 const dbname = 'peertube_test' + internalServerNumber
9 const username = 'peertube' 10 const username = 'peertube'
10 const password = 'peertube' 11 const password = 'peertube'
11 const host = 'localhost' 12 const host = 'localhost'
@@ -18,37 +19,37 @@ function getSequelize (serverNumber: number) {
18 logging: false 19 logging: false
19 }) 20 })
20 21
21 sequelizes[serverNumber] = seq 22 sequelizes[internalServerNumber] = seq
22 23
23 return seq 24 return seq
24} 25}
25 26
26function setActorField (serverNumber: number, to: string, field: string, value: string) { 27function setActorField (internalServerNumber: number, to: string, field: string, value: string) {
27 const seq = getSequelize(serverNumber) 28 const seq = getSequelize(internalServerNumber)
28 29
29 const options = { type: QueryTypes.UPDATE } 30 const options = { type: QueryTypes.UPDATE }
30 31
31 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options) 32 return seq.query(`UPDATE actor SET "${field}" = '${value}' WHERE url = '${to}'`, options)
32} 33}
33 34
34function setVideoField (serverNumber: number, uuid: string, field: string, value: string) { 35function setVideoField (internalServerNumber: number, uuid: string, field: string, value: string) {
35 const seq = getSequelize(serverNumber) 36 const seq = getSequelize(internalServerNumber)
36 37
37 const options = { type: QueryTypes.UPDATE } 38 const options = { type: QueryTypes.UPDATE }
38 39
39 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) 40 return seq.query(`UPDATE video SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
40} 41}
41 42
42function setPlaylistField (serverNumber: number, uuid: string, field: string, value: string) { 43function setPlaylistField (internalServerNumber: number, uuid: string, field: string, value: string) {
43 const seq = getSequelize(serverNumber) 44 const seq = getSequelize(internalServerNumber)
44 45
45 const options = { type: QueryTypes.UPDATE } 46 const options = { type: QueryTypes.UPDATE }
46 47
47 return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options) 48 return seq.query(`UPDATE "videoPlaylist" SET "${field}" = '${value}' WHERE uuid = '${uuid}'`, options)
48} 49}
49 50
50async function countVideoViewsOf (serverNumber: number, uuid: string) { 51async function countVideoViewsOf (internalServerNumber: number, uuid: string) {
51 const seq = getSequelize(serverNumber) 52 const seq = getSequelize(internalServerNumber)
52 53
53 // tslint:disable 54 // tslint:disable
54 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" INNER JOIN "video" ON "video"."id" = "videoView"."videoId" WHERE "video"."uuid" = '${uuid}'`
@@ -62,11 +63,11 @@ async function countVideoViewsOf (serverNumber: number, uuid: string) {
62 return parseInt(total + '', 10) 63 return parseInt(total + '', 10)
63} 64}
64 65
65async function closeAllSequelize (servers: any[]) { 66async function closeAllSequelize (servers: ServerInfo[]) {
66 for (let i = 1; i <= servers.length; i++) { 67 for (const server of servers) {
67 if (sequelizes[ i ]) { 68 if (sequelizes[ server.internalServerNumber ]) {
68 await sequelizes[ i ].close() 69 await sequelizes[ server.internalServerNumber ].close()
69 delete sequelizes[ i ] 70 delete sequelizes[ server.internalServerNumber ]
70 } 71 }
71 } 72 }
72} 73}
diff --git a/shared/extra-utils/search/videos.ts b/shared/extra-utils/search/videos.ts
index ba4627017..5cf8d8cf0 100644
--- a/shared/extra-utils/search/videos.ts
+++ b/shared/extra-utils/search/videos.ts
@@ -6,9 +6,11 @@ import { immutableAssign } from '../miscs/miscs'
6 6
7function searchVideo (url: string, search: string) { 7function searchVideo (url: string, search: string) {
8 const path = '/api/v1/search/videos' 8 const path = '/api/v1/search/videos'
9
10 const query = { sort: '-publishedAt', search: search }
9 const req = request(url) 11 const req = request(url)
10 .get(path) 12 .get(path)
11 .query({ sort: '-publishedAt', search }) 13 .query(query)
12 .set('Accept', 'application/json') 14 .set('Accept', 'application/json')
13 15
14 return req.expect(200) 16 return req.expect(200)
@@ -30,11 +32,15 @@ function searchVideoWithToken (url: string, search: string, token: string, query
30function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) { 32function searchVideoWithPagination (url: string, search: string, start: number, count: number, sort?: string) {
31 const path = '/api/v1/search/videos' 33 const path = '/api/v1/search/videos'
32 34
35 const query = {
36 start,
37 search,
38 count
39 }
40
33 const req = request(url) 41 const req = request(url)
34 .get(path) 42 .get(path)
35 .query({ start }) 43 .query(query)
36 .query({ search })
37 .query({ count })
38 44
39 if (sort) req.query({ sort }) 45 if (sort) req.query({ sort })
40 46
@@ -46,10 +52,11 @@ function searchVideoWithPagination (url: string, search: string, start: number,
46function searchVideoWithSort (url: string, search: string, sort: string) { 52function searchVideoWithSort (url: string, search: string, sort: string) {
47 const path = '/api/v1/search/videos' 53 const path = '/api/v1/search/videos'
48 54
55 const query = { search, sort }
56
49 return request(url) 57 return request(url)
50 .get(path) 58 .get(path)
51 .query({ search }) 59 .query(query)
52 .query({ sort })
53 .set('Accept', 'application/json') 60 .set('Accept', 'application/json')
54 .expect(200) 61 .expect(200)
55 .expect('Content-Type', /json/) 62 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts
index deb77e9c0..2b7965bc2 100644
--- a/shared/extra-utils/server/config.ts
+++ b/shared/extra-utils/server/config.ts
@@ -91,13 +91,15 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
91 transcoding: { 91 transcoding: {
92 enabled: true, 92 enabled: true,
93 allowAdditionalExtensions: true, 93 allowAdditionalExtensions: true,
94 allowAudioFiles: true,
94 threads: 1, 95 threads: 1,
95 resolutions: { 96 resolutions: {
96 '240p': false, 97 '240p': false,
97 '360p': true, 98 '360p': true,
98 '480p': true, 99 '480p': true,
99 '720p': false, 100 '720p': false,
100 '1080p': false 101 '1080p': false,
102 '2160p': false
101 }, 103 },
102 hls: { 104 hls: {
103 enabled: false 105 enabled: false
diff --git a/shared/extra-utils/server/follows.ts b/shared/extra-utils/server/follows.ts
index 1505804de..0379bb109 100644
--- a/shared/extra-utils/server/follows.ts
+++ b/shared/extra-utils/server/follows.ts
@@ -1,17 +1,21 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { ServerInfo } from './servers' 2import { ServerInfo } from './servers'
3import { waitJobs } from './jobs' 3import { waitJobs } from './jobs'
4import { makeGetRequest, makePostBodyRequest } from '..' 4import { makePostBodyRequest } from '../requests/requests'
5 5
6function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 6function getFollowersListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
7 const path = '/api/v1/server/followers' 7 const path = '/api/v1/server/followers'
8 8
9 const query = {
10 start,
11 count,
12 sort,
13 search
14 }
15
9 return request(url) 16 return request(url)
10 .get(path) 17 .get(path)
11 .query({ start }) 18 .query(query)
12 .query({ count })
13 .query({ sort })
14 .query({ search })
15 .set('Accept', 'application/json') 19 .set('Accept', 'application/json')
16 .expect(200) 20 .expect(200)
17 .expect('Content-Type', /json/) 21 .expect('Content-Type', /json/)
@@ -42,12 +46,16 @@ function rejectFollower (url: string, token: string, follower: string, statusCod
42function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) { 46function getFollowingListPaginationAndSort (url: string, start: number, count: number, sort: string, search?: string) {
43 const path = '/api/v1/server/following' 47 const path = '/api/v1/server/following'
44 48
49 const query = {
50 start,
51 count,
52 sort,
53 search
54 }
55
45 return request(url) 56 return request(url)
46 .get(path) 57 .get(path)
47 .query({ start }) 58 .query(query)
48 .query({ count })
49 .query({ sort })
50 .query({ search })
51 .set('Accept', 'application/json') 59 .set('Accept', 'application/json')
52 .expect(200) 60 .expect(200)
53 .expect('Content-Type', /json/) 61 .expect('Content-Type', /json/)
diff --git a/shared/extra-utils/server/servers.ts b/shared/extra-utils/server/servers.ts
index ed41bfa48..4c7d6862a 100644
--- a/shared/extra-utils/server/servers.ts
+++ b/shared/extra-utils/server/servers.ts
@@ -246,7 +246,7 @@ async function checkTmpIsEmpty (server: ServerInfo) {
246} 246}
247 247
248async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) { 248async function checkDirectoryIsEmpty (server: ServerInfo, directory: string) {
249 const testDirectory = 'test' + server.serverNumber 249 const testDirectory = 'test' + server.internalServerNumber
250 250
251 const directoryPath = join(root(), testDirectory, directory) 251 const directoryPath = join(root(), testDirectory, directory)
252 252
@@ -284,7 +284,7 @@ function cleanupTests (servers: ServerInfo[]) {
284} 284}
285 285
286async function waitUntilLog (server: ServerInfo, str: string, count = 1) { 286async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
287 const logfile = join(root(), 'test' + server.serverNumber, 'logs/peertube.log') 287 const logfile = join(root(), 'test' + server.internalServerNumber, 'logs/peertube.log')
288 288
289 while (true) { 289 while (true) {
290 const buf = await readFile(logfile) 290 const buf = await readFile(logfile)
diff --git a/shared/extra-utils/users/accounts.ts b/shared/extra-utils/users/accounts.ts
index f64a2dbad..627e17cc3 100644
--- a/shared/extra-utils/users/accounts.ts
+++ b/shared/extra-utils/users/accounts.ts
@@ -39,7 +39,7 @@ async function expectAccountFollows (url: string, nameWithDomain: string, follow
39 expect(account.followingCount).to.equal(followingCount, message) 39 expect(account.followingCount).to.equal(followingCount, message)
40} 40}
41 41
42async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: number) { 42async function checkActorFilesWereRemoved (filename: string, serverNumber: number) {
43 const testDirectory = 'test' + serverNumber 43 const testDirectory = 'test' + serverNumber
44 44
45 for (const directory of [ 'avatars' ]) { 45 for (const directory of [ 'avatars' ]) {
@@ -50,7 +50,7 @@ async function checkActorFilesWereRemoved (actorUUID: string, serverNumber: numb
50 50
51 const files = await readdir(directoryPath) 51 const files = await readdir(directoryPath)
52 for (const file of files) { 52 for (const file of files) {
53 expect(file).to.not.contain(actorUUID) 53 expect(file).to.not.contain(filename)
54 } 54 }
55 } 55 }
56} 56}
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts
index 495ff80d9..f7de542bf 100644
--- a/shared/extra-utils/users/user-notifications.ts
+++ b/shared/extra-utils/users/user-notifications.ts
@@ -380,7 +380,7 @@ async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string,
380 } 380 }
381 } 381 }
382 382
383 const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}` 383 const commentUrl = `http://localhost:${base.server.port}/videos/watch/${uuid};threadId=${threadId}`
384 function emailFinder (email: object) { 384 function emailFinder (email: object) {
385 return email[ 'text' ].indexOf(commentUrl) !== -1 385 return email[ 'text' ].indexOf(commentUrl) !== -1
386 } 386 }
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts
index 2bd37b8be..1c39881d6 100644
--- a/shared/extra-utils/users/users.ts
+++ b/shared/extra-utils/users/users.ts
@@ -1,10 +1,11 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' 2import { makeGetRequest, makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
3 3
4import { UserRole } from '../../index' 4import { UserCreate, UserRole } from '../../index'
5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' 5import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
6import { ServerInfo, userLogin } from '..' 6import { ServerInfo, userLogin } from '..'
7import { UserAdminFlag } from '../../models/users/user-flag.model' 7import { UserAdminFlag } from '../../models/users/user-flag.model'
8import { UserRegister } from '../../models/users/user-register.model'
8 9
9type CreateUserArgs = { url: string, 10type CreateUserArgs = { url: string,
10 accessToken: string, 11 accessToken: string,
@@ -70,6 +71,31 @@ function registerUser (url: string, username: string, password: string, specialS
70 .expect(specialStatus) 71 .expect(specialStatus)
71} 72}
72 73
74function registerUserWithChannel (options: {
75 url: string,
76 user: { username: string, password: string, displayName?: string },
77 channel: { name: string, displayName: string }
78}) {
79 const path = '/api/v1/users/register'
80 const body: UserRegister = {
81 username: options.user.username,
82 password: options.user.password,
83 email: options.user.username + '@example.com',
84 channel: options.channel
85 }
86
87 if (options.user.displayName) {
88 Object.assign(body, { displayName: options.user.displayName })
89 }
90
91 return makePostBodyRequest({
92 url: options.url,
93 path,
94 fields: body,
95 statusCodeExpected: 204
96 })
97}
98
73function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) { 99function getMyUserInformation (url: string, accessToken: string, specialStatus = 200) {
74 const path = '/api/v1/users/me' 100 const path = '/api/v1/users/me'
75 101
@@ -138,12 +164,16 @@ function getUsersList (url: string, accessToken: string) {
138function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) { 164function getUsersListPaginationAndSort (url: string, accessToken: string, start: number, count: number, sort: string, search?: string) {
139 const path = '/api/v1/users' 165 const path = '/api/v1/users'
140 166
167 const query = {
168 start,
169 count,
170 sort,
171 search
172 }
173
141 return request(url) 174 return request(url)
142 .get(path) 175 .get(path)
143 .query({ start }) 176 .query(query)
144 .query({ count })
145 .query({ sort })
146 .query({ search })
147 .set('Accept', 'application/json') 177 .set('Accept', 'application/json')
148 .set('Authorization', 'Bearer ' + accessToken) 178 .set('Authorization', 'Bearer ' + accessToken)
149 .expect(200) 179 .expect(200)
@@ -293,13 +323,16 @@ function askSendVerifyEmail (url: string, email: string) {
293 }) 323 })
294} 324}
295 325
296function verifyEmail (url: string, userId: number, verificationString: string, statusCodeExpected = 204) { 326function verifyEmail (url: string, userId: number, verificationString: string, isPendingEmail = false, statusCodeExpected = 204) {
297 const path = '/api/v1/users/' + userId + '/verify-email' 327 const path = '/api/v1/users/' + userId + '/verify-email'
298 328
299 return makePostBodyRequest({ 329 return makePostBodyRequest({
300 url, 330 url,
301 path, 331 path,
302 fields: { verificationString }, 332 fields: {
333 verificationString,
334 isPendingEmail
335 },
303 statusCodeExpected 336 statusCodeExpected
304 }) 337 })
305} 338}
@@ -312,6 +345,7 @@ export {
312 getMyUserInformation, 345 getMyUserInformation,
313 getMyUserVideoRating, 346 getMyUserVideoRating,
314 deleteMe, 347 deleteMe,
348 registerUserWithChannel,
315 getMyUserVideoQuotaUsed, 349 getMyUserVideoQuotaUsed,
316 getUsersList, 350 getUsersList,
317 getUsersListPaginationAndSort, 351 getUsersListPaginationAndSort,
diff --git a/shared/extra-utils/videos/video-channels.ts b/shared/extra-utils/videos/video-channels.ts
index 93a257bf9..3e79cf15a 100644
--- a/shared/extra-utils/videos/video-channels.ts
+++ b/shared/extra-utils/videos/video-channels.ts
@@ -1,6 +1,6 @@
1import * as request from 'supertest' 1import * as request from 'supertest'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos' 2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
3import { updateAvatarRequest } from '../requests/requests' 3import { makeGetRequest, updateAvatarRequest } from '../requests/requests'
4import { getMyUserInformation, ServerInfo } from '..' 4import { getMyUserInformation, ServerInfo } from '..'
5import { User } from '../..' 5import { User } from '../..'
6 6
@@ -19,14 +19,28 @@ function getVideoChannelsList (url: string, start: number, count: number, sort?:
19 .expect('Content-Type', /json/) 19 .expect('Content-Type', /json/)
20} 20}
21 21
22function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) { 22function getAccountVideoChannelsList (parameters: {
23 url: string,
24 accountName: string,
25 start?: number,
26 count?: number,
27 sort?: string,
28 specialStatus?: number
29}) {
30 const { url, accountName, start, count, sort = 'createdAt', specialStatus = 200 } = parameters
31
23 const path = '/api/v1/accounts/' + accountName + '/video-channels' 32 const path = '/api/v1/accounts/' + accountName + '/video-channels'
24 33
25 return request(url) 34 return makeGetRequest({
26 .get(path) 35 url,
27 .set('Accept', 'application/json') 36 path,
28 .expect(specialStatus) 37 query: {
29 .expect('Content-Type', /json/) 38 start,
39 count,
40 sort
41 },
42 statusCodeExpected: specialStatus
43 })
30} 44}
31 45
32function addVideoChannel ( 46function addVideoChannel (
@@ -60,12 +74,13 @@ function updateVideoChannel (
60 attributes: VideoChannelUpdate, 74 attributes: VideoChannelUpdate,
61 expectedStatus = 204 75 expectedStatus = 204
62) { 76) {
63 const body = {} 77 const body: any = {}
64 const path = '/api/v1/video-channels/' + channelName 78 const path = '/api/v1/video-channels/' + channelName
65 79
66 if (attributes.displayName) body['displayName'] = attributes.displayName 80 if (attributes.displayName) body.displayName = attributes.displayName
67 if (attributes.description) body['description'] = attributes.description 81 if (attributes.description) body.description = attributes.description
68 if (attributes.support) body['support'] = attributes.support 82 if (attributes.support) body.support = attributes.support
83 if (attributes.bulkVideosSupportUpdate) body.bulkVideosSupportUpdate = attributes.bulkVideosSupportUpdate
69 84
70 return request(url) 85 return request(url)
71 .put(path) 86 .put(path)
diff --git a/shared/extra-utils/videos/video-playlists.ts b/shared/extra-utils/videos/video-playlists.ts
index 4d110a131..fd62bef19 100644
--- a/shared/extra-utils/videos/video-playlists.ts
+++ b/shared/extra-utils/videos/video-playlists.ts
@@ -252,10 +252,10 @@ function reorderVideosPlaylist (options: {
252 252
253async function checkPlaylistFilesWereRemoved ( 253async function checkPlaylistFilesWereRemoved (
254 playlistUUID: string, 254 playlistUUID: string,
255 serverNumber: number, 255 internalServerNumber: number,
256 directories = [ 'thumbnails' ] 256 directories = [ 'thumbnails' ]
257) { 257) {
258 const testDirectory = 'test' + serverNumber 258 const testDirectory = 'test' + internalServerNumber
259 259
260 for (const directory of directories) { 260 for (const directory of directories) {
261 const directoryPath = join(root(), testDirectory, directory) 261 const directoryPath = join(root(), testDirectory, directory)
diff --git a/shared/extra-utils/videos/videos.ts b/shared/extra-utils/videos/videos.ts
index b5a07b792..c78563232 100644
--- a/shared/extra-utils/videos/videos.ts
+++ b/shared/extra-utils/videos/videos.ts
@@ -362,6 +362,10 @@ async function uploadVideo (url: string, accessToken: string, videoAttributesArg
362 .field('privacy', attributes.privacy.toString()) 362 .field('privacy', attributes.privacy.toString())
363 .field('channelId', attributes.channelId) 363 .field('channelId', attributes.channelId)
364 364
365 if (attributes.support !== undefined) {
366 req.field('support', attributes.support)
367 }
368
365 if (attributes.description !== undefined) { 369 if (attributes.description !== undefined) {
366 req.field('description', attributes.description) 370 req.field('description', attributes.description)
367 } 371 }
@@ -524,7 +528,6 @@ async function completeVideoCheck (
524 expect(video.nsfw).to.equal(attributes.nsfw) 528 expect(video.nsfw).to.equal(attributes.nsfw)
525 expect(video.description).to.equal(attributes.description) 529 expect(video.description).to.equal(attributes.description)
526 expect(video.account.id).to.be.a('number') 530 expect(video.account.id).to.be.a('number')
527 expect(video.account.uuid).to.be.a('string')
528 expect(video.account.host).to.equal(attributes.account.host) 531 expect(video.account.host).to.equal(attributes.account.host)
529 expect(video.account.name).to.equal(attributes.account.name) 532 expect(video.account.name).to.equal(attributes.account.name)
530 expect(video.channel.displayName).to.equal(attributes.channel.displayName) 533 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
@@ -568,8 +571,8 @@ async function completeVideoCheck (
568 expect(file).not.to.be.undefined 571 expect(file).not.to.be.undefined
569 572
570 let extension = extname(attributes.fixture) 573 let extension = extname(attributes.fixture)
571 // Transcoding enabled on server 2, extension will always be .mp4 574 // Transcoding enabled: extension will always be .mp4
572 if (attributes.account.host === 'localhost:9002') extension = '.mp4' 575 if (attributes.files.length > 1) extension = '.mp4'
573 576
574 const magnetUri = file.magnetUri 577 const magnetUri = file.magnetUri
575 expect(file.magnetUri).to.have.lengthOf.above(2) 578 expect(file.magnetUri).to.have.lengthOf.above(2)
diff --git a/shared/models/activitypub/activitypub-actor.ts b/shared/models/activitypub/activitypub-actor.ts
index 5e30bf783..53ec579bc 100644
--- a/shared/models/activitypub/activitypub-actor.ts
+++ b/shared/models/activitypub/activitypub-actor.ts
@@ -21,7 +21,6 @@ export interface ActivityPubActor {
21 attributedTo: ActivityPubAttributedTo[] 21 attributedTo: ActivityPubAttributedTo[]
22 22
23 support?: string 23 support?: string
24 uuid: string
25 publicKey: { 24 publicKey: {
26 id: string 25 id: string
27 owner: string 26 owner: string
diff --git a/shared/models/actors/account.model.ts b/shared/models/actors/account.model.ts
index 043a2507e..2ff4b9f5e 100644
--- a/shared/models/actors/account.model.ts
+++ b/shared/models/actors/account.model.ts
@@ -10,7 +10,6 @@ export interface Account extends Actor {
10 10
11export interface AccountSummary { 11export interface AccountSummary {
12 id: number 12 id: number
13 uuid: string
14 name: string 13 name: string
15 displayName: string 14 displayName: string
16 url: string 15 url: string
diff --git a/shared/models/actors/actor.model.ts b/shared/models/actors/actor.model.ts
index a3953874d..1dbf5f638 100644
--- a/shared/models/actors/actor.model.ts
+++ b/shared/models/actors/actor.model.ts
@@ -2,7 +2,6 @@ import { Avatar } from '../avatars/avatar.model'
2 2
3export interface Actor { 3export interface Actor {
4 id: number 4 id: number
5 uuid: string
6 url: string 5 url: string
7 name: string 6 name: string
8 host: string 7 host: string
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts
index ca52eff4b..670553d16 100644
--- a/shared/models/server/custom-config.model.ts
+++ b/shared/models/server/custom-config.model.ts
@@ -54,6 +54,7 @@ export interface CustomConfig {
54 transcoding: { 54 transcoding: {
55 enabled: boolean 55 enabled: boolean
56 allowAdditionalExtensions: boolean 56 allowAdditionalExtensions: boolean
57 allowAudioFiles: boolean
57 threads: number 58 threads: number
58 resolutions: { 59 resolutions: {
59 '240p': boolean 60 '240p': boolean
@@ -61,6 +62,7 @@ export interface CustomConfig {
61 '480p': boolean 62 '480p': boolean
62 '720p': boolean 63 '720p': boolean
63 '1080p': boolean 64 '1080p': boolean
65 '2160p': boolean
64 } 66 }
65 hls: { 67 hls: {
66 enabled: boolean 68 enabled: boolean
diff --git a/shared/models/users/user-register.model.ts b/shared/models/users/user-register.model.ts
new file mode 100644
index 000000000..cf9a43a67
--- /dev/null
+++ b/shared/models/users/user-register.model.ts
@@ -0,0 +1,12 @@
1export interface UserRegister {
2 username: string
3 password: string
4 email: string
5
6 displayName?: string
7
8 channel?: {
9 name: string
10 displayName: string
11 }
12}
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts
index 2f6a3c719..b5823b47a 100644
--- a/shared/models/users/user.model.ts
+++ b/shared/models/users/user.model.ts
@@ -9,6 +9,7 @@ export interface User {
9 id: number 9 id: number
10 username: string 10 username: string
11 email: string 11 email: string
12 pendingEmail: string | null
12 emailVerified: boolean 13 emailVerified: boolean
13 nsfwPolicy: NSFWPolicyType 14 nsfwPolicy: NSFWPolicyType
14 15
diff --git a/shared/models/videos/channel/video-channel-update.model.ts b/shared/models/videos/channel/video-channel-update.model.ts
index 3626ce8a9..8dde9188b 100644
--- a/shared/models/videos/channel/video-channel-update.model.ts
+++ b/shared/models/videos/channel/video-channel-update.model.ts
@@ -1,5 +1,7 @@
1export interface VideoChannelUpdate { 1export interface VideoChannelUpdate {
2 displayName: string 2 displayName?: string
3 description?: string 3 description?: string
4 support?: string 4 support?: string
5
6 bulkVideosSupportUpdate?: boolean
5} 7}
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index 14a813f8f..de4c26b3d 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -12,7 +12,6 @@ export interface VideoChannel extends Actor {
12 12
13export interface VideoChannelSummary { 13export interface VideoChannelSummary {
14 id: number 14 id: number
15 uuid: string
16 name: string 15 name: string
17 displayName: string 16 displayName: string
18 url: string 17 url: string
diff --git a/shared/models/videos/playlist/video-playlist-update.model.ts b/shared/models/videos/playlist/video-playlist-update.model.ts
index 0ff5bcb0f..a6a3f74d9 100644
--- a/shared/models/videos/playlist/video-playlist-update.model.ts
+++ b/shared/models/videos/playlist/video-playlist-update.model.ts
@@ -1,8 +1,8 @@
1import { VideoPlaylistPrivacy } from './video-playlist-privacy.model' 1import { VideoPlaylistPrivacy } from './video-playlist-privacy.model'
2 2
3export interface VideoPlaylistUpdate { 3export interface VideoPlaylistUpdate {
4 displayName: string 4 displayName?: string
5 privacy: VideoPlaylistPrivacy 5 privacy?: VideoPlaylistPrivacy
6 6
7 description?: string 7 description?: string
8 videoChannelId?: number 8 videoChannelId?: number
diff --git a/shared/models/videos/video-resolution.enum.ts b/shared/models/videos/video-resolution.enum.ts
index 7da5e7100..51efa2e8b 100644
--- a/shared/models/videos/video-resolution.enum.ts
+++ b/shared/models/videos/video-resolution.enum.ts
@@ -5,7 +5,8 @@ export enum VideoResolution {
5 H_360P = 360, 5 H_360P = 360,
6 H_480P = 480, 6 H_480P = 480,
7 H_720P = 720, 7 H_720P = 720,
8 H_1080P = 1080 8 H_1080P = 1080,
9 H_4K = 2160
9} 10}
10 11
11/** 12/**
@@ -33,11 +34,14 @@ function getBaseBitrate (resolution: VideoResolution) {
33 // quality according to Google Live Encoder: 1,500 - 4,000 Kbps 34 // quality according to Google Live Encoder: 1,500 - 4,000 Kbps
34 // Quality according to YouTube Video Info: 1752 Kbps 35 // Quality according to YouTube Video Info: 1752 Kbps
35 return 1750 * 1000 36 return 1750 * 1000
36 case VideoResolution.H_1080P: // fallthrough 37 case VideoResolution.H_1080P:
37 default:
38 // quality according to Google Live Encoder: 3000 - 6000 Kbps 38 // quality according to Google Live Encoder: 3000 - 6000 Kbps
39 // Quality according to YouTube Video Info: 3277 Kbps 39 // Quality according to YouTube Video Info: 3277 Kbps
40 return 3300 * 1000 40 return 3300 * 1000
41 case VideoResolution.H_4K: // fallthrough
42 default:
43 // quality according to Google Live Encoder: 13000 - 34000 Kbps
44 return 15000 * 1000
41 } 45 }
42} 46}
43 47
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts
index 963268674..0489147e4 100644
--- a/shared/models/videos/video.model.ts
+++ b/shared/models/videos/video.model.ts
@@ -1,6 +1,5 @@
1import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index' 1import { AccountSummary, VideoChannelSummary, VideoResolution, VideoState } from '../../index'
2import { Account } from '../actors' 2import { Account } from '../actors'
3import { Avatar } from '../avatars/avatar.model'
4import { VideoChannel } from './channel/video-channel.model' 3import { VideoChannel } from './channel/video-channel.model'
5import { VideoPrivacy } from './video-privacy.enum' 4import { VideoPrivacy } from './video-privacy.enum'
6import { VideoScheduleUpdate } from './video-schedule-update.model' 5import { VideoScheduleUpdate } from './video-schedule-update.model'
@@ -68,9 +67,9 @@ export interface VideoDetails extends Video {
68 descriptionPath: string 67 descriptionPath: string
69 support: string 68 support: string
70 channel: VideoChannel 69 channel: VideoChannel
70 account: Account
71 tags: string[] 71 tags: string[]
72 files: VideoFile[] 72 files: VideoFile[]
73 account: Account
74 commentsEnabled: boolean 73 commentsEnabled: boolean
75 downloadEnabled: boolean 74 downloadEnabled: boolean
76 75
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml
index 0ab0c71c4..0dc50e403 100644
--- a/support/doc/api/openapi.yaml
+++ b/support/doc/api/openapi.yaml
@@ -38,7 +38,7 @@ info:
38 } 38 }
39 ``` 39 ```
40externalDocs: 40externalDocs:
41 url: https://docs.joinpeertube.org/api.html 41 url: https://docs.joinpeertube.org/#/api-rest-reference.html
42tags: 42tags:
43 - name: Accounts 43 - name: Accounts
44 description: > 44 description: >
@@ -99,6 +99,7 @@ x-tagGroups:
99 - name: Videos 99 - name: Videos
100 tags: 100 tags:
101 - Video 101 - Video
102 - Video Caption
102 - Video Channel 103 - Video Channel
103 - Video Comment 104 - Video Comment
104 - Video Following 105 - Video Following
@@ -152,7 +153,7 @@ paths:
152 content: 153 content:
153 application/json: 154 application/json:
154 schema: 155 schema:
155 $ref: '#/components/schemas/Video' 156 $ref: '#/components/schemas/VideoListResponse'
156 x-code-samples: 157 x-code-samples:
157 - lang: JavaScript 158 - lang: JavaScript
158 source: | 159 source: |
@@ -575,9 +576,7 @@ paths:
575 content: 576 content:
576 application/json: 577 application/json:
577 schema: 578 schema:
578 type: array 579 $ref: '#/components/schemas/VideoListResponse'
579 items:
580 $ref: '#/components/schemas/Video'
581 /users/me/subscriptions: 580 /users/me/subscriptions:
582 get: 581 get:
583 summary: Get subscriptions of the current user 582 summary: Get subscriptions of the current user
@@ -638,9 +637,7 @@ paths:
638 content: 637 content:
639 application/json: 638 application/json:
640 schema: 639 schema:
641 type: array 640 $ref: '#/components/schemas/VideoListResponse'
642 items:
643 $ref: '#/components/schemas/Video'
644 '/users/me/subscriptions/{uri}': 641 '/users/me/subscriptions/{uri}':
645 get: 642 get:
646 summary: Get subscription of the current user for a given uri 643 summary: Get subscription of the current user for a given uri
@@ -730,9 +727,7 @@ paths:
730 content: 727 content:
731 application/json: 728 application/json:
732 schema: 729 schema:
733 type: array 730 $ref: '#/components/schemas/VideoListResponse'
734 items:
735 $ref: '#/components/schemas/Video'
736 /videos/categories: 731 /videos/categories:
737 get: 732 get:
738 summary: Get list of video licences known by the server 733 summary: Get list of video licences known by the server
@@ -849,21 +844,8 @@ paths:
849 commentsEnabled: 844 commentsEnabled:
850 description: Enable or disable comments for this video 845 description: Enable or disable comments for this video
851 type: string 846 type: string
852 scheduleUpdate: &ref_0 847 scheduleUpdate:
853 type: object 848 $ref: '#/components/schemas/VideoScheduledUpdate'
854 properties:
855 privacy:
856 type: string
857 enum:
858 - Public
859 - Unlisted
860 description: Video privacy target
861 updateAt:
862 type: string
863 format: date
864 description: When to update the video
865 required:
866 - updateAt
867 get: 849 get:
868 summary: Get a video by its id 850 summary: Get a video by its id
869 tags: 851 tags:
@@ -876,7 +858,7 @@ paths:
876 content: 858 content:
877 application/json: 859 application/json:
878 schema: 860 schema:
879 $ref: '#/components/schemas/Video' 861 $ref: '#/components/schemas/VideoDetails'
880 delete: 862 delete:
881 summary: Delete a video by its id 863 summary: Delete a video by its id
882 security: 864 security:
@@ -1025,7 +1007,7 @@ paths:
1025 description: Video preview file 1007 description: Video preview file
1026 type: string 1008 type: string
1027 privacy: 1009 privacy:
1028 $ref: '#/components/schemas/VideoPrivacy' 1010 $ref: '#/components/schemas/VideoPrivacySet'
1029 category: 1011 category:
1030 description: Video category 1012 description: Video category
1031 type: string 1013 type: string
@@ -1058,7 +1040,8 @@ paths:
1058 commentsEnabled: 1040 commentsEnabled:
1059 description: Enable or disable comments for this video 1041 description: Enable or disable comments for this video
1060 type: string 1042 type: string
1061 scheduleUpdate: *ref_0 1043 scheduleUpdate:
1044 $ref: '#/components/schemas/VideoScheduledUpdate'
1062 required: 1045 required:
1063 - videofile 1046 - videofile
1064 - channelId 1047 - channelId
@@ -1129,7 +1112,7 @@ paths:
1129 description: Video preview file 1112 description: Video preview file
1130 type: string 1113 type: string
1131 privacy: 1114 privacy:
1132 $ref: '#/components/schemas/VideoPrivacy' 1115 $ref: '#/components/schemas/VideoPrivacySet'
1133 category: 1116 category:
1134 description: Video category 1117 description: Video category
1135 type: string 1118 type: string
@@ -1162,7 +1145,8 @@ paths:
1162 commentsEnabled: 1145 commentsEnabled:
1163 description: Enable or disable comments for this video 1146 description: Enable or disable comments for this video
1164 type: string 1147 type: string
1165 scheduleUpdate: *ref_0 1148 scheduleUpdate:
1149 $ref: '#/components/schemas/VideoScheduledUpdate'
1166 required: 1150 required:
1167 - channelId 1151 - channelId
1168 - name 1152 - name
@@ -1247,6 +1231,58 @@ paths:
1247 type: array 1231 type: array
1248 items: 1232 items:
1249 $ref: '#/components/schemas/VideoBlacklist' 1233 $ref: '#/components/schemas/VideoBlacklist'
1234 /videos/{id}/captions:
1235 get:
1236 summary: Get list of video's captions
1237 tags:
1238 - Video Caption
1239 parameters:
1240 - $ref: '#/components/parameters/id2'
1241 responses:
1242 '200':
1243 description: successful operation
1244 content:
1245 application/json:
1246 schema:
1247 type: object
1248 properties:
1249 total:
1250 type: integer
1251 data:
1252 type: array
1253 items:
1254 $ref: '#/components/schemas/VideoCaption'
1255 /videos/{id}/captions/{captionLanguage}:
1256 put:
1257 summary: Add or replace a video caption
1258 tags:
1259 - Video Caption
1260 parameters:
1261 - $ref: '#/components/parameters/id2'
1262 - $ref: '#/components/parameters/captionLanguage'
1263 requestBody:
1264 content:
1265 multipart/form-data:
1266 schema:
1267 type: object
1268 properties:
1269 captionfile:
1270 description: The file to upload.
1271 type: string
1272 format: binary
1273 responses:
1274 '204':
1275 $ref: '#/paths/~1users~1me/put/responses/204'
1276 delete:
1277 summary: Delete a video caption
1278 tags:
1279 - Video Caption
1280 parameters:
1281 - $ref: '#/components/parameters/id2'
1282 - $ref: '#/components/parameters/captionLanguage'
1283 responses:
1284 '204':
1285 $ref: '#/paths/~1users~1me/put/responses/204'
1250 /video-channels: 1286 /video-channels:
1251 get: 1287 get:
1252 summary: Get list of video channels 1288 summary: Get list of video channels
@@ -1275,7 +1311,10 @@ paths:
1275 '204': 1311 '204':
1276 $ref: '#/paths/~1users~1me/put/responses/204' 1312 $ref: '#/paths/~1users~1me/put/responses/204'
1277 requestBody: 1313 requestBody:
1278 $ref: '#/components/requestBodies/VideoChannelInput' 1314 content:
1315 application/json:
1316 schema:
1317 $ref: '#/components/schemas/VideoChannelCreate'
1279 '/video-channels/{channelHandle}': 1318 '/video-channels/{channelHandle}':
1280 get: 1319 get:
1281 summary: Get a video channel by its id 1320 summary: Get a video channel by its id
@@ -1302,7 +1341,10 @@ paths:
1302 '204': 1341 '204':
1303 $ref: '#/paths/~1users~1me/put/responses/204' 1342 $ref: '#/paths/~1users~1me/put/responses/204'
1304 requestBody: 1343 requestBody:
1305 $ref: '#/components/requestBodies/VideoChannelInput' 1344 content:
1345 application/json:
1346 schema:
1347 $ref: '#/components/schemas/VideoChannelUpdate'
1306 delete: 1348 delete:
1307 summary: Delete a video channel by its id 1349 summary: Delete a video channel by its id
1308 security: 1350 security:
@@ -1318,6 +1360,7 @@ paths:
1318 get: 1360 get:
1319 summary: Get videos of a video channel by its id 1361 summary: Get videos of a video channel by its id
1320 tags: 1362 tags:
1363 - Video
1321 - Video Channel 1364 - Video Channel
1322 parameters: 1365 parameters:
1323 - $ref: '#/components/parameters/channelHandle' 1366 - $ref: '#/components/parameters/channelHandle'
@@ -1327,7 +1370,7 @@ paths:
1327 content: 1370 content:
1328 application/json: 1371 application/json:
1329 schema: 1372 schema:
1330 $ref: '#/components/schemas/Video' 1373 $ref: '#/components/schemas/VideoListResponse'
1331 '/accounts/{name}/video-channels': 1374 '/accounts/{name}/video-channels':
1332 get: 1375 get:
1333 summary: Get video channels of an account by its name 1376 summary: Get video channels of an account by its name
@@ -1443,7 +1486,7 @@ paths:
1443 schema: 1486 schema:
1444 $ref: '#/components/schemas/CommentThreadPostResponse' 1487 $ref: '#/components/schemas/CommentThreadPostResponse'
1445 delete: 1488 delete:
1446 summary: 'Delete a comment in a comment therad by its id, of a video by its id' 1489 summary: 'Delete a comment in a comment thread by its id, of a video by its id'
1447 security: 1490 security:
1448 - OAuth2: [] 1491 - OAuth2: []
1449 tags: 1492 tags:
@@ -1487,9 +1530,7 @@ paths:
1487 content: 1530 content:
1488 application/json: 1531 application/json:
1489 schema: 1532 schema:
1490 type: array 1533 $ref: '#/components/schemas/VideoListResponse'
1491 items:
1492 $ref: '#/components/schemas/Video'
1493servers: 1534servers:
1494 - url: 'https://peertube.cpy.re/api/v1' 1535 - url: 'https://peertube.cpy.re/api/v1'
1495 description: Live Test Server (live data - stable version) 1536 description: Live Test Server (live data - stable version)
@@ -1611,6 +1652,13 @@ components:
1611 description: The video id or uuid 1652 description: The video id or uuid
1612 schema: 1653 schema:
1613 type: string 1654 type: string
1655 captionLanguage:
1656 name: captionLanguage
1657 in: path
1658 required: true
1659 description: The caption language
1660 schema:
1661 type: string
1614 channelHandle: 1662 channelHandle:
1615 name: channelHandle 1663 name: channelHandle
1616 in: path 1664 in: path
@@ -1722,12 +1770,6 @@ components:
1722 type: array 1770 type: array
1723 items: 1771 items:
1724 type: string 1772 type: string
1725 requestBodies:
1726 VideoChannelInput:
1727 content:
1728 application/json:
1729 schema:
1730 $ref: '#/components/schemas/VideoChannelInput'
1731 securitySchemes: 1773 securitySchemes:
1732 OAuth2: 1774 OAuth2:
1733 description: > 1775 description: >
@@ -1739,7 +1781,7 @@ components:
1739 1781
1740 - Have an account with sufficient authorization levels 1782 - Have an account with sufficient authorization levels
1741 1783
1742 - [Generate](https://docs.joinpeertube.org/lang/en/devdocs/rest.html) a 1784 - [Generate](https://docs.joinpeertube.org/#/api-rest-getting-started) a
1743 Bearer Token 1785 Bearer Token
1744 1786
1745 - Make Authenticated Requests 1787 - Make Authenticated Requests
@@ -1764,12 +1806,129 @@ components:
1764 type: string 1806 type: string
1765 label: 1807 label:
1766 type: string 1808 type: string
1767 VideoPrivacy: 1809 VideoPrivacySet:
1768 type: string 1810 type: integer
1769 enum: 1811 enum:
1770 - Public 1812 - 1
1771 - Unlisted 1813 - 2
1772 - Private 1814 - 3
1815 description: 'The video privacy (Public = 1, Unlisted = 2, Private = 3)'
1816 VideoPrivacyConstant:
1817 properties:
1818 id:
1819 type: integer
1820 enum:
1821 - 1
1822 - 2
1823 - 3
1824 label:
1825 type: string
1826 VideoStateConstant:
1827 properties:
1828 id:
1829 type: integer
1830 enum:
1831 - 1
1832 - 2
1833 - 3
1834 description: 'The video state (Published = 1, to transcode = 2, to import = 3)'
1835 label:
1836 type: string
1837 VideoResolutionConstant:
1838 properties:
1839 id:
1840 type: integer
1841 description: 'Video resolution (240, 360, 720 ...)'
1842 label:
1843 type: string
1844 VideoScheduledUpdate:
1845 properties:
1846 privacy:
1847 $ref: '#/components/schemas/VideoPrivacySet'
1848 description: Video privacy target
1849 updateAt:
1850 type: string
1851 format: date
1852 description: When to update the video
1853 required:
1854 - updateAt
1855 VideoAccountSummary:
1856 properties:
1857 id:
1858 type: number
1859 name:
1860 type: string
1861 displayName:
1862 type: string
1863 url:
1864 type: string
1865 host:
1866 type: string
1867 avatar:
1868 nullable: true
1869 $ref: '#/components/schemas/Avatar'
1870 VideoChannelSummary:
1871 properties:
1872 id:
1873 type: number
1874 name:
1875 type: string
1876 displayName:
1877 type: string
1878 url:
1879 type: string
1880 host:
1881 type: string
1882 avatar:
1883 nullable: true
1884 $ref: '#/components/schemas/Avatar'
1885 PlaylistElement:
1886 properties:
1887 position:
1888 type: number
1889 startTimestamp:
1890 type: number
1891 stopTimestamp:
1892 type: number
1893 VideoFile:
1894 properties:
1895 magnetUri:
1896 type: string
1897 resolution:
1898 $ref: '#/components/schemas/VideoResolutionConstant'
1899 size:
1900 type: number
1901 description: 'Video file size in bytes'
1902 torrentUrl:
1903 type: string
1904 torrentDownaloadUrl:
1905 type: string
1906 fileUrl:
1907 type: string
1908 fileDownloadUrl:
1909 type: string
1910 fps:
1911 type: number
1912 VideoStreamingPlaylists:
1913 properties:
1914 id:
1915 type: number
1916 type:
1917 type: number
1918 enum:
1919 - 1
1920 description: 'Playlist type (HLS = 1)'
1921 playlistUrl:
1922 type: string
1923 segmentsSha256Url:
1924 type: string
1925 redundancies:
1926 type: array
1927 items:
1928 type: object
1929 properties:
1930 baseUrl:
1931 type: string
1773 Video: 1932 Video:
1774 properties: 1933 properties:
1775 id: 1934 id:
@@ -1782,6 +1941,8 @@ components:
1782 type: string 1941 type: string
1783 updatedAt: 1942 updatedAt:
1784 type: string 1943 type: string
1944 originallyPublishedAt:
1945 type: string
1785 category: 1946 category:
1786 $ref: '#/components/schemas/VideoConstantNumber' 1947 $ref: '#/components/schemas/VideoConstantNumber'
1787 licence: 1948 licence:
@@ -1789,7 +1950,7 @@ components:
1789 language: 1950 language:
1790 $ref: '#/components/schemas/VideoConstantString' 1951 $ref: '#/components/schemas/VideoConstantString'
1791 privacy: 1952 privacy:
1792 $ref: '#/components/schemas/VideoPrivacy' 1953 $ref: '#/components/schemas/VideoPrivacyConstant'
1793 description: 1954 description:
1794 type: string 1955 type: string
1795 duration: 1956 duration:
@@ -1812,19 +1973,66 @@ components:
1812 type: number 1973 type: number
1813 nsfw: 1974 nsfw:
1814 type: boolean 1975 type: boolean
1976 waitTranscoding:
1977 type: boolean
1978 nullable: true
1979 state:
1980 $ref: '#/components/schemas/VideoStateConstant'
1981 scheduledUpdate:
1982 nullable: true
1983 $ref: '#/components/schemas/VideoScheduledUpdate'
1984 blacklisted:
1985 nullable: true
1986 type: boolean
1987 blacklistedReason:
1988 nullable: true
1989 type: string
1815 account: 1990 account:
1991 $ref: '#/components/schemas/VideoAccountSummary'
1992 channel:
1993 $ref: '#/components/schemas/VideoChannelSummary'
1994 userHistory:
1995 nullable: true
1816 type: object 1996 type: object
1817 properties: 1997 properties:
1818 name: 1998 currentTime:
1819 type: string 1999 type: number
1820 displayName: 2000 playlistElement:
1821 type: string 2001 nullable: true
1822 url: 2002 $ref: '#/components/schemas/PlaylistElement'
2003 VideoDetails:
2004 allOf:
2005 - $ref: '#/components/schemas/Video'
2006 - type: object
2007 properties:
2008 descriptionPath:
1823 type: string 2009 type: string
1824 host: 2010 support:
1825 type: string 2011 type: string
1826 avatar: 2012 channel:
1827 $ref: '#/components/schemas/Avatar' 2013 $ref: '#/components/schemas/VideoChannel'
2014 account:
2015 $ref: '#/components/schemas/Account'
2016 tags:
2017 type: array
2018 items:
2019 type: string
2020 files:
2021 type: array
2022 items:
2023 $ref: '#/components/schemas/VideoFile'
2024 commentsEnabled:
2025 type: boolean
2026 downloadEnabled:
2027 type: boolean
2028 trackerUrls:
2029 type: array
2030 items:
2031 type: string
2032 streamingPlaylists:
2033 type: array
2034 items:
2035 $ref: '#/components/schemas/VideoStreamingPlaylists'
1828 VideoAbuse: 2036 VideoAbuse:
1829 properties: 2037 properties:
1830 id: 2038 id:
@@ -1917,6 +2125,12 @@ components:
1917 type: array 2125 type: array
1918 items: 2126 items:
1919 $ref: '#/components/schemas/VideoCommentThreadTree' 2127 $ref: '#/components/schemas/VideoCommentThreadTree'
2128 VideoCaption:
2129 properties:
2130 language:
2131 $ref: '#/components/schemas/VideoConstantString'
2132 captionPath:
2133 type: string
1920 Avatar: 2134 Avatar:
1921 properties: 2135 properties:
1922 path: 2136 path:
@@ -1966,6 +2180,13 @@ components:
1966 autoPlayVideo: 2180 autoPlayVideo:
1967 type: boolean 2181 type: boolean
1968 role: 2182 role:
2183 type: integer
2184 enum:
2185 - 0
2186 - 1
2187 - 2
2188 description: 'The user role (Admin = 0, Moderator = 1, User = 2)'
2189 roleLabel:
1969 type: string 2190 type: string
1970 enum: 2191 enum:
1971 - User 2192 - User
@@ -2096,6 +2317,14 @@ components:
2096 properties: 2317 properties:
2097 comment: 2318 comment:
2098 $ref: '#/components/schemas/VideoComment' 2319 $ref: '#/components/schemas/VideoComment'
2320 VideoListResponse:
2321 properties:
2322 total:
2323 type: number
2324 data:
2325 type: array
2326 items:
2327 $ref: '#/components/schemas/Video'
2099 AddUser: 2328 AddUser:
2100 properties: 2329 properties:
2101 username: 2330 username:
@@ -2115,12 +2344,11 @@ components:
2115 description: 'The user daily video quota ' 2344 description: 'The user daily video quota '
2116 role: 2345 role:
2117 type: integer 2346 type: integer
2118 format: int32
2119 enum: 2347 enum:
2120 - 0 2348 - 0
2121 - 1 2349 - 1
2122 - 2 2350 - 2
2123 description: 'The user role ' 2351 description: 'The user role (Admin = 0, Moderator = 1, User = 2)'
2124 required: 2352 required:
2125 - username 2353 - username
2126 - password 2354 - password
@@ -2143,8 +2371,12 @@ components:
2143 type: string 2371 type: string
2144 description: 'The updated daily video quota of the user ' 2372 description: 'The updated daily video quota of the user '
2145 role: 2373 role:
2146 type: string 2374 type: integer
2147 description: 'The updated role of the user ' 2375 enum:
2376 - 0
2377 - 1
2378 - 2
2379 description: 'The user role (Admin = 0, Moderator = 1, User = 2)'
2148 required: 2380 required:
2149 - id 2381 - id
2150 - email 2382 - email
@@ -2202,14 +2434,45 @@ components:
2202 email: 2434 email:
2203 type: string 2435 type: string
2204 description: 'The email of the user ' 2436 description: 'The email of the user '
2437 displayName:
2438 type: string
2439 description: 'The user display name'
2440 channel:
2441 type: object
2442 properties:
2443 name:
2444 type: string
2445 description: 'The default channel name'
2446 displayName:
2447 type: string
2448 description: 'The default channel display name'
2449
2205 required: 2450 required:
2206 - username 2451 - username
2207 - password 2452 - password
2208 - email 2453 - email
2209 VideoChannelInput: 2454 VideoChannelCreate:
2210 properties: 2455 properties:
2211 name: 2456 name:
2212 type: string 2457 type: string
2458 displayName:
2459 type: string
2460 description:
2461 type: string
2462 support:
2463 type: string
2464 required:
2465 - name
2466 - displayName
2467 VideoChannelUpdate:
2468 properties:
2469 displayName:
2470 type: string
2213 description: 2471 description:
2214 type: string 2472 type: string
2473 support:
2474 type: string
2475 bulkVideosSupportUpdate:
2476 type: boolean
2477 description: 'Update all videos support field of this channel'
2215 2478
diff --git a/support/doc/api/quickstart.md b/support/doc/api/quickstart.md
index 00874a1c9..2222be741 100644
--- a/support/doc/api/quickstart.md
+++ b/support/doc/api/quickstart.md
@@ -47,7 +47,7 @@ $ curl -H 'Authorization: Bearer 90286a0bdf0f7315d9d3fe8dabf9e1d2be9c97d0' https
47``` 47```
48 48
49 49
50### List videos 50## List videos
51 51
52```bash 52```bash
53$ curl https://peertube.example.com/api/v1/videos 53$ curl https://peertube.example.com/api/v1/videos
diff --git a/support/doc/dependencies.md b/support/doc/dependencies.md
index 77cec3ba3..1b27abaa7 100644
--- a/support/doc/dependencies.md
+++ b/support/doc/dependencies.md
@@ -26,7 +26,7 @@
26 26
27 3. Install certbot (choose instructions for nginx and your distribution) : 27 3. Install certbot (choose instructions for nginx and your distribution) :
28 [https://certbot.eff.org/all-instructions](https://certbot.eff.org/all-instructions) 28 [https://certbot.eff.org/all-instructions](https://certbot.eff.org/all-instructions)
29 4. Install NodeJS 8.x (current LTS): 29 4. Install NodeJS 8.x:
30 [https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions) 30 [https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions](https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions)
31 5. Install yarn, and be sure to have [a recent version](https://github.com/yarnpkg/yarn/releases/latest): 31 5. Install yarn, and be sure to have [a recent version](https://github.com/yarnpkg/yarn/releases/latest):
32 [https://yarnpkg.com/en/docs/install#linux-tab](https://yarnpkg.com/en/docs/install#linux-tab) 32 [https://yarnpkg.com/en/docs/install#linux-tab](https://yarnpkg.com/en/docs/install#linux-tab)
@@ -56,7 +56,7 @@ $ sudo systemctl start redis postgresql
56 1. Run: 56 1. Run:
57 57
58``` 58```
59$ sudo pacman -S nodejs yarn ffmpeg postgresql openssl redis git wget unzip python2 base-devel npm nginx 59$ sudo pacman -S nodejs-lts-dubnium yarn ffmpeg postgresql openssl redis git wget unzip python2 base-devel npm nginx
60``` 60```
61 61
62Now that dependencies are installed, before running PeerTube you should start PostgreSQL and Redis: 62Now that dependencies are installed, before running PeerTube you should start PostgreSQL and Redis:
@@ -66,7 +66,7 @@ $ sudo systemctl start redis postgresql
66 66
67## CentOS 7 67## CentOS 7
68 68
69 1. Install NodeJS 8.x (current LTS): 69 1. Install NodeJS 8.x:
70 [https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora) 70 [https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora)
71 2. Install yarn: 71 2. Install yarn:
72 [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install) 72 [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install)
@@ -114,7 +114,7 @@ su my-peertube-user
114``` 114```
1152. (Optional) Install certbot (choose instructions for nginx and your distribution) : 1152. (Optional) Install certbot (choose instructions for nginx and your distribution) :
116[https://certbot.eff.org/all-instructions](https://certbot.eff.org/all-instructions) 116[https://certbot.eff.org/all-instructions](https://certbot.eff.org/all-instructions)
1173. Install NodeJS 8.x (current LTS): 1173. Install NodeJS 8.x:
118[https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora) 118[https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora](https://nodejs.org/en/download/package-manager/#enterprise-linux-and-fedora)
1194. Install yarn: 1194. Install yarn:
120[https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install) 120[https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install)
diff --git a/support/doc/development/client/code.md b/support/doc/development/client/code.md
deleted file mode 100644
index 235116e78..000000000
--- a/support/doc/development/client/code.md
+++ /dev/null
@@ -1,67 +0,0 @@
1# Client code documentation
2
3The client is a HTML/CSS/JavaScript web application (single page application -> SPA) developed with [TypeScript](https://www.typescriptlang.org/)/[Angular](https://angular.io/).
4
5
6## Technologies
7
8 * [TypeScript](https://www.typescriptlang.org/) -> Language
9 * [Angular](https://angular.io) -> JavaScript framework
10 * [SASS](http://sass-lang.com/) -> CSS framework
11 * [Webpack](https://webpack.js.org/) -> Source builder (compile TypeScript, SASS files, bundle them...)
12 * [Bootstrap](http://getbootstrap.com/) -> CSS framework
13 * [WebTorrent](https://webtorrent.io/) -> JavaScript library to make P2P in the browser
14 * [VideoJS](http://videojs.com/) -> JavaScript player framework
15
16
17## Files
18
19The client files are in the `client` directory. The Webpack 2 configurations files are in `client/config` and the source files in `client/src`.
20The client modules description are in the [client/package.json](/client/package.json). There are many modules that are used to compile the web application in development or production mode.
21Here is the description of the useful `client` files directory:
22
23 tslint.json -> TypeScript linter rules
24 tsconfig.json -> TypeScript configuration for the compilation
25 .bootstraprc -> Bootstrap configuration file (which module we need)
26 config -> Webpack configuration files
27 src
28 |__ app -> TypeScript files for Angular application
29 |__ assets -> static files (images...)
30 |__ sass -> SASS files that are global for the application
31 |__ standalone -> files outside the Angular application (embed HTML page...)
32 |__ index.html -> root HTML file for our Angular application
33 |__ main.ts -> Main TypeScript file that boostraps our Angular application
34 |__ polyfills.ts -> Polyfills imports (ES 2015...)
35
36Details of the Angular application file structure. It tries to follow [the official Angular styleguide](https://angular.io/docs/ts/latest/guide/style-guide.html).
37
38 app
39 |__ +admin -> Admin components (followers, users...)
40 |__ account -> Account components (password change...)
41 |__ core -> Core components/services
42 |__ header -> Header components (logo, search...)
43 |__ login -> Login component
44 |__ menu -> Menu component (on the left)
45 |__ shared -> Shared components/services (search component, REST services...)
46 |__ signup -> Signup form
47 |__ videos -> Video components (list, watch, upload...)
48 |__ app.component.{html,scss,ts} -> Main application component
49 |__ app-routing.module.ts -> Main Angular routes
50 |__ app.module.ts -> Angular root module that imports all submodules we need
51
52## Conventions
53
54Uses [TSLint](https://palantir.github.io/tslint/) for TypeScript linting and [Angular styleguide](https://angular.io/docs/ts/latest/guide/style-guide.html).
55
56## Concepts
57
58In a Angular application, we create components that we put together. Each component is defined by an HTML structure, a TypeScript file and optionally a SASS file.
59If you are not familiar with Angular I recommend you to read the [quickstart guide](https://angular.io/docs/ts/latest/quickstart.html).
60
61## Components tree
62
63![Components tree](/support/doc/development/client/components-tree.svg)
64
65## Newcomers
66
67The main client component is `app.component.ts`. You can begin to look at this file. Then you could navigate in the different submodules to see how components are built.
diff --git a/support/doc/development/client/components-tree.png b/support/doc/development/client/components-tree.png
deleted file mode 100644
index 09582d742..000000000
--- a/support/doc/development/client/components-tree.png
+++ /dev/null
Binary files differ
diff --git a/support/doc/development/client/components-tree.svg b/support/doc/development/client/components-tree.svg
deleted file mode 100644
index fd6951d93..000000000
--- a/support/doc/development/client/components-tree.svg
+++ /dev/null
@@ -1,2 +0,0 @@
1<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2<svg xmlns="http://www.w3.org/2000/svg" style="background-color: rgb(255, 255, 255);" xmlns:xlink="http://www.w3.org/1999/xlink" width="1141px" height="311px" version="1.1" content="&lt;mxfile userAgent=&quot;Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0&quot; version=&quot;8.9.9&quot; editor=&quot;www.draw.io&quot; type=&quot;device&quot;&gt;&lt;diagram name=&quot;Page-1&quot; id=&quot;8be7db5e-9885-9541-e5e4-cf9e4eb3a109&quot;&gt;7Zpbb5swFMc/TaTtZQLMLY9d1nYP2zSp2u3RwQ54dTAypkn36WeDSSCmVdcR3FTpQwXHN/z7G59zHGZgsd5ec1hknxnCdOY5aDsDH2aeFzuB/K8M943Bd93GkHKCGlPHcEP+YG10tLUiCJe9ioIxKkjRNyYsz3EiejbIOdv0q60Y7Y9awBQbhpsEUtP6gyCRaasbzvcFHzFJMz107EVNwRImtylnVa7Hm3lgVf81xWvY9qUnWmYQsU3HBC5nYMEZE83VervAVKFtsTXtrh4o3T03x7l4SgNPT6gU9+3cMZIo9C3jImMpyyG93Fvf1/PDqgdH3mViTeWlKy/xloifyvwu0He/2pJc8PtOkbr9pTsoBeTiQqkmDTnLcWu7IpTqOjhHbY2EwrIkSWPUVdQQv7EQ93opwUowado//ifGCl1vxXKhq7nq3kSmKZas4omG4ulFCHmKdS1NTuHqNNOYrzFbYzlHWYFjCgW5668sqBdouqu3F0leaJ2GNQPOWbNnahZb08w9a/ZMzSJrmnlnzZ6pGbCmWTymZk/F9rC2gxyeRTSaiKAfvFKCPpiKYGiF4CO7yBR0/f+kq5t+ZUQO7Dk6k5jPdbCjEwm3DX7aLppn0K0ONNo9xtNkG3XreEyOl/RSTLWt6LHvIK10pxdFUWdw60K6Na1eDz6lMt1TkDcZEfimgPU8NjLh7KM+dFor6fcWjDJe9wOwiwIc1X6Ts1vcKZmHEYDhY27uDnOBt4+6MF0K/P5CbRO8TSd9bG1ZN3N0RmA76obz8Ko7hWAEmGs8tBWMtKHrOYD8Z80Ca5qN6wVe27vk29IFGP7jO0GYlTMvhGvlEPJlWewmdgQ3giCOV8mQGwmTGC9XI7mR+CDeaaOSKfyIbzC+REQFY2+qgjKIZPuqQFDgt5I6lQO/R+ROXqainnxjWvJDi2zWq3csgQIcI39IoNhbgnAkPx8c+vkofBcYEsUDCgUjKBQYCn0i5fFCp2mQ+laRhgbSH1Ak2YkzNcLRSZlGBtOLJJE+8tRXauBa3J1j891nKclPHOnu1wwbSHdBzznYa3/O6gZ77Wq3EIV70whz6pnTkGiuLdHARMcQr1A0YC3fnZuuGq2P6VUmSaNsZlHtrtlBesUoVd+knDZU9zCkBAbU+VBAGY3A1DWY1um/6nZZlfjk0Xr22AIzqfxWYn7qSA8DyyGkg/nPGEgHEqAlq15A+mPwG6D8INLQt3k4ZZ4ADpxCfVUf9nnOF6aOra7q7/Be6CnUfykRRTaVMM8JB5T4rB53l/a/Qg3mjk0NzB2mdYiLDOY5pkfcvyfB6zoTngzI2/03t823A/vvmsHlXw==&lt;/diagram&gt;&lt;/mxfile&gt;"><defs/><g transform="translate(0.5,0.5)"><path d="M 390 80 L 390 104 L 130 104 L 130 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 130 126.88 L 126.5 119.88 L 130 121.63 L 133.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 280 104 L 280 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 280 126.88 L 276.5 119.88 L 280 121.63 L 283.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 560 104 L 560 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 560 126.88 L 556.5 119.88 L 560 121.63 L 563.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 430 104 L 430 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 430 126.88 L 426.5 119.88 L 430 121.63 L 433.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 690 104 L 690 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 690 126.88 L 686.5 119.88 L 690 121.63 L 693.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 820 104 L 820 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 820 126.88 L 816.5 119.88 L 820 121.63 L 823.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 950 104 L 950 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 950 126.88 L 946.5 119.88 L 950 121.63 L 953.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 390 80 L 390 104 L 1080 104 L 1080 121.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 1080 126.88 L 1076.5 119.88 L 1080 121.63 L 1083.5 119.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><ellipse cx="390" cy="40" rx="60" ry="40" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/><g transform="translate(345.5,34.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="88" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 89px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">App component</div></div></foreignObject><text x="44" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">App component</text></switch></g><path d="M 430 208 L 430 232 L 370 232 L 370 250.13" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 370 255.38 L 366.5 248.38 L 370 250.13 L 373.5 248.38 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 430 208 L 430 232 L 470 232 L 470 250.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 470 255.88 L 466.5 248.88 L 470 250.63 L 473.5 248.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 430 208 L 430 232 L 570 232 L 570 250.13" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 570 255.38 L 566.5 248.38 L 570 250.13 L 573.5 248.38 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><ellipse cx="430" cy="168" rx="60" ry="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/><g transform="translate(408.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="43" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 44px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Videos </div></div></foreignObject><text x="22" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Videos </text></switch></g><ellipse cx="570" cy="282" rx="40" ry="25" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(524.5,262.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="91" height="38" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 91px; white-space: normal; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Edit (upload/update)<div><br /></div></div></div></foreignObject><text x="46" y="25" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Edit (upload/update)&lt;div&gt;&lt;br&gt;&lt;/div&gt;</text></switch></g><ellipse cx="470" cy="282" rx="40" ry="25" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(459.5,275.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="21" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 22px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">List</div></div></foreignObject><text x="11" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">List</text></switch></g><ellipse cx="370" cy="282" rx="40" ry="25" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(352.5,275.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="34" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 35px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Watch</div></div></foreignObject><text x="17" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Watch</text></switch></g><ellipse cx="560" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(537.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="45" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 46px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Account</div></div></foreignObject><text x="23" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Account</text></switch></g><ellipse cx="280" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(263.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="32" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 33px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Login</div></div></foreignObject><text x="16" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Login</text></switch></g><path d="M 130 208 L 130 231 L 45 231 L 45 246.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 45 251.88 L 41.5 244.88 L 45 246.63 L 48.5 244.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 130 208 L 130 231 L 155 231 L 155 246.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 155 251.88 L 151.5 244.88 L 155 246.63 L 158.5 244.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 130 208 L 130 231 L 260 231 L 260 246.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><path d="M 260 251.88 L 256.5 244.88 L 260 246.63 L 263.5 244.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/><ellipse cx="130" cy="168" rx="60" ry="40" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/><g transform="translate(111.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="36" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 37px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Admin</div></div></foreignObject><text x="18" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Admin</text></switch></g><ellipse cx="45" cy="282" rx="45" ry="28.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/><g transform="translate(22.5,275.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="44" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 45px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Follows</div></div></foreignObject><text x="22" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Follows</text></switch></g><ellipse cx="155" cy="282" rx="45" ry="28.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/><g transform="translate(116.5,275.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="77" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 78px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video abuses</div></div></foreignObject><text x="39" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Video abuses</text></switch></g><ellipse cx="260" cy="282" rx="40" ry="28.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/><g transform="translate(243.5,275.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="33" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Users</div></div></foreignObject><text x="17" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Users</text></switch></g><ellipse cx="690" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(673.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="33" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 34px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">About</div></div></foreignObject><text x="17" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">About</text></switch></g><ellipse cx="820" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(773.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="92" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 93px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><div>Page Not Found</div></div></div></foreignObject><text x="46" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">&lt;div&gt;Page Not Found&lt;/div&gt;</text></switch></g><ellipse cx="950" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(916.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="66" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 67px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;"><div>My Account</div></div></div></foreignObject><text x="33" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">&lt;div&gt;My Account&lt;/div&gt;</text></switch></g><ellipse cx="1080" cy="168" rx="60" ry="40" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/><g transform="translate(1034.5,162.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="90" height="11" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; vertical-align: top; width: 91px; white-space: nowrap; overflow-wrap: normal; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Channels</div></div></foreignObject><text x="45" y="11" fill="#000000" text-anchor="middle" font-size="11px" font-family="Helvetica">Video Channels</text></switch></g></g></svg> \ No newline at end of file
diff --git a/support/doc/development/client/components-tree.xml b/support/doc/development/client/components-tree.xml
deleted file mode 100644
index 5a37c48bc..000000000
--- a/support/doc/development/client/components-tree.xml
+++ /dev/null
@@ -1 +0,0 @@
1<mxfile userAgent="Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0" version="8.9.9" editor="www.draw.io" type="device"><diagram name="Page-1" id="8be7db5e-9885-9541-e5e4-cf9e4eb3a109">7Zpbb5swFMc/TaTtZQLMLY9d1nYP2zSp2u3RwQ54dTAypkn36WeDSSCmVdcR3FTpQwXHN/z7G59zHGZgsd5ec1hknxnCdOY5aDsDH2aeFzuB/K8M943Bd93GkHKCGlPHcEP+YG10tLUiCJe9ioIxKkjRNyYsz3EiejbIOdv0q60Y7Y9awBQbhpsEUtP6gyCRaasbzvcFHzFJMz107EVNwRImtylnVa7Hm3lgVf81xWvY9qUnWmYQsU3HBC5nYMEZE83VervAVKFtsTXtrh4o3T03x7l4SgNPT6gU9+3cMZIo9C3jImMpyyG93Fvf1/PDqgdH3mViTeWlKy/xloifyvwu0He/2pJc8PtOkbr9pTsoBeTiQqkmDTnLcWu7IpTqOjhHbY2EwrIkSWPUVdQQv7EQ93opwUowado//ifGCl1vxXKhq7nq3kSmKZas4omG4ulFCHmKdS1NTuHqNNOYrzFbYzlHWYFjCgW5668sqBdouqu3F0leaJ2GNQPOWbNnahZb08w9a/ZMzSJrmnlnzZ6pGbCmWTymZk/F9rC2gxyeRTSaiKAfvFKCPpiKYGiF4CO7yBR0/f+kq5t+ZUQO7Dk6k5jPdbCjEwm3DX7aLppn0K0ONNo9xtNkG3XreEyOl/RSTLWt6LHvIK10pxdFUWdw60K6Na1eDz6lMt1TkDcZEfimgPU8NjLh7KM+dFor6fcWjDJe9wOwiwIc1X6Ts1vcKZmHEYDhY27uDnOBt4+6MF0K/P5CbRO8TSd9bG1ZN3N0RmA76obz8Ko7hWAEmGs8tBWMtKHrOYD8Z80Ca5qN6wVe27vk29IFGP7jO0GYlTMvhGvlEPJlWewmdgQ3giCOV8mQGwmTGC9XI7mR+CDeaaOSKfyIbzC+REQFY2+qgjKIZPuqQFDgt5I6lQO/R+ROXqainnxjWvJDi2zWq3csgQIcI39IoNhbgnAkPx8c+vkofBcYEsUDCgUjKBQYCn0i5fFCp2mQ+laRhgbSH1Ak2YkzNcLRSZlGBtOLJJE+8tRXauBa3J1j891nKclPHOnu1wwbSHdBzznYa3/O6gZ77Wq3EIV70whz6pnTkGiuLdHARMcQr1A0YC3fnZuuGq2P6VUmSaNsZlHtrtlBesUoVd+knDZU9zCkBAbU+VBAGY3A1DWY1um/6nZZlfjk0Xr22AIzqfxWYn7qSA8DyyGkg/nPGEgHEqAlq15A+mPwG6D8INLQt3k4ZZ4ADpxCfVUf9nnOF6aOra7q7/Be6CnUfykRRTaVMM8JB5T4rB53l/a/Qg3mjk0NzB2mdYiLDOY5pkfcvyfB6zoTngzI2/03t823A/vvmsHlXw==</diagram></mxfile> \ No newline at end of file
diff --git a/support/doc/development/server/code.md b/support/doc/development/server/code.md
deleted file mode 100644
index 3894c2542..000000000
--- a/support/doc/development/server/code.md
+++ /dev/null
@@ -1,58 +0,0 @@
1# Server code documentation
2
3The server is a web server developed with [TypeScript](https://www.typescriptlang.org/)/[Express](http://expressjs.com).
4
5
6## Technologies
7
8 * [TypeScript](https://www.typescriptlang.org/) -> Language
9 * [PostgreSQL](https://www.postgresql.org/) -> Database
10 * [Redis](https://redis.io/) -> Job queue/cache
11 * [Express](http://expressjs.com) -> Web server framework
12 * [Sequelize](http://docs.sequelizejs.com/en/v3/) -> SQL ORM
13 * [WebTorrent](https://webtorrent.io/) -> BitTorrent tracker and torrent creation
14 * [Mocha](https://mochajs.org/) -> Test framework
15
16
17## Files
18
19The server main file is [server.ts](/server.ts).
20The server modules description are in the [package.json](/package.json) at the project root.
21All other server files are in the [server](/server) directory:
22
23 server.ts -> app initialization, main routes configuration (static routes...)
24 config -> server YAML configurations (for tests, production...)
25 scripts -> Scripts files for npm run
26 server
27 |__ controllers -> API routes/controllers files
28 |__ helpers -> functions used by different part of the project (logger, utils...)
29 |__ initializers -> functions used at the server startup (installer, database, constants...)
30 |__ lib -> library function (WebTorrent, OAuth2, ActivityPub...)
31 |__ middlewares -> middlewares for controllers (requests validators, requests pagination...)
32 |__ models -> Sequelize models for each SQL tables (videos, users, accounts...)
33 |__ tests -> API tests and real world simulations (to test the decentralized feature...)
34
35
36## Conventions
37
38Uses [JavaScript Standard Style](http://standardjs.com/).
39
40## Architecture
41
42The server is composed by:
43
44 * a REST API (relying on the Express framework) documented on http://docs.joinpeertube.org/api.html
45 * a WebTorrent Tracker (slightly custom version of [webtorrent/bittorrent-tracker](https://github.com/webtorrent/bittorrent-tracker#server))
46
47A video is seeded by the server with the [WebSeed](http://www.bittorrent.org/beps/bep_0019.html) protocol (HTTP).
48
49![Architecture scheme](/support/doc/development/server/upload-video.png)
50
51When a user uploads a video, the REST API creates the torrent file and then adds it to its database.
52
53If a user wants to watch the video, the tracker will indicate all other users that are watching the video + the HTTP url for the WebSeed.
54
55## Newcomers
56
57The server entrypoint is [server.ts](/server.ts). Looking at this file is a good start.
58Then you can try to understand the [controllers](/server/controllers): they are the entrypoints of each API request.
diff --git a/support/doc/development/server/peertube-architecture-server.xml b/support/doc/development/server/peertube-architecture-server.xml
deleted file mode 100644
index 3299307a1..000000000
--- a/support/doc/development/server/peertube-architecture-server.xml
+++ /dev/null
@@ -1 +0,0 @@
1<mxfile userAgent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" version="7.9.7" editor="www.draw.io" type="device"><diagram id="033390a3-e8de-cf4f-5be1-41b3d99c78ae" name="Page-1">3VpRc5s4EP41nmkekgGEwH6M3aS9md5Mpnbby6MA2dBgxIHs2P31J4EECGGbxLhNzu1MYLXC0re7364Wj8BsvfuUoTT8mwQ4HllGsBuBjyPLMm3LGvH/RrAvJeOxWQpWWRQIpVowj35hITSEdBMFOFcUKSExjVJV6JMkwT5VZCjLyLOqtiSx+q0pWmFNMPdRrEt/RAENxS4sp5Z/xtEqlN9sOpNyxEP+0yojm0R838gCy+JTDq+RfJbYaB6igDw3ROBuBGYZIbS8Wu9mOObYStjKefcHRqt1ZzihfSaAcsIWxRssV1ysi+4lFsVuMNc3R2D6HEYUz1Pk89FnZn0mC+k6FsPLKI5nJCZZMVfuHUxzmpEn3Bgxig8b0VcsNrHFGcW7hkjs4BMma0yzPVMRo7YAUzgbELfPteVcqRI2rGZPhBAJb1lVT64RYxcCtG4AbQ3AbznOmMQcFsixj32/C0hvDG04EJCmpSLpmjqUlU4TSmcAJOEhJAd2yd+CJJzAP4ckGGuI4YDxm7glGQ3JiiQovqul0xpTQ8UP7yL6DxffQHH3KJQYPNm+HLKgvH8U03KKMnrLuZgJEpJgKbuP+MLFAwKp4ccozyO/FAoV/pifmNK9SBBoQwkT1ev/QkgqrUwSKtRM85gNc7LJfIGKIG22qhWWWsJuHLCjhs5wjGi0VRPGWUwinawOgFmGEWV5kC0xxEUOzIr9sIwWMaNZTsxWPQ2iLbtc8cvbIKi1tyyHkmIWz8SIIg/l1SS2msY8zV+Ym1PVDTKcR7+QVyhw26UkSmixXTgdwY9MguJolXBTsiWywAVTHi4RS6q3YmAdBUHhazHycDytUmVXwjhtT+nlWlBWZYFYrZJau4LVuDEhtJR4vRa83tvK4uEPHJWGClkuc+ZabTeo1tDLMyaaY3y9my+Y5PbhL81yL2PDAOJxYHex4djygOMMw4aTsZqg3Q4ydOCFyNC5PBmaKhm6ChkabTKsqa7Jh+Yb4EOZpJqEODgfFlPZNtG+oSDYRA+kncyfjuJCjtuqa1v6zhge02cX5QpeG5J2V+Gs03H+lBfHD17GpBhn/O7DD+x9Xcyuupl4GtFFxfJpRnwSv0tydoYi52vGzsBUCcR6Q+QsY6bhCczAlQ01n1hkDFpe1/bKwy9jc2wyPne72HziuAANxOaOc5rOJ5dic0ODaBA2tw7SeXXzWNH+eyhtTdk2aVC5/SaoHMDW0cgelpqBXkffY+qHVV3sx1FJryhNY0aBNCLJu+RYyTwDFMAGtNQEe+2Y55GsXJB0xL28b7HAICQM9B7BILTgHq7y/j+08MdOvGbHiVfGJgOOhY446eYdefRDYTCLfYnxMy//0nCz9hIUxfnNzc2B+urMDMti0+ruHgWO58CBMixo9eE6D0zuhbpHpt7SlFB6NYqlgPugAqfz74bIgeu88M5bpmDZ6a4elE+Z42yrVELl49SvYGLvBW0K1S4iCptGFKL+XN3lIIRpL+MijEOmh5OB7A5PdrJNo8PscIjKytXM/rChdTdpk8YEcbqsG0v8JDPHOLjqCE9lrl/0sfhUqtbFJ2LzMhz+Lnga6jwN+tZvjZMTdCdqWpcvQi5b40G3dWBzjtR42uzW+xz7EhWDqb9u4PUYM3GHM38hK56LLpFPIP/XlU+c4jMMr1iGWnJXJNIgFjDpIBZzkITiaFB/K8iEP7emk3bsd0XgS19LaG8legWfrcfepGfoNQCFR4j63KJaDZC2kcq9iDnHmmTqY9qVebl/7TGvCDaJ6KV7sH0puZcXgA4G7ukFQ5HrOSci+Hsgfw/JFHQE9Ntoa7fz5Mm2NrxwW9vSqLrunTDv4ERmGaLs4xXg58Xi4UrztPfQPJH5f5AG9dh1Fbtcn9k8GbY5oh/ovosK/vQpuyrYZzJTc9VLHa6xc+Bw7U68oX4tBEGPw3XXKesVtRC7rX/LVZqu/sEcuPsP</diagram></mxfile> \ No newline at end of file
diff --git a/support/doc/development/server/upload-video.png b/support/doc/development/server/upload-video.png
deleted file mode 100644
index 7edc06792..000000000
--- a/support/doc/development/server/upload-video.png
+++ /dev/null
Binary files differ
diff --git a/support/doc/production.md b/support/doc/production.md
index 2eba6e6a3..4f20cf140 100644
--- a/support/doc/production.md
+++ b/support/doc/production.md
@@ -204,6 +204,9 @@ logs. You can set another password with:
204$ cd /var/www/peertube/peertube-latest && NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u root 204$ cd /var/www/peertube/peertube-latest && NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run reset-password -- -u root
205``` 205```
206 206
207Alternatively you can set the environment variable `PT_INITIAL_ROOT_PASSWORD`,
208to your own administrator password, although it must be 6 characters or more.
209
207### What now? 210### What now?
208 211
209Now your instance is up you can: 212Now your instance is up you can:
diff --git a/support/doc/tools.md b/support/doc/tools.md
index 086cd5cff..ba6e2b12d 100644
--- a/support/doc/tools.md
+++ b/support/doc/tools.md
@@ -35,7 +35,8 @@ You need to follow all the following steps even if you are on a PeerTube server
35 35
36### Dependencies 36### Dependencies
37 37
38Install the [PeerTube dependencies](dependencies.md). 38Install the [PeerTube dependencies](dependencies.md) except PostgreSQL and Redis.
39PeerTube only supports NodeJS 8.x or 10.x.
39 40
40### Installation 41### Installation
41 42
@@ -44,18 +45,14 @@ Clone the PeerTube repo to get the latest version (even if you are on your PeerT
44``` 45```
45$ git clone https://github.com/Chocobozzz/PeerTube.git 46$ git clone https://github.com/Chocobozzz/PeerTube.git
46$ CLONE="$(pwd)/PeerTube" 47$ CLONE="$(pwd)/PeerTube"
47```
48
49Run ``yarn install --pure-lockfile``
50```
51$ cd ${CLONE} 48$ cd ${CLONE}
52$ yarn install --pure-lockfile
53``` 49```
54 50
55Build server tools: 51Install dependencies and build CLI tools:
52
56``` 53```
57$ cd ${CLONE} 54$ NOCLIENT=1 yarn install --pure-lockfile
58$ npm run build:server 55$ npm run setup:cli
59``` 56```
60 57
61### CLI wrapper 58### CLI wrapper
diff --git a/support/docker/production/.env b/support/docker/production/.env
index 7b9092642..c8393d0ce 100644
--- a/support/docker/production/.env
+++ b/support/docker/production/.env
@@ -5,8 +5,7 @@ PEERTUBE_WEBSERVER_PORT=443
5PEERTUBE_WEBSERVER_HTTPS=true 5PEERTUBE_WEBSERVER_HTTPS=true
6# If you need more than one IP as trust_proxy 6# If you need more than one IP as trust_proxy
7# pass them as a comma separated array: 7# pass them as a comma separated array:
8PEERTUBE_TRUST_PROXY=["127.0.0.1"] 8PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "172.18.0.0/16"]
9#PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "192.168.1.0/24"]
10#PEERTUBE_SMTP_USERNAME= 9#PEERTUBE_SMTP_USERNAME=
11#PEERTUBE_SMTP_PASSWORD= 10#PEERTUBE_SMTP_PASSWORD=
12PEERTUBE_SMTP_HOSTNAME=postfix 11PEERTUBE_SMTP_HOSTNAME=postfix
diff --git a/support/docker/production/config/custom-environment-variables.yaml b/support/docker/production/config/custom-environment-variables.yaml
index bd4ac1215..d5b878830 100644
--- a/support/docker/production/config/custom-environment-variables.yaml
+++ b/support/docker/production/config/custom-environment-variables.yaml
@@ -106,6 +106,9 @@ transcoding:
106 1080: 106 1080:
107 __name: "PEERTUBE_TRANSCODING_1080P" 107 __name: "PEERTUBE_TRANSCODING_1080P"
108 __format: "json" 108 __format: "json"
109 2160:
110 __name: "PEERTUBE_TRANSCODING_2160P"
111 __format: "json"
109 112
110instance: 113instance:
111 name: "PEERTUBE_INSTANCE_NAME" 114 name: "PEERTUBE_INSTANCE_NAME"
diff --git a/support/docker/production/docker-compose.yml b/support/docker/production/docker-compose.yml
index df21a14d4..263fb6e95 100644
--- a/support/docker/production/docker-compose.yml
+++ b/support/docker/production/docker-compose.yml
@@ -72,3 +72,10 @@ services:
72 labels: 72 labels:
73 traefik.enable: "false" 73 traefik.enable: "false"
74 restart: "always" 74 restart: "always"
75
76networks:
77 default:
78 ipam:
79 driver: default
80 config:
81 - subnet: 172.18.0.0/16
diff --git a/tsconfig.json b/tsconfig.json
index 5ad4b3e10..4d2bdd6ba 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,6 +17,7 @@
17 "typeRoots": [ "node_modules/@types", "server/typings" ] 17 "typeRoots": [ "node_modules/@types", "server/typings" ]
18 }, 18 },
19 "exclude": [ 19 "exclude": [
20 "server/tools/",
20 "client/node_modules", 21 "client/node_modules",
21 "node_modules", 22 "node_modules",
22 "dist", 23 "dist",
diff --git a/yarn.lock b/yarn.lock
index f2cc0ee05..18a0f2611 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,12 +2,28 @@
2# yarn lockfile v1 2# yarn lockfile v1
3 3
4 4
5"@babel/runtime@7.0.0": 5"@babel/code-frame@^7.0.0":
6 version "7.0.0" 6 version "7.0.0"
7 resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0.tgz#adeb78fedfc855aa05bc041640f3f6f98e85424c" 7 resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
8 integrity sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA== 8 integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
9 dependencies: 9 dependencies:
10 regenerator-runtime "^0.12.0" 10 "@babel/highlight" "^7.0.0"
11
12"@babel/highlight@^7.0.0":
13 version "7.0.0"
14 resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
15 integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==
16 dependencies:
17 chalk "^2.0.0"
18 esutils "^2.0.2"
19 js-tokens "^4.0.0"
20
21"@babel/runtime@^7.0.0":
22 version "7.4.5"
23 resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
24 integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
25 dependencies:
26 regenerator-runtime "^0.13.2"
11 27
12"@samverschueren/stream-to-observable@^0.3.0": 28"@samverschueren/stream-to-observable@^0.3.0":
13 version "0.3.0" 29 version "0.3.0"
@@ -28,10 +44,10 @@
28 resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.1.tgz#81f218213bebcc5f740efe9648272c774a2e4b4b" 44 resolved "https://registry.yarnpkg.com/@types/async-lock/-/async-lock-1.1.1.tgz#81f218213bebcc5f740efe9648272c774a2e4b4b"
29 integrity sha512-TU1X8jmAU2BjwKryBFV/GDezz7Ge0xu9ZuYC7dy6wKj4hnL0JcxeseCOr/G2JkGylff6hdUBrR+Ee5ApAQeU5g== 45 integrity sha512-TU1X8jmAU2BjwKryBFV/GDezz7Ge0xu9ZuYC7dy6wKj4hnL0JcxeseCOr/G2JkGylff6hdUBrR+Ee5ApAQeU5g==
30 46
31"@types/async@^2.0.40": 47"@types/async@^3.0.0":
32 version "2.4.1" 48 version "3.0.0"
33 resolved "https://registry.yarnpkg.com/@types/async/-/async-2.4.1.tgz#43c3b2c60eab41c25ca0009c07ca7d619d943119" 49 resolved "https://registry.yarnpkg.com/@types/async/-/async-3.0.0.tgz#d403560ee2aabccdb7936cb9a3fee5f147d626bd"
34 integrity sha512-C09BK/wXzbW+/JK9zckhe+FeSbg7NmvVjUWwApnw7ksRpUq3ecGLiq2Aw1LlY4Z/VmtdhSaIs7jO5/MWRYMcOA== 50 integrity sha512-DvEhEeG8ynipwkg7LtNHlO99+vEVbin+E+3YuzeJCM9TREewJ1B5fdZsQzykR7fKuh6rKh8mEir36zKd3uafOA==
35 51
36"@types/bcrypt@^3.0.0": 52"@types/bcrypt@^3.0.0":
37 version "3.0.0" 53 version "3.0.0"
@@ -125,17 +141,17 @@
125 "@types/express" "*" 141 "@types/express" "*"
126 142
127"@types/express-serve-static-core@*": 143"@types/express-serve-static-core@*":
128 version "4.16.2" 144 version "4.16.6"
129 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.2.tgz#5ee8a22e602005be6767df6b2cba9879df3f75aa" 145 resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.6.tgz#66d4b29ece3e2fb6e5aac2232723002426e651bd"
130 integrity sha512-qgc8tjnDrc789rAQed8NoiFLV5VGcItA4yWNFphqGU0RcuuQngD00g3LHhWIK3HQ2XeDgVCmlNPDlqi3fWBHnQ== 146 integrity sha512-8wr3CA/EMybyb6/V8qvTRKiNkPmgUA26uA9XWD6hlA0yFDuqi4r2L0C2B0U2HAYltJamoYJszlkaWM31vrKsHg==
131 dependencies: 147 dependencies:
132 "@types/node" "*" 148 "@types/node" "*"
133 "@types/range-parser" "*" 149 "@types/range-parser" "*"
134 150
135"@types/express@*", "@types/express@^4.0.35": 151"@types/express@*", "@types/express@^4.0.35":
136 version "4.16.1" 152 version "4.17.0"
137 resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.1.tgz#d756bd1a85c34d87eaf44c888bad27ba8a4b7cf0" 153 resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.0.tgz#49eaedb209582a86f12ed9b725160f12d04ef287"
138 integrity sha512-V0clmJow23WeyblmACoxbHBu2JKlE5TiIme6Lem14FnPW9gsttyHtk6wq7njcdIWH1njAaFgR8gW09lgY98gQg== 154 integrity sha512-CjaMu57cjgjuZbh9DpkloeGxV45CnMGlVd+XpG7Gm9QgVrd7KFq+X4HY0vM+2v0bczS48Wg7bvnMY5TN+Xmcfw==
139 dependencies: 155 dependencies:
140 "@types/body-parser" "*" 156 "@types/body-parser" "*"
141 "@types/express-serve-static-core" "*" 157 "@types/express-serve-static-core" "*"
@@ -155,17 +171,17 @@
155 dependencies: 171 dependencies:
156 "@types/node" "*" 172 "@types/node" "*"
157 173
158"@types/fs-extra@^5.0.4": 174"@types/fs-extra@^7.0.0":
159 version "5.0.5" 175 version "7.0.0"
160 resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" 176 resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-7.0.0.tgz#9c4ad9e1339e7448a76698829def1f159c1b636c"
161 integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== 177 integrity sha512-ndoMMbGyuToTy4qB6Lex/inR98nPiNHacsgMPvy+zqMLgSxbt8VtWpDArpGp69h1fEDQHn1KB+9DWD++wgbwYA==
162 dependencies: 178 dependencies:
163 "@types/node" "*" 179 "@types/node" "*"
164 180
165"@types/ioredis@*": 181"@types/ioredis@*":
166 version "4.0.10" 182 version "4.0.11"
167 resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.0.10.tgz#ca8bd95ca7d5fee32cbc5a0bf92fc29264bee237" 183 resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.0.11.tgz#4acf9d7912944bcf1e96dd6adba2f16c07ed9ae7"
168 integrity sha512-1ImAFcW5eg4f9UrftlVQPmGCkK1VyCqJ12c2wy3NlEQvzBC0GX64WlN01xj5iETqvsjczJHU+CRMPraBy9pX+g== 184 integrity sha512-spuEM4/UxlFH95NCrMojH9FTP3SLm8N2Itb9f0z0pca1IVdlPHobjps+KccEiUuYdfhzpdKOox8yGoqO9vahaw==
169 dependencies: 185 dependencies:
170 "@types/node" "*" 186 "@types/node" "*"
171 187
@@ -177,9 +193,9 @@
177 "@types/node" "*" 193 "@types/node" "*"
178 194
179"@types/lodash@^4.14.64": 195"@types/lodash@^4.14.64":
180 version "4.14.123" 196 version "4.14.133"
181 resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" 197 resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.133.tgz#430721c96da22dd1694443e68e6cec7ba1c1003d"
182 integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== 198 integrity sha512-/3JqnvPnY58GLzG3Y7fpphOhATV1DDZ/Ak3DQufjlRK5E4u+s0CfClfNFtAGBabw+jDGtRFbOZe+Z02ZMWCBNQ==
183 199
184"@types/magnet-uri@*", "@types/magnet-uri@^5.1.1": 200"@types/magnet-uri@*", "@types/magnet-uri@^5.1.1":
185 version "5.1.2" 201 version "5.1.2"
@@ -213,9 +229,9 @@
213 "@types/node" "*" 229 "@types/node" "*"
214 230
215"@types/mocha@^5.0.0": 231"@types/mocha@^5.0.0":
216 version "5.2.6" 232 version "5.2.7"
217 resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.6.tgz#b8622d50557dd155e9f2f634b7d68fd38de5e94b" 233 resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
218 integrity sha512-1axi39YdtBI7z957vdqXI4Ac25e7YihYQtJa+Clnxg1zTJEaIRbndt71O3sP4GAMgiAm0pY26/b9BrY4MR/PMw== 234 integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
219 235
220"@types/morgan@^1.7.32": 236"@types/morgan@^1.7.32":
221 version "1.7.35" 237 version "1.7.35"
@@ -232,22 +248,27 @@
232 "@types/express" "*" 248 "@types/express" "*"
233 249
234"@types/node@*": 250"@types/node@*":
235 version "11.13.0" 251 version "12.0.5"
236 resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.0.tgz#b0df8d6ef9b5001b2be3a94d909ce3c29a80f9e1" 252 resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.5.tgz#ac14404c33d1a789973c45379a67f7f7e58a01b9"
237 integrity sha512-rx29MMkRdVmzunmiA4lzBYJNnXsW/PhG4kMBy2ATsYaDjGGR75dCFEVVROKpNwlVdcUX3xxlghKQOeDPBJobng== 253 integrity sha512-CFLSALoE+93+Hcb5pFjp0J1uMrrbLRe+L1+gFwerJ776R3TACSF0kTVRQ7AvRa7aFx70nqYHAc7wQPlt9kY2Mg==
238 254
239"@types/node@^10.0.8": 255"@types/node@^10.0.8":
240 version "10.14.4" 256 version "10.14.8"
241 resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.4.tgz#1c586b991457cbb58fef51bc4e0cfcfa347714b5" 257 resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.8.tgz#fe444203ecef1162348cd6deb76c62477b2cc6e9"
242 integrity sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg== 258 integrity sha512-I4+DbJEhLEg4/vIy/2gkWDvXBOOtPKV9EnLhYjMoqxcRW+TTZtUftkHktz/a8suoD5mUL7m6ReLrkPvSsCQQmw==
243 259
244"@types/nodemailer@^4.3.1": 260"@types/nodemailer@^6.2.0":
245 version "4.6.7" 261 version "6.2.0"
246 resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-4.6.7.tgz#ac3c594fac6daf7f71eb4d31c9e4c88b62dbcb87" 262 resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.2.0.tgz#369d3c6ff51201670f3e5469b00c1d204ec8f894"
247 integrity sha512-slR1wz8I8O20CHNBNhYhObRZ8zeG5FnfFUWLZKk1f0UDYaLZOsBjUfCC9VEFi7oSRCC886DfKmq1JncPDMOrng== 263 integrity sha512-WGGEk/BGRLuYF3gyoTwbtKg5tCexZzb5lkTsis2k7GkAzlg4x2299/SC6Ssdj3X/5TzT1BHVc8zcFg/7KSzBLw==
248 dependencies: 264 dependencies:
249 "@types/node" "*" 265 "@types/node" "*"
250 266
267"@types/normalize-package-data@^2.4.0":
268 version "2.4.0"
269 resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
270 integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
271
251"@types/oauth2-server@^3.0.8": 272"@types/oauth2-server@^3.0.8":
252 version "3.0.10" 273 version "3.0.10"
253 resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.10.tgz#ea671a6ad3d02062aac5f7c1ba1fb9c468314db0" 274 resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.10.tgz#ea671a6ad3d02062aac5f7c1ba1fb9c468314db0"
@@ -284,9 +305,9 @@
284 integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== 305 integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
285 306
286"@types/redis@*", "@types/redis@^2.8.5": 307"@types/redis@*", "@types/redis@^2.8.5":
287 version "2.8.12" 308 version "2.8.13"
288 resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.12.tgz#6405d7ece0d6cc037151b7141cef9ad3cd06f3ac" 309 resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.13.tgz#fdd76d9a8b7c36ff24d8506a94ef2a890c87f498"
289 integrity sha512-eT5cGYr08OnF6OlAHdc2hVOBAKBpfQQNQHsWEvUwRPFiXRd+vv+hOHSSIo4xB7M5vZOZdjMT2OUlXYqo3YlIGQ== 310 integrity sha512-p86cm5P6DMotUqCS6odQRz0JJwc5QXZw9eyH0ALVIqmq12yqtex5ighWyGFHKxak9vaA/GF/Ilu0KZ0MuXXUbg==
290 dependencies: 311 dependencies:
291 "@types/node" "*" 312 "@types/node" "*"
292 313
@@ -309,9 +330,9 @@
309 "@types/mime" "*" 330 "@types/mime" "*"
310 331
311"@types/sharp@^0.22.1": 332"@types/sharp@^0.22.1":
312 version "0.22.1" 333 version "0.22.2"
313 resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.22.1.tgz#4fccaa9cc580d859916bee693aa7ae7447d0e0ba" 334 resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.22.2.tgz#c9b613816ec000d94f153259b25ae41f874a75f5"
314 integrity sha512-Reri4hhX77JBx6HWt2a2CLO0xjHMFHDaeBYMslUsTSHGRFQnk+VpoH25g7/L6QzyyNNmxTekK+j+lFFjK3OkqA== 335 integrity sha512-oH49f42h3nf/qys0weYsaTGiMv67wPB769ynCoPfBAVwjjxFF3QtIPEe3MfhwyNjQAhQhTEfnmMKvVZfcFkhIw==
315 dependencies: 336 dependencies:
316 "@types/node" "*" 337 "@types/node" "*"
317 338
@@ -377,15 +398,7 @@
377 "@types/events" "*" 398 "@types/events" "*"
378 "@types/node" "*" 399 "@types/node" "*"
379 400
380JSONStream@^1.3.4, JSONStream@^1.3.5: 401abbrev@1:
381 version "1.3.5"
382 resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
383 integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
384 dependencies:
385 jsonparse "^1.2.0"
386 through ">=2.2.7 <3"
387
388abbrev@1, abbrev@~1.1.1:
389 version "1.1.1" 402 version "1.1.1"
390 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 403 resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
391 integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== 404 integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
@@ -406,13 +419,13 @@ accepts@~1.2.12:
406 mime-types "~2.1.6" 419 mime-types "~2.1.6"
407 negotiator "0.5.3" 420 negotiator "0.5.3"
408 421
409accepts@~1.3.4, accepts@~1.3.5: 422accepts@~1.3.4, accepts@~1.3.7:
410 version "1.3.5" 423 version "1.3.7"
411 resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 424 resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
412 integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= 425 integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
413 dependencies: 426 dependencies:
414 mime-types "~2.1.18" 427 mime-types "~2.1.24"
415 negotiator "0.6.1" 428 negotiator "0.6.2"
416 429
417acorn-jsx@^3.0.0: 430acorn-jsx@^3.0.0:
418 version "3.0.1" 431 version "3.0.1"
@@ -446,20 +459,6 @@ after@0.8.2:
446 resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" 459 resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
447 integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= 460 integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
448 461
449agent-base@4, agent-base@^4.1.0, agent-base@~4.2.1:
450 version "4.2.1"
451 resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
452 integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
453 dependencies:
454 es6-promisify "^5.0.0"
455
456agentkeepalive@^3.4.1:
457 version "3.5.2"
458 resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
459 integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==
460 dependencies:
461 humanize-ms "^1.2.1"
462
463ajv-keywords@^1.0.0: 462ajv-keywords@^1.0.0:
464 version "1.5.1" 463 version "1.5.1"
465 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" 464 resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -515,28 +514,23 @@ ansi-regex@^3.0.0:
515 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 514 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
516 integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 515 integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
517 516
517ansi-regex@^4.1.0:
518 version "4.1.0"
519 resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
520 integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
521
518ansi-styles@^2.2.1: 522ansi-styles@^2.2.1:
519 version "2.2.1" 523 version "2.2.1"
520 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 524 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
521 integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= 525 integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=
522 526
523ansi-styles@^3.2.1: 527ansi-styles@^3.2.0, ansi-styles@^3.2.1:
524 version "3.2.1" 528 version "3.2.1"
525 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 529 resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
526 integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 530 integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
527 dependencies: 531 dependencies:
528 color-convert "^1.9.0" 532 color-convert "^1.9.0"
529 533
530ansicolors@~0.3.2:
531 version "0.3.2"
532 resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979"
533 integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=
534
535ansistyles@~0.1.3:
536 version "0.1.3"
537 resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539"
538 integrity sha1-XeYEFb2gcbs3EnhUyGT0GyMlRTk=
539
540any-observable@^0.3.0: 534any-observable@^0.3.0:
541 version "0.3.0" 535 version "0.3.0"
542 resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" 536 resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b"
@@ -565,34 +559,11 @@ append-field@^1.0.0:
565 resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" 559 resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
566 integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= 560 integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=
567 561
568application-config-path@^0.1.0: 562aproba@^1.0.3:
569 version "0.1.0"
570 resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f"
571 integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8=
572
573application-config@^1.0.1:
574 version "1.0.1"
575 resolved "https://registry.yarnpkg.com/application-config/-/application-config-1.0.1.tgz#5aa2e2a5ed6abd2e5d1d473d3596f574044fe9e7"
576 integrity sha1-WqLipe1qvS5dHUc9NZb1dARP6ec=
577 dependencies:
578 application-config-path "^0.1.0"
579 mkdirp "^0.5.1"
580
581aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2:
582 version "1.2.0" 563 version "1.2.0"
583 resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" 564 resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
584 integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== 565 integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
585 566
586"aproba@^1.1.2 || 2", aproba@^2.0.0:
587 version "2.0.0"
588 resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
589 integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
590
591archy@~1.0.0:
592 version "1.0.0"
593 resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40"
594 integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=
595
596are-we-there-yet@~1.1.2: 567are-we-there-yet@~1.1.2:
597 version "1.1.5" 568 version "1.1.5"
598 resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" 569 resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
@@ -665,11 +636,6 @@ arrify@^1.0.1:
665 resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 636 resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
666 integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= 637 integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=
667 638
668asap@^2.0.0:
669 version "2.0.6"
670 resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
671 integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
672
673asn1@~0.2.3: 639asn1@~0.2.3:
674 version "0.2.4" 640 version "0.2.4"
675 resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 641 resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
@@ -698,11 +664,11 @@ assign-symbols@^1.0.0:
698 integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= 664 integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
699 665
700async-each@^1.0.1: 666async-each@^1.0.1:
701 version "1.0.2" 667 version "1.0.3"
702 resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.2.tgz#8b8a7ca2a658f927e9f307d6d1a42f4199f0f735" 668 resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
703 integrity sha512-6xrbvN0MOBKSJDdonmSSz2OwFSgxRaVtBDes26mj9KIGtDo+g9xosFRSC+i1gQh2oAN/tQ62AI/pGZGQjVOiRg== 669 integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
704 670
705async-limiter@~1.0.0: 671async-limiter@^1.0.0, async-limiter@~1.0.0:
706 version "1.0.0" 672 version "1.0.0"
707 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" 673 resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
708 integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== 674 integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
@@ -719,7 +685,12 @@ async@1.5.1:
719 resolved "https://registry.yarnpkg.com/async/-/async-1.5.1.tgz#b05714f4b11b357bf79adaffdd06da42d0766c10" 685 resolved "https://registry.yarnpkg.com/async/-/async-1.5.1.tgz#b05714f4b11b357bf79adaffdd06da42d0766c10"
720 integrity sha1-sFcU9LEbNXv3mtr/3QbaQtB2bBA= 686 integrity sha1-sFcU9LEbNXv3mtr/3QbaQtB2bBA=
721 687
722async@>=0.2.9, async@^2.0.0, async@^2.6.1: 688async@>=0.2.9, async@^3.0.1:
689 version "3.0.1"
690 resolved "https://registry.yarnpkg.com/async/-/async-3.0.1.tgz#dfeb34657d1e63c94c0eee424297bf8a2c9a8182"
691 integrity sha512-ZswD8vwPtmBZzbn9xyi8XBQWXH3AvOQ43Za1KWYq7JeycrZuUYzx01KvHcVbXltjqH4y0MWrQ33008uLTqXuDw==
692
693async@^2.6.1:
723 version "2.6.2" 694 version "2.6.2"
724 resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381" 695 resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
725 integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg== 696 integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
@@ -756,15 +727,6 @@ aws4@^1.8.0:
756 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" 727 resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
757 integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== 728 integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
758 729
759babel-code-frame@^6.22.0:
760 version "6.26.0"
761 resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
762 integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=
763 dependencies:
764 chalk "^1.1.3"
765 esutils "^2.0.2"
766 js-tokens "^3.0.2"
767
768backo2@1.0.2: 730backo2@1.0.2:
769 version "1.0.2" 731 version "1.0.2"
770 resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 732 resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
@@ -817,12 +779,12 @@ bcrypt-pbkdf@^1.0.0:
817 dependencies: 779 dependencies:
818 tweetnacl "^0.14.3" 780 tweetnacl "^0.14.3"
819 781
820bcrypt@3.0.5: 782bcrypt@3.0.6:
821 version "3.0.5" 783 version "3.0.6"
822 resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-3.0.5.tgz#37a296c48ebf39fe6b28e4da3a221bf80da5aa26" 784 resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-3.0.6.tgz#f607846df62d27e60d5e795612c4f67d70206eb2"
823 integrity sha512-m4o91nB+Ce8696Ao4R3B/WtVWTc1Lszgd098/OIjU9D/URmdYwT3ooBs9uv1b97J5YhZweTq9lldPefTYZ0TwA== 785 integrity sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==
824 dependencies: 786 dependencies:
825 nan "2.13.1" 787 nan "2.13.2"
826 node-pre-gyp "0.12.0" 788 node-pre-gyp "0.12.0"
827 789
828bencode@^2.0.0: 790bencode@^2.0.0:
@@ -839,17 +801,6 @@ better-assert@~1.0.0:
839 dependencies: 801 dependencies:
840 callsite "1.0.0" 802 callsite "1.0.0"
841 803
842bin-links@^1.1.2:
843 version "1.1.2"
844 resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.2.tgz#fb74bd54bae6b7befc6c6221f25322ac830d9757"
845 integrity sha512-8eEHVgYP03nILphilltWjeIjMbKyJo3wvp9K816pHbhP301ismzw15mxAAEVQ/USUwcP++1uNrbERbp8lOA6Fg==
846 dependencies:
847 bluebird "^3.5.0"
848 cmd-shim "^2.0.2"
849 gentle-fs "^2.0.0"
850 graceful-fs "^4.1.11"
851 write-file-atomic "^2.3.0"
852
853binary-extensions@^1.0.0: 804binary-extensions@^1.0.0:
854 version "1.13.1" 805 version "1.13.1"
855 resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" 806 resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -860,18 +811,13 @@ binary-search@^1.3.4:
860 resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.5.tgz#479ad009589e0273cf54e5d74ab1546c489078ce" 811 resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.5.tgz#479ad009589e0273cf54e5d74ab1546c489078ce"
861 integrity sha512-RHFP0AdU6KAB0CCZsRMU2CJTk2EpL8GLURT+4gilpjr1f/7M91FgUMnXuQLmf3OKLet34gjuNFwO7e4agdX5pw== 812 integrity sha512-RHFP0AdU6KAB0CCZsRMU2CJTk2EpL8GLURT+4gilpjr1f/7M91FgUMnXuQLmf3OKLet34gjuNFwO7e4agdX5pw==
862 813
863bindings@^1.3.0, bindings@^1.5.0: 814bindings@^1.3.0:
864 version "1.5.0" 815 version "1.5.0"
865 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" 816 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
866 integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== 817 integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
867 dependencies: 818 dependencies:
868 file-uri-to-path "1.0.0" 819 file-uri-to-path "1.0.0"
869 820
870bindings@~1.2.1:
871 version "1.2.1"
872 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11"
873 integrity sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=
874
875bindings@~1.3.0: 821bindings@~1.3.0:
876 version "1.3.1" 822 version "1.3.1"
877 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" 823 resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
@@ -938,16 +884,15 @@ bittorrent-protocol@^3.0.0:
938 xtend "^4.0.0" 884 xtend "^4.0.0"
939 885
940bittorrent-tracker@^9.0.0: 886bittorrent-tracker@^9.0.0:
941 version "9.10.1" 887 version "9.11.0"
942 resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.10.1.tgz#5de14aac012a287af394d3cc9eda1ec6cc956f11" 888 resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.11.0.tgz#9911f9c14e5a29f84990a0c31b3d83dd16eb2876"
943 integrity sha512-n5zTL/g6Wt0rb2EnkiyiaGYhth7I/N0/xMqGUpvGX/7g1scDGBVPhJnXR8lfp3/OMj681fv40o4q/otECMtZSA== 889 integrity sha512-T1zvW/kSeEnWT4I3JE+6c7aZbO5jtleZyQe911SyzIxFF9DvtUNWXud3p5ZUkXaoI2xXwfpvlks5VFj5SKEB+A==
944 dependencies: 890 dependencies:
945 bencode "^2.0.0" 891 bencode "^2.0.0"
946 bittorrent-peerid "^1.0.2" 892 bittorrent-peerid "^1.0.2"
947 bn.js "^4.4.0" 893 bn.js "^4.4.0"
948 compact2string "^1.2.0" 894 compact2string "^1.2.0"
949 debug "^3.1.0" 895 debug "^4.0.1"
950 inherits "^2.0.1"
951 ip "^1.0.1" 896 ip "^1.0.1"
952 lru "^3.0.0" 897 lru "^3.0.0"
953 minimist "^1.1.1" 898 minimist "^1.1.1"
@@ -964,7 +909,6 @@ bittorrent-tracker@^9.0.0:
964 uniq "^1.0.1" 909 uniq "^1.0.1"
965 unordered-array-remove "^1.0.2" 910 unordered-array-remove "^1.0.2"
966 ws "^6.0.0" 911 ws "^6.0.0"
967 xtend "^4.0.0"
968 optionalDependencies: 912 optionalDependencies:
969 bufferutil "^4.0.0" 913 bufferutil "^4.0.0"
970 utf-8-validate "^5.0.1" 914 utf-8-validate "^5.0.1"
@@ -1001,13 +945,6 @@ block-stream2@^1.0.0:
1001 inherits "^2.0.1" 945 inherits "^2.0.1"
1002 readable-stream "^2.0.4" 946 readable-stream "^2.0.4"
1003 947
1004block-stream@*:
1005 version "0.0.9"
1006 resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
1007 integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
1008 dependencies:
1009 inherits "~2.0.0"
1010
1011bluebird@3.5.0: 948bluebird@3.5.0:
1012 version "3.5.0" 949 version "3.5.0"
1013 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" 950 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
@@ -1018,10 +955,10 @@ bluebird@^2.10.0:
1018 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" 955 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
1019 integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= 956 integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=
1020 957
1021bluebird@^3.0.5, bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.3: 958bluebird@^3.0.5, bluebird@^3.5.0, bluebird@^3.5.3:
1022 version "3.5.3" 959 version "3.5.5"
1023 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" 960 resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
1024 integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== 961 integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
1025 962
1026bn.js@=2.0.4: 963bn.js@=2.0.4:
1027 version "2.0.4" 964 version "2.0.4"
@@ -1038,21 +975,21 @@ bn.js@^4.4.0:
1038 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" 975 resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
1039 integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== 976 integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==
1040 977
1041body-parser@1.18.3, body-parser@^1.12.4: 978body-parser@1.19.0, body-parser@^1.12.4:
1042 version "1.18.3" 979 version "1.19.0"
1043 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" 980 resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
1044 integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= 981 integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
1045 dependencies: 982 dependencies:
1046 bytes "3.0.0" 983 bytes "3.1.0"
1047 content-type "~1.0.4" 984 content-type "~1.0.4"
1048 debug "2.6.9" 985 debug "2.6.9"
1049 depd "~1.1.2" 986 depd "~1.1.2"
1050 http-errors "~1.6.3" 987 http-errors "1.7.2"
1051 iconv-lite "0.4.23" 988 iconv-lite "0.4.24"
1052 on-finished "~2.3.0" 989 on-finished "~2.3.0"
1053 qs "6.5.2" 990 qs "6.7.0"
1054 raw-body "2.3.3" 991 raw-body "2.4.0"
1055 type-is "~1.6.16" 992 type-is "~1.6.17"
1056 993
1057boxen@^1.2.1: 994boxen@^1.2.1:
1058 version "1.3.0" 995 version "1.3.0"
@@ -1166,24 +1103,19 @@ builtin-modules@^1.1.1:
1166 resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 1103 resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
1167 integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= 1104 integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
1168 1105
1169builtins@^1.0.3:
1170 version "1.0.3"
1171 resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88"
1172 integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=
1173
1174bull@^3.4.2: 1106bull@^3.4.2:
1175 version "3.7.0" 1107 version "3.10.0"
1176 resolved "https://registry.yarnpkg.com/bull/-/bull-3.7.0.tgz#ec9a8721a2cfb0421c501d28553ac1f9f025414d" 1108 resolved "https://registry.yarnpkg.com/bull/-/bull-3.10.0.tgz#5879b1201d0ed0c987fa1f42114d3dc5abf3da68"
1177 integrity sha512-DHCALp+OOahK+q2hB3sZQew0CJn4W3zYIQsdMlnBCy7JYCnJ/bdj0MFHjo5k0ZhNZxzwhLErXt1yd3llV494UQ== 1109 integrity sha512-LbQsc7c+eYd7IaJD7tS373yKLYttjTfoPZ+9xYYlPM5+gutAjofSTsESOGGyaxyX2lE1dkg+eWhUK5kAPl5Zow==
1178 dependencies: 1110 dependencies:
1179 cron-parser "^2.7.3" 1111 cron-parser "^2.7.3"
1180 debuglog "^1.0.0" 1112 debuglog "^1.0.0"
1181 get-port latest 1113 get-port "^5.0.0"
1182 ioredis "^4.5.1" 1114 ioredis "^4.5.1"
1183 lodash "^4.17.11" 1115 lodash "^4.17.11"
1184 p-timeout "^2.0.1" 1116 p-timeout "^3.1.0"
1185 promise.prototype.finally "^3.1.0" 1117 promise.prototype.finally "^3.1.0"
1186 semver "^5.6.0" 1118 semver "^6.1.1"
1187 util.promisify "^1.0.0" 1119 util.promisify "^1.0.0"
1188 uuid "^3.2.1" 1120 uuid "^3.2.1"
1189 1121
@@ -1195,46 +1127,11 @@ busboy@^0.2.11:
1195 dicer "0.2.5" 1127 dicer "0.2.5"
1196 readable-stream "1.1.x" 1128 readable-stream "1.1.x"
1197 1129
1198byline@^5.0.0: 1130bytes@3.1.0, bytes@^3.0.0:
1199 version "5.0.0"
1200 resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1"
1201 integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=
1202
1203byte-size@^5.0.1:
1204 version "5.0.1"
1205 resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-5.0.1.tgz#4b651039a5ecd96767e71a3d7ed380e48bed4191"
1206 integrity sha512-/XuKeqWocKsYa/cBY1YbSJSWWqTi4cFgr9S6OyM7PBaPbr9zvNGwWP33vt0uqGhwDdN+y3yhbXVILEUpnwEWGw==
1207
1208bytes@3.0.0:
1209 version "3.0.0"
1210 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
1211 integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
1212
1213bytes@^3.0.0:
1214 version "3.1.0" 1131 version "3.1.0"
1215 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 1132 resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
1216 integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 1133 integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
1217 1134
1218cacache@^11.0.1, cacache@^11.3.2:
1219 version "11.3.2"
1220 resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
1221 integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
1222 dependencies:
1223 bluebird "^3.5.3"
1224 chownr "^1.1.1"
1225 figgy-pudding "^3.5.1"
1226 glob "^7.1.3"
1227 graceful-fs "^4.1.15"
1228 lru-cache "^5.1.1"
1229 mississippi "^3.0.0"
1230 mkdirp "^0.5.1"
1231 move-concurrently "^1.0.1"
1232 promise-inflight "^1.0.1"
1233 rimraf "^2.6.2"
1234 ssri "^6.0.1"
1235 unique-filename "^1.1.1"
1236 y18n "^4.0.0"
1237
1238cache-base@^1.0.1: 1135cache-base@^1.0.1:
1239 version "1.0.1" 1136 version "1.0.1"
1240 resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" 1137 resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
@@ -1250,11 +1147,6 @@ cache-base@^1.0.1:
1250 union-value "^1.0.0" 1147 union-value "^1.0.0"
1251 unset-value "^1.0.0" 1148 unset-value "^1.0.0"
1252 1149
1253call-limit@~1.1.0:
1254 version "1.1.0"
1255 resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.0.tgz#6fd61b03f3da42a2cd0ec2b60f02bd0e71991fea"
1256 integrity sha1-b9YbA/PaQqLNDsK2DwK9DnGZH+o=
1257
1258call-me-maybe@^1.0.1: 1150call-me-maybe@^1.0.1:
1259 version "1.0.1" 1151 version "1.0.1"
1260 resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" 1152 resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -1302,9 +1194,9 @@ camelcase@^4.0.0, camelcase@^4.1.0:
1302 integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= 1194 integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
1303 1195
1304camelcase@^5.0.0: 1196camelcase@^5.0.0:
1305 version "5.3.0" 1197 version "5.3.1"
1306 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.0.tgz#0a110882cbeba41f72f99fcf918f4a0a92a13ebf" 1198 resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
1307 integrity sha512-Y05ICatFYPAfykDIB7VdwSJ0LUl1yq/BwO2OpyGGLjiRe1fgzTwVypPiWnzkGFOVFHXrCXUNBl86bpjBhZWSJg== 1199 integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
1308 1200
1309camelize@1.0.0: 1201camelize@1.0.0:
1310 version "1.0.0" 1202 version "1.0.0"
@@ -1322,12 +1214,12 @@ caseless@~0.12.0:
1322 integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= 1214 integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
1323 1215
1324chai-json-schema@^1.5.0: 1216chai-json-schema@^1.5.0:
1325 version "1.5.0" 1217 version "1.5.1"
1326 resolved "https://registry.yarnpkg.com/chai-json-schema/-/chai-json-schema-1.5.0.tgz#6960719e40f71fd5b377c9282e5c9a46799474f6" 1218 resolved "https://registry.yarnpkg.com/chai-json-schema/-/chai-json-schema-1.5.1.tgz#d9ae4c8f8c6e24ff4d402ceddfaa865d1ca107f4"
1327 integrity sha1-aWBxnkD3H9Wzd8koLlyaRnmUdPY= 1219 integrity sha512-TR/xPDxRhqwFFCWg1HgL8nNWbpNfUwaib6pBN++QKpnd0t+o3+MBvAn5CM1mpdUMaM76oJAtUjGKdjGad01lIA==
1328 dependencies: 1220 dependencies:
1329 jsonpointer.js "0.4.0" 1221 jsonpointer.js "0.4.0"
1330 tv4 "~1.2.7" 1222 tv4 "^1.3.0"
1331 1223
1332chai-xml@^0.3.2: 1224chai-xml@^0.3.2:
1333 version "0.3.2" 1225 version "0.3.2"
@@ -1368,7 +1260,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
1368 strip-ansi "^3.0.0" 1260 strip-ansi "^3.0.0"
1369 supports-color "^2.0.0" 1261 supports-color "^2.0.0"
1370 1262
1371chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: 1263chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2:
1372 version "2.4.2" 1264 version "2.4.2"
1373 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 1265 resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
1374 integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 1266 integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1392,10 +1284,10 @@ check-error@^1.0.2:
1392 resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" 1284 resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
1393 integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 1285 integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
1394 1286
1395chokidar@^2.1.0: 1287chokidar@^2.1.5:
1396 version "2.1.5" 1288 version "2.1.6"
1397 resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.5.tgz#0ae8434d962281a5f56c72869e79cb6d9d86ad4d" 1289 resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
1398 integrity sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A== 1290 integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
1399 dependencies: 1291 dependencies:
1400 anymatch "^2.0.0" 1292 anymatch "^2.0.0"
1401 async-each "^1.0.1" 1293 async-each "^1.0.1"
@@ -1446,6 +1338,11 @@ circular-json@^0.3.1:
1446 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" 1338 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
1447 integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== 1339 integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
1448 1340
1341circular-json@^0.5.9:
1342 version "0.5.9"
1343 resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d"
1344 integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ==
1345
1449class-utils@^0.3.5: 1346class-utils@^0.3.5:
1450 version "0.3.6" 1347 version "0.3.6"
1451 resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" 1348 resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -1461,14 +1358,6 @@ cli-boxes@^1.0.0:
1461 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" 1358 resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
1462 integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM= 1359 integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
1463 1360
1464cli-columns@^3.1.2:
1465 version "3.1.2"
1466 resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-3.1.2.tgz#6732d972979efc2ae444a1f08e08fa139c96a18e"
1467 integrity sha1-ZzLZcpee/CrkRKHwjgj6E5yWoY4=
1468 dependencies:
1469 string-width "^2.0.0"
1470 strip-ansi "^3.0.1"
1471
1472cli-cursor@^1.0.1: 1361cli-cursor@^1.0.1:
1473 version "1.0.2" 1362 version "1.0.2"
1474 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" 1363 resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
@@ -1483,23 +1372,6 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0:
1483 dependencies: 1372 dependencies:
1484 restore-cursor "^2.0.0" 1373 restore-cursor "^2.0.0"
1485 1374
1486cli-table3@^0.5.0, cli-table3@^0.5.1:
1487 version "0.5.1"
1488 resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
1489 integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
1490 dependencies:
1491 object-assign "^4.1.0"
1492 string-width "^2.1.1"
1493 optionalDependencies:
1494 colors "^1.1.2"
1495
1496cli-table@^0.3.1:
1497 version "0.3.1"
1498 resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23"
1499 integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM=
1500 dependencies:
1501 colors "1.0.3"
1502
1503cli-truncate@^0.2.1: 1375cli-truncate@^0.2.1:
1504 version "0.2.1" 1376 version "0.2.1"
1505 resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" 1377 resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
@@ -1522,10 +1394,14 @@ cliui@^4.0.0:
1522 strip-ansi "^4.0.0" 1394 strip-ansi "^4.0.0"
1523 wrap-ansi "^2.0.0" 1395 wrap-ansi "^2.0.0"
1524 1396
1525clone@^1.0.2: 1397cliui@^5.0.0:
1526 version "1.0.4" 1398 version "5.0.0"
1527 resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" 1399 resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
1528 integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= 1400 integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
1401 dependencies:
1402 string-width "^3.1.0"
1403 strip-ansi "^5.2.0"
1404 wrap-ansi "^5.1.0"
1529 1405
1530closest-to@~2.0.0: 1406closest-to@~2.0.0:
1531 version "2.0.0" 1407 version "2.0.0"
@@ -1545,14 +1421,6 @@ cluster-key-slot@^1.0.6:
1545 resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29" 1421 resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.12.tgz#d5deff2a520717bc98313979b687309b2d368e29"
1546 integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg== 1422 integrity sha512-21O0kGmvED5OJ7ZTdqQ5lQQ+sjuez33R+d35jZKLwqUb5mqcPHUsxOSzj61+LHVtxGZd1kShbQM3MjB/gBJkVg==
1547 1423
1548cmd-shim@^2.0.2, cmd-shim@~2.0.2:
1549 version "2.0.2"
1550 resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb"
1551 integrity sha1-b8vamUg6j9FdfTChlspp1oii79s=
1552 dependencies:
1553 graceful-fs "^4.1.2"
1554 mkdirp "~0.5.0"
1555
1556co-bluebird@^1.1.0: 1424co-bluebird@^1.1.0:
1557 version "1.1.0" 1425 version "1.1.0"
1558 resolved "https://registry.yarnpkg.com/co-bluebird/-/co-bluebird-1.1.0.tgz#c8b9f3a9320a7ed30987dcca1a5c3cff59655c7c" 1426 resolved "https://registry.yarnpkg.com/co-bluebird/-/co-bluebird-1.1.0.tgz#c8b9f3a9320a7ed30987dcca1a5c3cff59655c7c"
@@ -1617,10 +1485,10 @@ color@3.0.x:
1617 color-convert "^1.9.1" 1485 color-convert "^1.9.1"
1618 color-string "^1.5.2" 1486 color-string "^1.5.2"
1619 1487
1620color@^3.1.0: 1488color@^3.1.1:
1621 version "3.1.0" 1489 version "3.1.2"
1622 resolved "https://registry.yarnpkg.com/color/-/color-3.1.0.tgz#d8e9fb096732875774c84bf922815df0308d0ffc" 1490 resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10"
1623 integrity sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg== 1491 integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==
1624 dependencies: 1492 dependencies:
1625 color-convert "^1.9.1" 1493 color-convert "^1.9.1"
1626 color-string "^1.5.2" 1494 color-string "^1.5.2"
@@ -1630,7 +1498,7 @@ colornames@^1.1.1:
1630 resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" 1498 resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96"
1631 integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= 1499 integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=
1632 1500
1633colors@1.0.3, colors@1.0.x: 1501colors@1.0.x:
1634 version "1.0.3" 1502 version "1.0.3"
1635 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" 1503 resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
1636 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= 1504 integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
@@ -1641,25 +1509,17 @@ colors@^1.1.2, colors@^1.2.1:
1641 integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== 1509 integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
1642 1510
1643colorspace@1.1.x: 1511colorspace@1.1.x:
1644 version "1.1.1" 1512 version "1.1.2"
1645 resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.1.tgz#9ac2491e1bc6f8fb690e2176814f8d091636d972" 1513 resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5"
1646 integrity sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw== 1514 integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==
1647 dependencies: 1515 dependencies:
1648 color "3.0.x" 1516 color "3.0.x"
1649 text-hex "1.0.x" 1517 text-hex "1.0.x"
1650 1518
1651columnify@~1.5.4:
1652 version "1.5.4"
1653 resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
1654 integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=
1655 dependencies:
1656 strip-ansi "^3.0.0"
1657 wcwidth "^1.0.0"
1658
1659combined-stream@^1.0.6, combined-stream@~1.0.6: 1519combined-stream@^1.0.6, combined-stream@~1.0.6:
1660 version "1.0.7" 1520 version "1.0.8"
1661 resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" 1521 resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
1662 integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== 1522 integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
1663 dependencies: 1523 dependencies:
1664 delayed-stream "~1.0.0" 1524 delayed-stream "~1.0.0"
1665 1525
@@ -1671,9 +1531,9 @@ commander@2.9.0:
1671 graceful-readlink ">= 1.0.0" 1531 graceful-readlink ">= 1.0.0"
1672 1532
1673commander@^2.12.1, commander@^2.13.0, commander@^2.14.1, commander@^2.7.1, commander@^2.8.1, commander@^2.9.0: 1533commander@^2.12.1, commander@^2.13.0, commander@^2.14.1, commander@^2.7.1, commander@^2.8.1, commander@^2.9.0:
1674 version "2.19.0" 1534 version "2.20.0"
1675 resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" 1535 resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
1676 integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== 1536 integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
1677 1537
1678compact2string@^1.2.0: 1538compact2string@^1.2.0:
1679 version "1.4.1" 1539 version "1.4.1"
@@ -1692,11 +1552,16 @@ component-emitter@1.1.2:
1692 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" 1552 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3"
1693 integrity sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM= 1553 integrity sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=
1694 1554
1695component-emitter@1.2.1, component-emitter@^1.2.0, component-emitter@^1.2.1: 1555component-emitter@1.2.1:
1696 version "1.2.1" 1556 version "1.2.1"
1697 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" 1557 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
1698 integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= 1558 integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=
1699 1559
1560component-emitter@^1.2.0, component-emitter@^1.2.1:
1561 version "1.3.0"
1562 resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
1563 integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
1564
1700component-inherit@0.0.3: 1565component-inherit@0.0.3:
1701 version "0.0.3" 1566 version "0.0.3"
1702 resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" 1567 resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
@@ -1707,7 +1572,7 @@ concat-map@0.0.1:
1707 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 1572 resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
1708 integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 1573 integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
1709 1574
1710concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2: 1575concat-stream@^1.4.6, concat-stream@^1.5.2:
1711 version "1.6.2" 1576 version "1.6.2"
1712 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" 1577 resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
1713 integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== 1578 integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -1717,7 +1582,7 @@ concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2:
1717 readable-stream "^2.2.2" 1582 readable-stream "^2.2.2"
1718 typedarray "^0.0.6" 1583 typedarray "^0.0.6"
1719 1584
1720concurrently@^4.0.1: 1585concurrently@^4.1.0:
1721 version "4.1.0" 1586 version "4.1.0"
1722 resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.1.0.tgz#17fdf067da71210685d9ea554423ef239da30d33" 1587 resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.1.0.tgz#17fdf067da71210685d9ea554423ef239da30d33"
1723 integrity sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg== 1588 integrity sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==
@@ -1732,18 +1597,10 @@ concurrently@^4.0.1:
1732 tree-kill "^1.1.0" 1597 tree-kill "^1.1.0"
1733 yargs "^12.0.1" 1598 yargs "^12.0.1"
1734 1599
1735config-chain@^1.1.12:
1736 version "1.1.12"
1737 resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
1738 integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==
1739 dependencies:
1740 ini "^1.3.4"
1741 proto-list "~1.2.1"
1742
1743config@^3.0.0: 1600config@^3.0.0:
1744 version "3.0.1" 1601 version "3.1.0"
1745 resolved "https://registry.yarnpkg.com/config/-/config-3.0.1.tgz#c3118e2eb45fdfd135277339f87e2492559cb147" 1602 resolved "https://registry.yarnpkg.com/config/-/config-3.1.0.tgz#c7d49c4171e8f6cb61c1d69e22647ffd60492548"
1746 integrity sha512-TBNrrk2b6AybUohqXw2AydglFBL9b/+1GG93Di6Fm6x1SyVJ5PYgo+mqY2X0KpU9m0PJDSbFaC5H95utSphtLw== 1603 integrity sha512-t6oDeNQbsIWa+D/KF4959TANzjSHLv1BA/hvL8tHEA3OUSWgBXELKaONSI6nr9oanbKs0DXonjOWLcrtZ3yTAA==
1747 dependencies: 1604 dependencies:
1748 json5 "^1.0.1" 1605 json5 "^1.0.1"
1749 1606
@@ -1759,7 +1616,7 @@ configstore@^3.0.0:
1759 write-file-atomic "^2.0.0" 1616 write-file-atomic "^2.0.0"
1760 xdg-basedir "^3.0.0" 1617 xdg-basedir "^3.0.0"
1761 1618
1762console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: 1619console-control-strings@^1.0.0, console-control-strings@~1.1.0:
1763 version "1.1.0" 1620 version "1.1.0"
1764 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 1621 resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
1765 integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= 1622 integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
@@ -1769,10 +1626,12 @@ content-disposition@0.5.1:
1769 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b" 1626 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.1.tgz#87476c6a67c8daa87e32e87616df883ba7fb071b"
1770 integrity sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs= 1627 integrity sha1-h0dsamfI2qh+Muh2Ft+IO6f7Bxs=
1771 1628
1772content-disposition@0.5.2: 1629content-disposition@0.5.3:
1773 version "0.5.2" 1630 version "0.5.3"
1774 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 1631 resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
1775 integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= 1632 integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
1633 dependencies:
1634 safe-buffer "5.1.2"
1776 1635
1777content-security-policy-builder@2.0.0: 1636content-security-policy-builder@2.0.0:
1778 version "2.0.0" 1637 version "2.0.0"
@@ -1807,32 +1666,25 @@ cookie@0.3.1:
1807 resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 1666 resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
1808 integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= 1667 integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
1809 1668
1669cookie@0.4.0:
1670 version "0.4.0"
1671 resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
1672 integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
1673
1810cookiejar@^2.1.0: 1674cookiejar@^2.1.0:
1811 version "2.1.2" 1675 version "2.1.2"
1812 resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" 1676 resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
1813 integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA== 1677 integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
1814 1678
1815copy-concurrently@^1.0.0:
1816 version "1.0.5"
1817 resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0"
1818 integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==
1819 dependencies:
1820 aproba "^1.1.1"
1821 fs-write-stream-atomic "^1.0.8"
1822 iferr "^0.1.5"
1823 mkdirp "^0.5.1"
1824 rimraf "^2.5.4"
1825 run-queue "^1.0.0"
1826
1827copy-descriptor@^0.1.0: 1679copy-descriptor@^0.1.0:
1828 version "0.1.1" 1680 version "0.1.1"
1829 resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" 1681 resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
1830 integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= 1682 integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
1831 1683
1832core-js@^2.5.7: 1684core-js@^2.5.7:
1833 version "2.6.5" 1685 version "2.6.9"
1834 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" 1686 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
1835 integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== 1687 integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
1836 1688
1837core-util-is@1.0.2, core-util-is@~1.0.0: 1689core-util-is@1.0.2, core-util-is@~1.0.0:
1838 version "1.0.2" 1690 version "1.0.2"
@@ -1847,14 +1699,14 @@ cors@^2.8.1:
1847 object-assign "^4" 1699 object-assign "^4"
1848 vary "^1" 1700 vary "^1"
1849 1701
1850cosmiconfig@^5.0.2, cosmiconfig@^5.0.7: 1702cosmiconfig@^5.2.0:
1851 version "5.2.0" 1703 version "5.2.1"
1852 resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.0.tgz#45038e4d28a7fe787203aede9c25bca4a08b12c8" 1704 resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
1853 integrity sha512-nxt+Nfc3JAqf4WIWd0jXLjTJZmsPLrA9DDc4nRw2KFJQJK7DNooqSXrNI7tzLG50CF8axczly5UV929tBmh/7g== 1705 integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
1854 dependencies: 1706 dependencies:
1855 import-fresh "^2.0.0" 1707 import-fresh "^2.0.0"
1856 is-directory "^0.3.1" 1708 is-directory "^0.3.1"
1857 js-yaml "^3.13.0" 1709 js-yaml "^3.13.1"
1858 parse-json "^4.0.0" 1710 parse-json "^4.0.0"
1859 1711
1860create-error-class@^3.0.0: 1712create-error-class@^3.0.0:
@@ -1884,9 +1736,9 @@ create-torrent@^3.24.5, create-torrent@^3.33.0:
1884 simple-sha1 "^2.0.0" 1736 simple-sha1 "^2.0.0"
1885 1737
1886cron-parser@^2.7.3: 1738cron-parser@^2.7.3:
1887 version "2.10.0" 1739 version "2.11.0"
1888 resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.10.0.tgz#d6bed39cfa6163930f89b1ade82df7b46f52b82b" 1740 resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.11.0.tgz#c3bf477e01de6a56938d6625b92efd6cec30a8a5"
1889 integrity sha512-E181Gbg+wYT0hSikwBOokL7VHgJDYUlFsRFHIlnTP8GGefhcIyf8PSc2IXztmghj5mhAZupU0n3jKfEpZVEmVg== 1741 integrity sha512-L5LAGlvq2xmCLErhjQRX8IL5v72y8jhGOaxrarYOhse0kJjJGb/vY/0sV/c7F/SylJGkUIY2iZPPJXZD3glZqA==
1890 dependencies: 1742 dependencies:
1891 is-nan "^1.2.1" 1743 is-nan "^1.2.1"
1892 moment-timezone "^0.5.23" 1744 moment-timezone "^0.5.23"
@@ -1926,11 +1778,6 @@ cycle@1.0.x:
1926 resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" 1778 resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
1927 integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= 1779 integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
1928 1780
1929cyclist@~0.2.2:
1930 version "0.2.2"
1931 resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
1932 integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
1933
1934d@1: 1781d@1:
1935 version "1.0.0" 1782 version "1.0.0"
1936 resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" 1783 resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
@@ -1955,14 +1802,6 @@ date-fns@^1.23.0, date-fns@^1.27.2:
1955 resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" 1802 resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
1956 integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== 1803 integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
1957 1804
1958deasync@^0.1.4:
1959 version "0.1.14"
1960 resolved "https://registry.yarnpkg.com/deasync/-/deasync-0.1.14.tgz#232ea2252b443948cad033d792eb3b24b0a3d828"
1961 integrity sha512-wN8sIuEqIwyQh72AG7oY6YQODCxIp1eXzEZlZznBuwDF8Q03Tdy9QNp1BNZXeadXoklNrw+Ip1fch+KXo/+ASw==
1962 dependencies:
1963 bindings "~1.2.1"
1964 node-addon-api "^1.6.0"
1965
1966debug@2.2.0, debug@~2.2.0: 1805debug@2.2.0, debug@~2.2.0:
1967 version "2.2.0" 1806 version "2.2.0"
1968 resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 1807 resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
@@ -1977,21 +1816,14 @@ debug@2.3.3:
1977 dependencies: 1816 dependencies:
1978 ms "0.7.2" 1817 ms "0.7.2"
1979 1818
1980debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: 1819debug@2.6.9, debug@^2.1.1, debug@^2.2.0, debug@^2.3.3:
1981 version "2.6.9" 1820 version "2.6.9"
1982 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 1821 resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
1983 integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 1822 integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
1984 dependencies: 1823 dependencies:
1985 ms "2.0.0" 1824 ms "2.0.0"
1986 1825
1987debug@3.1.0, debug@~3.1.0: 1826debug@3.2.6, debug@^3.1.0, debug@^3.2.6:
1988 version "3.1.0"
1989 resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
1990 integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
1991 dependencies:
1992 ms "2.0.0"
1993
1994debug@3.2.6, debug@^3.1.0:
1995 version "3.2.6" 1827 version "3.2.6"
1996 resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 1828 resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
1997 integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 1829 integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -2005,12 +1837,19 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
2005 dependencies: 1837 dependencies:
2006 ms "^2.1.1" 1838 ms "^2.1.1"
2007 1839
2008debuglog@^1.0.0, debuglog@^1.0.1: 1840debug@~3.1.0:
1841 version "3.1.0"
1842 resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
1843 integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
1844 dependencies:
1845 ms "2.0.0"
1846
1847debuglog@^1.0.0:
2009 version "1.0.1" 1848 version "1.0.1"
2010 resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" 1849 resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
2011 integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= 1850 integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
2012 1851
2013decamelize@^1.1.1, decamelize@^1.2.0: 1852decamelize@^1.2.0:
2014 version "1.2.0" 1853 version "1.2.0"
2015 resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 1854 resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
2016 integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 1855 integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -2066,13 +1905,6 @@ deep-object-diff@^1.1.0:
2066 resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" 1905 resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a"
2067 integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== 1906 integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw==
2068 1907
2069defaults@^1.0.3:
2070 version "1.0.3"
2071 resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d"
2072 integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=
2073 dependencies:
2074 clone "^1.0.2"
2075
2076define-properties@^1.1.1, define-properties@^1.1.2: 1908define-properties@^1.1.1, define-properties@^1.1.2:
2077 version "1.1.3" 1909 version "1.1.3"
2078 resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 1910 resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@@ -2130,9 +1962,9 @@ delegates@^1.0.0:
2130 integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 1962 integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
2131 1963
2132denque@^1.1.0: 1964denque@^1.1.0:
2133 version "1.4.0" 1965 version "1.4.1"
2134 resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" 1966 resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
2135 integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== 1967 integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
2136 1968
2137depd@2.0.0: 1969depd@2.0.0:
2138 version "2.0.0" 1970 version "2.0.0"
@@ -2144,46 +1976,16 @@ depd@~1.1.0, depd@~1.1.2:
2144 resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 1976 resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
2145 integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 1977 integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
2146 1978
2147descrevit@^0.1.1:
2148 version "0.1.1"
2149 resolved "https://registry.yarnpkg.com/descrevit/-/descrevit-0.1.1.tgz#c0f5840de0a0f7b1b8b4078569b173327947d5da"
2150 integrity sha1-wPWEDeCg97G4tAeFabFzMnlH1do=
2151 dependencies:
2152 deasync "^0.1.4"
2153
2154destroy@~1.0.4: 1979destroy@~1.0.4:
2155 version "1.0.4" 1980 version "1.0.4"
2156 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 1981 resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
2157 integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 1982 integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
2158 1983
2159detect-file@^1.0.0:
2160 version "1.0.0"
2161 resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
2162 integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
2163
2164detect-indent@~5.0.0:
2165 version "5.0.0"
2166 resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
2167 integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
2168
2169detect-libc@^1.0.2, detect-libc@^1.0.3: 1984detect-libc@^1.0.2, detect-libc@^1.0.3:
2170 version "1.0.3" 1985 version "1.0.3"
2171 resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 1986 resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
2172 integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= 1987 integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
2173 1988
2174detect-newline@^2.1.0:
2175 version "2.1.0"
2176 resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
2177 integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=
2178
2179dezalgo@^1.0.0, dezalgo@~1.0.3:
2180 version "1.0.3"
2181 resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456"
2182 integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=
2183 dependencies:
2184 asap "^2.0.0"
2185 wrappy "1"
2186
2187diagnostics@^1.1.1: 1989diagnostics@^1.1.1:
2188 version "1.1.1" 1990 version "1.1.1"
2189 resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" 1991 resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a"
@@ -2201,21 +2003,21 @@ dicer@0.2.5:
2201 readable-stream "1.1.x" 2003 readable-stream "1.1.x"
2202 streamsearch "0.1.2" 2004 streamsearch "0.1.2"
2203 2005
2204diff@3.5.0, diff@^3.1.0, diff@^3.2.0: 2006diff@3.5.0, diff@^3.2.0:
2205 version "3.5.0" 2007 version "3.5.0"
2206 resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 2008 resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
2207 integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 2009 integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
2208 2010
2011diff@^4.0.1:
2012 version "4.0.1"
2013 resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
2014 integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
2015
2209dns-prefetch-control@0.1.0: 2016dns-prefetch-control@0.1.0:
2210 version "0.1.0" 2017 version "0.1.0"
2211 resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2" 2018 resolved "https://registry.yarnpkg.com/dns-prefetch-control/-/dns-prefetch-control-0.1.0.tgz#60ddb457774e178f1f9415f0cabb0e85b0b300b2"
2212 integrity sha1-YN20V3dOF48flBXwyrsOhbCzALI= 2019 integrity sha1-YN20V3dOF48flBXwyrsOhbCzALI=
2213 2020
2214docopt@~0.6.2:
2215 version "0.6.2"
2216 resolved "https://registry.yarnpkg.com/docopt/-/docopt-0.6.2.tgz#b28e9e2220da5ec49f7ea5bb24a47787405eeb11"
2217 integrity sha1-so6eIiDaXsSffqW7JKR3h0Be6xE=
2218
2219doctrine@0.7.2: 2021doctrine@0.7.2:
2220 version "0.7.2" 2022 version "0.7.2"
2221 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" 2023 resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523"
@@ -2237,14 +2039,6 @@ dont-sniff-mimetype@1.0.0:
2237 resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58" 2039 resolved "https://registry.yarnpkg.com/dont-sniff-mimetype/-/dont-sniff-mimetype-1.0.0.tgz#5932890dc9f4e2f19e5eb02a20026e5e5efc8f58"
2238 integrity sha1-WTKJDcn04vGeXrAqIAJuXl78j1g= 2040 integrity sha1-WTKJDcn04vGeXrAqIAJuXl78j1g=
2239 2041
2240dot-json@^1.0.3:
2241 version "1.1.0"
2242 resolved "https://registry.yarnpkg.com/dot-json/-/dot-json-1.1.0.tgz#36dc7d40c00afaef2823b715b94325ba39f1bd89"
2243 integrity sha512-PiQZW9/C8xILPYK2bOye/cbPZrakNEkt28jFb8RlPCwsoMAHYYw9T8JoACxgttHL9Y2AmdqVvibbZJHtLgeqTQ==
2244 dependencies:
2245 docopt "~0.6.2"
2246 underscore-keypath "~0.0.22"
2247
2248dot-prop@^4.1.0: 2042dot-prop@^4.1.0:
2249 version "4.2.0" 2043 version "4.2.0"
2250 resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" 2044 resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
@@ -2252,11 +2046,6 @@ dot-prop@^4.1.0:
2252 dependencies: 2046 dependencies:
2253 is-obj "^1.0.0" 2047 is-obj "^1.0.0"
2254 2048
2255dotenv@^5.0.1:
2256 version "5.0.1"
2257 resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef"
2258 integrity sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==
2259
2260dottie@^2.0.0: 2049dottie@^2.0.0:
2261 version "2.0.1" 2050 version "2.0.1"
2262 resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.1.tgz#697ad9d72004db7574d21f892466a3c285893659" 2051 resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.1.tgz#697ad9d72004db7574d21f892466a3c285893659"
@@ -2272,7 +2061,7 @@ duplexer3@^0.1.4:
2272 resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" 2061 resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
2273 integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= 2062 integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
2274 2063
2275duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0: 2064duplexify@^3.2.0, duplexify@^3.5.0, duplexify@^3.6.0:
2276 version "3.7.1" 2065 version "3.7.1"
2277 resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" 2066 resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
2278 integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== 2067 integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
@@ -2297,11 +2086,6 @@ ecdsa-sig-formatter@1.0.11:
2297 dependencies: 2086 dependencies:
2298 safe-buffer "^5.0.1" 2087 safe-buffer "^5.0.1"
2299 2088
2300editor@~1.0.0:
2301 version "1.0.0"
2302 resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742"
2303 integrity sha1-YMf4e9YrzGqJT6jM1q+3gjok90I=
2304
2305ee-first@1.1.1: 2089ee-first@1.1.1:
2306 version "1.1.1" 2090 version "1.1.1"
2307 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 2091 resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -2322,6 +2106,11 @@ elliptic@=3.0.3:
2322 hash.js "^1.0.0" 2106 hash.js "^1.0.0"
2323 inherits "^2.0.1" 2107 inherits "^2.0.1"
2324 2108
2109emoji-regex@^7.0.1:
2110 version "7.0.3"
2111 resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
2112 integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
2113
2325enabled@1.0.x: 2114enabled@1.0.x:
2326 version "1.0.2" 2115 version "1.0.2"
2327 resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" 2116 resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93"
@@ -2334,7 +2123,7 @@ encodeurl@~1.0.2:
2334 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 2123 resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
2335 integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 2124 integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
2336 2125
2337encoding@^0.1.11, encoding@^0.1.12, encoding@~0.1.12: 2126encoding@^0.1.12, encoding@~0.1.12:
2338 version "0.1.12" 2127 version "0.1.12"
2339 resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 2128 resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
2340 integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= 2129 integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
@@ -2435,18 +2224,6 @@ env-variable@0.0.x:
2435 resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" 2224 resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88"
2436 integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== 2225 integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==
2437 2226
2438err-code@^1.0.0:
2439 version "1.1.2"
2440 resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
2441 integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=
2442
2443errno@~0.1.7:
2444 version "0.1.7"
2445 resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
2446 integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
2447 dependencies:
2448 prr "~1.0.1"
2449
2450error-ex@^1.3.1: 2227error-ex@^1.3.1:
2451 version "1.3.2" 2228 version "1.3.2"
2452 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 2229 resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -2476,9 +2253,9 @@ es-to-primitive@^1.2.0:
2476 is-symbol "^1.0.2" 2253 is-symbol "^1.0.2"
2477 2254
2478es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: 2255es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46:
2479 version "0.10.49" 2256 version "0.10.50"
2480 resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.49.tgz#059a239de862c94494fec28f8150c977028c6c5e" 2257 resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
2481 integrity sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg== 2258 integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==
2482 dependencies: 2259 dependencies:
2483 es6-iterator "~2.0.3" 2260 es6-iterator "~2.0.3"
2484 es6-symbol "~3.1.1" 2261 es6-symbol "~3.1.1"
@@ -2505,18 +2282,6 @@ es6-map@^0.1.3:
2505 es6-symbol "~3.1.1" 2282 es6-symbol "~3.1.1"
2506 event-emitter "~0.3.5" 2283 event-emitter "~0.3.5"
2507 2284
2508es6-promise@^4.0.3:
2509 version "4.2.6"
2510 resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f"
2511 integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==
2512
2513es6-promisify@^5.0.0:
2514 version "5.0.0"
2515 resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
2516 integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
2517 dependencies:
2518 es6-promise "^4.0.3"
2519
2520es6-promisify@^6.0.0: 2285es6-promisify@^6.0.0:
2521 version "6.0.1" 2286 version "6.0.1"
2522 resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.1.tgz#6edaa45f3bd570ffe08febce66f7116be4b1cdb6" 2287 resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-6.0.1.tgz#6edaa45f3bd570ffe08febce66f7116be4b1cdb6"
@@ -2663,19 +2428,6 @@ event-emitter@^0.3.5, event-emitter@~0.3.5:
2663 d "1" 2428 d "1"
2664 es5-ext "~0.10.14" 2429 es5-ext "~0.10.14"
2665 2430
2666execa@^0.10.0:
2667 version "0.10.0"
2668 resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50"
2669 integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==
2670 dependencies:
2671 cross-spawn "^6.0.0"
2672 get-stream "^3.0.0"
2673 is-stream "^1.1.0"
2674 npm-run-path "^2.0.0"
2675 p-finally "^1.0.0"
2676 signal-exit "^3.0.0"
2677 strip-eof "^1.0.0"
2678
2679execa@^0.7.0: 2431execa@^0.7.0:
2680 version "0.7.0" 2432 version "0.7.0"
2681 resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" 2433 resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
@@ -2725,17 +2477,10 @@ expand-template@^2.0.3:
2725 resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" 2477 resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
2726 integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== 2478 integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
2727 2479
2728expand-tilde@^2.0.0, expand-tilde@^2.0.2: 2480expect-ct@0.2.0:
2729 version "2.0.2" 2481 version "0.2.0"
2730 resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" 2482 resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.2.0.tgz#3a54741b6ed34cc7a93305c605f63cd268a54a62"
2731 integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= 2483 integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g==
2732 dependencies:
2733 homedir-polyfill "^1.0.1"
2734
2735expect-ct@0.1.1:
2736 version "0.1.1"
2737 resolved "https://registry.yarnpkg.com/expect-ct/-/expect-ct-0.1.1.tgz#de84476a2dbcb85000d5903737e9bc8a5ba7b897"
2738 integrity sha512-ngXzTfoRGG7fYens3/RMb6yYoVLvLMfmsSllP/mZPxNHgFq41TmPSLF/nLY7fwoclI2vElvAmILFWGUYqdjfCg==
2739 2484
2740express-oauth-server@^2.0.0: 2485express-oauth-server@^2.0.0:
2741 version "2.0.0" 2486 version "2.0.0"
@@ -2746,12 +2491,10 @@ express-oauth-server@^2.0.0:
2746 express "^4.13.3" 2491 express "^4.13.3"
2747 oauth2-server "3.0.0" 2492 oauth2-server "3.0.0"
2748 2493
2749express-rate-limit@^3.1.0: 2494express-rate-limit@^4.0.4:
2750 version "3.4.0" 2495 version "4.0.4"
2751 resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-3.4.0.tgz#c053ee294feac6d3529863de549438749fd83ef1" 2496 resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-4.0.4.tgz#a495338ae9e58c856b66d1346ec0d86f43ba2e43"
2752 integrity sha512-SktWQGHhTQfIOZykiVIaoqmHCptqq177fEbumVytWsMpEqe+g78IFrfzivJTimoCdMZ5+vYJ5/a/w1darXMv+A== 2497 integrity sha512-DLRj2vMO7Xgai8qWKU9O6ZztF2bdDmfFNFi9k3G9BPzJ+7MG7eWaaBikbe0eBpNGSxU8JziwW0PQKG78aNWa6g==
2753 dependencies:
2754 defaults "^1.0.3"
2755 2498
2756express-validator@^5.0.0: 2499express-validator@^5.0.0:
2757 version "5.3.1" 2500 version "5.3.1"
@@ -2793,38 +2536,38 @@ express@4.13.4:
2793 vary "~1.0.1" 2536 vary "~1.0.1"
2794 2537
2795express@^4.12.4, express@^4.13.3: 2538express@^4.12.4, express@^4.13.3:
2796 version "4.16.4" 2539 version "4.17.1"
2797 resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" 2540 resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
2798 integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== 2541 integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
2799 dependencies: 2542 dependencies:
2800 accepts "~1.3.5" 2543 accepts "~1.3.7"
2801 array-flatten "1.1.1" 2544 array-flatten "1.1.1"
2802 body-parser "1.18.3" 2545 body-parser "1.19.0"
2803 content-disposition "0.5.2" 2546 content-disposition "0.5.3"
2804 content-type "~1.0.4" 2547 content-type "~1.0.4"
2805 cookie "0.3.1" 2548 cookie "0.4.0"
2806 cookie-signature "1.0.6" 2549 cookie-signature "1.0.6"
2807 debug "2.6.9" 2550 debug "2.6.9"
2808 depd "~1.1.2" 2551 depd "~1.1.2"
2809 encodeurl "~1.0.2" 2552 encodeurl "~1.0.2"
2810 escape-html "~1.0.3" 2553 escape-html "~1.0.3"
2811 etag "~1.8.1" 2554 etag "~1.8.1"
2812 finalhandler "1.1.1" 2555 finalhandler "~1.1.2"
2813 fresh "0.5.2" 2556 fresh "0.5.2"
2814 merge-descriptors "1.0.1" 2557 merge-descriptors "1.0.1"
2815 methods "~1.1.2" 2558 methods "~1.1.2"
2816 on-finished "~2.3.0" 2559 on-finished "~2.3.0"
2817 parseurl "~1.3.2" 2560 parseurl "~1.3.3"
2818 path-to-regexp "0.1.7" 2561 path-to-regexp "0.1.7"
2819 proxy-addr "~2.0.4" 2562 proxy-addr "~2.0.5"
2820 qs "6.5.2" 2563 qs "6.7.0"
2821 range-parser "~1.2.0" 2564 range-parser "~1.2.1"
2822 safe-buffer "5.1.2" 2565 safe-buffer "5.1.2"
2823 send "0.16.2" 2566 send "0.17.1"
2824 serve-static "1.13.2" 2567 serve-static "1.14.1"
2825 setprototypeof "1.1.0" 2568 setprototypeof "1.1.1"
2826 statuses "~1.4.0" 2569 statuses "~1.5.0"
2827 type-is "~1.6.16" 2570 type-is "~1.6.18"
2828 utils-merge "1.0.1" 2571 utils-merge "1.0.1"
2829 vary "~1.1.2" 2572 vary "~1.1.2"
2830 2573
@@ -2897,21 +2640,16 @@ fast-safe-stringify@^2.0.4:
2897 resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" 2640 resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2"
2898 integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg== 2641 integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==
2899 2642
2900feature-policy@0.2.0: 2643feature-policy@0.3.0:
2901 version "0.2.0" 2644 version "0.3.0"
2902 resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.2.0.tgz#22096de49ab240176878ffe2bde2f6ff04d48c43" 2645 resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
2903 integrity sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw== 2646 integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==
2904 2647
2905fecha@^2.3.3: 2648fecha@^2.3.3:
2906 version "2.3.3" 2649 version "2.3.3"
2907 resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" 2650 resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd"
2908 integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== 2651 integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==
2909 2652
2910figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
2911 version "3.5.1"
2912 resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
2913 integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
2914
2915figures@^1.3.5, figures@^1.7.0: 2653figures@^1.3.5, figures@^1.7.0:
2916 version "1.7.0" 2654 version "1.7.0"
2917 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" 2655 resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -2970,52 +2708,32 @@ finalhandler@0.4.1:
2970 on-finished "~2.3.0" 2708 on-finished "~2.3.0"
2971 unpipe "~1.0.0" 2709 unpipe "~1.0.0"
2972 2710
2973finalhandler@1.1.1: 2711finalhandler@~1.1.2:
2974 version "1.1.1" 2712 version "1.1.2"
2975 resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 2713 resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
2976 integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== 2714 integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
2977 dependencies: 2715 dependencies:
2978 debug "2.6.9" 2716 debug "2.6.9"
2979 encodeurl "~1.0.2" 2717 encodeurl "~1.0.2"
2980 escape-html "~1.0.3" 2718 escape-html "~1.0.3"
2981 on-finished "~2.3.0" 2719 on-finished "~2.3.0"
2982 parseurl "~1.3.2" 2720 parseurl "~1.3.3"
2983 statuses "~1.4.0" 2721 statuses "~1.5.0"
2984 unpipe "~1.0.0" 2722 unpipe "~1.0.0"
2985 2723
2986find-npm-prefix@^1.0.2: 2724find-up@3.0.0, find-up@^3.0.0:
2987 version "1.0.2"
2988 resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf"
2989 integrity sha512-KEftzJ+H90x6pcKtdXZEPsQse8/y/UnvzRKrOSQFprnrGaFuJ62fVkP34Iu2IYuMvyauCyoLTNkJZgrrGA2wkA==
2990
2991find-parent-dir@^0.3.0:
2992 version "0.3.0"
2993 resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54"
2994 integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=
2995
2996find-up@^2.1.0:
2997 version "2.1.0"
2998 resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
2999 integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c=
3000 dependencies:
3001 locate-path "^2.0.0"
3002
3003find-up@^3.0.0:
3004 version "3.0.0" 2725 version "3.0.0"
3005 resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 2726 resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
3006 integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 2727 integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
3007 dependencies: 2728 dependencies:
3008 locate-path "^3.0.0" 2729 locate-path "^3.0.0"
3009 2730
3010findup-sync@2.0.0: 2731find-up@^4.0.0:
3011 version "2.0.0" 2732 version "4.0.0"
3012 resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" 2733 resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.0.0.tgz#c367f8024de92efb75f2d4906536d24682065c3a"
3013 integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= 2734 integrity sha512-zoH7ZWPkRdgwYCDVoQTzqjG8JSPANhtvLhh4KVUHyKnaUJJrNeFmWIkTcNuJmR3GLMEmGYEf2S2bjgx26JTF+Q==
3014 dependencies: 2735 dependencies:
3015 detect-file "^1.0.0" 2736 locate-path "^5.0.0"
3016 is-glob "^3.1.0"
3017 micromatch "^3.0.4"
3018 resolve-dir "^1.0.1"
3019 2737
3020flat-cache@^1.2.1: 2738flat-cache@^1.2.1:
3021 version "1.3.4" 2739 version "1.3.4"
@@ -3039,11 +2757,6 @@ flatten@^1.0.2:
3039 resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" 2757 resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
3040 integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I= 2758 integrity sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=
3041 2759
3042flexbuffer@0.0.6:
3043 version "0.0.6"
3044 resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30"
3045 integrity sha1-A5/fI/iCPkQMOPMnfm/vEXQhWzA=
3046
3047fluent-ffmpeg@^2.1.0: 2760fluent-ffmpeg@^2.1.0:
3048 version "2.1.2" 2761 version "2.1.2"
3049 resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz#c952de2240f812ebda0aa8006d7776ee2acf7d74" 2762 resolved "https://registry.yarnpkg.com/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz#c952de2240f812ebda0aa8006d7776ee2acf7d74"
@@ -3052,14 +2765,6 @@ fluent-ffmpeg@^2.1.0:
3052 async ">=0.2.9" 2765 async ">=0.2.9"
3053 which "^1.1.1" 2766 which "^1.1.1"
3054 2767
3055flush-write-stream@^1.0.0:
3056 version "1.1.1"
3057 resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
3058 integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
3059 dependencies:
3060 inherits "^2.0.3"
3061 readable-stream "^2.3.6"
3062
3063fn-name@~2.0.1: 2768fn-name@~2.0.1:
3064 version "2.0.1" 2769 version "2.0.1"
3065 resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" 2770 resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7"
@@ -3106,10 +2811,10 @@ fragment-cache@^0.2.1:
3106 dependencies: 2811 dependencies:
3107 map-cache "^0.2.2" 2812 map-cache "^0.2.2"
3108 2813
3109frameguard@3.0.0: 2814frameguard@3.1.0:
3110 version "3.0.0" 2815 version "3.1.0"
3111 resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.0.0.tgz#7bcad469ee7b96e91d12ceb3959c78235a9272e9" 2816 resolved "https://registry.yarnpkg.com/frameguard/-/frameguard-3.1.0.tgz#bd1442cca1d67dc346a6751559b6d04502103a22"
3112 integrity sha1-e8rUae57lukdEs6zlZx4I1qScuk= 2817 integrity sha512-TxgSKM+7LTA6sidjOiSZK9wxY0ffMPY3Wta//MqwmX0nZuEHc8QrkV8Fh3ZhMJeiH+Uyh/tcaarImRy8u77O7g==
3113 2818
3114fresh@0.3.0: 2819fresh@0.3.0:
3115 version "0.3.0" 2820 version "0.3.0"
@@ -3121,22 +2826,6 @@ fresh@0.5.2:
3121 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 2826 resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
3122 integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 2827 integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
3123 2828
3124from2@^1.3.0:
3125 version "1.3.0"
3126 resolved "https://registry.yarnpkg.com/from2/-/from2-1.3.0.tgz#88413baaa5f9a597cfde9221d86986cd3c061dfd"
3127 integrity sha1-iEE7qqX5pZfP3pIh2GmGzTwGHf0=
3128 dependencies:
3129 inherits "~2.0.1"
3130 readable-stream "~1.1.10"
3131
3132from2@^2.1.0:
3133 version "2.3.0"
3134 resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
3135 integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
3136 dependencies:
3137 inherits "^2.0.1"
3138 readable-stream "^2.0.0"
3139
3140front-matter@2.1.2: 2829front-matter@2.1.2:
3141 version "2.1.2" 2830 version "2.1.2"
3142 resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb" 2831 resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb"
@@ -3175,63 +2864,34 @@ fs-extra@^3.0.1:
3175 jsonfile "^3.0.0" 2864 jsonfile "^3.0.0"
3176 universalify "^0.1.0" 2865 universalify "^0.1.0"
3177 2866
3178fs-extra@^7.0.0: 2867fs-extra@^8.0.1:
3179 version "7.0.1" 2868 version "8.0.1"
3180 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" 2869 resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.0.1.tgz#90294081f978b1f182f347a440a209154344285b"
3181 integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== 2870 integrity sha512-W+XLrggcDzlle47X/XnS7FXrXu9sDo+Ze9zpndeBxdgv88FHLm1HtmkhEwavruS6koanBjp098rUpHs65EmG7A==
3182 dependencies: 2871 dependencies:
3183 graceful-fs "^4.1.2" 2872 graceful-fs "^4.1.2"
3184 jsonfile "^4.0.0" 2873 jsonfile "^4.0.0"
3185 universalify "^0.1.0" 2874 universalify "^0.1.0"
3186 2875
3187fs-minipass@^1.2.5: 2876fs-minipass@^1.2.5:
3188 version "1.2.5" 2877 version "1.2.6"
3189 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" 2878 resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07"
3190 integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== 2879 integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==
3191 dependencies: 2880 dependencies:
3192 minipass "^2.2.1" 2881 minipass "^2.2.1"
3193 2882
3194fs-vacuum@^1.2.10, fs-vacuum@~1.2.10:
3195 version "1.2.10"
3196 resolved "https://registry.yarnpkg.com/fs-vacuum/-/fs-vacuum-1.2.10.tgz#b7629bec07a4031a2548fdf99f5ecf1cc8b31e36"
3197 integrity sha1-t2Kb7AekAxolSP35n17PHMizHjY=
3198 dependencies:
3199 graceful-fs "^4.1.2"
3200 path-is-inside "^1.0.1"
3201 rimraf "^2.5.2"
3202
3203fs-write-stream-atomic@^1.0.8, fs-write-stream-atomic@~1.0.10:
3204 version "1.0.10"
3205 resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
3206 integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=
3207 dependencies:
3208 graceful-fs "^4.1.2"
3209 iferr "^0.1.5"
3210 imurmurhash "^0.1.4"
3211 readable-stream "1 || 2"
3212
3213fs.realpath@^1.0.0: 2883fs.realpath@^1.0.0:
3214 version "1.0.0" 2884 version "1.0.0"
3215 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 2885 resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
3216 integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 2886 integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
3217 2887
3218fsevents@^1.2.7: 2888fsevents@^1.2.7:
3219 version "1.2.7" 2889 version "1.2.9"
3220 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" 2890 resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f"
3221 integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== 2891 integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==
3222 dependencies: 2892 dependencies:
3223 nan "^2.9.2" 2893 nan "^2.12.1"
3224 node-pre-gyp "^0.10.0" 2894 node-pre-gyp "^0.12.0"
3225
3226fstream@^1.0.0, fstream@^1.0.2:
3227 version "1.0.11"
3228 resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
3229 integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=
3230 dependencies:
3231 graceful-fs "^4.1.2"
3232 inherits "~2.0.0"
3233 mkdirp ">=0.5 0"
3234 rimraf "2"
3235 2895
3236function-bind@^1.1.1: 2896function-bind@^1.1.1:
3237 version "1.1.1" 2897 version "1.1.1"
@@ -3275,25 +2935,6 @@ generate-object-property@^1.1.0:
3275 dependencies: 2935 dependencies:
3276 is-property "^1.0.0" 2936 is-property "^1.0.0"
3277 2937
3278genfun@^5.0.0:
3279 version "5.0.0"
3280 resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
3281 integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
3282
3283gentle-fs@^2.0.0, gentle-fs@^2.0.1:
3284 version "2.0.1"
3285 resolved "https://registry.yarnpkg.com/gentle-fs/-/gentle-fs-2.0.1.tgz#585cfd612bfc5cd52471fdb42537f016a5ce3687"
3286 integrity sha512-cEng5+3fuARewXktTEGbwsktcldA+YsnUEaXZwcK/3pjSE1X9ObnTs+/8rYf8s+RnIcQm2D5x3rwpN7Zom8Bew==
3287 dependencies:
3288 aproba "^1.1.2"
3289 fs-vacuum "^1.2.10"
3290 graceful-fs "^4.1.11"
3291 iferr "^0.1.5"
3292 mkdirp "^0.5.1"
3293 path-is-inside "^1.0.2"
3294 read-cmd-shim "^1.0.1"
3295 slide "^1.1.6"
3296
3297get-browser-rtc@^1.0.0: 2938get-browser-rtc@^1.0.0:
3298 version "1.0.2" 2939 version "1.0.2"
3299 resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9" 2940 resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9"
@@ -3304,6 +2945,11 @@ get-caller-file@^1.0.1:
3304 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" 2945 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
3305 integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== 2946 integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
3306 2947
2948get-caller-file@^2.0.1:
2949 version "2.0.5"
2950 resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
2951 integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
2952
3307get-func-name@^2.0.0: 2953get-func-name@^2.0.0:
3308 version "2.0.0" 2954 version "2.0.0"
3309 resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" 2955 resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
@@ -3314,22 +2960,29 @@ get-own-enumerable-property-symbols@^3.0.0:
3314 resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203" 2960 resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203"
3315 integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg== 2961 integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==
3316 2962
3317get-port@latest: 2963get-port@^5.0.0:
3318 version "4.2.0" 2964 version "5.0.0"
3319 resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" 2965 resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.0.0.tgz#aa22b6b86fd926dd7884de3e23332c9f70c031a6"
3320 integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== 2966 integrity sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==
2967 dependencies:
2968 type-fest "^0.3.0"
3321 2969
3322get-stdin@^6.0.0: 2970get-stdin@^6.0.0:
3323 version "6.0.0" 2971 version "6.0.0"
3324 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" 2972 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
3325 integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== 2973 integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
3326 2974
2975get-stdin@^7.0.0:
2976 version "7.0.0"
2977 resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
2978 integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==
2979
3327get-stream@^3.0.0: 2980get-stream@^3.0.0:
3328 version "3.0.0" 2981 version "3.0.0"
3329 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" 2982 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
3330 integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= 2983 integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
3331 2984
3332get-stream@^4.0.0, get-stream@^4.1.0: 2985get-stream@^4.0.0:
3333 version "4.1.0" 2986 version "4.1.0"
3334 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" 2987 resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
3335 integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== 2988 integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
@@ -3373,7 +3026,7 @@ glob@7.1.2:
3373 once "^1.3.0" 3026 once "^1.3.0"
3374 path-is-absolute "^1.0.0" 3027 path-is-absolute "^1.0.0"
3375 3028
3376glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@~7.1.1: 3029glob@7.1.3:
3377 version "7.1.3" 3030 version "7.1.3"
3378 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 3031 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
3379 integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 3032 integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -3385,6 +3038,18 @@ glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@~7.1.1:
3385 once "^1.3.0" 3038 once "^1.3.0"
3386 path-is-absolute "^1.0.0" 3039 path-is-absolute "^1.0.0"
3387 3040
3041glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@~7.1.1:
3042 version "7.1.4"
3043 resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
3044 integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
3045 dependencies:
3046 fs.realpath "^1.0.0"
3047 inflight "^1.0.4"
3048 inherits "2"
3049 minimatch "^3.0.4"
3050 once "^1.3.0"
3051 path-is-absolute "^1.0.0"
3052
3388global-dirs@^0.1.0: 3053global-dirs@^0.1.0:
3389 version "0.1.1" 3054 version "0.1.1"
3390 resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" 3055 resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
@@ -3392,26 +3057,6 @@ global-dirs@^0.1.0:
3392 dependencies: 3057 dependencies:
3393 ini "^1.3.4" 3058 ini "^1.3.4"
3394 3059
3395global-modules@^1.0.0:
3396 version "1.0.0"
3397 resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
3398 integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
3399 dependencies:
3400 global-prefix "^1.0.1"
3401 is-windows "^1.0.1"
3402 resolve-dir "^1.0.0"
3403
3404global-prefix@^1.0.1:
3405 version "1.0.2"
3406 resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
3407 integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
3408 dependencies:
3409 expand-tilde "^2.0.2"
3410 homedir-polyfill "^1.0.1"
3411 ini "^1.3.4"
3412 is-windows "^1.0.1"
3413 which "^1.2.14"
3414
3415globals@^9.2.0: 3060globals@^9.2.0:
3416 version "9.18.0" 3061 version "9.18.0"
3417 resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" 3062 resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@@ -3461,7 +3106,7 @@ got@^6.7.1:
3461 unzip-response "^2.0.1" 3106 unzip-response "^2.0.1"
3462 url-parse-lax "^1.0.0" 3107 url-parse-lax "^1.0.0"
3463 3108
3464graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: 3109graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
3465 version "4.1.15" 3110 version "4.1.15"
3466 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" 3111 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
3467 integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== 3112 integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
@@ -3530,7 +3175,7 @@ has-symbols@^1.0.0:
3530 resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" 3175 resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
3531 integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= 3176 integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=
3532 3177
3533has-unicode@^2.0.0, has-unicode@~2.0.1: 3178has-unicode@^2.0.0:
3534 version "2.0.1" 3179 version "2.0.1"
3535 resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 3180 resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
3536 integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= 3181 integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
@@ -3609,24 +3254,24 @@ helmet-csp@2.7.1:
3609 platform "1.3.5" 3254 platform "1.3.5"
3610 3255
3611helmet@^3.12.1: 3256helmet@^3.12.1:
3612 version "3.16.0" 3257 version "3.18.0"
3613 resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.16.0.tgz#7df41a4bfe4c83d90147c1e30d70893f92a9d97c" 3258 resolved "https://registry.yarnpkg.com/helmet/-/helmet-3.18.0.tgz#37666f7c861bd1ff3015e0cdb903a43501e3da3e"
3614 integrity sha512-rsTKRogc5OYGlvSHuq5QsmOsOzF6uDoMqpfh+Np8r23+QxDq+SUx90Rf8HyIKQVl7H6NswZEwfcykinbAeZ6UQ== 3259 integrity sha512-TsKlGE5UVkV0NiQ4PllV9EVfZklPjyzcMEMjWlyI/8S6epqgRT+4s4GHVgc25x0TixsKvp3L7c91HQQt5l0+QA==
3615 dependencies: 3260 dependencies:
3616 depd "2.0.0" 3261 depd "2.0.0"
3617 dns-prefetch-control "0.1.0" 3262 dns-prefetch-control "0.1.0"
3618 dont-sniff-mimetype "1.0.0" 3263 dont-sniff-mimetype "1.0.0"
3619 expect-ct "0.1.1" 3264 expect-ct "0.2.0"
3620 feature-policy "0.2.0" 3265 feature-policy "0.3.0"
3621 frameguard "3.0.0" 3266 frameguard "3.1.0"
3622 helmet-crossdomain "0.3.0" 3267 helmet-crossdomain "0.3.0"
3623 helmet-csp "2.7.1" 3268 helmet-csp "2.7.1"
3624 hide-powered-by "1.0.0" 3269 hide-powered-by "1.0.0"
3625 hpkp "2.0.0" 3270 hpkp "2.0.0"
3626 hsts "2.2.0" 3271 hsts "2.2.0"
3627 ienoopen "1.1.0" 3272 ienoopen "1.1.0"
3628 nocache "2.0.0" 3273 nocache "2.1.0"
3629 referrer-policy "1.1.0" 3274 referrer-policy "1.2.0"
3630 x-xss-protection "1.1.0" 3275 x-xss-protection "1.1.0"
3631 3276
3632hh-mm-ss@~1.2.0: 3277hh-mm-ss@~1.2.0:
@@ -3641,14 +3286,7 @@ hide-powered-by@1.0.0:
3641 resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b" 3286 resolved "https://registry.yarnpkg.com/hide-powered-by/-/hide-powered-by-1.0.0.tgz#4a85ad65881f62857fc70af7174a1184dccce32b"
3642 integrity sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys= 3287 integrity sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys=
3643 3288
3644homedir-polyfill@^1.0.1: 3289hosted-git-info@^2.1.4:
3645 version "1.0.3"
3646 resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
3647 integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
3648 dependencies:
3649 parse-passwd "^1.0.0"
3650
3651hosted-git-info@^2.1.4, hosted-git-info@^2.6.0, hosted-git-info@^2.7.1:
3652 version "2.7.1" 3290 version "2.7.1"
3653 resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" 3291 resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
3654 integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== 3292 integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==
@@ -3665,20 +3303,16 @@ hsts@2.2.0:
3665 dependencies: 3303 dependencies:
3666 depd "2.0.0" 3304 depd "2.0.0"
3667 3305
3668http-cache-semantics@^3.8.1: 3306http-errors@1.7.2, http-errors@~1.7.2:
3669 version "3.8.1" 3307 version "1.7.2"
3670 resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" 3308 resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
3671 integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== 3309 integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
3672
3673http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
3674 version "1.6.3"
3675 resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
3676 integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
3677 dependencies: 3310 dependencies:
3678 depd "~1.1.2" 3311 depd "~1.1.2"
3679 inherits "2.0.3" 3312 inherits "2.0.3"
3680 setprototypeof "1.1.0" 3313 setprototypeof "1.1.1"
3681 statuses ">= 1.4.0 < 2" 3314 statuses ">= 1.5.0 < 2"
3315 toidentifier "1.0.0"
3682 3316
3683http-errors@~1.3.1: 3317http-errors@~1.3.1:
3684 version "1.3.1" 3318 version "1.3.1"
@@ -3688,14 +3322,6 @@ http-errors@~1.3.1:
3688 inherits "~2.0.1" 3322 inherits "~2.0.1"
3689 statuses "1" 3323 statuses "1"
3690 3324
3691http-proxy-agent@^2.1.0:
3692 version "2.1.0"
3693 resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
3694 integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
3695 dependencies:
3696 agent-base "4"
3697 debug "3.1.0"
3698
3699http-signature@^1.2.0, http-signature@~1.2.0: 3325http-signature@^1.2.0, http-signature@~1.2.0:
3700 version "1.2.0" 3326 version "1.2.0"
3701 resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 3327 resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -3705,50 +3331,28 @@ http-signature@^1.2.0, http-signature@~1.2.0:
3705 jsprim "^1.2.2" 3331 jsprim "^1.2.2"
3706 sshpk "^1.7.0" 3332 sshpk "^1.7.0"
3707 3333
3708https-proxy-agent@^2.2.1: 3334husky@^2.4.0:
3709 version "2.2.1" 3335 version "2.4.0"
3710 resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" 3336 resolved "https://registry.yarnpkg.com/husky/-/husky-2.4.0.tgz#1bac7c44588f6e91f808b72efc82d24a57194f36"
3711 integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== 3337 integrity sha512-3k1wuZU20gFkphNWMjh2ISCFaqfbaLY7R9FST2Mj9HeRhUK9ydj9qQR8qfXlog3EctVGsyeilcZkIT7uBZDDVA==
3712 dependencies:
3713 agent-base "^4.1.0"
3714 debug "^3.1.0"
3715
3716humanize-ms@^1.2.1:
3717 version "1.2.1"
3718 resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
3719 integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
3720 dependencies:
3721 ms "^2.0.0"
3722
3723husky@^1.0.0-rc.4:
3724 version "1.3.1"
3725 resolved "https://registry.yarnpkg.com/husky/-/husky-1.3.1.tgz#26823e399300388ca2afff11cfa8a86b0033fae0"
3726 integrity sha512-86U6sVVVf4b5NYSZ0yvv88dRgBSSXXmHaiq5pP4KDj5JVzdwKgBjEtUPOm8hcoytezFwbU+7gotXNhpHdystlg==
3727 dependencies: 3338 dependencies:
3728 cosmiconfig "^5.0.7" 3339 cosmiconfig "^5.2.0"
3729 execa "^1.0.0" 3340 execa "^1.0.0"
3730 find-up "^3.0.0" 3341 find-up "^3.0.0"
3731 get-stdin "^6.0.0" 3342 get-stdin "^7.0.0"
3732 is-ci "^2.0.0" 3343 is-ci "^2.0.0"
3733 pkg-dir "^3.0.0" 3344 pkg-dir "^4.1.0"
3734 please-upgrade-node "^3.1.1" 3345 please-upgrade-node "^3.1.1"
3735 read-pkg "^4.0.1" 3346 read-pkg "^5.1.1"
3736 run-node "^1.0.0" 3347 run-node "^1.0.0"
3737 slash "^2.0.0" 3348 slash "^3.0.0"
3738 3349
3739i@0.3.x: 3350i@0.3.x:
3740 version "0.3.6" 3351 version "0.3.6"
3741 resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d" 3352 resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
3742 integrity sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0= 3353 integrity sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=
3743 3354
3744iconv-lite@0.4.23: 3355iconv-lite@0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
3745 version "0.4.23"
3746 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
3747 integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
3748 dependencies:
3749 safer-buffer ">= 2.1.2 < 3"
3750
3751iconv-lite@^0.4.4, iconv-lite@~0.4.13:
3752 version "0.4.24" 3356 version "0.4.24"
3753 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 3357 resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
3754 integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 3358 integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -3760,16 +3364,6 @@ ienoopen@1.1.0:
3760 resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.1.0.tgz#411e5d530c982287dbdc3bb31e7a9c9e32630974" 3364 resolved "https://registry.yarnpkg.com/ienoopen/-/ienoopen-1.1.0.tgz#411e5d530c982287dbdc3bb31e7a9c9e32630974"
3761 integrity sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ== 3365 integrity sha512-MFs36e/ca6ohEKtinTJ5VvAJ6oDRAYFdYXweUnGY9L9vcoqFOU4n2ZhmJ0C4z/cwGZ3YIQRSB3XZ1+ghZkY5NQ==
3762 3366
3763iferr@^0.1.5:
3764 version "0.1.5"
3765 resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
3766 integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
3767
3768iferr@^1.0.2:
3769 version "1.0.2"
3770 resolved "https://registry.yarnpkg.com/iferr/-/iferr-1.0.2.tgz#e9fde49a9da06dc4a4194c6c9ed6d08305037a6d"
3771 integrity sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg==
3772
3773ignore-by-default@^1.0.1: 3367ignore-by-default@^1.0.1:
3774 version "1.0.1" 3368 version "1.0.1"
3775 resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" 3369 resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
@@ -3825,7 +3419,7 @@ inflection@1.12.0:
3825 resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" 3419 resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416"
3826 integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY= 3420 integrity sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=
3827 3421
3828inflight@^1.0.4, inflight@~1.0.6: 3422inflight@^1.0.4:
3829 version "1.0.6" 3423 version "1.0.6"
3830 resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 3424 resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
3831 integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 3425 integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
@@ -3833,7 +3427,7 @@ inflight@^1.0.4, inflight@~1.0.6:
3833 once "^1.3.0" 3427 once "^1.3.0"
3834 wrappy "1" 3428 wrappy "1"
3835 3429
3836inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: 3430inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
3837 version "2.0.3" 3431 version "2.0.3"
3838 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 3432 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
3839 integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 3433 integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
@@ -3843,25 +3437,11 @@ inherits@=2.0.1:
3843 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" 3437 resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
3844 integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= 3438 integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
3845 3439
3846ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: 3440ini@^1.3.4, ini@~1.3.0:
3847 version "1.3.5" 3441 version "1.3.5"
3848 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" 3442 resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
3849 integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== 3443 integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
3850 3444
3851init-package-json@^1.10.3:
3852 version "1.10.3"
3853 resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe"
3854 integrity sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==
3855 dependencies:
3856 glob "^7.1.1"
3857 npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0"
3858 promzard "^0.3.0"
3859 read "~1.0.1"
3860 read-package-json "1 || 2"
3861 semver "2.x || 3.x || 4 || 5"
3862 validate-npm-package-license "^3.0.1"
3863 validate-npm-package-name "^3.0.0"
3864
3865inquirer@^0.12.0: 3445inquirer@^0.12.0:
3866 version "0.12.0" 3446 version "0.12.0"
3867 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" 3447 resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
@@ -3881,25 +3461,19 @@ inquirer@^0.12.0:
3881 strip-ansi "^3.0.0" 3461 strip-ansi "^3.0.0"
3882 through "^2.3.6" 3462 through "^2.3.6"
3883 3463
3884invert-kv@^1.0.0:
3885 version "1.0.0"
3886 resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
3887 integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY=
3888
3889invert-kv@^2.0.0: 3464invert-kv@^2.0.0:
3890 version "2.0.0" 3465 version "2.0.0"
3891 resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" 3466 resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
3892 integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== 3467 integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
3893 3468
3894ioredis@^4.5.1: 3469ioredis@^4.5.1:
3895 version "4.9.0" 3470 version "4.9.5"
3896 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.9.0.tgz#0c52de498363309ebd48b5f6695d9d432b0f6669" 3471 resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.9.5.tgz#0bbba0a9faae93485d3231e1b819d2d4e23271d9"
3897 integrity sha512-YzfCLsN++Ct43QqGK9CWxaEK6OUvJ7rnENieAPNw3DVp/oF2uBrP2NJChbhO74Ng3LWA+i5zdIEUsZYr6dKDIQ== 3472 integrity sha512-L9MVfvX4F3LScTMEgriCGixzqinJsYy7Mt0NPX8RyuOTmx5JW0744pM4Ze2KVQcP3J0zvKYZ1LywAB6KIq7PYg==
3898 dependencies: 3473 dependencies:
3899 cluster-key-slot "^1.0.6" 3474 cluster-key-slot "^1.0.6"
3900 debug "^3.1.0" 3475 debug "^3.1.0"
3901 denque "^1.1.0" 3476 denque "^1.1.0"
3902 flexbuffer "0.0.6"
3903 lodash.defaults "^4.2.0" 3477 lodash.defaults "^4.2.0"
3904 lodash.flatten "^4.4.0" 3478 lodash.flatten "^4.4.0"
3905 redis-commands "1.4.0" 3479 redis-commands "1.4.0"
@@ -3907,10 +3481,10 @@ ioredis@^4.5.1:
3907 redis-parser "^3.0.0" 3481 redis-parser "^3.0.0"
3908 standard-as-callback "^2.0.1" 3482 standard-as-callback "^2.0.1"
3909 3483
3910ip-anonymize@^0.0.6: 3484ip-anonymize@^0.1.0:
3911 version "0.0.6" 3485 version "0.1.0"
3912 resolved "https://registry.yarnpkg.com/ip-anonymize/-/ip-anonymize-0.0.6.tgz#d2c513e448e874e8cc380d03404691b94b018e68" 3486 resolved "https://registry.yarnpkg.com/ip-anonymize/-/ip-anonymize-0.1.0.tgz#5ead504d01871c5c28189a25382f852036b57f7e"
3913 integrity sha1-0sUT5EjodOjMOA0DQEaRuUsBjmg= 3487 integrity sha512-cZJu+N5JKKFGMK0eEQWNaQMn2EhCysciVM6eotCJwfqotj16BTfVchKsJCH6mQAT9N0GC7oWRcsZ6Lb8dDiwTA==
3914 3488
3915ip-regex@^2.1.0: 3489ip-regex@^2.1.0:
3916 version "2.1.0" 3490 version "2.1.0"
@@ -3918,13 +3492,13 @@ ip-regex@^2.1.0:
3918 integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk= 3492 integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
3919 3493
3920ip-set@^1.0.0: 3494ip-set@^1.0.0:
3921 version "1.0.1" 3495 version "1.0.2"
3922 resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.1.tgz#633b66d0bd6c8d0de968d053263c9120d3b6727e" 3496 resolved "https://registry.yarnpkg.com/ip-set/-/ip-set-1.0.2.tgz#be4f119f82c124836455993dfcd554639c7007de"
3923 integrity sha1-Yztm0L1sjQ3paNBTJjyRINO2cn4= 3497 integrity sha512-Mb6kv78bTi4RNAIIWL8Bbre7hXOR2pNUi3j8FaQkLaitf/ZWxkq3/iIwXNYk2ACO3IMfdVdQrOkUtwZblO7uBA==
3924 dependencies: 3498 dependencies:
3925 ip "^1.1.3" 3499 ip "^1.1.3"
3926 3500
3927ip@^1.0.1, ip@^1.1.3, ip@^1.1.5: 3501ip@^1.0.1, ip@^1.1.3:
3928 version "1.1.5" 3502 version "1.1.5"
3929 resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" 3503 resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
3930 integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= 3504 integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
@@ -3934,11 +3508,6 @@ ipaddr.js@1.0.5:
3934 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.0.5.tgz#5fa78cf301b825c78abc3042d812723049ea23c7" 3508 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.0.5.tgz#5fa78cf301b825c78abc3042d812723049ea23c7"
3935 integrity sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c= 3509 integrity sha1-X6eM8wG4JceKvDBC2BJyMEnqI8c=
3936 3510
3937ipaddr.js@1.8.0:
3938 version "1.8.0"
3939 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
3940 integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4=
3941
3942ipaddr.js@1.9.0, "ipaddr.js@>= 0.1.5", ipaddr.js@^1.0.1: 3511ipaddr.js@1.9.0, "ipaddr.js@>= 0.1.5", ipaddr.js@^1.0.1:
3943 version "1.9.0" 3512 version "1.9.0"
3944 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" 3513 resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
@@ -4135,9 +3704,9 @@ is-my-ip-valid@^1.0.0:
4135 integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== 3704 integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==
4136 3705
4137is-my-json-valid@^2.10.0: 3706is-my-json-valid@^2.10.0:
4138 version "2.19.0" 3707 version "2.20.0"
4139 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz#8fd6e40363cd06b963fa877d444bfb5eddc62175" 3708 resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz#1345a6fca3e8daefc10d0fa77067f54cedafd59a"
4140 integrity sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q== 3709 integrity sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==
4141 dependencies: 3710 dependencies:
4142 generate-function "^2.0.0" 3711 generate-function "^2.0.0"
4143 generate-object-property "^1.1.0" 3712 generate-object-property "^1.1.0"
@@ -4256,7 +3825,7 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0:
4256 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 3825 resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
4257 integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= 3826 integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
4258 3827
4259is-windows@^1.0.1, is-windows@^1.0.2: 3828is-windows@^1.0.2:
4260 version "1.0.2" 3829 version "1.0.2"
4261 resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" 3830 resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
4262 integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== 3831 integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
@@ -4282,9 +3851,9 @@ isexe@^2.0.0:
4282 integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 3851 integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
4283 3852
4284iso-639-3@^1.0.1: 3853iso-639-3@^1.0.1:
4285 version "1.1.0" 3854 version "1.2.0"
4286 resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.1.0.tgz#83722daf55490a707c318ae18a33ba3bab06c843" 3855 resolved "https://registry.yarnpkg.com/iso-639-3/-/iso-639-3-1.2.0.tgz#eee1f5e6ca2bbb33e3ecc910857c1c12e8b295be"
4287 integrity sha512-l3BAnxNpyRIZA4mEzI2md/YVrxQ3hI8hiQe7TFyQknjyOh8vCzobZuAXTFHELco0FBkYRx4FkAlIqkKrHhnzgw== 3856 integrity sha512-jNvD2P4JHNckQH7pc0R0SQ4oPCpyEtgs0nTtjB+DZCUDdygz0cOAxlcnq5KgNjjsqMHbR4Sbgwz2+DflzAZvlQ==
4288 3857
4289isobject@^2.0.0: 3858isobject@^2.0.0:
4290 version "2.1.0" 3859 version "2.1.0"
@@ -4303,23 +3872,15 @@ isstream@0.1.x, isstream@~0.1.2:
4303 resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 3872 resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
4304 integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= 3873 integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
4305 3874
4306js-tokens@^3.0.2: 3875js-tokens@^4.0.0:
4307 version "3.0.2" 3876 version "4.0.0"
4308 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 3877 resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
4309 integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= 3878 integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
4310
4311js-yaml@3.12.0:
4312 version "3.12.0"
4313 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
4314 integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==
4315 dependencies:
4316 argparse "^1.0.7"
4317 esprima "^4.0.0"
4318 3879
4319js-yaml@^3.12.1, js-yaml@^3.13.0, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4: 3880js-yaml@3.13.1, js-yaml@^3.12.1, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4:
4320 version "3.13.0" 3881 version "3.13.1"
4321 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" 3882 resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
4322 integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== 3883 integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
4323 dependencies: 3884 dependencies:
4324 argparse "^1.0.7" 3885 argparse "^1.0.7"
4325 esprima "^4.0.0" 3886 esprima "^4.0.0"
@@ -4329,7 +3890,7 @@ jsbn@~0.1.0:
4329 resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 3890 resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
4330 integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= 3891 integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
4331 3892
4332json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: 3893json-parse-better-errors@^1.0.1:
4333 version "1.0.2" 3894 version "1.0.2"
4334 resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" 3895 resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
4335 integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== 3896 integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -4425,11 +3986,6 @@ jsonld@~1.1.0:
4425 semver "^5.5.0" 3986 semver "^5.5.0"
4426 xmldom "0.1.19" 3987 xmldom "0.1.19"
4427 3988
4428jsonparse@^1.2.0:
4429 version "1.3.1"
4430 resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
4431 integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
4432
4433jsonpointer.js@0.4.0: 3989jsonpointer.js@0.4.0:
4434 version "0.4.0" 3990 version "0.4.0"
4435 resolved "https://registry.yarnpkg.com/jsonpointer.js/-/jsonpointer.js-0.4.0.tgz#002cb123f767aafdeb0196132ce5c4f9941ccaba" 3991 resolved "https://registry.yarnpkg.com/jsonpointer.js/-/jsonpointer.js-0.4.0.tgz#002cb123f767aafdeb0196132ce5c4f9941ccaba"
@@ -4565,18 +4121,6 @@ latest-version@^3.0.0:
4565 dependencies: 4121 dependencies:
4566 package-json "^4.0.0" 4122 package-json "^4.0.0"
4567 4123
4568lazy-property@~1.0.0:
4569 version "1.0.0"
4570 resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147"
4571 integrity sha1-hN3Es3Bnm6i9TNz6TAa0PVcREUc=
4572
4573lcid@^1.0.0:
4574 version "1.0.0"
4575 resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
4576 integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=
4577 dependencies:
4578 invert-kv "^1.0.0"
4579
4580lcid@^2.0.0: 4124lcid@^2.0.0:
4581 version "2.0.0" 4125 version "2.0.0"
4582 resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" 4126 resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf"
@@ -4592,140 +4136,6 @@ levn@^0.3.0, levn@~0.3.0:
4592 prelude-ls "~1.1.2" 4136 prelude-ls "~1.1.2"
4593 type-check "~0.3.2" 4137 type-check "~0.3.2"
4594 4138
4595libcipm@^3.0.3:
4596 version "3.0.3"
4597 resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-3.0.3.tgz#2e764effe0b90d458790dab3165794c804075ed3"
4598 integrity sha512-71V5CpTI+zFydTc5IjJ/tx8JHbXEJvmYF2zaSVW1V3X1rRnRjXqh44iuiyry1xgi3ProUQ1vX1uwFiWs00+2og==
4599 dependencies:
4600 bin-links "^1.1.2"
4601 bluebird "^3.5.1"
4602 figgy-pudding "^3.5.1"
4603 find-npm-prefix "^1.0.2"
4604 graceful-fs "^4.1.11"
4605 ini "^1.3.5"
4606 lock-verify "^2.0.2"
4607 mkdirp "^0.5.1"
4608 npm-lifecycle "^2.0.3"
4609 npm-logical-tree "^1.2.1"
4610 npm-package-arg "^6.1.0"
4611 pacote "^9.1.0"
4612 read-package-json "^2.0.13"
4613 rimraf "^2.6.2"
4614 worker-farm "^1.6.0"
4615
4616libnpm@^2.0.1:
4617 version "2.0.1"
4618 resolved "https://registry.yarnpkg.com/libnpm/-/libnpm-2.0.1.tgz#a48fcdee3c25e13c77eb7c60a0efe561d7fb0d8f"
4619 integrity sha512-qTKoxyJvpBxHZQB6k0AhSLajyXq9ZE/lUsZzuHAplr2Bpv9G+k4YuYlExYdUCeVRRGqcJt8hvkPh4tBwKoV98w==
4620 dependencies:
4621 bin-links "^1.1.2"
4622 bluebird "^3.5.3"
4623 find-npm-prefix "^1.0.2"
4624 libnpmaccess "^3.0.1"
4625 libnpmconfig "^1.2.1"
4626 libnpmhook "^5.0.2"
4627 libnpmorg "^1.0.0"
4628 libnpmpublish "^1.1.0"
4629 libnpmsearch "^2.0.0"
4630 libnpmteam "^1.0.1"
4631 lock-verify "^2.0.2"
4632 npm-lifecycle "^2.1.0"
4633 npm-logical-tree "^1.2.1"
4634 npm-package-arg "^6.1.0"
4635 npm-profile "^4.0.1"
4636 npm-registry-fetch "^3.8.0"
4637 npmlog "^4.1.2"
4638 pacote "^9.2.3"
4639 read-package-json "^2.0.13"
4640 stringify-package "^1.0.0"
4641
4642libnpmaccess@^3.0.1:
4643 version "3.0.1"
4644 resolved "https://registry.yarnpkg.com/libnpmaccess/-/libnpmaccess-3.0.1.tgz#5b3a9de621f293d425191aa2e779102f84167fa8"
4645 integrity sha512-RlZ7PNarCBt+XbnP7R6PoVgOq9t+kou5rvhaInoNibhPO7eMlRfS0B8yjatgn2yaHIwWNyoJDolC/6Lc5L/IQA==
4646 dependencies:
4647 aproba "^2.0.0"
4648 get-stream "^4.0.0"
4649 npm-package-arg "^6.1.0"
4650 npm-registry-fetch "^3.8.0"
4651
4652libnpmconfig@^1.2.1:
4653 version "1.2.1"
4654 resolved "https://registry.yarnpkg.com/libnpmconfig/-/libnpmconfig-1.2.1.tgz#c0c2f793a74e67d4825e5039e7a02a0044dfcbc0"
4655 integrity sha512-9esX8rTQAHqarx6qeZqmGQKBNZR5OIbl/Ayr0qQDy3oXja2iFVQQI81R6GZ2a02bSNZ9p3YOGX1O6HHCb1X7kA==
4656 dependencies:
4657 figgy-pudding "^3.5.1"
4658 find-up "^3.0.0"
4659 ini "^1.3.5"
4660
4661libnpmhook@^5.0.2:
4662 version "5.0.2"
4663 resolved "https://registry.yarnpkg.com/libnpmhook/-/libnpmhook-5.0.2.tgz#d12817b0fb893f36f1d5be20017f2aea25825d94"
4664 integrity sha512-vLenmdFWhRfnnZiNFPNMog6CK7Ujofy2TWiM2CrpZUjBRIhHkJeDaAbJdYCT6W4lcHtyrJR8yXW8KFyq6UAp1g==
4665 dependencies:
4666 aproba "^2.0.0"
4667 figgy-pudding "^3.4.1"
4668 get-stream "^4.0.0"
4669 npm-registry-fetch "^3.8.0"
4670
4671libnpmorg@^1.0.0:
4672 version "1.0.0"
4673 resolved "https://registry.yarnpkg.com/libnpmorg/-/libnpmorg-1.0.0.tgz#979b868c48ba28c5820e3bb9d9e73c883c16a232"
4674 integrity sha512-o+4eVJBoDGMgRwh2lJY0a8pRV2c/tQM/SxlqXezjcAg26Qe9jigYVs+Xk0vvlYDWCDhP0g74J8UwWeAgsB7gGw==
4675 dependencies:
4676 aproba "^2.0.0"
4677 figgy-pudding "^3.4.1"
4678 get-stream "^4.0.0"
4679 npm-registry-fetch "^3.8.0"
4680
4681libnpmpublish@^1.1.0:
4682 version "1.1.1"
4683 resolved "https://registry.yarnpkg.com/libnpmpublish/-/libnpmpublish-1.1.1.tgz#ff0c6bb0b4ad2bda2ad1f5fba6760a4af37125f0"
4684 integrity sha512-nefbvJd/wY38zdt+b9SHL6171vqBrMtZ56Gsgfd0duEKb/pB8rDT4/ObUQLrHz1tOfht1flt2zM+UGaemzAG5g==
4685 dependencies:
4686 aproba "^2.0.0"
4687 figgy-pudding "^3.5.1"
4688 get-stream "^4.0.0"
4689 lodash.clonedeep "^4.5.0"
4690 normalize-package-data "^2.4.0"
4691 npm-package-arg "^6.1.0"
4692 npm-registry-fetch "^3.8.0"
4693 semver "^5.5.1"
4694 ssri "^6.0.1"
4695
4696libnpmsearch@^2.0.0:
4697 version "2.0.0"
4698 resolved "https://registry.yarnpkg.com/libnpmsearch/-/libnpmsearch-2.0.0.tgz#de05af47ada81554a5f64276a69599070d4a5685"
4699 integrity sha512-vd+JWbTGzOSfiOc+72MU6y7WqmBXn49egCCrIXp27iE/88bX8EpG64ST1blWQI1bSMUr9l1AKPMVsqa2tS5KWA==
4700 dependencies:
4701 figgy-pudding "^3.5.1"
4702 get-stream "^4.0.0"
4703 npm-registry-fetch "^3.8.0"
4704
4705libnpmteam@^1.0.1:
4706 version "1.0.1"
4707 resolved "https://registry.yarnpkg.com/libnpmteam/-/libnpmteam-1.0.1.tgz#ff704b1b6c06ea674b3b1101ac3e305f5114f213"
4708 integrity sha512-gDdrflKFCX7TNwOMX1snWojCoDE5LoRWcfOC0C/fqF7mBq8Uz9zWAX4B2RllYETNO7pBupBaSyBDkTAC15cAMg==
4709 dependencies:
4710 aproba "^2.0.0"
4711 figgy-pudding "^3.4.1"
4712 get-stream "^4.0.0"
4713 npm-registry-fetch "^3.8.0"
4714
4715libnpx@^10.2.0:
4716 version "10.2.0"
4717 resolved "https://registry.yarnpkg.com/libnpx/-/libnpx-10.2.0.tgz#1bf4a1c9f36081f64935eb014041da10855e3102"
4718 integrity sha512-X28coei8/XRCt15cYStbLBph+KGhFra4VQhRBPuH/HHMkC5dxM8v24RVgUsvODKCrUZ0eTgiTqJp6zbl0sskQQ==
4719 dependencies:
4720 dotenv "^5.0.1"
4721 npm-package-arg "^6.0.0"
4722 rimraf "^2.6.2"
4723 safe-buffer "^5.1.0"
4724 update-notifier "^2.3.0"
4725 which "^1.3.0"
4726 y18n "^4.0.0"
4727 yargs "^11.0.0"
4728
4729libxmljs@0.19.5: 4139libxmljs@0.19.5:
4730 version "0.19.5" 4140 version "0.19.5"
4731 resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.5.tgz#b2f34cc12fd6a3e43670c604c42a902f339ea54d" 4141 resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.5.tgz#b2f34cc12fd6a3e43670c604c42a902f339ea54d"
@@ -4736,18 +4146,17 @@ libxmljs@0.19.5:
4736 node-pre-gyp "~0.11.0" 4146 node-pre-gyp "~0.11.0"
4737 4147
4738lint-staged@^8.0.4: 4148lint-staged@^8.0.4:
4739 version "8.1.5" 4149 version "8.2.0"
4740 resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.5.tgz#372476fe1a58b8834eb562ed4c99126bd60bdd79" 4150 resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.2.0.tgz#3d4149a229580815c955047a7acd8f09054be5a9"
4741 integrity sha512-e5ZavfnSLcBJE1BTzRTqw6ly8OkqVyO3GL2M6teSmTBYQ/2BuueD5GIt2RPsP31u/vjKdexUyDCxSyK75q4BDA== 4151 integrity sha512-DxguyxGOIfb67wZ6EOrqzjAbw6ZH9XK3YS74HO+erJf6+SAQeJJPN//GBOG5xhdt2THeuXjVPaHcCYOWGZwRbA==
4742 dependencies: 4152 dependencies:
4743 chalk "^2.3.1" 4153 chalk "^2.3.1"
4744 commander "^2.14.1" 4154 commander "^2.14.1"
4745 cosmiconfig "^5.0.2" 4155 cosmiconfig "^5.2.0"
4746 debug "^3.1.0" 4156 debug "^3.1.0"
4747 dedent "^0.7.0" 4157 dedent "^0.7.0"
4748 del "^3.0.0" 4158 del "^3.0.0"
4749 execa "^1.0.0" 4159 execa "^1.0.0"
4750 find-parent-dir "^0.3.0"
4751 g-status "^2.0.2" 4160 g-status "^2.0.2"
4752 is-glob "^4.0.0" 4161 is-glob "^4.0.0"
4753 is-windows "^1.0.2" 4162 is-windows "^1.0.2"
@@ -4764,7 +4173,7 @@ lint-staged@^8.0.4:
4764 staged-git-files "1.1.2" 4173 staged-git-files "1.1.2"
4765 string-argv "^0.0.2" 4174 string-argv "^0.0.2"
4766 stringify-object "^3.2.2" 4175 stringify-object "^3.2.2"
4767 yup "^0.26.10" 4176 yup "^0.27.0"
4768 4177
4769listr-silent-renderer@^1.1.1: 4178listr-silent-renderer@^1.1.1:
4770 version "1.1.1" 4179 version "1.1.1"
@@ -4821,14 +4230,6 @@ load-ip-set@^2.1.0:
4821 simple-get "^3.0.0" 4230 simple-get "^3.0.0"
4822 split "^1.0.0" 4231 split "^1.0.0"
4823 4232
4824locate-path@^2.0.0:
4825 version "2.0.0"
4826 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
4827 integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=
4828 dependencies:
4829 p-locate "^2.0.0"
4830 path-exists "^3.0.0"
4831
4832locate-path@^3.0.0: 4233locate-path@^3.0.0:
4833 version "3.0.0" 4234 version "3.0.0"
4834 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 4235 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
@@ -4837,49 +4238,18 @@ locate-path@^3.0.0:
4837 p-locate "^3.0.0" 4238 p-locate "^3.0.0"
4838 path-exists "^3.0.0" 4239 path-exists "^3.0.0"
4839 4240
4840lock-verify@^2.0.2, lock-verify@^2.1.0: 4241locate-path@^5.0.0:
4841 version "2.1.0" 4242 version "5.0.0"
4842 resolved "https://registry.yarnpkg.com/lock-verify/-/lock-verify-2.1.0.tgz#fff4c918b8db9497af0c5fa7f6d71555de3ceb47" 4243 resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
4843 integrity sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg== 4244 integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
4844 dependencies:
4845 npm-package-arg "^6.1.0"
4846 semver "^5.4.1"
4847
4848lockfile@^1.0.4:
4849 version "1.0.4"
4850 resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609"
4851 integrity sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==
4852 dependencies:
4853 signal-exit "^3.0.2"
4854
4855lodash._baseuniq@~4.6.0:
4856 version "4.6.0"
4857 resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
4858 integrity sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=
4859 dependencies: 4245 dependencies:
4860 lodash._createset "~4.0.0" 4246 p-locate "^4.1.0"
4861 lodash._root "~3.0.0"
4862
4863lodash._createset@~4.0.0:
4864 version "4.0.3"
4865 resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
4866 integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=
4867
4868lodash._root@~3.0.0:
4869 version "3.0.1"
4870 resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
4871 integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=
4872 4247
4873lodash.capitalize@^4.1.0: 4248lodash.capitalize@^4.1.0:
4874 version "4.2.1" 4249 version "4.2.1"
4875 resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" 4250 resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
4876 integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= 4251 integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk=
4877 4252
4878lodash.clonedeep@^4.5.0, lodash.clonedeep@~4.5.0:
4879 version "4.5.0"
4880 resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
4881 integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
4882
4883lodash.defaults@^4.2.0: 4253lodash.defaults@^4.2.0:
4884 version "4.2.0" 4254 version "4.2.0"
4885 resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" 4255 resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
@@ -4905,21 +4275,6 @@ lodash.kebabcase@^4.0.0:
4905 resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" 4275 resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36"
4906 integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= 4276 integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY=
4907 4277
4908lodash.union@~4.6.0:
4909 version "4.6.0"
4910 resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
4911 integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
4912
4913lodash.uniq@~4.5.0:
4914 version "4.5.0"
4915 resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
4916 integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
4917
4918lodash.without@~4.4.0:
4919 version "4.4.0"
4920 resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac"
4921 integrity sha1-PNRXSgC2e643OpS3SHcmQFB7eqw=
4922
4923lodash@4.17.4: 4278lodash@4.17.4:
4924 version "4.17.4" 4279 version "4.17.4"
4925 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 4280 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -4974,7 +4329,7 @@ lowercase-keys@^1.0.0:
4974 resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" 4329 resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
4975 integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== 4330 integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
4976 4331
4977lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3, lru-cache@^4.1.5: 4332lru-cache@4.1.x, lru-cache@^4.0.1:
4978 version "4.1.5" 4333 version "4.1.5"
4979 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" 4334 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
4980 integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== 4335 integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@@ -4982,13 +4337,6 @@ lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3, lru-cache
4982 pseudomap "^1.0.2" 4337 pseudomap "^1.0.2"
4983 yallist "^2.1.2" 4338 yallist "^2.1.2"
4984 4339
4985lru-cache@^5.1.1:
4986 version "5.1.1"
4987 resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
4988 integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
4989 dependencies:
4990 yallist "^3.0.2"
4991
4992lru-queue@0.1: 4340lru-queue@0.1:
4993 version "0.1.0" 4341 version "0.1.0"
4994 resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" 4342 resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3"
@@ -5049,23 +4397,6 @@ make-error@^1.1.1:
5049 resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" 4397 resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
5050 integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== 4398 integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
5051 4399
5052make-fetch-happen@^4.0.1:
5053 version "4.0.1"
5054 resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz#141497cb878f243ba93136c83d8aba12c216c083"
5055 integrity sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==
5056 dependencies:
5057 agentkeepalive "^3.4.1"
5058 cacache "^11.0.1"
5059 http-cache-semantics "^3.8.1"
5060 http-proxy-agent "^2.1.0"
5061 https-proxy-agent "^2.2.1"
5062 lru-cache "^4.1.2"
5063 mississippi "^3.0.0"
5064 node-fetch-npm "^2.0.2"
5065 promise-retry "^1.1.1"
5066 socks-proxy-agent "^4.0.0"
5067 ssri "^6.0.0"
5068
5069map-age-cleaner@^0.1.1: 4400map-age-cleaner@^0.1.1:
5070 version "0.1.3" 4401 version "0.1.3"
5071 resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" 4402 resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
@@ -5085,10 +4416,10 @@ map-visit@^1.0.0:
5085 dependencies: 4416 dependencies:
5086 object-visit "^1.0.0" 4417 object-visit "^1.0.0"
5087 4418
5088marked-man@^0.4.2: 4419marked-man@^0.6.0:
5089 version "0.4.2" 4420 version "0.6.0"
5090 resolved "https://registry.yarnpkg.com/marked-man/-/marked-man-0.4.2.tgz#4507ee0bafa04f46f9b48082f62b3593c8d667bb" 4421 resolved "https://registry.yarnpkg.com/marked-man/-/marked-man-0.6.0.tgz#ac049f840cf633a0f679af58f321c7de3dd583d8"
5091 integrity sha512-4AMgee2zyjzgybboMBjAfDQ1ZUnBFDmxTKld7xuEc9N5tY3T7CqQYqYfKAkUc73tNLeOdwKcR3N5p1toXLiHAA== 4422 integrity sha512-lqzGe2uPo0KPO5J5yyfZ6PbEpZS6VIMHIzltXNzdwTNBysD0pNLtTaGrtT5R4aFT8UaVR3Fuz9rN3SEFYnea5Q==
5092 4423
5093matcher@^1.0.0: 4424matcher@^1.0.0:
5094 version "1.1.1" 4425 version "1.1.1"
@@ -5106,11 +4437,6 @@ md5@^2.2.1:
5106 crypt "~0.0.1" 4437 crypt "~0.0.1"
5107 is-buffer "~1.1.1" 4438 is-buffer "~1.1.1"
5108 4439
5109meant@~1.0.1:
5110 version "1.0.1"
5111 resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.1.tgz#66044fea2f23230ec806fb515efea29c44d2115d"
5112 integrity sha512-UakVLFjKkbbUwNWJ2frVLnnAtbb7D7DsloxRd3s/gDpI8rdv8W5Hp3NaDb+POBI1fQdeussER6NB8vpcRURvlg==
5113
5114media-typer@0.3.0: 4440media-typer@0.3.0:
5115 version "0.3.0" 4441 version "0.3.0"
5116 resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 4442 resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -5125,13 +4451,6 @@ mediasource@^2.1.0, mediasource@^2.2.2:
5125 readable-stream "^3.0.0" 4451 readable-stream "^3.0.0"
5126 to-arraybuffer "^1.0.1" 4452 to-arraybuffer "^1.0.1"
5127 4453
5128mem@^1.1.0:
5129 version "1.1.0"
5130 resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
5131 integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
5132 dependencies:
5133 mimic-fn "^1.0.0"
5134
5135mem@^4.0.0: 4454mem@^4.0.0:
5136 version "4.3.0" 4455 version "4.3.0"
5137 resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" 4456 resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
@@ -5175,7 +4494,7 @@ methods@^1.1.1, methods@^1.1.2, methods@~1.1.2:
5175 resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 4494 resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
5176 integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 4495 integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
5177 4496
5178micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: 4497micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
5179 version "3.1.10" 4498 version "3.1.10"
5180 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" 4499 resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
5181 integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== 4500 integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -5194,37 +4513,32 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8:
5194 snapdragon "^0.8.1" 4513 snapdragon "^0.8.1"
5195 to-regex "^3.0.2" 4514 to-regex "^3.0.2"
5196 4515
5197mime-db@~1.38.0: 4516mime-db@1.40.0:
5198 version "1.38.0" 4517 version "1.40.0"
5199 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" 4518 resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
5200 integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== 4519 integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
5201 4520
5202mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.18, mime-types@~2.1.19, mime-types@~2.1.6: 4521mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.6:
5203 version "2.1.22" 4522 version "2.1.24"
5204 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" 4523 resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
5205 integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== 4524 integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
5206 dependencies: 4525 dependencies:
5207 mime-db "~1.38.0" 4526 mime-db "1.40.0"
5208 4527
5209mime@1.3.4: 4528mime@1.3.4:
5210 version "1.3.4" 4529 version "1.3.4"
5211 resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" 4530 resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
5212 integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM= 4531 integrity sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=
5213 4532
5214mime@1.4.1: 4533mime@1.6.0, mime@^1.3.4, mime@^1.4.1:
5215 version "1.4.1"
5216 resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
5217 integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
5218
5219mime@^1.3.4, mime@^1.4.1:
5220 version "1.6.0" 4534 version "1.6.0"
5221 resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 4535 resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
5222 integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 4536 integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
5223 4537
5224mime@^2.4.0: 4538mime@^2.4.0:
5225 version "2.4.0" 4539 version "2.4.3"
5226 resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6" 4540 resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.3.tgz#229687331e86f68924e6cb59e1cdd937f18275fe"
5227 integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w== 4541 integrity sha512-QgrPRJfE+riq5TPZMcHZOtm8c6K/yYrMbKIoRfapfiGLxS8OTeIfRhUGW5LU7MlRa52KOAGCfUNruqLrIBvWZw==
5228 4542
5229mimelib@^0.3.0: 4543mimelib@^0.3.0:
5230 version "0.3.1" 4544 version "0.3.1"
@@ -5276,7 +4590,7 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0:
5276 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 4590 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
5277 integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= 4591 integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
5278 4592
5279minipass@^2.2.1, minipass@^2.3.4, minipass@^2.3.5: 4593minipass@^2.2.1, minipass@^2.3.5:
5280 version "2.3.5" 4594 version "2.3.5"
5281 resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" 4595 resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
5282 integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== 4596 integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
@@ -5284,29 +4598,13 @@ minipass@^2.2.1, minipass@^2.3.4, minipass@^2.3.5:
5284 safe-buffer "^5.1.2" 4598 safe-buffer "^5.1.2"
5285 yallist "^3.0.0" 4599 yallist "^3.0.0"
5286 4600
5287minizlib@^1.1.1: 4601minizlib@^1.2.1:
5288 version "1.2.1" 4602 version "1.2.1"
5289 resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" 4603 resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614"
5290 integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== 4604 integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==
5291 dependencies: 4605 dependencies:
5292 minipass "^2.2.1" 4606 minipass "^2.2.1"
5293 4607
5294mississippi@^3.0.0:
5295 version "3.0.0"
5296 resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
5297 integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==
5298 dependencies:
5299 concat-stream "^1.5.0"
5300 duplexify "^3.4.2"
5301 end-of-stream "^1.1.0"
5302 flush-write-stream "^1.0.0"
5303 from2 "^2.1.0"
5304 parallel-transform "^1.1.0"
5305 pump "^3.0.0"
5306 pumpify "^1.3.3"
5307 stream-each "^1.1.0"
5308 through2 "^2.0.0"
5309
5310mixin-deep@^1.2.0: 4608mixin-deep@^1.2.0:
5311 version "1.3.1" 4609 version "1.3.1"
5312 resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" 4610 resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
@@ -5315,46 +4613,56 @@ mixin-deep@^1.2.0:
5315 for-in "^1.0.2" 4613 for-in "^1.0.2"
5316 is-extendable "^1.0.1" 4614 is-extendable "^1.0.1"
5317 4615
5318mkdirp@0.5.1, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: 4616mkdirp@0.5.1, mkdirp@0.x.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1:
5319 version "0.5.1" 4617 version "0.5.1"
5320 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 4618 resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
5321 integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 4619 integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
5322 dependencies: 4620 dependencies:
5323 minimist "0.0.8" 4621 minimist "0.0.8"
5324 4622
4623mocha-parallel-tests@^2.1.0:
4624 version "2.1.2"
4625 resolved "https://registry.yarnpkg.com/mocha-parallel-tests/-/mocha-parallel-tests-2.1.2.tgz#2f5c24022257e5fc6c63a6bd22d49c1487496b26"
4626 integrity sha512-FatFg3MHLio9ir1oP6J0HNEo6R5344JC1y+We90iALdiT9F9xNPN0KbGXxRNlGlSl0GodfSESKbRzBvT9ctgIw==
4627 dependencies:
4628 circular-json "^0.5.9"
4629 debug "^4.1.1"
4630 uuid "^3.3.2"
4631 yargs "^13.2.2"
4632
5325mocha@^6.0.0: 4633mocha@^6.0.0:
5326 version "6.0.2" 4634 version "6.1.4"
5327 resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.0.2.tgz#cdc1a6fdf66472c079b5605bac59d29807702d2c" 4635 resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.1.4.tgz#e35fada242d5434a7e163d555c705f6875951640"
5328 integrity sha512-RtTJsmmToGyeTznSOMoM6TPEk1A84FQaHIciKrRqARZx+B5ccJ5tXlmJzEKGBxZdqk9UjpRsesZTUkZmR5YnuQ== 4636 integrity sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==
5329 dependencies: 4637 dependencies:
5330 ansi-colors "3.2.3" 4638 ansi-colors "3.2.3"
5331 browser-stdout "1.3.1" 4639 browser-stdout "1.3.1"
5332 debug "3.2.6" 4640 debug "3.2.6"
5333 diff "3.5.0" 4641 diff "3.5.0"
5334 escape-string-regexp "1.0.5" 4642 escape-string-regexp "1.0.5"
5335 findup-sync "2.0.0" 4643 find-up "3.0.0"
5336 glob "7.1.3" 4644 glob "7.1.3"
5337 growl "1.10.5" 4645 growl "1.10.5"
5338 he "1.2.0" 4646 he "1.2.0"
5339 js-yaml "3.12.0" 4647 js-yaml "3.13.1"
5340 log-symbols "2.2.0" 4648 log-symbols "2.2.0"
5341 minimatch "3.0.4" 4649 minimatch "3.0.4"
5342 mkdirp "0.5.1" 4650 mkdirp "0.5.1"
5343 ms "2.1.1" 4651 ms "2.1.1"
5344 node-environment-flags "1.0.4" 4652 node-environment-flags "1.0.5"
5345 object.assign "4.1.0" 4653 object.assign "4.1.0"
5346 strip-json-comments "2.0.1" 4654 strip-json-comments "2.0.1"
5347 supports-color "6.0.0" 4655 supports-color "6.0.0"
5348 which "1.3.1" 4656 which "1.3.1"
5349 wide-align "1.1.3" 4657 wide-align "1.1.3"
5350 yargs "12.0.5" 4658 yargs "13.2.2"
5351 yargs-parser "11.1.1" 4659 yargs-parser "13.0.0"
5352 yargs-unparser "1.5.0" 4660 yargs-unparser "1.5.0"
5353 4661
5354moment-timezone@^0.5.21, moment-timezone@^0.5.23: 4662moment-timezone@^0.5.21, moment-timezone@^0.5.23:
5355 version "0.5.23" 4663 version "0.5.25"
5356 resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" 4664 resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.25.tgz#a11bfa2f74e088327f2cd4c08b3e7bdf55957810"
5357 integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== 4665 integrity sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw==
5358 dependencies: 4666 dependencies:
5359 moment ">= 2.9.0" 4667 moment ">= 2.9.0"
5360 4668
@@ -5374,18 +4682,6 @@ morgan@^1.5.3:
5374 on-finished "~2.3.0" 4682 on-finished "~2.3.0"
5375 on-headers "~1.0.1" 4683 on-headers "~1.0.1"
5376 4684
5377move-concurrently@^1.0.1:
5378 version "1.0.1"
5379 resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
5380 integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=
5381 dependencies:
5382 aproba "^1.1.1"
5383 copy-concurrently "^1.0.0"
5384 fs-write-stream-atomic "^1.0.8"
5385 mkdirp "^0.5.1"
5386 rimraf "^2.5.4"
5387 run-queue "^1.0.3"
5388
5389mp4-box-encoding@^1.1.0, mp4-box-encoding@^1.3.0: 4685mp4-box-encoding@^1.1.0, mp4-box-encoding@^1.3.0:
5390 version "1.3.0" 4686 version "1.3.0"
5391 resolved "https://registry.yarnpkg.com/mp4-box-encoding/-/mp4-box-encoding-1.3.0.tgz#2a6f750947ff68c3a498fd76cd6424c53d995d48" 4687 resolved "https://registry.yarnpkg.com/mp4-box-encoding/-/mp4-box-encoding-1.3.0.tgz#2a6f750947ff68c3a498fd76cd6424c53d995d48"
@@ -5421,7 +4717,7 @@ ms@2.0.0:
5421 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 4717 resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
5422 integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 4718 integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
5423 4719
5424ms@2.1.1, ms@^2.0.0, ms@^2.1.1: 4720ms@2.1.1, ms@^2.1.1:
5425 version "2.1.1" 4721 version "2.1.1"
5426 resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 4722 resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
5427 integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 4723 integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
@@ -5458,16 +4754,16 @@ mute-stream@~0.0.4:
5458 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" 4754 resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
5459 integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== 4755 integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
5460 4756
5461nan@2.13.1: 4757nan@2.13.2:
5462 version "2.13.1"
5463 resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd"
5464 integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA==
5465
5466nan@^2.10.0, nan@^2.13.1, nan@^2.9.2:
5467 version "2.13.2" 4758 version "2.13.2"
5468 resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" 4759 resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
5469 integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== 4760 integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
5470 4761
4762nan@^2.10.0, nan@^2.12.1, nan@^2.13.2:
4763 version "2.14.0"
4764 resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
4765 integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
4766
5471nan@~2.10.0: 4767nan@~2.10.0:
5472 version "2.10.0" 4768 version "2.10.0"
5473 resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" 4769 resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f"
@@ -5501,11 +4797,11 @@ ncp@1.0.x:
5501 integrity sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY= 4797 integrity sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=
5502 4798
5503needle@^2.2.1: 4799needle@^2.2.1:
5504 version "2.2.4" 4800 version "2.4.0"
5505 resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" 4801 resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
5506 integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== 4802 integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==
5507 dependencies: 4803 dependencies:
5508 debug "^2.1.2" 4804 debug "^3.2.6"
5509 iconv-lite "^0.4.4" 4805 iconv-lite "^0.4.4"
5510 sax "^1.2.4" 4806 sax "^1.2.4"
5511 4807
@@ -5519,19 +4815,16 @@ negotiator@0.6.1:
5519 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 4815 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
5520 integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= 4816 integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
5521 4817
4818negotiator@0.6.2:
4819 version "0.6.2"
4820 resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
4821 integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
4822
5522netmask@^1.0.6: 4823netmask@^1.0.6:
5523 version "1.0.6" 4824 version "1.0.6"
5524 resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" 4825 resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35"
5525 integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= 4826 integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=
5526 4827
5527netrc-parser@^3.1.6:
5528 version "3.1.6"
5529 resolved "https://registry.yarnpkg.com/netrc-parser/-/netrc-parser-3.1.6.tgz#7243c9ec850b8e805b9bdc7eae7b1450d4a96e72"
5530 integrity sha512-lY+fmkqSwntAAjfP63jB4z5p5WbuZwyMCD3pInT7dpHU/Gc6Vv90SAC6A0aNiqaRGHiuZFBtiwu+pu8W/Eyotw==
5531 dependencies:
5532 debug "^3.1.0"
5533 execa "^0.10.0"
5534
5535next-event@^1.0.0: 4828next-event@^1.0.0:
5536 version "1.0.0" 4829 version "1.0.0"
5537 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8" 4830 resolved "https://registry.yarnpkg.com/next-event/-/next-event-1.0.0.tgz#e7778acde2e55802e0ad1879c39cf6f75eda61d8"
@@ -5547,38 +4840,25 @@ nice-try@^1.0.4:
5547 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" 4840 resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
5548 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== 4841 integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
5549 4842
5550nocache@2.0.0: 4843nocache@2.1.0:
5551 version "2.0.0" 4844 version "2.1.0"
5552 resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.0.0.tgz#202b48021a0c4cbde2df80de15a17443c8b43980" 4845 resolved "https://registry.yarnpkg.com/nocache/-/nocache-2.1.0.tgz#120c9ffec43b5729b1d5de88cd71aa75a0ba491f"
5553 integrity sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA= 4846 integrity sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==
5554 4847
5555node-abi@^2.7.0: 4848node-abi@^2.7.0:
5556 version "2.7.1" 4849 version "2.8.0"
5557 resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.7.1.tgz#a8997ae91176a5fbaa455b194976e32683cda643" 4850 resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.8.0.tgz#bd2e88dbe6a6871e6dd08553e0605779325737ec"
5558 integrity sha512-OV8Bq1OrPh6z+Y4dqwo05HqrRL9YNF7QVMRfq1/pguwKLG+q9UB/Lk0x5qXjO23JjJg+/jqCHSTaG1P3tfKfuw== 4851 integrity sha512-1/aa2clS0pue0HjckL62CsbhWWU35HARvBDXcJtYKbYR7LnIutmpxmXbuDMV9kEviD2lP/wACOgWmmwljghHyQ==
5559 dependencies: 4852 dependencies:
5560 semver "^5.4.1" 4853 semver "^5.4.1"
5561 4854
5562node-addon-api@^1.6.0: 4855node-environment-flags@1.0.5:
5563 version "1.6.2" 4856 version "1.0.5"
5564 resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217" 4857 resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a"
5565 integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA== 4858 integrity sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==
5566
5567node-environment-flags@1.0.4:
5568 version "1.0.4"
5569 resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.4.tgz#0b784a6551426bfc16d3b2208424dcbc2b2ff038"
5570 integrity sha512-M9rwCnWVLW7PX+NUWe3ejEdiLYinRpsEre9hMkU/6NS4h+EEulYaDH1gCEZ2gyXsmw+RXYDaV2JkkTNcsPDJ0Q==
5571 dependencies: 4859 dependencies:
5572 object.getownpropertydescriptors "^2.0.3" 4860 object.getownpropertydescriptors "^2.0.3"
5573 4861 semver "^5.7.0"
5574node-fetch-npm@^2.0.2:
5575 version "2.0.2"
5576 resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
5577 integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==
5578 dependencies:
5579 encoding "^0.1.11"
5580 json-parse-better-errors "^1.0.0"
5581 safe-buffer "^5.1.1"
5582 4862
5583node-forge@^0.7.1: 4863node-forge@^0.7.1:
5584 version "0.7.6" 4864 version "0.7.6"
@@ -5590,25 +4870,7 @@ node-gyp-build@~3.7.0:
5590 resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" 4870 resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
5591 integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== 4871 integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
5592 4872
5593node-gyp@^3.8.0: 4873node-pre-gyp@0.12.0, node-pre-gyp@^0.12.0:
5594 version "3.8.0"
5595 resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
5596 integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==
5597 dependencies:
5598 fstream "^1.0.0"
5599 glob "^7.0.3"
5600 graceful-fs "^4.1.2"
5601 mkdirp "^0.5.0"
5602 nopt "2 || 3"
5603 npmlog "0 || 1 || 2 || 3 || 4"
5604 osenv "0"
5605 request "^2.87.0"
5606 rimraf "2"
5607 semver "~5.3.0"
5608 tar "^2.0.0"
5609 which "1"
5610
5611node-pre-gyp@0.12.0:
5612 version "0.12.0" 4874 version "0.12.0"
5613 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" 4875 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149"
5614 integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== 4876 integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==
@@ -5624,22 +4886,6 @@ node-pre-gyp@0.12.0:
5624 semver "^5.3.0" 4886 semver "^5.3.0"
5625 tar "^4" 4887 tar "^4"
5626 4888
5627node-pre-gyp@^0.10.0:
5628 version "0.10.3"
5629 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
5630 integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==
5631 dependencies:
5632 detect-libc "^1.0.2"
5633 mkdirp "^0.5.1"
5634 needle "^2.2.1"
5635 nopt "^4.0.1"
5636 npm-packlist "^1.1.6"
5637 npmlog "^4.0.2"
5638 rc "^1.2.7"
5639 rimraf "^2.6.1"
5640 semver "^5.3.0"
5641 tar "^4"
5642
5643node-pre-gyp@~0.11.0: 4889node-pre-gyp@~0.11.0:
5644 version "0.11.0" 4890 version "0.11.0"
5645 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054" 4891 resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz#db1f33215272f692cd38f03238e3e9b47c5dd054"
@@ -5681,16 +4927,16 @@ nodemailer-shared@^1.1.0:
5681 nodemailer-fetch "1.6.0" 4927 nodemailer-fetch "1.6.0"
5682 4928
5683nodemailer@^6.0.0: 4929nodemailer@^6.0.0:
5684 version "6.0.0" 4930 version "6.2.1"
5685 resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.0.0.tgz#d9761128771739dc87c1fdd747f569b7f135cb02" 4931 resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.2.1.tgz#20d773925eb8f7a06166a0b62c751dc8290429f3"
5686 integrity sha512-PMQJyLhoNAMoBU1hEh5aaUkpa/tcDNwzS7s7zow/myKfoEoZewMxUuWZqQ5yjYsAnvE484KSkYH5s6iPvcjhCg== 4932 integrity sha512-TagB7iuIi9uyNgHExo8lUDq3VK5/B0BpbkcjIgNvxbtVrjNqq0DwAOTuzALPVkK76kMhTSzIgHqg8X1uklVs6g==
5687 4933
5688nodemon@^1.18.6: 4934nodemon@^1.18.6:
5689 version "1.18.10" 4935 version "1.19.1"
5690 resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.10.tgz#3ba63f64eb4c283cf3e4f75f30817e9d4f393afe" 4936 resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.1.tgz#576f0aad0f863aabf8c48517f6192ff987cd5071"
5691 integrity sha512-we51yBb1TfEvZamFchRgcfLbVYgg0xlGbyXmOtbBzDwxwgewYS/YbZ5tnlnsH51+AoSTTsT3A2E/FloUbtH8cQ== 4937 integrity sha512-/DXLzd/GhiaDXXbGId5BzxP1GlsqtMGM9zTmkWrgXtSqjKmGSbLicM/oAy4FR0YWm14jCHRwnR31AHS2dYFHrg==
5692 dependencies: 4938 dependencies:
5693 chokidar "^2.1.0" 4939 chokidar "^2.1.5"
5694 debug "^3.1.0" 4940 debug "^3.1.0"
5695 ignore-by-default "^1.0.1" 4941 ignore-by-default "^1.0.1"
5696 minimatch "^3.0.4" 4942 minimatch "^3.0.4"
@@ -5706,14 +4952,7 @@ noop-logger@^0.1.1:
5706 resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" 4952 resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2"
5707 integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= 4953 integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=
5708 4954
5709"nopt@2 || 3": 4955nopt@^4.0.1:
5710 version "3.0.6"
5711 resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
5712 integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k=
5713 dependencies:
5714 abbrev "1"
5715
5716nopt@^4.0.1, nopt@~4.0.1:
5717 version "4.0.1" 4956 version "4.0.1"
5718 resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" 4957 resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
5719 integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= 4958 integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=
@@ -5728,7 +4967,7 @@ nopt@~1.0.10:
5728 dependencies: 4967 dependencies:
5729 abbrev "1" 4968 abbrev "1"
5730 4969
5731normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: 4970normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
5732 version "2.5.0" 4971 version "2.5.0"
5733 resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" 4972 resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
5734 integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== 4973 integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
@@ -5750,61 +4989,12 @@ normalize-path@^3.0.0:
5750 resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 4989 resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
5751 integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 4990 integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
5752 4991
5753npm-audit-report@^1.3.2:
5754 version "1.3.2"
5755 resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-1.3.2.tgz#303bc78cd9e4c226415076a4f7e528c89fc77018"
5756 integrity sha512-abeqS5ONyXNaZJPGAf6TOUMNdSe1Y6cpc9MLBRn+CuUoYbfdca6AxOyXVlfIv9OgKX+cacblbG5w7A6ccwoTPw==
5757 dependencies:
5758 cli-table3 "^0.5.0"
5759 console-control-strings "^1.1.0"
5760
5761npm-bundled@^1.0.1: 4992npm-bundled@^1.0.1:
5762 version "1.0.6" 4993 version "1.0.6"
5763 resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" 4994 resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
5764 integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== 4995 integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==
5765 4996
5766npm-cache-filename@~1.0.2: 4997npm-packlist@^1.1.6:
5767 version "1.0.2"
5768 resolved "https://registry.yarnpkg.com/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz#ded306c5b0bfc870a9e9faf823bc5f283e05ae11"
5769 integrity sha1-3tMGxbC/yHCp6fr4I7xfKD4FrhE=
5770
5771npm-install-checks@~3.0.0:
5772 version "3.0.0"
5773 resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-3.0.0.tgz#d4aecdfd51a53e3723b7b2f93b2ee28e307bc0d7"
5774 integrity sha1-1K7N/VGlPjcjt7L5Oy7ijjB7wNc=
5775 dependencies:
5776 semver "^2.3.0 || 3.x || 4 || 5"
5777
5778npm-lifecycle@^2.0.3, npm-lifecycle@^2.1.0:
5779 version "2.1.0"
5780 resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz#1eda2eedb82db929e3a0c50341ab0aad140ed569"
5781 integrity sha512-QbBfLlGBKsktwBZLj6AviHC6Q9Y3R/AY4a2PYSIRhSKSS0/CxRyD/PfxEX6tPeOCXQgMSNdwGeECacstgptc+g==
5782 dependencies:
5783 byline "^5.0.0"
5784 graceful-fs "^4.1.11"
5785 node-gyp "^3.8.0"
5786 resolve-from "^4.0.0"
5787 slide "^1.1.6"
5788 uid-number "0.0.6"
5789 umask "^1.1.0"
5790 which "^1.3.1"
5791
5792npm-logical-tree@^1.2.1:
5793 version "1.2.1"
5794 resolved "https://registry.yarnpkg.com/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz#44610141ca24664cad35d1e607176193fd8f5b88"
5795 integrity sha512-AJI/qxDB2PWI4LG1CYN579AY1vCiNyWfkiquCsJWqntRu/WwimVrC8yXeILBFHDwxfOejxewlmnvW9XXjMlYIg==
5796
5797"npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0:
5798 version "6.1.0"
5799 resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1"
5800 integrity sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==
5801 dependencies:
5802 hosted-git-info "^2.6.0"
5803 osenv "^0.1.5"
5804 semver "^5.5.0"
5805 validate-npm-package-name "^3.0.0"
5806
5807npm-packlist@^1.1.12, npm-packlist@^1.1.6, npm-packlist@^1.4.1:
5808 version "1.4.1" 4998 version "1.4.1"
5809 resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" 4999 resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc"
5810 integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== 5000 integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==
@@ -5819,36 +5009,6 @@ npm-path@^2.0.2:
5819 dependencies: 5009 dependencies:
5820 which "^1.2.10" 5010 which "^1.2.10"
5821 5011
5822npm-pick-manifest@^2.2.3:
5823 version "2.2.3"
5824 resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40"
5825 integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==
5826 dependencies:
5827 figgy-pudding "^3.5.1"
5828 npm-package-arg "^6.0.0"
5829 semver "^5.4.1"
5830
5831npm-profile@^4.0.1:
5832 version "4.0.1"
5833 resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-4.0.1.tgz#d350f7a5e6b60691c7168fbb8392c3603583f5aa"
5834 integrity sha512-NQ1I/1Q7YRtHZXkcuU1/IyHeLy6pd+ScKg4+DQHdfsm769TGq6HPrkbuNJVJS4zwE+0mvvmeULzQdWn2L2EsVA==
5835 dependencies:
5836 aproba "^1.1.2 || 2"
5837 figgy-pudding "^3.4.1"
5838 npm-registry-fetch "^3.8.0"
5839
5840npm-registry-fetch@^3.8.0, npm-registry-fetch@^3.9.0:
5841 version "3.9.0"
5842 resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.9.0.tgz#44d841780e2833f06accb34488f8c7450d1a6856"
5843 integrity sha512-srwmt8YhNajAoSAaDWndmZgx89lJwIZ1GWxOuckH4Coek4uHv5S+o/l9FLQe/awA+JwTnj4FJHldxhlXdZEBmw==
5844 dependencies:
5845 JSONStream "^1.3.4"
5846 bluebird "^3.5.1"
5847 figgy-pudding "^3.4.1"
5848 lru-cache "^4.1.3"
5849 make-fetch-happen "^4.0.1"
5850 npm-package-arg "^6.1.0"
5851
5852npm-run-path@^2.0.0: 5012npm-run-path@^2.0.0:
5853 version "2.0.2" 5013 version "2.0.2"
5854 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" 5014 resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -5856,11 +5016,6 @@ npm-run-path@^2.0.0:
5856 dependencies: 5016 dependencies:
5857 path-key "^2.0.0" 5017 path-key "^2.0.0"
5858 5018
5859npm-user-validate@~1.0.0:
5860 version "1.0.0"
5861 resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951"
5862 integrity sha1-jOyg9c6gTU6TUZ73LQVXp1Ei6VE=
5863
5864npm-which@^3.0.1: 5019npm-which@^3.0.1:
5865 version "3.0.1" 5020 version "3.0.1"
5866 resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" 5021 resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa"
@@ -5870,121 +5025,7 @@ npm-which@^3.0.1:
5870 npm-path "^2.0.2" 5025 npm-path "^2.0.2"
5871 which "^1.2.10" 5026 which "^1.2.10"
5872 5027
5873npm@*: 5028npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2:
5874 version "6.9.0"
5875 resolved "https://registry.yarnpkg.com/npm/-/npm-6.9.0.tgz#5296720486814a64a7fb082de00c4b5cfd11211f"
5876 integrity sha512-91V+zB5hDxO+Jyp2sUKS7juHlIM95dGQxTeQtmZI1nAI/7kjWXFipPrtwwKjhyKmV4GsS2LzJhrxRjGWsU9z/w==
5877 dependencies:
5878 JSONStream "^1.3.5"
5879 abbrev "~1.1.1"
5880 ansicolors "~0.3.2"
5881 ansistyles "~0.1.3"
5882 aproba "^2.0.0"
5883 archy "~1.0.0"
5884 bin-links "^1.1.2"
5885 bluebird "^3.5.3"
5886 byte-size "^5.0.1"
5887 cacache "^11.3.2"
5888 call-limit "~1.1.0"
5889 chownr "^1.1.1"
5890 ci-info "^2.0.0"
5891 cli-columns "^3.1.2"
5892 cli-table3 "^0.5.1"
5893 cmd-shim "~2.0.2"
5894 columnify "~1.5.4"
5895 config-chain "^1.1.12"
5896 detect-indent "~5.0.0"
5897 detect-newline "^2.1.0"
5898 dezalgo "~1.0.3"
5899 editor "~1.0.0"
5900 figgy-pudding "^3.5.1"
5901 find-npm-prefix "^1.0.2"
5902 fs-vacuum "~1.2.10"
5903 fs-write-stream-atomic "~1.0.10"
5904 gentle-fs "^2.0.1"
5905 glob "^7.1.3"
5906 graceful-fs "^4.1.15"
5907 has-unicode "~2.0.1"
5908 hosted-git-info "^2.7.1"
5909 iferr "^1.0.2"
5910 inflight "~1.0.6"
5911 inherits "~2.0.3"
5912 ini "^1.3.5"
5913 init-package-json "^1.10.3"
5914 is-cidr "^3.0.0"
5915 json-parse-better-errors "^1.0.2"
5916 lazy-property "~1.0.0"
5917 libcipm "^3.0.3"
5918 libnpm "^2.0.1"
5919 libnpmhook "^5.0.2"
5920 libnpx "^10.2.0"
5921 lock-verify "^2.1.0"
5922 lockfile "^1.0.4"
5923 lodash._baseuniq "~4.6.0"
5924 lodash.clonedeep "~4.5.0"
5925 lodash.union "~4.6.0"
5926 lodash.uniq "~4.5.0"
5927 lodash.without "~4.4.0"
5928 lru-cache "^4.1.5"
5929 meant "~1.0.1"
5930 mississippi "^3.0.0"
5931 mkdirp "~0.5.1"
5932 move-concurrently "^1.0.1"
5933 node-gyp "^3.8.0"
5934 nopt "~4.0.1"
5935 normalize-package-data "^2.5.0"
5936 npm-audit-report "^1.3.2"
5937 npm-cache-filename "~1.0.2"
5938 npm-install-checks "~3.0.0"
5939 npm-lifecycle "^2.1.0"
5940 npm-package-arg "^6.1.0"
5941 npm-packlist "^1.4.1"
5942 npm-pick-manifest "^2.2.3"
5943 npm-registry-fetch "^3.9.0"
5944 npm-user-validate "~1.0.0"
5945 npmlog "~4.1.2"
5946 once "~1.4.0"
5947 opener "^1.5.1"
5948 osenv "^0.1.5"
5949 pacote "^9.5.0"
5950 path-is-inside "~1.0.2"
5951 promise-inflight "~1.0.1"
5952 qrcode-terminal "^0.12.0"
5953 query-string "^6.2.0"
5954 qw "~1.0.1"
5955 read "~1.0.7"
5956 read-cmd-shim "~1.0.1"
5957 read-installed "~4.0.3"
5958 read-package-json "^2.0.13"
5959 read-package-tree "^5.2.2"
5960 readable-stream "^3.1.1"
5961 request "^2.88.0"
5962 retry "^0.12.0"
5963 rimraf "^2.6.3"
5964 safe-buffer "^5.1.2"
5965 semver "^5.6.0"
5966 sha "~2.0.1"
5967 slide "~1.1.6"
5968 sorted-object "~2.0.1"
5969 sorted-union-stream "~2.1.3"
5970 ssri "^6.0.1"
5971 stringify-package "^1.0.0"
5972 tar "^4.4.8"
5973 text-table "~0.2.0"
5974 tiny-relative-date "^1.3.0"
5975 uid-number "0.0.6"
5976 umask "~1.1.0"
5977 unique-filename "^1.1.1"
5978 unpipe "~1.0.0"
5979 update-notifier "^2.5.0"
5980 uuid "^3.3.2"
5981 validate-npm-package-license "^3.0.4"
5982 validate-npm-package-name "~3.0.0"
5983 which "^1.3.1"
5984 worker-farm "^1.6.0"
5985 write-file-atomic "^2.4.2"
5986
5987"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2, npmlog@~4.1.2:
5988 version "4.1.2" 5029 version "4.1.2"
5989 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 5030 resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
5990 integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== 5031 integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -6041,9 +5082,9 @@ object-copy@^0.1.0:
6041 kind-of "^3.0.3" 5082 kind-of "^3.0.3"
6042 5083
6043object-keys@^1.0.11, object-keys@^1.0.12: 5084object-keys@^1.0.11, object-keys@^1.0.12:
6044 version "1.1.0" 5085 version "1.1.1"
6045 resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" 5086 resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
6046 integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== 5087 integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
6047 5088
6048object-visit@^1.0.0: 5089object-visit@^1.0.0:
6049 version "1.0.1" 5090 version "1.0.1"
@@ -6089,7 +5130,7 @@ on-headers@~1.0.1:
6089 resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" 5130 resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
6090 integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== 5131 integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
6091 5132
6092once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: 5133once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0:
6093 version "1.4.0" 5134 version "1.4.0"
6094 resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 5135 resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
6095 integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 5136 integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@@ -6134,11 +5175,6 @@ openapi-schema-validation@^0.4.2:
6134 jsonschema-draft4 "^1.0.0" 5175 jsonschema-draft4 "^1.0.0"
6135 swagger-schema-official "2.0.0-bab6bed" 5176 swagger-schema-official "2.0.0-bab6bed"
6136 5177
6137opener@^1.5.1:
6138 version "1.5.1"
6139 resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
6140 integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
6141
6142optionator@^0.8.1: 5178optionator@^0.8.1:
6143 version "0.8.2" 5179 version "0.8.2"
6144 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 5180 resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
@@ -6161,16 +5197,7 @@ os-homedir@^1.0.0, os-homedir@^1.0.1:
6161 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 5197 resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
6162 integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= 5198 integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
6163 5199
6164os-locale@^2.0.0: 5200os-locale@^3.0.0, os-locale@^3.1.0:
6165 version "2.1.0"
6166 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
6167 integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
6168 dependencies:
6169 execa "^0.7.0"
6170 lcid "^1.0.0"
6171 mem "^1.1.0"
6172
6173os-locale@^3.0.0:
6174 version "3.1.0" 5201 version "3.1.0"
6175 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" 5202 resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
6176 integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== 5203 integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==
@@ -6184,7 +5211,7 @@ os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2:
6184 resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 5211 resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
6185 integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 5212 integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
6186 5213
6187osenv@0, osenv@^0.1.4, osenv@^0.1.5: 5214osenv@^0.1.4:
6188 version "0.1.5" 5215 version "0.1.5"
6189 resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" 5216 resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
6190 integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== 5217 integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
@@ -6203,31 +5230,17 @@ p-finally@^1.0.0:
6203 integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= 5230 integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
6204 5231
6205p-is-promise@^2.0.0: 5232p-is-promise@^2.0.0:
6206 version "2.0.0" 5233 version "2.1.0"
6207 resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5" 5234 resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
6208 integrity sha512-pzQPhYMCAgLAKPWD2jC3Se9fEfrD9npNos0y150EeqZll7akhEgGhTW/slB6lHku8AvYGiJ+YJ5hfHKePPgFWg== 5235 integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==
6209
6210p-limit@^1.1.0:
6211 version "1.3.0"
6212 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
6213 integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==
6214 dependencies:
6215 p-try "^1.0.0"
6216 5236
6217p-limit@^2.0.0: 5237p-limit@^2.0.0, p-limit@^2.2.0:
6218 version "2.2.0" 5238 version "2.2.0"
6219 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" 5239 resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
6220 integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== 5240 integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
6221 dependencies: 5241 dependencies:
6222 p-try "^2.0.0" 5242 p-try "^2.0.0"
6223 5243
6224p-locate@^2.0.0:
6225 version "2.0.0"
6226 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
6227 integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=
6228 dependencies:
6229 p-limit "^1.1.0"
6230
6231p-locate@^3.0.0: 5244p-locate@^3.0.0:
6232 version "3.0.0" 5245 version "3.0.0"
6233 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 5246 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
@@ -6235,28 +5248,30 @@ p-locate@^3.0.0:
6235 dependencies: 5248 dependencies:
6236 p-limit "^2.0.0" 5249 p-limit "^2.0.0"
6237 5250
5251p-locate@^4.1.0:
5252 version "4.1.0"
5253 resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
5254 integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
5255 dependencies:
5256 p-limit "^2.2.0"
5257
6238p-map@^1.1.1: 5258p-map@^1.1.1:
6239 version "1.2.0" 5259 version "1.2.0"
6240 resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" 5260 resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
6241 integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== 5261 integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
6242 5262
6243p-map@^2.0.0: 5263p-map@^2.0.0:
6244 version "2.0.0" 5264 version "2.1.0"
6245 resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.0.0.tgz#be18c5a5adeb8e156460651421aceca56c213a50" 5265 resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
6246 integrity sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w== 5266 integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
6247 5267
6248p-timeout@^2.0.1: 5268p-timeout@^3.1.0:
6249 version "2.0.1" 5269 version "3.1.0"
6250 resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" 5270 resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.1.0.tgz#198c1f503bb973e9b9727177a276c80afd6851f3"
6251 integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== 5271 integrity sha512-C27DYI+tCroT8J8cTEyySGydl2B7FlxrGNF5/wmMbl1V+jeehUCzEE/BVgzRebdm2K3ZitKOKx8YbdFumDyYmw==
6252 dependencies: 5272 dependencies:
6253 p-finally "^1.0.0" 5273 p-finally "^1.0.0"
6254 5274
6255p-try@^1.0.0:
6256 version "1.0.0"
6257 resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
6258 integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=
6259
6260p-try@^2.0.0: 5275p-try@^2.0.0:
6261 version "2.2.0" 5276 version "2.2.0"
6262 resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 5277 resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -6284,48 +5299,6 @@ packet-reader@1.0.0:
6284 resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 5299 resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74"
6285 integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== 5300 integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==
6286 5301
6287pacote@^9.1.0, pacote@^9.2.3, pacote@^9.5.0:
6288 version "9.5.0"
6289 resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.5.0.tgz#85f3013a3f6dd51c108b0ccabd3de8102ddfaeda"
6290 integrity sha512-aUplXozRbzhaJO48FaaeClmN+2Mwt741MC6M3bevIGZwdCaP7frXzbUOfOWa91FPHoLITzG0hYaKY363lxO3bg==
6291 dependencies:
6292 bluebird "^3.5.3"
6293 cacache "^11.3.2"
6294 figgy-pudding "^3.5.1"
6295 get-stream "^4.1.0"
6296 glob "^7.1.3"
6297 lru-cache "^5.1.1"
6298 make-fetch-happen "^4.0.1"
6299 minimatch "^3.0.4"
6300 minipass "^2.3.5"
6301 mississippi "^3.0.0"
6302 mkdirp "^0.5.1"
6303 normalize-package-data "^2.4.0"
6304 npm-package-arg "^6.1.0"
6305 npm-packlist "^1.1.12"
6306 npm-pick-manifest "^2.2.3"
6307 npm-registry-fetch "^3.8.0"
6308 osenv "^0.1.5"
6309 promise-inflight "^1.0.1"
6310 promise-retry "^1.1.1"
6311 protoduck "^5.0.1"
6312 rimraf "^2.6.2"
6313 safe-buffer "^5.1.2"
6314 semver "^5.6.0"
6315 ssri "^6.0.1"
6316 tar "^4.4.8"
6317 unique-filename "^1.1.1"
6318 which "^1.3.1"
6319
6320parallel-transform@^1.1.0:
6321 version "1.1.0"
6322 resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"
6323 integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=
6324 dependencies:
6325 cyclist "~0.2.2"
6326 inherits "^2.0.3"
6327 readable-stream "^2.1.5"
6328
6329parse-json@^4.0.0: 5302parse-json@^4.0.0:
6330 version "4.0.0" 5303 version "4.0.0"
6331 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" 5304 resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -6339,11 +5312,6 @@ parse-numeric-range@^0.0.2:
6339 resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4" 5312 resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4"
6340 integrity sha1-tPCdQTx6282Yf26SM8e0shDJOOQ= 5313 integrity sha1-tPCdQTx6282Yf26SM8e0shDJOOQ=
6341 5314
6342parse-passwd@^1.0.0:
6343 version "1.0.0"
6344 resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
6345 integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
6346
6347parse-torrent@^6.0.0, parse-torrent@^6.1.2: 5315parse-torrent@^6.0.0, parse-torrent@^6.1.2:
6348 version "6.1.2" 5316 version "6.1.2"
6349 resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-6.1.2.tgz#99da5bdd23435a1cb7e8e7a63847c4efb21b1956" 5317 resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-6.1.2.tgz#99da5bdd23435a1cb7e8e7a63847c4efb21b1956"
@@ -6378,10 +5346,10 @@ parseuri@0.0.5:
6378 dependencies: 5346 dependencies:
6379 better-assert "~1.0.0" 5347 better-assert "~1.0.0"
6380 5348
6381parseurl@~1.3.1, parseurl@~1.3.2: 5349parseurl@~1.3.1, parseurl@~1.3.3:
6382 version "1.3.2" 5350 version "1.3.3"
6383 resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 5351 resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
6384 integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= 5352 integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
6385 5353
6386pascalcase@^0.1.1: 5354pascalcase@^0.1.1:
6387 version "0.1.1" 5355 version "0.1.1"
@@ -6410,7 +5378,7 @@ path-is-absolute@^1.0.0:
6410 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 5378 resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
6411 integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 5379 integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
6412 5380
6413path-is-inside@^1.0.1, path-is-inside@^1.0.2, path-is-inside@~1.0.2: 5381path-is-inside@^1.0.1, path-is-inside@^1.0.2:
6414 version "1.0.2" 5382 version "1.0.2"
6415 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 5383 resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
6416 integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 5384 integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
@@ -6482,20 +5450,20 @@ pg-pool@^2.0.4:
6482 integrity sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g== 5450 integrity sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==
6483 5451
6484pg-types@~2.0.0: 5452pg-types@~2.0.0:
6485 version "2.0.0" 5453 version "2.0.1"
6486 resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.0.0.tgz#038ddc302a0340efcdb46d0581cc7caa2303cbba" 5454 resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.0.1.tgz#b8585a37f2a9c7b386747e44574799549e5f4933"
6487 integrity sha512-THUD7gQll5tys+5eQ8Rvs7DjHiIC3bLqixk3gMN9Hu8UrCBAOjf35FoI39rTGGc3lM2HU/R+Knpxvd11mCwOMA== 5455 integrity sha512-b7y6QM1VF5nOeX9ukMQ0h8a9z89mojrBHXfJeSug4mhL0YpxNBm83ot2TROyoAmX/ZOX3UbwVO4EbH7i1ZZNiw==
6488 dependencies: 5456 dependencies:
6489 pg-int8 "1.0.1" 5457 pg-int8 "1.0.1"
6490 postgres-array "~2.0.0" 5458 postgres-array "~2.0.0"
6491 postgres-bytea "~1.0.0" 5459 postgres-bytea "~1.0.0"
6492 postgres-date "~1.0.0" 5460 postgres-date "~1.0.4"
6493 postgres-interval "^1.1.0" 5461 postgres-interval "^1.1.0"
6494 5462
6495pg@^7.4.1: 5463pg@^7.4.1:
6496 version "7.9.0" 5464 version "7.11.0"
6497 resolved "https://registry.yarnpkg.com/pg/-/pg-7.9.0.tgz#04f0024d810544463f47dbb5aada2486aa7dcc36" 5465 resolved "https://registry.yarnpkg.com/pg/-/pg-7.11.0.tgz#a8b9ae9cf19199b7952b72957573d0a9c5e67c0c"
6498 integrity sha512-GkzteBFpsIoIBCSuomqik3IGvhqAtTr32jclR24RmUg170Jrn6ypwR97YalFHrsE1iaW8T0aAH13dmij8QUQ0g== 5466 integrity sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==
6499 dependencies: 5467 dependencies:
6500 buffer-writer "2.0.0" 5468 buffer-writer "2.0.0"
6501 packet-reader "1.0.0" 5469 packet-reader "1.0.0"
@@ -6541,12 +5509,12 @@ pinkie@^2.0.0:
6541 resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 5509 resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
6542 integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= 5510 integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
6543 5511
6544pkg-dir@^3.0.0: 5512pkg-dir@^4.1.0:
6545 version "3.0.0" 5513 version "4.2.0"
6546 resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" 5514 resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
6547 integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== 5515 integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
6548 dependencies: 5516 dependencies:
6549 find-up "^3.0.0" 5517 find-up "^4.0.0"
6550 5518
6551pkginfo@0.3.x: 5519pkginfo@0.3.x:
6552 version "0.3.1" 5520 version "0.3.1"
@@ -6590,10 +5558,10 @@ postgres-bytea@~1.0.0:
6590 resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 5558 resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35"
6591 integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU= 5559 integrity sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=
6592 5560
6593postgres-date@~1.0.0: 5561postgres-date@~1.0.4:
6594 version "1.0.3" 5562 version "1.0.4"
6595 resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.3.tgz#e2d89702efdb258ff9d9cee0fe91bd06975257a8" 5563 resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.4.tgz#1c2728d62ef1bff49abdd35c1f86d4bdf118a728"
6596 integrity sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g= 5564 integrity sha512-bESRvKVuTrjoBluEcpv2346+6kgB7UlnqWZsnbnCccTNq/pqfj1j6oBaN5+b/NrDXepYUT/HKadqv3iS9lJuVA==
6597 5565
6598postgres-interval@^1.1.0: 5566postgres-interval@^1.1.0:
6599 version "1.2.0" 5567 version "1.2.0"
@@ -6602,10 +5570,10 @@ postgres-interval@^1.1.0:
6602 dependencies: 5570 dependencies:
6603 xtend "^4.0.0" 5571 xtend "^4.0.0"
6604 5572
6605prebuild-install@^5.2.5: 5573prebuild-install@^5.3.0:
6606 version "5.2.5" 5574 version "5.3.0"
6607 resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.5.tgz#c7485911fe98950b7f7cd15bb9daee11b875cc44" 5575 resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8"
6608 integrity sha512-6uZgMVg7yDfqlP5CPurVhtq3hUKBFNufiar4J5hZrlHTo59DDBEtyxw01xCdFss9j0Zb9+qzFVf/s4niayba3w== 5576 integrity sha512-aaLVANlj4HgZweKttFNUVNRxDukytuIuxeK2boIMHjagNJCiVKWFsKF4tCE3ql3GbrD2tExPQ7/pwtEJcHNZeg==
6609 dependencies: 5577 dependencies:
6610 detect-libc "^1.0.3" 5578 detect-libc "^1.0.3"
6611 expand-template "^2.0.3" 5579 expand-template "^2.0.3"
@@ -6644,19 +5612,6 @@ progress@^1.1.8:
6644 resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" 5612 resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
6645 integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= 5613 integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=
6646 5614
6647promise-inflight@^1.0.1, promise-inflight@~1.0.1:
6648 version "1.0.1"
6649 resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
6650 integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
6651
6652promise-retry@^1.1.1:
6653 version "1.1.1"
6654 resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
6655 integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=
6656 dependencies:
6657 err-code "^1.0.0"
6658 retry "^0.10.0"
6659
6660promise.prototype.finally@^3.1.0: 5615promise.prototype.finally@^3.1.0:
6661 version "3.1.0" 5616 version "3.1.0"
6662 resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" 5617 resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e"
@@ -6687,30 +5642,11 @@ prompt@^1.0.0:
6687 utile "0.3.x" 5642 utile "0.3.x"
6688 winston "2.1.x" 5643 winston "2.1.x"
6689 5644
6690promzard@^0.3.0:
6691 version "0.3.0"
6692 resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee"
6693 integrity sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=
6694 dependencies:
6695 read "1"
6696
6697property-expr@^1.5.0: 5645property-expr@^1.5.0:
6698 version "1.5.1" 5646 version "1.5.1"
6699 resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" 5647 resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f"
6700 integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== 5648 integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==
6701 5649
6702proto-list@~1.2.1:
6703 version "1.2.4"
6704 resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
6705 integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
6706
6707protoduck@^5.0.1:
6708 version "5.0.1"
6709 resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f"
6710 integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==
6711 dependencies:
6712 genfun "^5.0.0"
6713
6714proxy-addr@~1.0.10: 5650proxy-addr@~1.0.10:
6715 version "1.0.10" 5651 version "1.0.10"
6716 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.0.10.tgz#0d40a82f801fc355567d2ecb65efe3f077f121c5" 5652 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.0.10.tgz#0d40a82f801fc355567d2ecb65efe3f077f121c5"
@@ -6719,18 +5655,13 @@ proxy-addr@~1.0.10:
6719 forwarded "~0.1.0" 5655 forwarded "~0.1.0"
6720 ipaddr.js "1.0.5" 5656 ipaddr.js "1.0.5"
6721 5657
6722proxy-addr@~2.0.4: 5658proxy-addr@~2.0.5:
6723 version "2.0.4" 5659 version "2.0.5"
6724 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" 5660 resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
6725 integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== 5661 integrity sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==
6726 dependencies: 5662 dependencies:
6727 forwarded "~0.1.2" 5663 forwarded "~0.1.2"
6728 ipaddr.js "1.8.0" 5664 ipaddr.js "1.9.0"
6729
6730prr@~1.0.1:
6731 version "1.0.1"
6732 resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
6733 integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
6734 5665
6735pseudomap@^1.0.2: 5666pseudomap@^1.0.2:
6736 version "1.0.2" 5667 version "1.0.2"
@@ -6738,14 +5669,14 @@ pseudomap@^1.0.2:
6738 integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 5669 integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
6739 5670
6740psl@^1.1.24: 5671psl@^1.1.24:
6741 version "1.1.31" 5672 version "1.1.32"
6742 resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" 5673 resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db"
6743 integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== 5674 integrity sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==
6744 5675
6745pstree.remy@^1.1.6: 5676pstree.remy@^1.1.6:
6746 version "1.1.6" 5677 version "1.1.7"
6747 resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.6.tgz#73a55aad9e2d95814927131fbf4dc1b62d259f47" 5678 resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3"
6748 integrity sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w== 5679 integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==
6749 5680
6750pump@^1.0.0: 5681pump@^1.0.0:
6751 version "1.0.3" 5682 version "1.0.3"
@@ -6790,44 +5721,25 @@ punycode@^2.1.0:
6790 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 5721 resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
6791 integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 5722 integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
6792 5723
6793qrcode-terminal@^0.12.0:
6794 version "0.12.0"
6795 resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
6796 integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
6797
6798qs@4.0.0: 5724qs@4.0.0:
6799 version "4.0.0" 5725 version "4.0.0"
6800 resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" 5726 resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607"
6801 integrity sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc= 5727 integrity sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=
6802 5728
6803qs@6.5.2, qs@~6.5.2: 5729qs@6.7.0, qs@^6.5.1:
6804 version "6.5.2"
6805 resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
6806 integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
6807
6808qs@^6.5.1:
6809 version "6.7.0" 5730 version "6.7.0"
6810 resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 5731 resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
6811 integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 5732 integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
6812 5733
6813query-string@^6.2.0: 5734qs@~6.5.2:
6814 version "6.4.2" 5735 version "6.5.2"
6815 resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.4.2.tgz#8be1dbd105306aebf86022144f575a29d516b713" 5736 resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
6816 integrity sha512-DfJqAen17LfLA3rQ+H5S4uXphrF+ANU1lT2ijds4V/Tj4gZxA3gx5/tg1bz7kYCmwna7LyJNCYqO7jNRzo3aLw== 5737 integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
6817 dependencies:
6818 decode-uri-component "^0.2.0"
6819 split-on-first "^1.0.0"
6820 strict-uri-encode "^2.0.0"
6821
6822qw@~1.0.1:
6823 version "1.0.1"
6824 resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4"
6825 integrity sha1-77/cdA+a0FQwRCassYNBLMi5ltQ=
6826 5738
6827random-access-file@^2.0.1: 5739random-access-file@^2.0.1:
6828 version "2.1.0" 5740 version "2.1.2"
6829 resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.0.tgz#7b3b6623d47e2f89282e77f0c9c9ae6da3cd9039" 5741 resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.2.tgz#eeb32e50b9831f32060516862381152ae4e05aa6"
6830 integrity sha512-W2hY3DboLETMclybTVzyqCNVKx1MjqUwZPzkpkkMD2t9mbGEtkV2SKWPqAJ/FTrAtnWB7aGwl0NDUS82da0KdQ== 5742 integrity sha512-dZo7HEcEPbZ/6XLXC4GXypiWvFbXVkdeMrJTi0B94pBJwddt/AvJh8GaQhso6KGYROGYCI/VWdHbmRDtkwT9pQ==
6831 dependencies: 5743 dependencies:
6832 mkdirp "^0.5.1" 5744 mkdirp "^0.5.1"
6833 random-access-storage "^1.1.1" 5745 random-access-storage "^1.1.1"
@@ -6851,10 +5763,10 @@ randombytes@^2.0.3, randombytes@^2.0.5:
6851 dependencies: 5763 dependencies:
6852 safe-buffer "^5.1.0" 5764 safe-buffer "^5.1.0"
6853 5765
6854range-parser@^1.2.0, range-parser@~1.2.0: 5766range-parser@^1.2.0, range-parser@~1.2.1:
6855 version "1.2.0" 5767 version "1.2.1"
6856 resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 5768 resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
6857 integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= 5769 integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
6858 5770
6859range-parser@~1.0.3: 5771range-parser@~1.0.3:
6860 version "1.0.3" 5772 version "1.0.3"
@@ -6868,14 +5780,14 @@ range-slice-stream@^2.0.0:
6868 dependencies: 5780 dependencies:
6869 readable-stream "^3.0.2" 5781 readable-stream "^3.0.2"
6870 5782
6871raw-body@2.3.3: 5783raw-body@2.4.0:
6872 version "2.3.3" 5784 version "2.4.0"
6873 resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" 5785 resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
6874 integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== 5786 integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
6875 dependencies: 5787 dependencies:
6876 bytes "3.0.0" 5788 bytes "3.1.0"
6877 http-errors "1.6.3" 5789 http-errors "1.7.2"
6878 iconv-lite "0.4.23" 5790 iconv-lite "0.4.24"
6879 unpipe "1.0.0" 5791 unpipe "1.0.0"
6880 5792
6881rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: 5793rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
@@ -6898,50 +5810,6 @@ rdf-canonize@^0.2.1:
6898 node-forge "^0.7.1" 5810 node-forge "^0.7.1"
6899 semver "^5.4.1" 5811 semver "^5.4.1"
6900 5812
6901read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1:
6902 version "1.0.1"
6903 resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b"
6904 integrity sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=
6905 dependencies:
6906 graceful-fs "^4.1.2"
6907
6908read-installed@~4.0.3:
6909 version "4.0.3"
6910 resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067"
6911 integrity sha1-/5uLZ/GH0eTCm5/rMfayI6zRkGc=
6912 dependencies:
6913 debuglog "^1.0.1"
6914 read-package-json "^2.0.0"
6915 readdir-scoped-modules "^1.0.0"
6916 semver "2 || 3 || 4 || 5"
6917 slide "~1.1.3"
6918 util-extend "^1.0.1"
6919 optionalDependencies:
6920 graceful-fs "^4.1.2"
6921
6922"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13:
6923 version "2.0.13"
6924 resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a"
6925 integrity sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==
6926 dependencies:
6927 glob "^7.1.1"
6928 json-parse-better-errors "^1.0.1"
6929 normalize-package-data "^2.0.0"
6930 slash "^1.0.0"
6931 optionalDependencies:
6932 graceful-fs "^4.1.2"
6933
6934read-package-tree@^5.2.2:
6935 version "5.2.2"
6936 resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.2.2.tgz#4b6a0ef2d943c1ea36a578214c9a7f6b7424f7a8"
6937 integrity sha512-rW3XWUUkhdKmN2JKB4FL563YAgtINifso5KShykufR03nJ5loGFlkUMe1g/yxmqX073SoYYTsgXu7XdDinKZuA==
6938 dependencies:
6939 debuglog "^1.0.1"
6940 dezalgo "^1.0.0"
6941 once "^1.3.0"
6942 read-package-json "^2.0.0"
6943 readdir-scoped-modules "^1.0.0"
6944
6945read-pkg@^4.0.1: 5813read-pkg@^4.0.1:
6946 version "4.0.1" 5814 version "4.0.1"
6947 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" 5815 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237"
@@ -6951,27 +5819,24 @@ read-pkg@^4.0.1:
6951 parse-json "^4.0.0" 5819 parse-json "^4.0.0"
6952 pify "^3.0.0" 5820 pify "^3.0.0"
6953 5821
6954read@1, read@1.0.x, read@~1.0.1, read@~1.0.7: 5822read-pkg@^5.1.1:
5823 version "5.1.1"
5824 resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.1.1.tgz#5cf234dde7a405c90c88a519ab73c467e9cb83f5"
5825 integrity sha512-dFcTLQi6BZ+aFUaICg7er+/usEoqFdQxiEBsEMNGoipenihtxxtdrQuBXvyANCEI8VuUIVYFgeHGx9sLLvim4w==
5826 dependencies:
5827 "@types/normalize-package-data" "^2.4.0"
5828 normalize-package-data "^2.5.0"
5829 parse-json "^4.0.0"
5830 type-fest "^0.4.1"
5831
5832read@1.0.x:
6955 version "1.0.7" 5833 version "1.0.7"
6956 resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" 5834 resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
6957 integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= 5835 integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=
6958 dependencies: 5836 dependencies:
6959 mute-stream "~0.0.4" 5837 mute-stream "~0.0.4"
6960 5838
6961"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.3, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.2, readable-stream@^2.3.4, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: 5839readable-stream@1.1.x, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.1.13-1:
6962 version "2.3.6"
6963 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
6964 integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
6965 dependencies:
6966 core-util-is "~1.0.0"
6967 inherits "~2.0.3"
6968 isarray "~1.0.0"
6969 process-nextick-args "~2.0.0"
6970 safe-buffer "~5.1.1"
6971 string_decoder "~1.1.1"
6972 util-deprecate "~1.0.1"
6973
6974readable-stream@1.1.x, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1.1.13-1, readable-stream@~1.1.10:
6975 version "1.1.14" 5840 version "1.1.14"
6976 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 5841 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
6977 integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= 5842 integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
@@ -6991,10 +5856,23 @@ readable-stream@1.1.x, "readable-stream@>=1.1.13-1 <1.2.0-0", readable-stream@^1
6991 isarray "0.0.1" 5856 isarray "0.0.1"
6992 string_decoder "~0.10.x" 5857 string_decoder "~0.10.x"
6993 5858
5859readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.3, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.2, readable-stream@^2.3.4, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
5860 version "2.3.6"
5861 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
5862 integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
5863 dependencies:
5864 core-util-is "~1.0.0"
5865 inherits "~2.0.3"
5866 isarray "~1.0.0"
5867 process-nextick-args "~2.0.0"
5868 safe-buffer "~5.1.1"
5869 string_decoder "~1.1.1"
5870 util-deprecate "~1.0.1"
5871
6994readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1: 5872readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1:
6995 version "3.3.0" 5873 version "3.4.0"
6996 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9" 5874 resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
6997 integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw== 5875 integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
6998 dependencies: 5876 dependencies:
6999 inherits "^2.0.3" 5877 inherits "^2.0.3"
7000 string_decoder "^1.1.1" 5878 string_decoder "^1.1.1"
@@ -7007,16 +5885,6 @@ readable-wrap@^1.0.0:
7007 dependencies: 5885 dependencies:
7008 readable-stream "^1.1.13-1" 5886 readable-stream "^1.1.13-1"
7009 5887
7010readdir-scoped-modules@^1.0.0:
7011 version "1.0.2"
7012 resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
7013 integrity sha1-n6+jfShr5dksuuve4DDcm19AZ0c=
7014 dependencies:
7015 debuglog "^1.0.1"
7016 dezalgo "^1.0.0"
7017 graceful-fs "^4.1.2"
7018 once "^1.3.0"
7019
7020readdirp@^2.2.1: 5888readdirp@^2.2.1:
7021 version "2.2.1" 5889 version "2.2.1"
7022 resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" 5890 resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -7040,11 +5908,16 @@ record-cache@^1.0.2:
7040 resolved "https://registry.yarnpkg.com/record-cache/-/record-cache-1.1.0.tgz#f8a467a691a469584b26e88d36b18afdb3932037" 5908 resolved "https://registry.yarnpkg.com/record-cache/-/record-cache-1.1.0.tgz#f8a467a691a469584b26e88d36b18afdb3932037"
7041 integrity sha512-u8rbtLEJV7HRacl/ZYwSBFD8NFyB3PfTTfGLP37IW3hftQCwu6z4Q2RLyxo1YJUNRTEzJfpLpGwVuEYdaIkG9Q== 5909 integrity sha512-u8rbtLEJV7HRacl/ZYwSBFD8NFyB3PfTTfGLP37IW3hftQCwu6z4Q2RLyxo1YJUNRTEzJfpLpGwVuEYdaIkG9Q==
7042 5910
7043redis-commands@1.4.0, redis-commands@^1.2.0: 5911redis-commands@1.4.0:
7044 version "1.4.0" 5912 version "1.4.0"
7045 resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f" 5913 resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.4.0.tgz#52f9cf99153efcce56a8f86af986bd04e988602f"
7046 integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw== 5914 integrity sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==
7047 5915
5916redis-commands@^1.2.0:
5917 version "1.5.0"
5918 resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
5919 integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
5920
7048redis-errors@^1.0.0, redis-errors@^1.2.0: 5921redis-errors@^1.0.0, redis-errors@^1.2.0:
7049 version "1.2.0" 5922 version "1.2.0"
7050 resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" 5923 resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
@@ -7071,20 +5944,20 @@ redis@^2.8.0:
7071 redis-commands "^1.2.0" 5944 redis-commands "^1.2.0"
7072 redis-parser "^2.6.0" 5945 redis-parser "^2.6.0"
7073 5946
7074referrer-policy@1.1.0: 5947referrer-policy@1.2.0:
7075 version "1.1.0" 5948 version "1.2.0"
7076 resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79" 5949 resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"
7077 integrity sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk= 5950 integrity sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==
7078 5951
7079reflect-metadata@^0.1.12: 5952reflect-metadata@^0.1.12:
7080 version "0.1.13" 5953 version "0.1.13"
7081 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" 5954 resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
7082 integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== 5955 integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
7083 5956
7084regenerator-runtime@^0.12.0: 5957regenerator-runtime@^0.13.2:
7085 version "0.12.1" 5958 version "0.13.2"
7086 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" 5959 resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
7087 integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== 5960 integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
7088 5961
7089regex-not@^1.0.0, regex-not@^1.0.2: 5962regex-not@^1.0.0, regex-not@^1.0.2:
7090 version "1.0.2" 5963 version "1.0.2"
@@ -7135,7 +6008,7 @@ repeat-string@^1.6.1:
7135 resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 6008 resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
7136 integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= 6009 integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
7137 6010
7138request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.88.0, request@~2.88.0: 6011request@^2.81.0, request@^2.83.0, request@~2.88.0:
7139 version "2.88.0" 6012 version "2.88.0"
7140 resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" 6013 resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
7141 integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== 6014 integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -7171,6 +6044,11 @@ require-main-filename@^1.0.1:
7171 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" 6044 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
7172 integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= 6045 integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=
7173 6046
6047require-main-filename@^2.0.0:
6048 version "2.0.0"
6049 resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
6050 integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
6051
7174require-uncached@^1.0.2: 6052require-uncached@^1.0.2:
7175 version "1.0.3" 6053 version "1.0.3"
7176 resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 6054 resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
@@ -7179,14 +6057,6 @@ require-uncached@^1.0.2:
7179 caller-path "^0.1.0" 6057 caller-path "^0.1.0"
7180 resolve-from "^1.0.0" 6058 resolve-from "^1.0.0"
7181 6059
7182resolve-dir@^1.0.0, resolve-dir@^1.0.1:
7183 version "1.0.1"
7184 resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
7185 integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
7186 dependencies:
7187 expand-tilde "^2.0.0"
7188 global-modules "^1.0.0"
7189
7190resolve-from@^1.0.0: 6060resolve-from@^1.0.0:
7191 version "1.0.1" 6061 version "1.0.1"
7192 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 6062 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
@@ -7202,11 +6072,6 @@ resolve-from@^3.0.0:
7202 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" 6072 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
7203 integrity sha1-six699nWiBvItuZTM17rywoYh0g= 6073 integrity sha1-six699nWiBvItuZTM17rywoYh0g=
7204 6074
7205resolve-from@^4.0.0:
7206 version "4.0.0"
7207 resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
7208 integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
7209
7210resolve-pkg@^1.0.0: 6075resolve-pkg@^1.0.0:
7211 version "1.0.0" 6076 version "1.0.0"
7212 resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-1.0.0.tgz#e19a15e78aca2e124461dc92b2e3943ef93494d9" 6077 resolved "https://registry.yarnpkg.com/resolve-pkg/-/resolve-pkg-1.0.0.tgz#e19a15e78aca2e124461dc92b2e3943ef93494d9"
@@ -7220,9 +6085,9 @@ resolve-url@^0.2.1:
7220 integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= 6085 integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
7221 6086
7222resolve@^1.10.0, resolve@^1.3.2: 6087resolve@^1.10.0, resolve@^1.3.2:
7223 version "1.10.0" 6088 version "1.11.1"
7224 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" 6089 resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
7225 integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== 6090 integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==
7226 dependencies: 6091 dependencies:
7227 path-parse "^1.0.6" 6092 path-parse "^1.0.6"
7228 6093
@@ -7254,22 +6119,12 @@ retry-as-promised@^3.1.0:
7254 dependencies: 6119 dependencies:
7255 any-promise "^1.3.0" 6120 any-promise "^1.3.0"
7256 6121
7257retry@^0.10.0:
7258 version "0.10.1"
7259 resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4"
7260 integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=
7261
7262retry@^0.12.0:
7263 version "0.12.0"
7264 resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
7265 integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
7266
7267revalidator@0.1.x: 6122revalidator@0.1.x:
7268 version "0.1.8" 6123 version "0.1.8"
7269 resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b" 6124 resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
7270 integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs= 6125 integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs=
7271 6126
7272rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.4.2, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2: 6127rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.4.2, rimraf@^2.6.1, rimraf@~2.6.2:
7273 version "2.6.3" 6128 version "2.6.3"
7274 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 6129 resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
7275 integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 6130 integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -7298,13 +6153,6 @@ run-parallel@^1.0.0, run-parallel@^1.1.2, run-parallel@^1.1.6:
7298 resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" 6153 resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
7299 integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== 6154 integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==
7300 6155
7301run-queue@^1.0.0, run-queue@^1.0.3:
7302 version "1.0.3"
7303 resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47"
7304 integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=
7305 dependencies:
7306 aproba "^1.1.1"
7307
7308run-series@^1.0.2: 6156run-series@^1.0.2:
7309 version "1.1.8" 6157 version "1.1.8"
7310 resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36" 6158 resolved "https://registry.yarnpkg.com/run-series/-/run-series-1.1.8.tgz#2c4558f49221e01cd6371ff4e0a1e203e460fc36"
@@ -7321,9 +6169,9 @@ rx-lite@^3.1.2:
7321 integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= 6169 integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=
7322 6170
7323rxjs@^6.3.3: 6171rxjs@^6.3.3:
7324 version "6.4.0" 6172 version "6.5.2"
7325 resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" 6173 resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7"
7326 integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== 6174 integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==
7327 dependencies: 6175 dependencies:
7328 tslib "^1.9.0" 6176 tslib "^1.9.0"
7329 6177
@@ -7345,9 +6193,9 @@ safe-regex@^1.1.0:
7345 integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 6193 integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
7346 6194
7347sass-lint@^1.12.1: 6195sass-lint@^1.12.1:
7348 version "1.12.1" 6196 version "1.13.1"
7349 resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83" 6197 resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f"
7350 integrity sha1-Yw9pwhaqIGuCMvsqqQe98zNrbYM= 6198 integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q==
7351 dependencies: 6199 dependencies:
7352 commander "^2.8.1" 6200 commander "^2.8.1"
7353 eslint "^2.7.0" 6201 eslint "^2.7.0"
@@ -7391,7 +6239,7 @@ semver-diff@^2.0.0:
7391 dependencies: 6239 dependencies:
7392 semver "^5.0.3" 6240 semver "^5.0.3"
7393 6241
7394"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: 6242"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.6.0, semver@^5.7.0:
7395 version "5.7.0" 6243 version "5.7.0"
7396 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" 6244 resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
7397 integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== 6245 integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
@@ -7401,10 +6249,10 @@ semver@4.3.2:
7401 resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7" 6249 resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
7402 integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c= 6250 integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
7403 6251
7404semver@~5.3.0: 6252semver@^6.0.0, semver@^6.1.1:
7405 version "5.3.0" 6253 version "6.1.1"
7406 resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 6254 resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
7407 integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= 6255 integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==
7408 6256
7409send@0.13.1: 6257send@0.13.1:
7410 version "0.13.1" 6258 version "0.13.1"
@@ -7442,10 +6290,10 @@ send@0.13.2:
7442 range-parser "~1.0.3" 6290 range-parser "~1.0.3"
7443 statuses "~1.2.1" 6291 statuses "~1.2.1"
7444 6292
7445send@0.16.2: 6293send@0.17.1:
7446 version "0.16.2" 6294 version "0.17.1"
7447 resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 6295 resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
7448 integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== 6296 integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
7449 dependencies: 6297 dependencies:
7450 debug "2.6.9" 6298 debug "2.6.9"
7451 depd "~1.1.2" 6299 depd "~1.1.2"
@@ -7454,12 +6302,12 @@ send@0.16.2:
7454 escape-html "~1.0.3" 6302 escape-html "~1.0.3"
7455 etag "~1.8.1" 6303 etag "~1.8.1"
7456 fresh "0.5.2" 6304 fresh "0.5.2"
7457 http-errors "~1.6.2" 6305 http-errors "~1.7.2"
7458 mime "1.4.1" 6306 mime "1.6.0"
7459 ms "2.0.0" 6307 ms "2.1.1"
7460 on-finished "~2.3.0" 6308 on-finished "~2.3.0"
7461 range-parser "~1.2.0" 6309 range-parser "~1.2.1"
7462 statuses "~1.4.0" 6310 statuses "~1.5.0"
7463 6311
7464sequelize-pool@^1.0.2: 6312sequelize-pool@^1.0.2:
7465 version "1.0.2" 6313 version "1.0.2"
@@ -7475,10 +6323,10 @@ sequelize-typescript@1.0.0-beta.2:
7475 dependencies: 6323 dependencies:
7476 glob "7.1.2" 6324 glob "7.1.2"
7477 6325
7478sequelize@5.7.4: 6326sequelize@5.8.7:
7479 version "5.7.4" 6327 version "5.8.7"
7480 resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.7.4.tgz#1631faadff65f3a345b9757fca60429c65ba8e57" 6328 resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.8.7.tgz#3af2fd6051277d5fb5b32054c7f8aac4c4a04107"
7481 integrity sha512-CaVYpAgZQEsGDuZ+Oq6uIZy4pxQxscotuh5UGIaFRa0VkTIgV0IiF7vAhSv+1Wn+NvhKCvgJJ85M34BP3AdGNg== 6329 integrity sha512-1rubZM8fAyCt5ipyS+3HJ3Jbmb8WesLdPJ3jIbTD+78EbuPZILFEA5fK0mliVRBx7oM7oPULeVX0lxSRXBV1jw==
7482 dependencies: 6330 dependencies:
7483 bluebird "^3.5.0" 6331 bluebird "^3.5.0"
7484 cls-bluebird "^2.1.0" 6332 cls-bluebird "^2.1.0"
@@ -7496,15 +6344,15 @@ sequelize@5.7.4:
7496 validator "^10.11.0" 6344 validator "^10.11.0"
7497 wkx "^0.4.6" 6345 wkx "^0.4.6"
7498 6346
7499serve-static@1.13.2: 6347serve-static@1.14.1:
7500 version "1.13.2" 6348 version "1.14.1"
7501 resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 6349 resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
7502 integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== 6350 integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
7503 dependencies: 6351 dependencies:
7504 encodeurl "~1.0.2" 6352 encodeurl "~1.0.2"
7505 escape-html "~1.0.3" 6353 escape-html "~1.0.3"
7506 parseurl "~1.3.2" 6354 parseurl "~1.3.3"
7507 send "0.16.2" 6355 send "0.17.1"
7508 6356
7509serve-static@~1.10.2: 6357serve-static@~1.10.2:
7510 version "1.10.3" 6358 version "1.10.3"
@@ -7540,32 +6388,23 @@ set-value@^2.0.0:
7540 is-plain-object "^2.0.3" 6388 is-plain-object "^2.0.3"
7541 split-string "^3.0.1" 6389 split-string "^3.0.1"
7542 6390
7543setprototypeof@1.1.0: 6391setprototypeof@1.1.1:
7544 version "1.1.0" 6392 version "1.1.1"
7545 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 6393 resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
7546 integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== 6394 integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
7547
7548sha@~2.0.1:
7549 version "2.0.1"
7550 resolved "https://registry.yarnpkg.com/sha/-/sha-2.0.1.tgz#6030822fbd2c9823949f8f72ed6411ee5cf25aae"
7551 integrity sha1-YDCCL70smCOUn49y7WQR7lzyWq4=
7552 dependencies:
7553 graceful-fs "^4.1.2"
7554 readable-stream "^2.0.2"
7555 6395
7556sharp@^0.22.0: 6396sharp@^0.22.0:
7557 version "0.22.0" 6397 version "0.22.1"
7558 resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.22.0.tgz#cf4cfcb019941fd06ac24555d9f5bc84536d29be" 6398 resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.22.1.tgz#a67c0e75567f03dd5a7861b901fec04072c5b0f4"
7559 integrity sha512-yInpiWYvVbE0hJylso2Q2A7QaYFBxGdSlVVHGeUf1F9JsQNAUpmaqdnX54TImgKbSCy9mQpEAoGm1pcKCZhCsQ== 6399 integrity sha512-lXzSk/FL5b/MpWrT1pQZneKe25stVjEbl6uhhJcTULm7PhmJgKKRbTDM/vtjyUuC/RLqL2PRyC4rpKwbv3soEw==
7560 dependencies: 6400 dependencies:
7561 bindings "^1.5.0" 6401 color "^3.1.1"
7562 color "^3.1.0"
7563 detect-libc "^1.0.3" 6402 detect-libc "^1.0.3"
7564 fs-copy-file-sync "^1.1.1" 6403 fs-copy-file-sync "^1.1.1"
7565 nan "^2.13.1" 6404 nan "^2.13.2"
7566 npmlog "^4.1.2" 6405 npmlog "^4.1.2"
7567 prebuild-install "^5.2.5" 6406 prebuild-install "^5.3.0"
7568 semver "^5.6.0" 6407 semver "^6.0.0"
7569 simple-get "^3.0.3" 6408 simple-get "^3.0.3"
7570 tar "^4.4.8" 6409 tar "^4.4.8"
7571 tunnel-agent "^0.6.0" 6410 tunnel-agent "^0.6.0"
@@ -7621,9 +6460,9 @@ simple-get@^3.0.0, simple-get@^3.0.1, simple-get@^3.0.3:
7621 simple-concat "^1.0.0" 6460 simple-concat "^1.0.0"
7622 6461
7623simple-git@^1.85.0: 6462simple-git@^1.85.0:
7624 version "1.110.0" 6463 version "1.113.0"
7625 resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.110.0.tgz#54eb179089d055a7783d32399246cebc9d9933e9" 6464 resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.113.0.tgz#668989728a1e9cf4ec6c72b69ea2eecc93489bea"
7626 integrity sha512-UYY0rQkknk0P5eb+KW+03F4TevZ9ou0H+LoGaj7iiVgpnZH4wdj/HTViy/1tNNkmIPcmtxuBqXWiYt2YwlRKOQ== 6465 integrity sha512-i9WVsrK2u0G/cASI9nh7voxOk9mhanWY9eGtWBDSYql6m49Yk5/Fan6uZsDr/xmzv8n+eQ8ahKCoEr8cvU3h+g==
7627 dependencies: 6466 dependencies:
7628 debug "^4.0.1" 6467 debug "^4.0.1"
7629 6468
@@ -7664,39 +6503,24 @@ simple-websocket@^7.0.1:
7664 ws "^6.0.0" 6503 ws "^6.0.0"
7665 6504
7666sitemap@^2.1.0: 6505sitemap@^2.1.0:
7667 version "2.1.0" 6506 version "2.2.0"
7668 resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-2.1.0.tgz#1633cb88c196d755ad94becfb1c1bcacc6d3425a" 6507 resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-2.2.0.tgz#98b8502762c5d7e8c77c9be5061dce85b326f1b0"
7669 integrity sha512-AkfA7RDVCITQo+j5CpXsMJlZ/8ENO2NtgMHYIh+YMvex2Hao/oe3MQgNa03p0aWY6srCfUA1Q02OgiWCAiuccA== 6508 integrity sha512-9Zoi3UBhSIt5jWENDRUbzsqLMJ+Fha3P2aQ2PRghmh0FOivtHsC4FAJdkAEKHvATajd74BWp/57Yh7kz/UA53Q==
7670 dependencies: 6509 dependencies:
7671 lodash "^4.17.10" 6510 lodash "^4.17.10"
7672 url-join "^4.0.0" 6511 url-join "^4.0.0"
7673 xmlbuilder "^10.0.0" 6512 xmlbuilder "^10.0.0"
7674 6513
7675slash@^1.0.0: 6514slash@^3.0.0:
7676 version "1.0.0" 6515 version "3.0.0"
7677 resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 6516 resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
7678 integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= 6517 integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
7679
7680slash@^2.0.0:
7681 version "2.0.0"
7682 resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
7683 integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
7684 6518
7685slice-ansi@0.0.4: 6519slice-ansi@0.0.4:
7686 version "0.0.4" 6520 version "0.0.4"
7687 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" 6521 resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
7688 integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= 6522 integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=
7689 6523
7690slide@^1.1.6, slide@~1.1.3, slide@~1.1.6:
7691 version "1.1.6"
7692 resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
7693 integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
7694
7695smart-buffer@4.0.2:
7696 version "4.0.2"
7697 resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.2.tgz#5207858c3815cc69110703c6b94e46c15634395d"
7698 integrity sha512-JDhEpTKzXusOqXZ0BUIdH+CjFdO/CR3tLlf5CN34IypI+xMmXW1uB16OOY8z3cICbJlDAVJzNbwBhNO0wt9OAw==
7699
7700smtp-connection@2.3.1: 6524smtp-connection@2.3.1:
7701 version "2.3.1" 6525 version "2.3.1"
7702 resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.3.1.tgz#d169c8f1c9a73854134cdabe6fb818237dfc4fba" 6526 resolved "https://registry.yarnpkg.com/smtp-connection/-/smtp-connection-2.3.1.tgz#d169c8f1c9a73854134cdabe6fb818237dfc4fba"
@@ -7836,35 +6660,6 @@ socket.io@^2.2.0:
7836 socket.io-client "2.2.0" 6660 socket.io-client "2.2.0"
7837 socket.io-parser "~3.3.0" 6661 socket.io-parser "~3.3.0"
7838 6662
7839socks-proxy-agent@^4.0.0:
7840 version "4.0.2"
7841 resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.2.tgz#3c8991f3145b2799e70e11bd5fbc8b1963116386"
7842 integrity sha512-NT6syHhI9LmuEMSK6Kd2V7gNv5KFZoLE7V5udWmn0de+3Mkj3UMA/AJPLyeNUVmElCurSHtUdM3ETpR3z770Wg==
7843 dependencies:
7844 agent-base "~4.2.1"
7845 socks "~2.3.2"
7846
7847socks@~2.3.2:
7848 version "2.3.2"
7849 resolved "https://registry.yarnpkg.com/socks/-/socks-2.3.2.tgz#ade388e9e6d87fdb11649c15746c578922a5883e"
7850 integrity sha512-pCpjxQgOByDHLlNqlnh/mNSAxIUkyBBuwwhTcV+enZGbDaClPvHdvm6uvOwZfFJkam7cGhBNbb4JxiP8UZkRvQ==
7851 dependencies:
7852 ip "^1.1.5"
7853 smart-buffer "4.0.2"
7854
7855sorted-object@~2.0.1:
7856 version "2.0.1"
7857 resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc"
7858 integrity sha1-fWMfS9OnmKJK8d/8+/6DM3pd9fw=
7859
7860sorted-union-stream@~2.1.3:
7861 version "2.1.3"
7862 resolved "https://registry.yarnpkg.com/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz#c7794c7e077880052ff71a8d4a2dbb4a9a638ac7"
7863 integrity sha1-x3lMfgd4gAUv9xqNSi27Sppjisc=
7864 dependencies:
7865 from2 "^1.3.0"
7866 stream-iterate "^1.1.0"
7867
7868source-map-resolve@^0.5.0: 6663source-map-resolve@^0.5.0:
7869 version "0.5.2" 6664 version "0.5.2"
7870 resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" 6665 resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259"
@@ -7877,9 +6672,9 @@ source-map-resolve@^0.5.0:
7877 urix "^0.1.0" 6672 urix "^0.1.0"
7878 6673
7879source-map-support@^0.5.0, source-map-support@^0.5.6: 6674source-map-support@^0.5.0, source-map-support@^0.5.6:
7880 version "0.5.11" 6675 version "0.5.12"
7881 resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.11.tgz#efac2ce0800355d026326a0ca23e162aeac9a4e2" 6676 resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
7882 integrity sha512-//sajEx/fGL3iw6fltKMdPvy8kL3kJ2O3iuYlRoT3k9Kb4BjOoZ+BZzaNHeuaruSt+Kf3Zk9tnfAQg9/AJqUVQ== 6677 integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==
7883 dependencies: 6678 dependencies:
7884 buffer-from "^1.0.0" 6679 buffer-from "^1.0.0"
7885 source-map "^0.6.0" 6680 source-map "^0.6.0"
@@ -7926,20 +6721,15 @@ spdx-expression-parse@^3.0.0:
7926 spdx-license-ids "^3.0.0" 6721 spdx-license-ids "^3.0.0"
7927 6722
7928spdx-license-ids@^3.0.0: 6723spdx-license-ids@^3.0.0:
7929 version "3.0.3" 6724 version "3.0.4"
7930 resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" 6725 resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1"
7931 integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== 6726 integrity sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==
7932 6727
7933speedometer@^1.0.0: 6728speedometer@^1.0.0:
7934 version "1.1.0" 6729 version "1.1.0"
7935 resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.1.0.tgz#a30b13abda45687a1a76977012c060f2ac8a7934" 6730 resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.1.0.tgz#a30b13abda45687a1a76977012c060f2ac8a7934"
7936 integrity sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ== 6731 integrity sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==
7937 6732
7938split-on-first@^1.0.0:
7939 version "1.0.0"
7940 resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.0.0.tgz#648af4ce9a28fbcaadd43274455f298b55025fc6"
7941 integrity sha512-mjA57TQtdWztVZ9THAjGNpgbuIrNfsNrGa5IyK94NoPaT4N14M+GI4jD7t4arLjFkYRQWdETC5RxFzLWouoB3A==
7942
7943split-string@^3.0.1, split-string@^3.0.2: 6733split-string@^3.0.1, split-string@^3.0.2:
7944 version "3.1.0" 6734 version "3.1.0"
7945 resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" 6735 resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
@@ -7993,13 +6783,6 @@ sshpk@^1.7.0:
7993 safer-buffer "^2.0.2" 6783 safer-buffer "^2.0.2"
7994 tweetnacl "~0.14.0" 6784 tweetnacl "~0.14.0"
7995 6785
7996ssri@^6.0.0, ssri@^6.0.1:
7997 version "6.0.1"
7998 resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
7999 integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
8000 dependencies:
8001 figgy-pudding "^3.5.1"
8002
8003stack-trace@0.0.x: 6786stack-trace@0.0.x:
8004 version "0.0.10" 6787 version "0.0.10"
8005 resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 6788 resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
@@ -8023,7 +6806,7 @@ static-extend@^0.1.1:
8023 define-property "^0.2.5" 6806 define-property "^0.2.5"
8024 object-copy "^0.1.0" 6807 object-copy "^0.1.0"
8025 6808
8026statuses@1, "statuses@>= 1.4.0 < 2": 6809statuses@1, "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
8027 version "1.5.0" 6810 version "1.5.0"
8028 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 6811 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
8029 integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 6812 integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -8038,27 +6821,6 @@ statuses@~1.2.1:
8038 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.2.1.tgz#dded45cc18256d51ed40aec142489d5c61026d28" 6821 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.2.1.tgz#dded45cc18256d51ed40aec142489d5c61026d28"
8039 integrity sha1-3e1FzBglbVHtQK7BQkidXGECbSg= 6822 integrity sha1-3e1FzBglbVHtQK7BQkidXGECbSg=
8040 6823
8041statuses@~1.4.0:
8042 version "1.4.0"
8043 resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
8044 integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
8045
8046stream-each@^1.1.0:
8047 version "1.2.3"
8048 resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
8049 integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==
8050 dependencies:
8051 end-of-stream "^1.1.0"
8052 stream-shift "^1.0.0"
8053
8054stream-iterate@^1.1.0:
8055 version "1.2.0"
8056 resolved "https://registry.yarnpkg.com/stream-iterate/-/stream-iterate-1.2.0.tgz#2bd7c77296c1702a46488b8ad41f79865eecd4e1"
8057 integrity sha1-K9fHcpbBcCpGSIuK1B95hl7s1OE=
8058 dependencies:
8059 readable-stream "^2.1.5"
8060 stream-shift "^1.0.0"
8061
8062stream-shift@^1.0.0: 6824stream-shift@^1.0.0:
8063 version "1.0.0" 6825 version "1.0.0"
8064 resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" 6826 resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
@@ -8109,11 +6871,6 @@ streamsearch@0.1.2:
8109 resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" 6871 resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
8110 integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= 6872 integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
8111 6873
8112strict-uri-encode@^2.0.0:
8113 version "2.0.0"
8114 resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
8115 integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
8116
8117string-argv@^0.0.2: 6874string-argv@^0.0.2:
8118 version "0.0.2" 6875 version "0.0.2"
8119 resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" 6876 resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736"
@@ -8136,6 +6893,15 @@ string-width@^1.0.1:
8136 is-fullwidth-code-point "^2.0.0" 6893 is-fullwidth-code-point "^2.0.0"
8137 strip-ansi "^4.0.0" 6894 strip-ansi "^4.0.0"
8138 6895
6896string-width@^3.0.0, string-width@^3.1.0:
6897 version "3.1.0"
6898 resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
6899 integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
6900 dependencies:
6901 emoji-regex "^7.0.1"
6902 is-fullwidth-code-point "^2.0.0"
6903 strip-ansi "^5.1.0"
6904
8139string2compact@^1.1.1, string2compact@^1.2.5: 6905string2compact@^1.1.1, string2compact@^1.2.5:
8140 version "1.3.0" 6906 version "1.3.0"
8141 resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.0.tgz#22d946127b082d1203c51316af60117a337423c3" 6907 resolved "https://registry.yarnpkg.com/string2compact/-/string2compact-1.3.0.tgz#22d946127b082d1203c51316af60117a337423c3"
@@ -8172,11 +6938,6 @@ stringify-object@^3.2.2:
8172 is-obj "^1.0.1" 6938 is-obj "^1.0.1"
8173 is-regexp "^1.0.0" 6939 is-regexp "^1.0.0"
8174 6940
8175stringify-package@^1.0.0:
8176 version "1.0.0"
8177 resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.0.tgz#e02828089333d7d45cd8c287c30aa9a13375081b"
8178 integrity sha512-JIQqiWmLiEozOC0b0BtxZ/AOUtdUZHCBPgqIZ2kSJJqGwgb9neo44XdTHUC4HZSGqi03hOeB7W/E8rAlKnGe9g==
8179
8180strip-ansi@^3.0.0, strip-ansi@^3.0.1: 6941strip-ansi@^3.0.0, strip-ansi@^3.0.1:
8181 version "3.0.1" 6942 version "3.0.1"
8182 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 6943 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -8191,6 +6952,13 @@ strip-ansi@^4.0.0:
8191 dependencies: 6952 dependencies:
8192 ansi-regex "^3.0.0" 6953 ansi-regex "^3.0.0"
8193 6954
6955strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
6956 version "5.2.0"
6957 resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
6958 integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
6959 dependencies:
6960 ansi-regex "^4.1.0"
6961
8194strip-eof@^1.0.0: 6962strip-eof@^1.0.0:
8195 version "1.0.0" 6963 version "1.0.0"
8196 resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" 6964 resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
@@ -8206,15 +6974,6 @@ strip-json-comments@~1.0.1:
8206 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" 6974 resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
8207 integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= 6975 integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=
8208 6976
8209summon-install@^0.4.3:
8210 version "0.4.6"
8211 resolved "https://registry.yarnpkg.com/summon-install/-/summon-install-0.4.6.tgz#25673446e8b92f8bc0afabc464aa7b73fe946bd5"
8212 integrity sha512-xLiRo8z2srFItquk4VXGfC6AsELPmFCYev5pipARHVCikrgCBdjHMxs2ZGfVcsOOKwTgEL6bVFutf5yF43GBZw==
8213 dependencies:
8214 descrevit "^0.1.1"
8215 dot-json "^1.0.3"
8216 npm "*"
8217
8218superagent@^3.8.3: 6977superagent@^3.8.3:
8219 version "3.8.3" 6978 version "3.8.3"
8220 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128" 6979 resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
@@ -8304,10 +7063,10 @@ symbol-observable@^1.1.0:
8304 resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" 7063 resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
8305 integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== 7064 integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
8306 7065
8307synchronous-promise@^2.0.5: 7066synchronous-promise@^2.0.6:
8308 version "2.0.7" 7067 version "2.0.9"
8309 resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.7.tgz#3574b3d2fae86b145356a4b89103e1577f646fe3" 7068 resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.9.tgz#b83db98e9e7ae826bf9c8261fd8ac859126c780a"
8310 integrity sha512-16GbgwTmFMYFyQMLvtQjvNWh30dsFe1cAW5Fg1wm5+dg84L9Pe36mftsIRU95/W2YsISxsz/xq4VB23sqpgb/A== 7069 integrity sha512-LO95GIW16x69LuND1nuuwM4pjgFGupg7pZ/4lU86AmchPKrhk0o2tpMU2unXRrqo81iAFe1YJ0nAGEVwsrZAgg==
8311 7070
8312table@^3.7.8: 7071table@^3.7.8:
8313 version "3.8.3" 7072 version "3.8.3"
@@ -8344,27 +7103,18 @@ tar-stream@^1.1.2:
8344 to-buffer "^1.1.1" 7103 to-buffer "^1.1.1"
8345 xtend "^4.0.0" 7104 xtend "^4.0.0"
8346 7105
8347tar@^2.0.0:
8348 version "2.2.1"
8349 resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
8350 integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=
8351 dependencies:
8352 block-stream "*"
8353 fstream "^1.0.2"
8354 inherits "2"
8355
8356tar@^4, tar@^4.4.8: 7106tar@^4, tar@^4.4.8:
8357 version "4.4.8" 7107 version "4.4.10"
8358 resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" 7108 resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1"
8359 integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== 7109 integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==
8360 dependencies: 7110 dependencies:
8361 chownr "^1.1.1" 7111 chownr "^1.1.1"
8362 fs-minipass "^1.2.5" 7112 fs-minipass "^1.2.5"
8363 minipass "^2.3.4" 7113 minipass "^2.3.5"
8364 minizlib "^1.1.1" 7114 minizlib "^1.2.1"
8365 mkdirp "^0.5.0" 7115 mkdirp "^0.5.0"
8366 safe-buffer "^5.1.2" 7116 safe-buffer "^5.1.2"
8367 yallist "^3.0.2" 7117 yallist "^3.0.3"
8368 7118
8369term-size@^1.2.0: 7119term-size@^1.2.0:
8370 version "1.2.0" 7120 version "1.2.0"
@@ -8404,7 +7154,7 @@ through2@^1.0.0:
8404 readable-stream ">=1.1.13-1 <1.2.0-0" 7154 readable-stream ">=1.1.13-1 <1.2.0-0"
8405 xtend ">=4.0.0 <4.1.0-0" 7155 xtend ">=4.0.0 <4.1.0-0"
8406 7156
8407through2@^2.0.0, through2@^2.0.3: 7157through2@^2.0.3:
8408 version "2.0.5" 7158 version "2.0.5"
8409 resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" 7159 resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
8410 integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== 7160 integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
@@ -8412,7 +7162,7 @@ through2@^2.0.0, through2@^2.0.3:
8412 readable-stream "~2.3.6" 7162 readable-stream "~2.3.6"
8413 xtend "~4.0.1" 7163 xtend "~4.0.1"
8414 7164
8415through@2, "through@>=2.2.7 <3", through@^2.3.6: 7165through@2, through@^2.3.6:
8416 version "2.3.8" 7166 version "2.3.8"
8417 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 7167 resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
8418 integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 7168 integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -8435,11 +7185,6 @@ timers-ext@^0.1.5:
8435 es5-ext "~0.10.46" 7185 es5-ext "~0.10.46"
8436 next-tick "1" 7186 next-tick "1"
8437 7187
8438tiny-relative-date@^1.3.0:
8439 version "1.3.0"
8440 resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07"
8441 integrity sha512-MOQHpzllWxDCHHaDno30hhLfbouoYlOI8YlMNtvKe1zXbjEVhbcEovQxvZrPvtiYW630GQDoMMarCnjfyfHA+A==
8442
8443tmp@0.0.x: 7188tmp@0.0.x:
8444 version "0.0.33" 7189 version "0.0.33"
8445 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" 7190 resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@@ -8498,6 +7243,11 @@ to-utf-8@^1.2.0:
8498 peek-stream "^1.1.1" 7243 peek-stream "^1.1.1"
8499 stream-splicer "^1.3.1" 7244 stream-splicer "^1.3.1"
8500 7245
7246toidentifier@1.0.0:
7247 version "1.0.0"
7248 resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
7249 integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
7250
8501toposort-class@^1.0.1: 7251toposort-class@^1.0.1:
8502 version "1.0.1" 7252 version "1.0.1"
8503 resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" 7253 resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"
@@ -8553,13 +7303,13 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
8553 resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" 7303 resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
8554 integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== 7304 integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
8555 7305
8556ts-node@8.0.3: 7306ts-node@8.2.0:
8557 version "8.0.3" 7307 version "8.2.0"
8558 resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.0.3.tgz#aa60b836a24dafd8bf21b54766841a232fdbc641" 7308 resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.2.0.tgz#4a89754b00560bb24cd54526e1685fa38c45f240"
8559 integrity sha512-2qayBA4vdtVRuDo11DEFSsD/SFsBXQBRZZhbRGSIkmYmVkWjULn/GGMdG10KVqkaGndljfaTD8dKjWgcejO8YA== 7309 integrity sha512-m8XQwUurkbYqXrKqr3WHCW310utRNvV5OnRVeISeea7LoCWVcdfeB/Ntl8JYWFh+WRoUAdBgESrzKochQt7sMw==
8560 dependencies: 7310 dependencies:
8561 arg "^4.1.0" 7311 arg "^4.1.0"
8562 diff "^3.1.0" 7312 diff "^4.0.1"
8563 make-error "^1.1.1" 7313 make-error "^1.1.1"
8564 source-map-support "^0.5.6" 7314 source-map-support "^0.5.6"
8565 yn "^3.0.0" 7315 yn "^3.0.0"
@@ -8591,17 +7341,17 @@ tslint-eslint-rules@^5.3.1:
8591 tsutils "^3.0.0" 7341 tsutils "^3.0.0"
8592 7342
8593tslint@^5.7.0: 7343tslint@^5.7.0:
8594 version "5.15.0" 7344 version "5.17.0"
8595 resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.15.0.tgz#6ffb180986d63afa1e531feb2a134dbf961e27d3" 7345 resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.17.0.tgz#f9f0ce2011d8e90debaa6e9b4975f24cd16852b8"
8596 integrity sha512-6bIEujKR21/3nyeoX2uBnE8s+tMXCQXhqMmaIPJpHmXJoBJPTLcI7/VHRtUwMhnLVdwLqqY3zmd8Dxqa5CVdJA== 7346 integrity sha512-pflx87WfVoYepTet3xLfDOLDm9Jqi61UXIKePOuca0qoAZyrGWonDG9VTbji58Fy+8gciUn8Bt7y69+KEVjc/w==
8597 dependencies: 7347 dependencies:
8598 babel-code-frame "^6.22.0" 7348 "@babel/code-frame" "^7.0.0"
8599 builtin-modules "^1.1.1" 7349 builtin-modules "^1.1.1"
8600 chalk "^2.3.0" 7350 chalk "^2.3.0"
8601 commander "^2.12.1" 7351 commander "^2.12.1"
8602 diff "^3.2.0" 7352 diff "^3.2.0"
8603 glob "^7.1.1" 7353 glob "^7.1.1"
8604 js-yaml "^3.13.0" 7354 js-yaml "^3.13.1"
8605 minimatch "^3.0.4" 7355 minimatch "^3.0.4"
8606 mkdirp "^0.5.1" 7356 mkdirp "^0.5.1"
8607 resolve "^1.3.2" 7357 resolve "^1.3.2"
@@ -8617,9 +7367,9 @@ tsutils@^2.29.0:
8617 tslib "^1.8.1" 7367 tslib "^1.8.1"
8618 7368
8619tsutils@^3.0.0: 7369tsutils@^3.0.0:
8620 version "3.9.1" 7370 version "3.14.0"
8621 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.9.1.tgz#2a40dc742943c71eca6d5c1994fcf999956be387" 7371 resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.14.0.tgz#bf8d5a7bae5369331fa0f2b0a5a10bd7f7396c77"
8622 integrity sha512-hrxVtLtPqQr//p8/msPT1X1UYXUjizqSit5d9AQ5k38TcV38NyecL5xODNxa73cLe/5sdiJ+w1FqzDhRBA/anA== 7372 integrity sha512-SmzGbB0l+8I0QwsPgjooFRaRvHLBLNYM8SeQ0k6rtNDru5sCGeLJcZdwilNndN+GysuFjF5EIYgN8GfFG6UeUw==
8623 dependencies: 7373 dependencies:
8624 tslib "^1.8.1" 7374 tslib "^1.8.1"
8625 7375
@@ -8630,10 +7380,10 @@ tunnel-agent@^0.6.0:
8630 dependencies: 7380 dependencies:
8631 safe-buffer "^5.0.1" 7381 safe-buffer "^5.0.1"
8632 7382
8633tv4@~1.2.7: 7383tv4@^1.3.0:
8634 version "1.2.7" 7384 version "1.3.0"
8635 resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.2.7.tgz#bd29389afc73ade49ae5f48142b5d544bf68d120" 7385 resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963"
8636 integrity sha1-vSk4mvxzreSa5fSBQrXVRL9o0SA= 7386 integrity sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM=
8637 7387
8638tweetnacl@^0.14.3, tweetnacl@~0.14.0: 7388tweetnacl@^0.14.3, tweetnacl@~0.14.0:
8639 version "0.14.5" 7389 version "0.14.5"
@@ -8657,6 +7407,16 @@ type-detect@^4.0.0, type-detect@^4.0.5:
8657 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" 7407 resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
8658 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== 7408 integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
8659 7409
7410type-fest@^0.3.0:
7411 version "0.3.1"
7412 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1"
7413 integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==
7414
7415type-fest@^0.4.1:
7416 version "0.4.1"
7417 resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.4.1.tgz#8bdf77743385d8a4f13ba95f610f5ccd68c728f8"
7418 integrity sha512-IwzA/LSfD2vC1/YDYMv/zHP4rDF1usCwllsDpbolT3D4fUepIO7f9K70jjmUewU/LmGUKJcwcVtDCpnKk4BPMw==
7419
8660type-is@1.6.15: 7420type-is@1.6.15:
8661 version "1.6.15" 7421 version "1.6.15"
8662 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 7422 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
@@ -8665,13 +7425,13 @@ type-is@1.6.15:
8665 media-typer "0.3.0" 7425 media-typer "0.3.0"
8666 mime-types "~2.1.15" 7426 mime-types "~2.1.15"
8667 7427
8668type-is@^1.6.4, type-is@~1.6.16, type-is@~1.6.6: 7428type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18, type-is@~1.6.6:
8669 version "1.6.16" 7429 version "1.6.18"
8670 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" 7430 resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
8671 integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== 7431 integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
8672 dependencies: 7432 dependencies:
8673 media-typer "0.3.0" 7433 media-typer "0.3.0"
8674 mime-types "~2.1.18" 7434 mime-types "~2.1.24"
8675 7435
8676typedarray-to-buffer@^3.0.0: 7436typedarray-to-buffer@^3.0.0:
8677 version "3.1.5" 7437 version "3.1.5"
@@ -8686,14 +7446,9 @@ typedarray@^0.0.6:
8686 integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 7446 integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
8687 7447
8688typescript@^3.4.3: 7448typescript@^3.4.3:
8689 version "3.4.3" 7449 version "3.5.1"
8690 resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f" 7450 resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
8691 integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ== 7451 integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==
8692
8693uid-number@0.0.6:
8694 version "0.0.6"
8695 resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
8696 integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=
8697 7452
8698uint64be@^2.0.2: 7453uint64be@^2.0.2:
8699 version "2.0.2" 7454 version "2.0.2"
@@ -8707,11 +7462,6 @@ ultron@1.0.x:
8707 resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" 7462 resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
8708 integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po= 7463 integrity sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=
8709 7464
8710umask@^1.1.0, umask@~1.1.0:
8711 version "1.1.0"
8712 resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d"
8713 integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=
8714
8715undefsafe@^2.0.2: 7465undefsafe@^2.0.2:
8716 version "2.0.2" 7466 version "2.0.2"
8717 resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" 7467 resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76"
@@ -8719,18 +7469,6 @@ undefsafe@^2.0.2:
8719 dependencies: 7469 dependencies:
8720 debug "^2.2.0" 7470 debug "^2.2.0"
8721 7471
8722underscore-keypath@~0.0.22:
8723 version "0.0.22"
8724 resolved "https://registry.yarnpkg.com/underscore-keypath/-/underscore-keypath-0.0.22.tgz#48a528392bb6efc424be1caa56da4b5faccf264d"
8725 integrity sha1-SKUoOSu278QkvhyqVtpLX6zPJk0=
8726 dependencies:
8727 underscore "*"
8728
8729underscore@*:
8730 version "1.9.1"
8731 resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
8732 integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
8733
8734union-value@^1.0.0: 7472union-value@^1.0.0:
8735 version "1.0.0" 7473 version "1.0.0"
8736 resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" 7474 resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@@ -8746,20 +7484,6 @@ uniq@^1.0.1:
8746 resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" 7484 resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
8747 integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= 7485 integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
8748 7486
8749unique-filename@^1.1.1:
8750 version "1.1.1"
8751 resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
8752 integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
8753 dependencies:
8754 unique-slug "^2.0.0"
8755
8756unique-slug@^2.0.0:
8757 version "2.0.1"
8758 resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6"
8759 integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg==
8760 dependencies:
8761 imurmurhash "^0.1.4"
8762
8763unique-string@^1.0.0: 7487unique-string@^1.0.0:
8764 version "1.0.0" 7488 version "1.0.0"
8765 resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" 7489 resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
@@ -8800,7 +7524,7 @@ upath@^1.1.1:
8800 resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068" 7524 resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.2.tgz#3db658600edaeeccbe6db5e684d67ee8c2acd068"
8801 integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q== 7525 integrity sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==
8802 7526
8803update-notifier@^2.3.0, update-notifier@^2.5.0: 7527update-notifier@^2.5.0:
8804 version "2.5.0" 7528 version "2.5.0"
8805 resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" 7529 resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
8806 integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== 7530 integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
@@ -8892,11 +7616,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
8892 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 7616 resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
8893 integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 7617 integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
8894 7618
8895util-extend@^1.0.1:
8896 version "1.0.3"
8897 resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f"
8898 integrity sha1-p8IW0mdUUWljeztu3GypEZ4v+T8=
8899
8900util.promisify@^1.0.0: 7619util.promisify@^1.0.0:
8901 version "1.0.0" 7620 version "1.0.0"
8902 resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" 7621 resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
@@ -8947,7 +7666,7 @@ uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2:
8947 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" 7666 resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
8948 integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== 7667 integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
8949 7668
8950validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4: 7669validate-npm-package-license@^3.0.1:
8951 version "3.0.4" 7670 version "3.0.4"
8952 resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" 7671 resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
8953 integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== 7672 integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
@@ -8955,18 +7674,16 @@ validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4:
8955 spdx-correct "^3.0.0" 7674 spdx-correct "^3.0.0"
8956 spdx-expression-parse "^3.0.0" 7675 spdx-expression-parse "^3.0.0"
8957 7676
8958validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: 7677validator@^10.0.0, validator@^10.11.0, validator@^10.4.0:
8959 version "3.0.0"
8960 resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e"
8961 integrity sha1-X6kS2B630MdK/BQN5zF/DKffQ34=
8962 dependencies:
8963 builtins "^1.0.3"
8964
8965validator@^10.0.0, validator@^10.11.0, validator@^10.2.0, validator@^10.4.0:
8966 version "10.11.0" 7678 version "10.11.0"
8967 resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" 7679 resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228"
8968 integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== 7680 integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==
8969 7681
7682validator@^11.0.0:
7683 version "11.0.0"
7684 resolved "https://registry.yarnpkg.com/validator/-/validator-11.0.0.tgz#fb10128bfb1fd14ce4ed36b79fc94289eae70667"
7685 integrity sha512-+wnGLYqaKV2++nUv60uGzUJyJQwYVOin6pn1tgEiFCeCQO60yeu3Og9/yPccbBX574kxIcEJicogkzx6s6eyag==
7686
8970vary@^1, vary@~1.1.2: 7687vary@^1, vary@~1.1.2:
8971 version "1.1.2" 7688 version "1.1.2"
8972 resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 7689 resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -9000,13 +7717,6 @@ videostream@^2.5.1:
9000 pump "^3.0.0" 7717 pump "^3.0.0"
9001 range-slice-stream "^2.0.0" 7718 range-slice-stream "^2.0.0"
9002 7719
9003wcwidth@^1.0.0:
9004 version "1.0.1"
9005 resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
9006 integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=
9007 dependencies:
9008 defaults "^1.0.3"
9009
9010webfinger.js@^2.6.6: 7720webfinger.js@^2.6.6:
9011 version "2.7.0" 7721 version "2.7.0"
9012 resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.7.0.tgz#403354a14a65aeeba64c1408c18a387487cea106" 7722 resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.7.0.tgz#403354a14a65aeeba64c1408c18a387487cea106"
@@ -9070,7 +7780,7 @@ which-pm-runs@^1.0.0:
9070 resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" 7780 resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
9071 integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= 7781 integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
9072 7782
9073which@1, which@1.3.1, which@^1.1.1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: 7783which@1.3.1, which@^1.1.1, which@^1.2.10, which@^1.2.9, which@^1.3.1:
9074 version "1.3.1" 7784 version "1.3.1"
9075 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 7785 resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
9076 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 7786 integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -9144,13 +7854,6 @@ wordwrap@~1.0.0:
9144 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 7854 resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
9145 integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= 7855 integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
9146 7856
9147worker-farm@^1.6.0:
9148 version "1.6.0"
9149 resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0"
9150 integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==
9151 dependencies:
9152 errno "~0.1.7"
9153
9154wrap-ansi@^2.0.0: 7857wrap-ansi@^2.0.0:
9155 version "2.1.0" 7858 version "2.1.0"
9156 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" 7859 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
@@ -9167,15 +7870,24 @@ wrap-ansi@^3.0.1:
9167 string-width "^2.1.1" 7870 string-width "^2.1.1"
9168 strip-ansi "^4.0.0" 7871 strip-ansi "^4.0.0"
9169 7872
7873wrap-ansi@^5.1.0:
7874 version "5.1.0"
7875 resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
7876 integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
7877 dependencies:
7878 ansi-styles "^3.2.0"
7879 string-width "^3.0.0"
7880 strip-ansi "^5.0.0"
7881
9170wrappy@1: 7882wrappy@1:
9171 version "1.0.2" 7883 version "1.0.2"
9172 resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 7884 resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
9173 integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 7885 integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
9174 7886
9175write-file-atomic@^2.0.0, write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: 7887write-file-atomic@^2.0.0:
9176 version "2.4.2" 7888 version "2.4.3"
9177 resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.2.tgz#a7181706dfba17855d221140a9c06e15fcdd87b9" 7889 resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
9178 integrity sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g== 7890 integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
9179 dependencies: 7891 dependencies:
9180 graceful-fs "^4.1.11" 7892 graceful-fs "^4.1.11"
9181 imurmurhash "^0.1.4" 7893 imurmurhash "^0.1.4"
@@ -9203,6 +7915,13 @@ ws@^6.0.0:
9203 dependencies: 7915 dependencies:
9204 async-limiter "~1.0.0" 7916 async-limiter "~1.0.0"
9205 7917
7918ws@^7.0.0:
7919 version "7.0.0"
7920 resolved "https://registry.yarnpkg.com/ws/-/ws-7.0.0.tgz#79351cbc3f784b3c20d0821baf4b4ff809ffbf51"
7921 integrity sha512-cknCal4k0EAOrh1SHHPPWWh4qm93g1IuGGGwBjWkXmCG7LsDtL8w9w+YVfaF+KSVwiHQKDIMsSLBVftKf9d1pg==
7922 dependencies:
7923 async-limiter "^1.0.0"
7924
9206ws@~6.1.0: 7925ws@~6.1.0:
9207 version "6.1.4" 7926 version "6.1.4"
9208 resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9" 7927 resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"
@@ -9231,9 +7950,9 @@ xhr2@^0.1.4:
9231 integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= 7950 integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8=
9232 7951
9233xliff@^4.0.0: 7952xliff@^4.0.0:
9234 version "4.3.0" 7953 version "4.3.1"
9235 resolved "https://registry.yarnpkg.com/xliff/-/xliff-4.3.0.tgz#de08098a63ead8afde0bae83f059b8cbf3badaf1" 7954 resolved "https://registry.yarnpkg.com/xliff/-/xliff-4.3.1.tgz#ae82d1c6283014aa7506c9957bcb3f95ed1ce505"
9236 integrity sha512-YDGHMdgmzDOjDm9ys+CdesxC9NBaxOcARR8fdJKHS8qY+XbW6g0BAaRBj0ssC1H9s5lrY0m2uqLCu0HB58K2ag== 7955 integrity sha512-SERrOPuKZ/5XyEiv+cXgIjidQq4vp6HYS0yBS2GwC1TDJiREFaUIin3qg4OBlD6jdFWdPOMwkXGackAvgZ6+LQ==
9237 dependencies: 7956 dependencies:
9238 xml-js "1.6.11" 7957 xml-js "1.6.11"
9239 7958
@@ -9287,11 +8006,6 @@ xmlhttprequest-ssl@~1.5.4:
9287 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 8006 resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
9288 integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= 8007 integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
9289 8008
9290y18n@^3.2.1:
9291 version "3.2.1"
9292 resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
9293 integrity sha1-bRX7qITAhnnA136I53WegR4H+kE=
9294
9295"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: 8009"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0:
9296 version "4.0.0" 8010 version "4.0.0"
9297 resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" 8011 resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
@@ -9302,12 +8016,20 @@ yallist@^2.1.2:
9302 resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 8016 resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
9303 integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 8017 integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
9304 8018
9305yallist@^3.0.0, yallist@^3.0.2: 8019yallist@^3.0.0, yallist@^3.0.3:
9306 version "3.0.3" 8020 version "3.0.3"
9307 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" 8021 resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
9308 integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== 8022 integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
9309 8023
9310yargs-parser@11.1.1, yargs-parser@^11.1.1: 8024yargs-parser@13.0.0:
8025 version "13.0.0"
8026 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b"
8027 integrity sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==
8028 dependencies:
8029 camelcase "^5.0.0"
8030 decamelize "^1.2.0"
8031
8032yargs-parser@^11.1.1:
9311 version "11.1.1" 8033 version "11.1.1"
9312 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" 8034 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
9313 integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== 8035 integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
@@ -9315,6 +8037,14 @@ yargs-parser@11.1.1, yargs-parser@^11.1.1:
9315 camelcase "^5.0.0" 8037 camelcase "^5.0.0"
9316 decamelize "^1.2.0" 8038 decamelize "^1.2.0"
9317 8039
8040yargs-parser@^13.0.0, yargs-parser@^13.1.0:
8041 version "13.1.0"
8042 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.0.tgz#7016b6dd03e28e1418a510e258be4bff5a31138f"
8043 integrity sha512-Yq+32PrijHRri0vVKQEm+ys8mbqWjLiwQkMFNXEENutzLPP0bE4Lcd4iA3OQY5HF+GD3xXxf0MEHb8E4/SA3AA==
8044 dependencies:
8045 camelcase "^5.0.0"
8046 decamelize "^1.2.0"
8047
9318yargs-parser@^8.0.0: 8048yargs-parser@^8.0.0:
9319 version "8.1.0" 8049 version "8.1.0"
9320 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" 8050 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
@@ -9322,13 +8052,6 @@ yargs-parser@^8.0.0:
9322 dependencies: 8052 dependencies:
9323 camelcase "^4.1.0" 8053 camelcase "^4.1.0"
9324 8054
9325yargs-parser@^9.0.2:
9326 version "9.0.2"
9327 resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
9328 integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=
9329 dependencies:
9330 camelcase "^4.1.0"
9331
9332yargs-unparser@1.5.0: 8055yargs-unparser@1.5.0:
9333 version "1.5.0" 8056 version "1.5.0"
9334 resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.5.0.tgz#f2bb2a7e83cbc87bb95c8e572828a06c9add6e0d" 8057 resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.5.0.tgz#f2bb2a7e83cbc87bb95c8e572828a06c9add6e0d"
@@ -9338,7 +8061,24 @@ yargs-unparser@1.5.0:
9338 lodash "^4.17.11" 8061 lodash "^4.17.11"
9339 yargs "^12.0.5" 8062 yargs "^12.0.5"
9340 8063
9341yargs@12.0.5, yargs@^12.0.1, yargs@^12.0.5: 8064yargs@13.2.2:
8065 version "13.2.2"
8066 resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.2.tgz#0c101f580ae95cea7f39d927e7770e3fdc97f993"
8067 integrity sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==
8068 dependencies:
8069 cliui "^4.0.0"
8070 find-up "^3.0.0"
8071 get-caller-file "^2.0.1"
8072 os-locale "^3.1.0"
8073 require-directory "^2.1.1"
8074 require-main-filename "^2.0.0"
8075 set-blocking "^2.0.0"
8076 string-width "^3.0.0"
8077 which-module "^2.0.0"
8078 y18n "^4.0.0"
8079 yargs-parser "^13.0.0"
8080
8081yargs@^12.0.1, yargs@^12.0.5:
9342 version "12.0.5" 8082 version "12.0.5"
9343 resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" 8083 resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
9344 integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== 8084 integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
@@ -9356,23 +8096,22 @@ yargs@12.0.5, yargs@^12.0.1, yargs@^12.0.5:
9356 y18n "^3.2.1 || ^4.0.0" 8096 y18n "^3.2.1 || ^4.0.0"
9357 yargs-parser "^11.1.1" 8097 yargs-parser "^11.1.1"
9358 8098
9359yargs@^11.0.0: 8099yargs@^13.2.2:
9360 version "11.1.0" 8100 version "13.2.4"
9361 resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" 8101 resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83"
9362 integrity sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A== 8102 integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==
9363 dependencies: 8103 dependencies:
9364 cliui "^4.0.0" 8104 cliui "^5.0.0"
9365 decamelize "^1.1.1" 8105 find-up "^3.0.0"
9366 find-up "^2.1.0" 8106 get-caller-file "^2.0.1"
9367 get-caller-file "^1.0.1" 8107 os-locale "^3.1.0"
9368 os-locale "^2.0.0"
9369 require-directory "^2.1.1" 8108 require-directory "^2.1.1"
9370 require-main-filename "^1.0.1" 8109 require-main-filename "^2.0.0"
9371 set-blocking "^2.0.0" 8110 set-blocking "^2.0.0"
9372 string-width "^2.0.0" 8111 string-width "^3.0.0"
9373 which-module "^2.0.0" 8112 which-module "^2.0.0"
9374 y18n "^3.2.1" 8113 y18n "^4.0.0"
9375 yargs-parser "^9.0.2" 8114 yargs-parser "^13.1.0"
9376 8115
9377yeast@0.1.2: 8116yeast@0.1.2:
9378 version "0.1.2" 8117 version "0.1.2"
@@ -9380,30 +8119,30 @@ yeast@0.1.2:
9380 integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= 8119 integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk=
9381 8120
9382yn@^3.0.0: 8121yn@^3.0.0:
9383 version "3.0.0" 8122 version "3.1.0"
9384 resolved "https://registry.yarnpkg.com/yn/-/yn-3.0.0.tgz#0073c6b56e92aed652fbdfd62431f2d6b9a7a091" 8123 resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114"
9385 integrity sha512-+Wo/p5VRfxUgBUGy2j/6KX2mj9AYJWOHuhMjMcbBFc3y54o9/4buK1ksBvuiK01C3kby8DH9lSmJdSxw+4G/2Q== 8124 integrity sha512-kKfnnYkbTfrAdd0xICNFw7Atm8nKpLcLv9AZGEt+kczL/WQVai4e2V6ZN8U/O+iI6WrNuJjNNOyu4zfhl9D3Hg==
9386 8125
9387youtube-dl@^1.12.2: 8126youtube-dl@^2.0.0:
9388 version "1.13.1" 8127 version "2.0.0"
9389 resolved "https://registry.yarnpkg.com/youtube-dl/-/youtube-dl-1.13.1.tgz#2da47c0dad3c5391e2172b7811da46501b82edc4" 8128 resolved "https://registry.yarnpkg.com/youtube-dl/-/youtube-dl-2.0.0.tgz#64f85beff21a5b0b5651437ea51d10effd3dad5a"
9390 integrity sha512-89mUKwOavaojNKQlyzW+A7Dph5G/oPYs6T/PTMcvgdRQ5E2+uDQgYPxWHQDMBhHOkxQaxvxQTiTPKQLdg0OI4w== 8129 integrity sha512-1R5bcnBMVqQIk85wqTJ8pxPq5UbzxZXJy4KSaf3N1dedZG4uAzq5zuoiHcNcsGle5JtoHNpTWDBjTDhHO0SipQ==
9391 dependencies: 8130 dependencies:
9392 hh-mm-ss "~1.2.0" 8131 hh-mm-ss "~1.2.0"
9393 mkdirp "~0.5.1" 8132 mkdirp "~0.5.1"
9394 request "~2.88.0" 8133 request "~2.88.0"
9395 streamify "~0.2.9" 8134 streamify "~0.2.9"
9396 8135
9397yup@^0.26.10: 8136yup@^0.27.0:
9398 version "0.26.10" 8137 version "0.27.0"
9399 resolved "https://registry.yarnpkg.com/yup/-/yup-0.26.10.tgz#3545839663289038faf25facfc07e11fd67c0cb1" 8138 resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7"
9400 integrity sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw== 8139 integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ==
9401 dependencies: 8140 dependencies:
9402 "@babel/runtime" "7.0.0" 8141 "@babel/runtime" "^7.0.0"
9403 fn-name "~2.0.1" 8142 fn-name "~2.0.1"
9404 lodash "^4.17.10" 8143 lodash "^4.17.11"
9405 property-expr "^1.5.0" 8144 property-expr "^1.5.0"
9406 synchronous-promise "^2.0.5" 8145 synchronous-promise "^2.0.6"
9407 toposort "^2.0.2" 8146 toposort "^2.0.2"
9408 8147
9409z-schema@^3.24.2: 8148z-schema@^3.24.2: